diff --git a/pom.xml b/pom.xml index 862aa65..659438d 100644 --- a/pom.xml +++ b/pom.xml @@ -69,9 +69,8 @@ 0.11.5 - jakarta.validation - jakarta.validation-api - 2.0.2 + org.springframework.boot + spring-boot-starter-validation diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/ExpenseController.java b/src/main/java/com/faf223/expensetrackerfaf/controller/ExpenseController.java index 152b166..079cd0f 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/controller/ExpenseController.java +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/ExpenseController.java @@ -10,9 +10,11 @@ import com.faf223.expensetrackerfaf.service.ExpenseCategoryService; import com.faf223.expensetrackerfaf.service.ExpenseService; import com.faf223.expensetrackerfaf.service.UserService; 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 jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -49,12 +51,12 @@ public class ExpenseController { } @PostMapping() - public ResponseEntity createNewExpense(@RequestBody ExpenseCreationDTO expenseDTO, + public ResponseEntity createNewExpense(@RequestBody @Valid ExpenseCreationDTO expenseDTO, BindingResult bindingResult) { - Expense expense = expenseMapper.toExpense(expenseDTO); - if(bindingResult.hasErrors()) - throw new TransactionNotCreatedException(ErrorResponse.from(bindingResult).getMessage()); + throw new TransactionNotCreatedException("Could not create new expense"); + + Expense expense = expenseMapper.toExpense(expenseDTO); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); @@ -71,25 +73,26 @@ public class ExpenseController { throw new TransactionNotCreatedException("Could not create new expense"); } - - // TODO: check if the expense belongs to the user @PatchMapping("/update/{id}") - public ResponseEntity updateExpense(@PathVariable long id, @RequestBody ExpenseCreationDTO expenseDTO, + public ResponseEntity updateExpense(@PathVariable long id, @RequestBody @Valid ExpenseCreationDTO expenseDTO, BindingResult bindingResult) { + if(bindingResult.hasErrors()) + throw new TransactionNotUpdatedException(ErrorResponse.from(bindingResult).getMessage()); + Expense expense = expenseService.getTransactionById(id); - if(expense == null) throw new TransactionsNotFoundException("The expense has not been found"); + if(expense == null) + throw new TransactionsNotFoundException("The expense has not been found"); + + if(!expenseService.belongsToUser(expense)) + throw new TransactionDoesNotBelongToTheUserException("The transaction does not belong to you"); ExpenseCategory category = expenseCategoryService.getCategoryById(expenseDTO.getExpenseCategory()); expense.setCategory(category); expense.setAmount(expenseDTO.getAmount()); - if (!bindingResult.hasErrors()) { - expenseService.createOrUpdate(expense); - return ResponseEntity.status(HttpStatus.CREATED).build(); - } else { - throw new TransactionNotUpdatedException(ErrorResponse.from(bindingResult).getMessage()); - } + expenseService.createOrUpdate(expense); + return ResponseEntity.status(HttpStatus.CREATED).build(); } @GetMapping("/personal-expenses") @@ -103,8 +106,8 @@ public class ExpenseController { String email = userDetails.getUsername(); List expenses; - expenses = date.map(localDate -> expenseService.getTransactionsByDate(localDate).stream().map(expenseMapper::toDto).toList()) - .orElseGet(() -> month.map(value -> expenseService.getTransactionsByMonth(value).stream().map(expenseMapper::toDto).toList()) + expenses = date.map(localDate -> expenseService.getTransactionsByDate(localDate, email).stream().map(expenseMapper::toDto).toList()) + .orElseGet(() -> month.map(value -> expenseService.getTransactionsByMonth(value, email).stream().map(expenseMapper::toDto).toList()) .orElseGet(() -> expenseService.getTransactionsByEmail(email).stream().map(expenseMapper::toDto).toList())); if (!expenses.isEmpty()) { diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/GlobalExceptionHandler.java b/src/main/java/com/faf223/expensetrackerfaf/controller/GlobalExceptionHandler.java index e903c63..17a003d 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/controller/GlobalExceptionHandler.java +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/GlobalExceptionHandler.java @@ -1,10 +1,7 @@ package com.faf223.expensetrackerfaf.controller; 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.TransactionsNotFoundException; -import com.faf223.expensetrackerfaf.util.exceptions.UserNotFoundException; +import com.faf223.expensetrackerfaf.util.exceptions.*; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -20,7 +17,7 @@ public class GlobalExceptionHandler { System.currentTimeMillis() ); - return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); + return new ResponseEntity<>(response, HttpStatus.FORBIDDEN); } @ExceptionHandler @@ -30,7 +27,7 @@ public class GlobalExceptionHandler { System.currentTimeMillis() ); - return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); + return new ResponseEntity<>(response, HttpStatus.NOT_MODIFIED); } @ExceptionHandler @@ -53,4 +50,34 @@ public class GlobalExceptionHandler { return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); } + @ExceptionHandler + private ResponseEntity handleTransactionNotUpdatedException(TransactionNotUpdatedException e) { + ErrorResponse response = new ErrorResponse( + e.getMessage(), + System.currentTimeMillis() + ); + + return new ResponseEntity<>(response, HttpStatus.NOT_MODIFIED); + } + + @ExceptionHandler + private ResponseEntity handleUserNotAuthenticatedException(UserNotAuthenticatedException e) { + ErrorResponse response = new ErrorResponse( + e.getMessage(), + System.currentTimeMillis() + ); + + return new ResponseEntity<>(response, HttpStatus.FORBIDDEN); + } + + @ExceptionHandler + private ResponseEntity handleUserNotCreatedException(UserNotCreatedException e) { + ErrorResponse response = new ErrorResponse( + e.getMessage(), + System.currentTimeMillis() + ); + + return new ResponseEntity<>(response, HttpStatus.NOT_MODIFIED); + } + } diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/IncomeController.java b/src/main/java/com/faf223/expensetrackerfaf/controller/IncomeController.java index 3b9bdd8..24208f3 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/controller/IncomeController.java +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/IncomeController.java @@ -10,9 +10,11 @@ import com.faf223.expensetrackerfaf.service.IncomeCategoryService; import com.faf223.expensetrackerfaf.service.IncomeService; import com.faf223.expensetrackerfaf.service.UserService; 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 jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -49,13 +51,13 @@ public class IncomeController { } @PostMapping() - public ResponseEntity createNewIncome(@RequestBody IncomeCreationDTO incomeDTO, + public ResponseEntity createNewIncome(@RequestBody @Valid IncomeCreationDTO incomeDTO, BindingResult bindingResult) { - Income income = incomeMapper.toIncome(incomeDTO); - if(bindingResult.hasErrors()) throw new TransactionNotCreatedException(ErrorResponse.from(bindingResult).getMessage()); + Income income = incomeMapper.toIncome(incomeDTO); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.getPrincipal() instanceof UserDetails userDetails) { @@ -71,24 +73,26 @@ public class IncomeController { throw new TransactionNotCreatedException("Could not create new expense"); } - // TODO: check if the income belongs to the user, extract logic into service @PatchMapping("/update/{id}") - public ResponseEntity updateIncome(@PathVariable long id, @RequestBody IncomeCreationDTO incomeDTO, + public ResponseEntity updateIncome(@PathVariable long id, @RequestBody @Valid IncomeCreationDTO incomeDTO, BindingResult bindingResult) { + if(bindingResult.hasErrors()) + throw new TransactionNotUpdatedException(ErrorResponse.from(bindingResult).getMessage()); + Income income = incomeService.getTransactionById(id); - if(income == null) throw new TransactionsNotFoundException("The income has not been found"); + if(income == null) + throw new TransactionsNotFoundException("The income has not been found"); + + if(!incomeService.belongsToUser(income)) + throw new TransactionDoesNotBelongToTheUserException("The transaction does not belong to you"); IncomeCategory category = incomeCategoryService.getCategoryById(incomeDTO.getIncomeCategory()); income.setCategory(category); income.setAmount(incomeDTO.getAmount()); - if (!bindingResult.hasErrors()) { - incomeService.createOrUpdate(income); - return ResponseEntity.status(HttpStatus.CREATED).build(); - } else { - throw new TransactionNotUpdatedException(ErrorResponse.from(bindingResult).getMessage()); - } + incomeService.createOrUpdate(income); + return ResponseEntity.status(HttpStatus.CREATED).build(); } @GetMapping("/personal-incomes") @@ -102,8 +106,8 @@ public class IncomeController { String email = userDetails.getUsername(); List incomes; - incomes = date.map(localDate -> incomeService.getTransactionsByDate(localDate).stream().map(incomeMapper::toDto).toList()) - .orElseGet(() -> month.map(value -> incomeService.getTransactionsByMonth(value).stream().map(incomeMapper::toDto).toList()) + incomes = date.map(localDate -> incomeService.getTransactionsByDate(localDate, email).stream().map(incomeMapper::toDto).toList()) + .orElseGet(() -> month.map(value -> incomeService.getTransactionsByMonth(value, email).stream().map(incomeMapper::toDto).toList()) .orElseGet(() -> incomeService.getTransactionsByEmail(email).stream().map(incomeMapper::toDto).toList())); if (!incomes.isEmpty()) { diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/UserController.java b/src/main/java/com/faf223/expensetrackerfaf/controller/UserController.java index c961523..160be82 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/controller/UserController.java +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/UserController.java @@ -5,7 +5,10 @@ import com.faf223.expensetrackerfaf.dto.UserDTO; import com.faf223.expensetrackerfaf.dto.mappers.UserMapper; import com.faf223.expensetrackerfaf.model.User; import com.faf223.expensetrackerfaf.service.UserService; +import com.faf223.expensetrackerfaf.util.errors.ErrorResponse; +import com.faf223.expensetrackerfaf.util.exceptions.UserNotCreatedException; import com.faf223.expensetrackerfaf.util.exceptions.UserNotFoundException; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -26,8 +29,11 @@ public class UserController { private final UserMapper userMapper; @PatchMapping() - public ResponseEntity updateUser(@RequestBody UserCreationDTO userDTO, + public ResponseEntity updateUser(@RequestBody @Valid UserCreationDTO userDTO, BindingResult bindingResult) { + if(bindingResult.hasErrors()) + throw new UserNotCreatedException(ErrorResponse.from(bindingResult).getMessage()); + User user = userMapper.toUser(userDTO); if (!bindingResult.hasErrors()) { userService.updateUser(user); @@ -37,7 +43,7 @@ public class UserController { } } - @GetMapping("/getUserData") + @GetMapping("/get-user-data") public ResponseEntity getUser() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/auth/RegisterRequest.java b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/RegisterRequest.java index ca50bad..ada6510 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/controller/auth/RegisterRequest.java +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/RegisterRequest.java @@ -13,10 +13,10 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class RegisterRequest { - private String firstname; // Change field name to match JSON - private String lastname; // Change field name to match JSON - private String username; // Change field name to match JSON - private String email; // Change field name to match JSON + private String firstname; + private String lastname; + private String username; + private String email; private String password; private Role role; } diff --git a/src/main/java/com/faf223/expensetrackerfaf/dto/ExpenseCreationDTO.java b/src/main/java/com/faf223/expensetrackerfaf/dto/ExpenseCreationDTO.java index 5434b68..f222e20 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/dto/ExpenseCreationDTO.java +++ b/src/main/java/com/faf223/expensetrackerfaf/dto/ExpenseCreationDTO.java @@ -1,16 +1,19 @@ package com.faf223.expensetrackerfaf.dto; -import com.faf223.expensetrackerfaf.model.ExpenseCategory; -import com.faf223.expensetrackerfaf.model.User; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import java.math.BigDecimal; -import java.time.LocalDate; @Data @AllArgsConstructor public class ExpenseCreationDTO { + @NotNull(message = "Category must not be null") private int expenseCategory; + + @NotNull(message = "Amount must not be null") + @DecimalMin(value = "0.0", inclusive = false, message = "Amount must be positive") private BigDecimal amount; } \ No newline at end of file diff --git a/src/main/java/com/faf223/expensetrackerfaf/dto/ExpenseDTO.java b/src/main/java/com/faf223/expensetrackerfaf/dto/ExpenseDTO.java index 0804ff6..e2c855d 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/dto/ExpenseDTO.java +++ b/src/main/java/com/faf223/expensetrackerfaf/dto/ExpenseDTO.java @@ -1,6 +1,8 @@ package com.faf223.expensetrackerfaf.dto; import com.faf223.expensetrackerfaf.model.ExpenseCategory; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; @@ -10,9 +12,19 @@ import java.time.LocalDate; @Data @AllArgsConstructor public class ExpenseDTO { + @NotNull(message = "ID must not be null") private long expenseId; + + @NotNull(message = "User must not be null") private UserDTO userDTO; + + @NotNull(message = "Category must not be null") private ExpenseCategory expenseCategory; + + @NotNull(message = "Date must not be null") private LocalDate date; + + @NotNull(message = "Amount must not be null") + @DecimalMin(value = "0.0", inclusive = false, message = "Amount must be positive") private BigDecimal amount; } \ No newline at end of file diff --git a/src/main/java/com/faf223/expensetrackerfaf/dto/IncomeCreationDTO.java b/src/main/java/com/faf223/expensetrackerfaf/dto/IncomeCreationDTO.java index 620071e..deabca8 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/dto/IncomeCreationDTO.java +++ b/src/main/java/com/faf223/expensetrackerfaf/dto/IncomeCreationDTO.java @@ -1,16 +1,19 @@ package com.faf223.expensetrackerfaf.dto; -import com.faf223.expensetrackerfaf.model.IncomeCategory; -import com.faf223.expensetrackerfaf.model.User; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import java.math.BigDecimal; -import java.time.LocalDate; @Data @AllArgsConstructor public class IncomeCreationDTO { + @NotNull(message = "Category must not be null") private int incomeCategory; + + @NotNull(message = "Amount must not be null") + @DecimalMin(value = "0.0", inclusive = false, message = "Amount must be positive") private BigDecimal amount; } \ No newline at end of file diff --git a/src/main/java/com/faf223/expensetrackerfaf/dto/IncomeDTO.java b/src/main/java/com/faf223/expensetrackerfaf/dto/IncomeDTO.java index ce2d912..94c2723 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/dto/IncomeDTO.java +++ b/src/main/java/com/faf223/expensetrackerfaf/dto/IncomeDTO.java @@ -1,6 +1,8 @@ package com.faf223.expensetrackerfaf.dto; import com.faf223.expensetrackerfaf.model.IncomeCategory; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; @@ -10,9 +12,19 @@ import java.time.LocalDate; @Data @AllArgsConstructor public class IncomeDTO { + @NotNull(message = "ID must not be null") private long incomeId; + + @NotNull(message = "User must not be null") private UserDTO userDTO; + + @NotNull(message = "Category must not be null") private IncomeCategory incomeCategory; + + @NotNull(message = "Date must not be null") private LocalDate date; + + @NotNull(message = "Amount must not be null") + @DecimalMin(value = "0.0", inclusive = false, message = "Amount must be positive") private BigDecimal amount; } \ No newline at end of file diff --git a/src/main/java/com/faf223/expensetrackerfaf/dto/UserCreationDTO.java b/src/main/java/com/faf223/expensetrackerfaf/dto/UserCreationDTO.java index edd0663..8ad40cc 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/dto/UserCreationDTO.java +++ b/src/main/java/com/faf223/expensetrackerfaf/dto/UserCreationDTO.java @@ -1,15 +1,28 @@ package com.faf223.expensetrackerfaf.dto; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class UserCreationDTO { - + @NotNull(message = "First name must not be null") + @NotEmpty(message = "First name must not be empty") private String firstname; + @NotNull(message = "Last name must not be null") + @NotEmpty(message = "Last name must not be empty") private String lastname; + @NotNull(message = "Username must not be null") + @NotEmpty(message = "Username must not be empty") private String username; + @NotNull(message = "Email must not be null") + @NotEmpty(message = "Email must not be empty") + @Email(message = "Email must be valid") private String email; + @NotNull(message = "Password must not be null") + @NotEmpty(message = "Password must not be empty") private String password; } diff --git a/src/main/java/com/faf223/expensetrackerfaf/dto/UserDTO.java b/src/main/java/com/faf223/expensetrackerfaf/dto/UserDTO.java index ff95f52..e2024b1 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/dto/UserDTO.java +++ b/src/main/java/com/faf223/expensetrackerfaf/dto/UserDTO.java @@ -1,14 +1,21 @@ package com.faf223.expensetrackerfaf.dto; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class UserDTO { - + @NotNull(message = "Name must not be null") + @NotEmpty(message = "Name must not be empty") private String name; + @NotNull(message = "Surname must not be null") + @NotEmpty(message = "Surname must not be empty") private String surname; + @NotNull(message = "Username must not be null") + @NotEmpty(message = "Username must not be empty") private String username; } diff --git a/src/main/java/com/faf223/expensetrackerfaf/model/Expense.java b/src/main/java/com/faf223/expensetrackerfaf/model/Expense.java index 322bb52..5e9cc9a 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/model/Expense.java +++ b/src/main/java/com/faf223/expensetrackerfaf/model/Expense.java @@ -1,7 +1,13 @@ package com.faf223.expensetrackerfaf.model; + import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; -import lombok.*; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; import java.math.BigDecimal; import java.time.LocalDate; @@ -24,9 +30,14 @@ public class Expense implements IMoneyTransaction { @ManyToOne @JoinColumn(name = "category_id") + @NotNull private ExpenseCategory category; + @NotNull private LocalDate date; + + @NotNull + @DecimalMin(value = "0.0", inclusive = false) private BigDecimal amount; public Expense(LocalDate date, BigDecimal amount) { diff --git a/src/main/java/com/faf223/expensetrackerfaf/model/ExpenseCategory.java b/src/main/java/com/faf223/expensetrackerfaf/model/ExpenseCategory.java index c5989ea..43076b3 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/model/ExpenseCategory.java +++ b/src/main/java/com/faf223/expensetrackerfaf/model/ExpenseCategory.java @@ -1,6 +1,8 @@ package com.faf223.expensetrackerfaf.model; import jakarta.persistence.*; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.Data; @Data @@ -12,5 +14,7 @@ public class ExpenseCategory implements IMoneyTransactionCategory { private Long id; @Column(name = "category_name") + @NotNull(message = "Name must not be null") + @NotEmpty(message = "Name must not be empty") private String name; } diff --git a/src/main/java/com/faf223/expensetrackerfaf/model/Income.java b/src/main/java/com/faf223/expensetrackerfaf/model/Income.java index cba3dac..f5514ad 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/model/Income.java +++ b/src/main/java/com/faf223/expensetrackerfaf/model/Income.java @@ -2,6 +2,8 @@ package com.faf223.expensetrackerfaf.model; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; import lombok.*; import java.math.BigDecimal; @@ -25,9 +27,14 @@ public class Income implements IMoneyTransaction { @ManyToOne @JoinColumn(name = "category_id") + @NotNull private IncomeCategory category; + @NotNull private LocalDate date; + + @NotNull + @DecimalMin(value = "0.0", inclusive = false) private BigDecimal amount; public Income(IncomeCategory incomeCategory, LocalDate date, BigDecimal amount) { diff --git a/src/main/java/com/faf223/expensetrackerfaf/model/IncomeCategory.java b/src/main/java/com/faf223/expensetrackerfaf/model/IncomeCategory.java index 237c41f..6fd4f99 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/model/IncomeCategory.java +++ b/src/main/java/com/faf223/expensetrackerfaf/model/IncomeCategory.java @@ -1,6 +1,8 @@ package com.faf223.expensetrackerfaf.model; import jakarta.persistence.*; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.Data; @Data @@ -12,6 +14,8 @@ public class IncomeCategory implements IMoneyTransactionCategory { private Long id; @Column(name = "category_name") + @NotNull(message = "Name must not be null") + @NotEmpty(message = "Name must not be empty") private String name; } diff --git a/src/main/java/com/faf223/expensetrackerfaf/model/User.java b/src/main/java/com/faf223/expensetrackerfaf/model/User.java index 9588570..93e8375 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/model/User.java +++ b/src/main/java/com/faf223/expensetrackerfaf/model/User.java @@ -1,6 +1,8 @@ package com.faf223.expensetrackerfaf.model; import jakarta.persistence.*; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.*; import java.util.List; @@ -17,15 +19,23 @@ public class User { private String userUuid; @Column(name = "name") + @NotNull(message = "First name must not be null") + @NotEmpty(message = "First name must not be empty") private String firstName; @Column(name = "surname") + @NotNull(message = "Last name must not be null") + @NotEmpty(message = "Last name must not be empty") private String lastName; @Column(name = "username") + @NotNull(message = "Username must not be null") + @NotEmpty(message = "Username must not be empty") private String username; @Transient + @NotNull(message = "Password must not be null") + @NotEmpty(message = "Password must not be empty") private String password; @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) diff --git a/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java b/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java index 4b644f0..82924df 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java +++ b/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java @@ -10,11 +10,12 @@ import com.faf223.expensetrackerfaf.model.User; import com.faf223.expensetrackerfaf.repository.CredentialRepository; import com.faf223.expensetrackerfaf.repository.UserRepository; import com.faf223.expensetrackerfaf.security.PersonDetails; +import com.faf223.expensetrackerfaf.util.exceptions.UserNotAuthenticatedException; +import com.faf223.expensetrackerfaf.util.exceptions.UserNotFoundException; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -55,7 +56,7 @@ public class AuthenticationService { public AuthenticationResponse authenticate(AuthenticationRequest request) { authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())); - Credential credential = credentialRepository.findByEmail(request.getEmail()).orElseThrow((() -> new UsernameNotFoundException("User not found"))); + Credential credential = credentialRepository.findByEmail(request.getEmail()).orElseThrow((() -> new UserNotFoundException("User not found"))); UserDetails userDetails = new PersonDetails(credential); String jwtToken = jwtService.generateToken(userDetails); @@ -79,7 +80,7 @@ public class AuthenticationService { .refreshToken(refreshToken) .build(); } else { - throw new RuntimeException("Invalid or expired refresh token"); + throw new UserNotAuthenticatedException("Invalid or expired refresh token"); } } diff --git a/src/main/java/com/faf223/expensetrackerfaf/service/ExpenseService.java b/src/main/java/com/faf223/expensetrackerfaf/service/ExpenseService.java index 1dfb483..119fd7a 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/service/ExpenseService.java +++ b/src/main/java/com/faf223/expensetrackerfaf/service/ExpenseService.java @@ -1,11 +1,15 @@ package com.faf223.expensetrackerfaf.service; -import com.faf223.expensetrackerfaf.model.Credential; -import com.faf223.expensetrackerfaf.model.Expense; -import com.faf223.expensetrackerfaf.model.IMoneyTransaction; +import com.faf223.expensetrackerfaf.model.*; import com.faf223.expensetrackerfaf.repository.CredentialRepository; import com.faf223.expensetrackerfaf.repository.ExpenseRepository; +import com.faf223.expensetrackerfaf.repository.UserRepository; +import com.faf223.expensetrackerfaf.util.exceptions.UserNotAuthenticatedException; +import com.faf223.expensetrackerfaf.util.exceptions.UserNotFoundException; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import java.time.LocalDate; @@ -20,6 +24,7 @@ public class ExpenseService implements ITransactionService { private final ExpenseRepository expenseRepository; private final CredentialRepository credentialRepository; + private final UserRepository userRepository; public void createOrUpdate(IMoneyTransaction expense) { expenseRepository.save((Expense) expense); @@ -40,6 +45,19 @@ public class ExpenseService implements ITransactionService { return expenseRepository.findByDate(date); } + @Override + public List getTransactionsByDate(LocalDate date, String email) { + return getTransactionsByDate(date) + .stream() + .filter(transaction -> { + Optional credential = credentialRepository.findByEmail(email); + if(credential.isEmpty()) + throw new UserNotFoundException("The user has not been found"); + return credential.get().getUser().equals(transaction.getUser()); + }) + .toList(); + } + // TODO: store transaction month in a separate field in the DB and change this logic @Override public List getTransactionsByMonth(Month month) { @@ -49,6 +67,19 @@ public class ExpenseService implements ITransactionService { return expenseRepository.findByDateBetween(startOfMonth, endOfMonth); } + @Override + public List getTransactionsByMonth(Month month, String email) { + return getTransactionsByMonth(month) + .stream() + .filter(transaction -> { + Optional credential = credentialRepository.findByEmail(email); + if(credential.isEmpty()) + throw new UserNotFoundException("The user has not been found"); + return credential.get().getUser().equals(transaction.getUser()); + }) + .toList(); + } + public List getTransactions() { return expenseRepository.findAll(); } @@ -60,4 +91,26 @@ public class ExpenseService implements ITransactionService { public void deleteTransactionById(long id) { expenseRepository.deleteById(id); } + + @Override + public boolean belongsToUser(IMoneyTransaction transaction) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication != null && authentication.getPrincipal() instanceof UserDetails userDetails) { + + if(authentication.getAuthorities().stream().noneMatch(authority -> authority.getAuthority().equals("ADMIN"))) { + + Optional credential = credentialRepository.findByEmail(userDetails.getUsername()); + if(credential.isEmpty()) throw new UserNotFoundException("The user has not been found"); + Optional user = userRepository.findById(credential.get().getUser().getUserUuid()); + if(user.isEmpty()) throw new UserNotFoundException("The user has not been found"); + + return user.get().getExpenses().contains((Expense) transaction); + + } + + } + + throw new UserNotAuthenticatedException("You are not authenticated"); + } } \ No newline at end of file diff --git a/src/main/java/com/faf223/expensetrackerfaf/service/ITransactionService.java b/src/main/java/com/faf223/expensetrackerfaf/service/ITransactionService.java index 953fcee..051c3fe 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/service/ITransactionService.java +++ b/src/main/java/com/faf223/expensetrackerfaf/service/ITransactionService.java @@ -1,6 +1,8 @@ package com.faf223.expensetrackerfaf.service; import com.faf223.expensetrackerfaf.model.IMoneyTransaction; +import com.faf223.expensetrackerfaf.model.Income; +import com.faf223.expensetrackerfaf.model.User; import java.time.LocalDate; import java.time.Month; @@ -12,8 +14,10 @@ public interface ITransactionService { List getTransactions(); List getTransactionsByEmail(String email); List getTransactionsByDate(LocalDate date); + List getTransactionsByDate(LocalDate date, String email); List getTransactionsByMonth(Month month); + List getTransactionsByMonth(Month month, String email); IMoneyTransaction getTransactionById(long id); void deleteTransactionById(long it); - + boolean belongsToUser(IMoneyTransaction transaction); } diff --git a/src/main/java/com/faf223/expensetrackerfaf/service/IncomeService.java b/src/main/java/com/faf223/expensetrackerfaf/service/IncomeService.java index 6ae4479..97fe35e 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/service/IncomeService.java +++ b/src/main/java/com/faf223/expensetrackerfaf/service/IncomeService.java @@ -1,12 +1,18 @@ package com.faf223.expensetrackerfaf.service; import com.faf223.expensetrackerfaf.model.Credential; -import com.faf223.expensetrackerfaf.model.Expense; import com.faf223.expensetrackerfaf.model.IMoneyTransaction; import com.faf223.expensetrackerfaf.model.Income; +import com.faf223.expensetrackerfaf.model.User; import com.faf223.expensetrackerfaf.repository.CredentialRepository; import com.faf223.expensetrackerfaf.repository.IncomeRepository; +import com.faf223.expensetrackerfaf.repository.UserRepository; +import com.faf223.expensetrackerfaf.util.exceptions.UserNotAuthenticatedException; +import com.faf223.expensetrackerfaf.util.exceptions.UserNotFoundException; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import java.time.LocalDate; @@ -21,6 +27,7 @@ public class IncomeService implements ITransactionService { private final IncomeRepository incomeRepository; private final CredentialRepository credentialRepository; + private final UserRepository userRepository; public void createOrUpdate(IMoneyTransaction income) { incomeRepository.save((Income) income); @@ -45,6 +52,19 @@ public class IncomeService implements ITransactionService { return incomeRepository.findByDate(date); } + @Override + public List getTransactionsByDate(LocalDate date, String email) { + return getTransactionsByDate(date) + .stream() + .filter(transaction -> { + Optional credential = credentialRepository.findByEmail(email); + if(credential.isEmpty()) + throw new UserNotFoundException("The user has not been found"); + return credential.get().getUser().equals(transaction.getUser()); + }) + .toList(); + } + // TODO: store transaction month in a separate field in the DB and change this logic @Override public List getTransactionsByMonth(Month month) { @@ -54,6 +74,19 @@ public class IncomeService implements ITransactionService { return incomeRepository.findByDateBetween(startOfMonth, endOfMonth); } + @Override + public List getTransactionsByMonth(Month month, String email) { + return getTransactionsByMonth(month) + .stream() + .filter(transaction -> { + Optional credential = credentialRepository.findByEmail(email); + if(credential.isEmpty()) + throw new UserNotFoundException("The user has not been found"); + return credential.get().getUser().equals(transaction.getUser()); + }) + .toList(); + } + public Income getTransactionById(long id) { return incomeRepository.findById(id).orElse(null); } @@ -61,4 +94,26 @@ public class IncomeService implements ITransactionService { public void deleteTransactionById(long id) { incomeRepository.deleteById(id); } + + @Override + public boolean belongsToUser(IMoneyTransaction transaction) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication != null && authentication.getPrincipal() instanceof UserDetails userDetails) { + + if(authentication.getAuthorities().stream().noneMatch(authority -> authority.getAuthority().equals("ADMIN"))) { + + Optional credential = credentialRepository.findByEmail(userDetails.getUsername()); + if(credential.isEmpty()) throw new UserNotFoundException("The user has not been found"); + Optional user = userRepository.findById(credential.get().getUser().getUserUuid()); + if(user.isEmpty()) throw new UserNotFoundException("The user has not been found"); + + return user.get().getIncomes().contains((Income) transaction); + + } + + } + + throw new UserNotAuthenticatedException("You are not authenticated"); + } } diff --git a/src/main/java/com/faf223/expensetrackerfaf/util/exceptions/UserNotAuthenticatedException.java b/src/main/java/com/faf223/expensetrackerfaf/util/exceptions/UserNotAuthenticatedException.java new file mode 100644 index 0000000..d292db8 --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/util/exceptions/UserNotAuthenticatedException.java @@ -0,0 +1,7 @@ +package com.faf223.expensetrackerfaf.util.exceptions; + +public class UserNotAuthenticatedException extends RuntimeException { + public UserNotAuthenticatedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/util/exceptions/UserNotCreatedException.java b/src/main/java/com/faf223/expensetrackerfaf/util/exceptions/UserNotCreatedException.java new file mode 100644 index 0000000..3889e6f --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/util/exceptions/UserNotCreatedException.java @@ -0,0 +1,7 @@ +package com.faf223.expensetrackerfaf.util.exceptions; + +public class UserNotCreatedException extends RuntimeException { + public UserNotCreatedException(String message) { + super(message); + } +}