Merge pull request #36 from lumijiez/dimas_restupdate

refactor code, add more flexibility for filtering transactions by periods of time
This commit was merged in pull request #36.
This commit is contained in:
Dima
2023-11-21 12:48:59 +02:00
committed by GitHub
10 changed files with 206 additions and 70 deletions

View File

@@ -1,8 +1,7 @@
package com.faf223.expensetrackerfaf.config; package com.faf223.expensetrackerfaf.config;
import com.faf223.expensetrackerfaf.repository.CredentialRepository;
import com.faf223.expensetrackerfaf.repository.UserRepository;
import com.faf223.expensetrackerfaf.security.PersonDetails; import com.faf223.expensetrackerfaf.security.PersonDetails;
import com.faf223.expensetrackerfaf.service.CredentialService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -19,12 +18,11 @@ import org.springframework.security.crypto.password.PasswordEncoder;
@RequiredArgsConstructor @RequiredArgsConstructor
public class ApplicationConfig { public class ApplicationConfig {
private final UserRepository userRepository; private final CredentialService credentialService;
private final CredentialRepository credentialRepository;
@Bean @Bean
public UserDetailsService userDetailsService() { public UserDetailsService userDetailsService() {
return username -> new PersonDetails(credentialRepository.findByEmail(username).orElseThrow((() -> new UsernameNotFoundException("User not found")))); return username -> new PersonDetails(credentialService.findByEmail(username).orElseThrow((() -> new UsernameNotFoundException("User not found"))));
} }
@Bean @Bean

View File

@@ -22,6 +22,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -96,25 +97,36 @@ public class ExpenseController {
} }
@GetMapping("/personal-expenses") @GetMapping("/personal-expenses")
@Transactional(readOnly = true)
public ResponseEntity<List<ExpenseDTO>> getExpensesByUser(@RequestParam Optional<LocalDate> date, public ResponseEntity<List<ExpenseDTO>> getExpensesByUser(@RequestParam Optional<LocalDate> date,
@RequestParam Optional<Month> month) { @RequestParam Optional<Integer> month,
@RequestParam Optional<Integer> startYear,
@RequestParam Optional<Integer> endYear,
@RequestParam Optional<String> lastUnit) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof UserDetails userDetails) { if (authentication != null && authentication.getPrincipal() instanceof UserDetails userDetails) {
String email = userDetails.getUsername(); String email = userDetails.getUsername();
List<ExpenseDTO> expenses; List<ExpenseDTO> expenses = Collections.emptyList();
expenses = date.map(localDate -> expenseService.getTransactionsByDate(localDate, email).stream().map(expenseMapper::toDto).toList()) if(date.isPresent())
.orElseGet(() -> month.map(value -> expenseService.getTransactionsByMonth(value, email).stream().map(expenseMapper::toDto).toList()) expenses = expenseService.getTransactionsByDate(date.get(), email).stream().map(expenseMapper::toDto).toList();
.orElseGet(() -> expenseService.getTransactionsByEmail(email).stream().map(expenseMapper::toDto).toList())); else if(month.isPresent())
expenses = expenseService.getTransactionsByMonth(Month.of(month.get()), email).stream().map(expenseMapper::toDto).toList();
else if(startYear.isPresent() && endYear.isPresent())
expenses = expenseService.getYearIntervalTransactions(email, startYear.get(), endYear.get()).stream().map(expenseMapper::toDto).toList();
else if(lastUnit.isPresent()) {
if(lastUnit.get().equalsIgnoreCase("week"))
expenses = expenseService.getLastWeekTransactions(email).stream().map(expenseMapper::toDto).toList();
else if(lastUnit.get().equalsIgnoreCase("month"))
expenses = expenseService.getLastMonthTransactions(email).stream().map(expenseMapper::toDto).toList();
if (!expenses.isEmpty()) {
return ResponseEntity.ok(expenses);
} else {
return ResponseEntity.ok(Collections.emptyList());
} }
return ResponseEntity.ok(expenses);
} }
throw new TransactionsNotFoundException("The expenses have not been found"); throw new TransactionsNotFoundException("The expenses have not been found");

View File

@@ -22,6 +22,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -96,25 +97,36 @@ public class IncomeController {
} }
@GetMapping("/personal-incomes") @GetMapping("/personal-incomes")
public ResponseEntity<List<IncomeDTO>> getExpensesByUser(@RequestParam Optional<LocalDate> date, @Transactional(readOnly = true)
@RequestParam Optional<Month> month) { public ResponseEntity<List<IncomeDTO>> getIncomesByUser(@RequestParam Optional<LocalDate> date,
@RequestParam Optional<Integer> month,
@RequestParam Optional<Integer> startYear,
@RequestParam Optional<Integer> endYear,
@RequestParam Optional<String> lastUnit) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof UserDetails userDetails) { if (authentication != null && authentication.getPrincipal() instanceof UserDetails userDetails) {
String email = userDetails.getUsername(); String email = userDetails.getUsername();
List<IncomeDTO> incomes; List<IncomeDTO> incomes = Collections.emptyList();
incomes = date.map(localDate -> incomeService.getTransactionsByDate(localDate, email).stream().map(incomeMapper::toDto).toList()) if(date.isPresent())
.orElseGet(() -> month.map(value -> incomeService.getTransactionsByMonth(value, email).stream().map(incomeMapper::toDto).toList()) incomes = incomeService.getTransactionsByDate(date.get(), email).stream().map(incomeMapper::toDto).toList();
.orElseGet(() -> incomeService.getTransactionsByEmail(email).stream().map(incomeMapper::toDto).toList())); else if(month.isPresent())
incomes = incomeService.getTransactionsByMonth(Month.of(month.get()), email).stream().map(incomeMapper::toDto).toList();
else if(startYear.isPresent() && endYear.isPresent())
incomes = incomeService.getYearIntervalTransactions(email, startYear.get(), endYear.get()).stream().map(incomeMapper::toDto).toList();
else if(lastUnit.isPresent()) {
if(lastUnit.get().equalsIgnoreCase("week"))
incomes = incomeService.getLastWeekTransactions(email).stream().map(incomeMapper::toDto).toList();
else if(lastUnit.get().equalsIgnoreCase("month"))
incomes = incomeService.getLastMonthTransactions(email).stream().map(incomeMapper::toDto).toList();
if (!incomes.isEmpty()) {
return ResponseEntity.ok(incomes);
} else {
return ResponseEntity.ok(Collections.emptyList());
} }
return ResponseEntity.ok(incomes);
} }
throw new TransactionsNotFoundException("The expenses have not been found"); throw new TransactionsNotFoundException("The expenses have not been found");

View File

@@ -3,6 +3,7 @@ package com.faf223.expensetrackerfaf.repository;
import com.faf223.expensetrackerfaf.model.Expense; import com.faf223.expensetrackerfaf.model.Expense;
import com.faf223.expensetrackerfaf.model.User; import com.faf223.expensetrackerfaf.model.User;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.time.LocalDate; import java.time.LocalDate;
@@ -14,4 +15,16 @@ public interface ExpenseRepository extends JpaRepository<Expense, Long> {
List<Expense> findByDate(LocalDate date); List<Expense> findByDate(LocalDate date);
List<Expense> findByDateBetween(LocalDate start, LocalDate end); List<Expense> findByDateBetween(LocalDate start, LocalDate end);
@Procedure(procedureName = "get_expenses_by_month")
List<Expense> filterByMonth(int month);
@Procedure(procedureName = "get_last_week_expenses")
List<Expense> findLastWeek();
@Procedure(procedureName = "get_last_month_expenses")
List<Expense> findLastMonth();
@Procedure(procedureName = "get_expenses_by_year_interval")
List<Expense> filterByYearInterval(int start, int end);
} }

View File

@@ -3,7 +3,9 @@ package com.faf223.expensetrackerfaf.repository;
import com.faf223.expensetrackerfaf.model.Income; import com.faf223.expensetrackerfaf.model.Income;
import com.faf223.expensetrackerfaf.model.User; import com.faf223.expensetrackerfaf.model.User;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.query.Procedure;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
@@ -14,4 +16,20 @@ public interface IncomeRepository extends JpaRepository<Income, Long> {
List<Income> findByDate(LocalDate date); List<Income> findByDate(LocalDate date);
List<Income> findByDateBetween(LocalDate start, LocalDate end); List<Income> findByDateBetween(LocalDate start, LocalDate end);
@Transactional(readOnly = true)
@Procedure(procedureName = "get_incomes_by_month")
List<Income> filterByMonth(int month);
@Transactional(readOnly = true)
@Procedure(procedureName = "get_last_week_incomes")
List<Income> findLastWeek();
@Transactional(readOnly = true)
@Procedure(procedureName = "get_last_month_incomes")
List<Income> findLastMonth();
@Transactional(readOnly = true)
@Procedure(procedureName = "get_incomes_by_year_interval")
List<Income> filterByYearInterval(int start, int end);
} }

View File

@@ -0,0 +1,20 @@
package com.faf223.expensetrackerfaf.service;
import com.faf223.expensetrackerfaf.model.Credential;
import com.faf223.expensetrackerfaf.repository.CredentialRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class CredentialService {
private final CredentialRepository credentialRepository;
public Optional<Credential> findByEmail(String email) {
return credentialRepository.findByEmail(email);
}
}

View File

@@ -4,6 +4,7 @@ import com.faf223.expensetrackerfaf.model.*;
import com.faf223.expensetrackerfaf.repository.CredentialRepository; import com.faf223.expensetrackerfaf.repository.CredentialRepository;
import com.faf223.expensetrackerfaf.repository.ExpenseRepository; import com.faf223.expensetrackerfaf.repository.ExpenseRepository;
import com.faf223.expensetrackerfaf.repository.UserRepository; import com.faf223.expensetrackerfaf.repository.UserRepository;
import com.faf223.expensetrackerfaf.util.TransactionFilter;
import com.faf223.expensetrackerfaf.util.exceptions.UserNotAuthenticatedException; import com.faf223.expensetrackerfaf.util.exceptions.UserNotAuthenticatedException;
import com.faf223.expensetrackerfaf.util.exceptions.UserNotFoundException; import com.faf223.expensetrackerfaf.util.exceptions.UserNotFoundException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -11,6 +12,7 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.Month; import java.time.Month;
@@ -25,6 +27,7 @@ public class ExpenseService implements ITransactionService {
private final ExpenseRepository expenseRepository; private final ExpenseRepository expenseRepository;
private final CredentialRepository credentialRepository; private final CredentialRepository credentialRepository;
private final UserRepository userRepository; private final UserRepository userRepository;
private final TransactionFilter transactionFilter;
public void createOrUpdate(IMoneyTransaction expense) { public void createOrUpdate(IMoneyTransaction expense) {
expenseRepository.save((Expense) expense); expenseRepository.save((Expense) expense);
@@ -47,37 +50,47 @@ public class ExpenseService implements ITransactionService {
@Override @Override
public List<Expense> getTransactionsByDate(LocalDate date, String email) { public List<Expense> getTransactionsByDate(LocalDate date, String email) {
return getTransactionsByDate(date) return (List<Expense>) transactionFilter.filterByEmail(getTransactionsByDate(date), email);
.stream()
.filter(transaction -> {
Optional<Credential> 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 @Override
public List<Expense> getTransactionsByMonth(Month month) { public List<Expense> getTransactionsByMonth(Month month) {
LocalDate startOfMonth = LocalDate.of(LocalDate.now().getYear(), month, 1); return expenseRepository.filterByMonth(month.getValue());
LocalDate endOfMonth = startOfMonth.plusMonths(1).minusDays(1);
return expenseRepository.findByDateBetween(startOfMonth, endOfMonth);
} }
@Override @Override
public List<Expense> getTransactionsByMonth(Month month, String email) { public List<Expense> getTransactionsByMonth(Month month, String email) {
return getTransactionsByMonth(month) return (List<Expense>) transactionFilter.filterByEmail(getTransactionsByMonth(month), email);
.stream() }
.filter(transaction -> {
Optional<Credential> credential = credentialRepository.findByEmail(email); @Override
if(credential.isEmpty()) public List<Expense> getLastWeekTransactions() {
throw new UserNotFoundException("The user has not been found"); return expenseRepository.findLastWeek();
return credential.get().getUser().equals(transaction.getUser()); }
})
.toList(); @Override
public List<Expense> getLastWeekTransactions(String email) {
return (List<Expense>) transactionFilter.filterByEmail(getLastWeekTransactions(), email);
}
@Override
public List<Expense> getLastMonthTransactions() {
return expenseRepository.findLastMonth();
}
@Override
public List<Expense> getLastMonthTransactions(String email) {
return (List<Expense>) transactionFilter.filterByEmail(getLastMonthTransactions(), email);
}
@Override
public List<Expense> getYearIntervalTransactions(int start, int end) {
return expenseRepository.filterByYearInterval(start, end);
}
@Override
public List<Expense> getYearIntervalTransactions(String email, int start, int end) {
return (List<Expense>) transactionFilter.filterByEmail(getYearIntervalTransactions(start, end), email);
} }
public List<Expense> getTransactions() { public List<Expense> getTransactions() {
@@ -113,4 +126,5 @@ public class ExpenseService implements ITransactionService {
throw new UserNotAuthenticatedException("You are not authenticated"); throw new UserNotAuthenticatedException("You are not authenticated");
} }
} }

View File

@@ -17,6 +17,12 @@ public interface ITransactionService {
List<? extends IMoneyTransaction> getTransactionsByDate(LocalDate date, String email); List<? extends IMoneyTransaction> getTransactionsByDate(LocalDate date, String email);
List<? extends IMoneyTransaction> getTransactionsByMonth(Month month); List<? extends IMoneyTransaction> getTransactionsByMonth(Month month);
List<? extends IMoneyTransaction> getTransactionsByMonth(Month month, String email); List<? extends IMoneyTransaction> getTransactionsByMonth(Month month, String email);
List<? extends IMoneyTransaction> getLastWeekTransactions();
List<? extends IMoneyTransaction> getLastWeekTransactions(String email);
List<? extends IMoneyTransaction> getLastMonthTransactions();
List<? extends IMoneyTransaction> getLastMonthTransactions(String email);
List<? extends IMoneyTransaction> getYearIntervalTransactions(int start, int end);
List<? extends IMoneyTransaction> getYearIntervalTransactions(String email, int start, int end);
IMoneyTransaction getTransactionById(long id); IMoneyTransaction getTransactionById(long id);
void deleteTransactionById(long it); void deleteTransactionById(long it);
boolean belongsToUser(IMoneyTransaction transaction); boolean belongsToUser(IMoneyTransaction transaction);

View File

@@ -7,6 +7,7 @@ import com.faf223.expensetrackerfaf.model.User;
import com.faf223.expensetrackerfaf.repository.CredentialRepository; import com.faf223.expensetrackerfaf.repository.CredentialRepository;
import com.faf223.expensetrackerfaf.repository.IncomeRepository; import com.faf223.expensetrackerfaf.repository.IncomeRepository;
import com.faf223.expensetrackerfaf.repository.UserRepository; import com.faf223.expensetrackerfaf.repository.UserRepository;
import com.faf223.expensetrackerfaf.util.TransactionFilter;
import com.faf223.expensetrackerfaf.util.exceptions.UserNotAuthenticatedException; import com.faf223.expensetrackerfaf.util.exceptions.UserNotAuthenticatedException;
import com.faf223.expensetrackerfaf.util.exceptions.UserNotFoundException; import com.faf223.expensetrackerfaf.util.exceptions.UserNotFoundException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -28,6 +29,7 @@ public class IncomeService implements ITransactionService {
private final IncomeRepository incomeRepository; private final IncomeRepository incomeRepository;
private final CredentialRepository credentialRepository; private final CredentialRepository credentialRepository;
private final UserRepository userRepository; private final UserRepository userRepository;
private final TransactionFilter transactionFilter;
public void createOrUpdate(IMoneyTransaction income) { public void createOrUpdate(IMoneyTransaction income) {
incomeRepository.save((Income) income); incomeRepository.save((Income) income);
@@ -54,37 +56,47 @@ public class IncomeService implements ITransactionService {
@Override @Override
public List<Income> getTransactionsByDate(LocalDate date, String email) { public List<Income> getTransactionsByDate(LocalDate date, String email) {
return getTransactionsByDate(date) return (List<Income>) transactionFilter.filterByEmail(getTransactionsByDate(date), email);
.stream()
.filter(transaction -> {
Optional<Credential> 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 @Override
public List<Income> getTransactionsByMonth(Month month) { public List<Income> getTransactionsByMonth(Month month) {
LocalDate startOfMonth = LocalDate.of(LocalDate.now().getYear(), month, 1); return incomeRepository.filterByMonth(month.getValue());
LocalDate endOfMonth = startOfMonth.plusMonths(1).minusDays(1);
return incomeRepository.findByDateBetween(startOfMonth, endOfMonth);
} }
@Override @Override
public List<Income> getTransactionsByMonth(Month month, String email) { public List<Income> getTransactionsByMonth(Month month, String email) {
return getTransactionsByMonth(month) return (List<Income>) transactionFilter.filterByEmail(getTransactionsByMonth(month), email);
.stream() }
.filter(transaction -> {
Optional<Credential> credential = credentialRepository.findByEmail(email); @Override
if(credential.isEmpty()) public List<Income> getLastWeekTransactions() {
throw new UserNotFoundException("The user has not been found"); return incomeRepository.findLastWeek();
return credential.get().getUser().equals(transaction.getUser()); }
})
.toList(); @Override
public List<Income> getLastWeekTransactions(String email) {
return (List<Income>) transactionFilter.filterByEmail(getLastWeekTransactions(), email);
}
@Override
public List<Income> getLastMonthTransactions() {
return incomeRepository.findLastMonth();
}
@Override
public List<Income> getLastMonthTransactions(String email) {
return (List<Income>) transactionFilter.filterByEmail(getLastMonthTransactions(), email);
}
@Override
public List<Income> getYearIntervalTransactions(int start, int end) {
return incomeRepository.filterByYearInterval(start, end);
}
@Override
public List<Income> getYearIntervalTransactions(String email, int start, int end) {
return (List<Income>) transactionFilter.filterByEmail(getYearIntervalTransactions(start, end), email);
} }
public Income getTransactionById(long id) { public Income getTransactionById(long id) {

View File

@@ -0,0 +1,31 @@
package com.faf223.expensetrackerfaf.util;
import com.faf223.expensetrackerfaf.model.Credential;
import com.faf223.expensetrackerfaf.model.IMoneyTransaction;
import com.faf223.expensetrackerfaf.service.CredentialService;
import com.faf223.expensetrackerfaf.util.exceptions.UserNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
@RequiredArgsConstructor
public class TransactionFilter {
private final CredentialService credentialService;
public List<? extends IMoneyTransaction> filterByEmail(List<? extends IMoneyTransaction> transactions, String email) {
return transactions
.stream()
.filter(transaction -> {
Optional<Credential> credential = credentialService.findByEmail(email);
if(credential.isEmpty())
throw new UserNotFoundException("The user has not been found");
return credential.get().getUser().equals(transaction.getUser());
})
.toList();
}
}