From ab64ae72320b4faa58b3a90cf0e92fc69e5b0d18 Mon Sep 17 00:00:00 2001 From: Adeleke Emmanuel Date: Wed, 28 Jan 2026 10:28:15 +0100 Subject: [PATCH 1/4] add update transaction and paginated fetch endpoints --- .../controller/TransactionController.java | 31 +++++++++++- .../repository/TransactionRepository.java | 12 +++++ .../service/TransactionService.java | 17 +++++++ .../service/TransactionServiceImpl.java | 50 +++++++++++++++++++ 4 files changed, 108 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java b/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java index 9267d01..d51ef59 100644 --- a/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java +++ b/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java @@ -1,13 +1,40 @@ package com.SkillsForge.expensetracker.controller; +import com.SkillsForge.expensetracker.app.enums.TransactionCategory; +import com.SkillsForge.expensetracker.app.enums.TransactionType; +import com.SkillsForge.expensetracker.persistence.entity.Transaction; import com.SkillsForge.expensetracker.service.TransactionService; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.*; @RequiredArgsConstructor @RestController @RequestMapping(value = "api/v1/transaction") public class TransactionController { private final TransactionService transactionService; + +// update a transaction by id + @PutMapping("/{id}") + public Transaction updateTransaction( + @PathVariable Long id, + @RequestBody Transaction transaction + ) { + return transactionService.updateTransaction(id, transaction); + } + +// fetch all transactions with pagination and optional filters + @GetMapping + public Page getAllTransactions( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) TransactionCategory category, + @RequestParam(required = false) TransactionType type + ) { + Pageable pageable = PageRequest.of(page, size); + return transactionService.getAllTransactions(category, type, pageable); + } + } diff --git a/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java b/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java index 3c64a03..5bb7dbb 100644 --- a/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java +++ b/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java @@ -1,7 +1,19 @@ package com.SkillsForge.expensetracker.persistence.repository; +import com.SkillsForge.expensetracker.app.enums.TransactionCategory; +import com.SkillsForge.expensetracker.app.enums.TransactionType; import com.SkillsForge.expensetracker.persistence.entity.Transaction; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface TransactionRepository extends JpaRepository { + + Page findByCategoryAndType( + TransactionCategory category, + TransactionType type, + Pageable pageable + ); + + } diff --git a/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java b/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java index e8a8a91..bdaf3a4 100644 --- a/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java +++ b/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java @@ -1,4 +1,21 @@ package com.SkillsForge.expensetracker.service; +import com.SkillsForge.expensetracker.app.enums.TransactionCategory; +import com.SkillsForge.expensetracker.app.enums.TransactionType; +import com.SkillsForge.expensetracker.persistence.entity.Transaction; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + public interface TransactionService { + + // update an existing transaction + Transaction updateTransaction(Long id, Transaction transaction); + + // fetch all transactions with pagination + Page getAllTransactions( + TransactionCategory category, + TransactionType type, + Pageable pageable + ); + } diff --git a/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java b/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java index fefaa2c..4b3b048 100644 --- a/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java +++ b/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java @@ -1,13 +1,63 @@ package com.SkillsForge.expensetracker.service; +import com.SkillsForge.expensetracker.app.enums.TransactionCategory; +import com.SkillsForge.expensetracker.app.enums.TransactionType; +import com.SkillsForge.expensetracker.persistence.entity.Transaction; +import com.SkillsForge.expensetracker.persistence.repository.TransactionRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; + + // Implementation class of transaction service @Service @Slf4j @RequiredArgsConstructor public class TransactionServiceImpl implements TransactionService{ + + private final TransactionRepository transactionRepository; + + @Override + public Transaction updateTransaction(Long id, Transaction transaction) { + + // fetch existing transaction by id or throw error + Transaction existingTransaction = transactionRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Transaction not found")); + + // update only allowed fields + existingTransaction + .setDescription(transaction.getDescription()) + .setAmount(transaction.getAmount()) + .setCategory(transaction.getCategory()) + .setType(transaction.getType()) + .setDate(transaction.getDate()); + + // update the edited fields + existingTransaction.setUpdatedAt(LocalDateTime.now()); + + // save back to db + return transactionRepository.save(existingTransaction); + } + + @Override + public Page getAllTransactions( + TransactionCategory category, + TransactionType type, + Pageable pageable + ) { + + if (category != null && type != null) { + return transactionRepository.findByCategoryAndType(category, type, pageable); + } + + return transactionRepository.findAll(pageable); + } + + + } From b268dcf7eb088669efce04e3889086688613e9ca Mon Sep 17 00:00:00 2001 From: Adeleke Emmanuel Date: Thu, 29 Jan 2026 13:03:20 +0100 Subject: [PATCH 2/4] use DTOs, filters and JPA specifications for transactions --- .../app/dto/TransactionUpdateRequest.java | 19 ++++++++ .../app/filter/TransactionFilter.java | 13 ++++++ .../controller/TransactionController.java | 23 +++++----- .../repository/TransactionRepository.java | 18 +++----- .../TransactionSpecification.java | 33 ++++++++++++++ .../service/TransactionService.java | 12 ++---- .../service/TransactionServiceImpl.java | 43 ++++++++----------- 7 files changed, 101 insertions(+), 60 deletions(-) create mode 100644 src/main/java/com/SkillsForge/expensetracker/app/dto/TransactionUpdateRequest.java create mode 100644 src/main/java/com/SkillsForge/expensetracker/app/filter/TransactionFilter.java create mode 100644 src/main/java/com/SkillsForge/expensetracker/persistence/specification/TransactionSpecification.java diff --git a/src/main/java/com/SkillsForge/expensetracker/app/dto/TransactionUpdateRequest.java b/src/main/java/com/SkillsForge/expensetracker/app/dto/TransactionUpdateRequest.java new file mode 100644 index 0000000..97461fb --- /dev/null +++ b/src/main/java/com/SkillsForge/expensetracker/app/dto/TransactionUpdateRequest.java @@ -0,0 +1,19 @@ +package com.SkillsForge.expensetracker.app.dto; + +import com.SkillsForge.expensetracker.app.enums.TransactionCategory; +import com.SkillsForge.expensetracker.app.enums.TransactionType; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; + +@Getter +@Setter +public class TransactionUpdateRequest { + + private String description; + private TransactionCategory category; + private TransactionType type; + private Long amount; + private LocalDate date; +} diff --git a/src/main/java/com/SkillsForge/expensetracker/app/filter/TransactionFilter.java b/src/main/java/com/SkillsForge/expensetracker/app/filter/TransactionFilter.java new file mode 100644 index 0000000..e9eab30 --- /dev/null +++ b/src/main/java/com/SkillsForge/expensetracker/app/filter/TransactionFilter.java @@ -0,0 +1,13 @@ +package com.SkillsForge.expensetracker.app.filter; + +import com.SkillsForge.expensetracker.app.enums.TransactionCategory; +import com.SkillsForge.expensetracker.app.enums.TransactionType; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TransactionFilter { + private TransactionCategory category; + private TransactionType type; +} diff --git a/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java b/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java index d51ef59..53e3ba7 100644 --- a/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java +++ b/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java @@ -1,7 +1,9 @@ package com.SkillsForge.expensetracker.controller; +import com.SkillsForge.expensetracker.app.dto.TransactionUpdateRequest; import com.SkillsForge.expensetracker.app.enums.TransactionCategory; import com.SkillsForge.expensetracker.app.enums.TransactionType; +import com.SkillsForge.expensetracker.app.filter.TransactionFilter; import com.SkillsForge.expensetracker.persistence.entity.Transaction; import com.SkillsForge.expensetracker.service.TransactionService; import lombok.RequiredArgsConstructor; @@ -12,29 +14,24 @@ @RequiredArgsConstructor @RestController -@RequestMapping(value = "api/v1/transaction") +@RequestMapping("/api/v1/transactions") public class TransactionController { + private final TransactionService transactionService; -// update a transaction by id @PutMapping("/{id}") public Transaction updateTransaction( @PathVariable Long id, - @RequestBody Transaction transaction + @RequestBody TransactionUpdateRequest request ) { - return transactionService.updateTransaction(id, transaction); + return transactionService.updateTransaction(id, request); } -// fetch all transactions with pagination and optional filters @GetMapping public Page getAllTransactions( - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size, - @RequestParam(required = false) TransactionCategory category, - @RequestParam(required = false) TransactionType type + @ModelAttribute TransactionFilter filter, + Pageable pageable ) { - Pageable pageable = PageRequest.of(page, size); - return transactionService.getAllTransactions(category, type, pageable); + return transactionService.getAllTransactions(filter, pageable); } - -} +} \ No newline at end of file diff --git a/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java b/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java index 5bb7dbb..6856687 100644 --- a/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java +++ b/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java @@ -1,19 +1,11 @@ package com.SkillsForge.expensetracker.persistence.repository; -import com.SkillsForge.expensetracker.app.enums.TransactionCategory; -import com.SkillsForge.expensetracker.app.enums.TransactionType; import com.SkillsForge.expensetracker.persistence.entity.Transaction; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -public interface TransactionRepository extends JpaRepository { +public interface TransactionRepository + extends JpaRepository, + JpaSpecificationExecutor { - Page findByCategoryAndType( - TransactionCategory category, - TransactionType type, - Pageable pageable - ); - - -} +} \ No newline at end of file diff --git a/src/main/java/com/SkillsForge/expensetracker/persistence/specification/TransactionSpecification.java b/src/main/java/com/SkillsForge/expensetracker/persistence/specification/TransactionSpecification.java new file mode 100644 index 0000000..9ed4a5e --- /dev/null +++ b/src/main/java/com/SkillsForge/expensetracker/persistence/specification/TransactionSpecification.java @@ -0,0 +1,33 @@ +package com.SkillsForge.expensetracker.persistence.specification; + +import com.SkillsForge.expensetracker.app.filter.TransactionFilter; +import com.SkillsForge.expensetracker.persistence.entity.Transaction; +import org.springframework.data.jpa.domain.Specification; + +public class TransactionSpecification { + + public static Specification withFilters(TransactionFilter filter) { + + return (root, query, cb) -> { + + var predicate = cb.conjunction(); + + if (filter.getCategory() != null) { + predicate = cb.and( + predicate, + cb.equal(root.get("category"), filter.getCategory()) + ); + } + + if (filter.getType() != null) { + predicate = cb.and( + predicate, + cb.equal(root.get("type"), filter.getType()) + ); + } + return predicate; + }; + + } +} + diff --git a/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java b/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java index bdaf3a4..2c8bfc9 100644 --- a/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java +++ b/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java @@ -1,21 +1,17 @@ package com.SkillsForge.expensetracker.service; -import com.SkillsForge.expensetracker.app.enums.TransactionCategory; -import com.SkillsForge.expensetracker.app.enums.TransactionType; +import com.SkillsForge.expensetracker.app.dto.TransactionUpdateRequest; +import com.SkillsForge.expensetracker.app.filter.TransactionFilter; import com.SkillsForge.expensetracker.persistence.entity.Transaction; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; public interface TransactionService { - // update an existing transaction - Transaction updateTransaction(Long id, Transaction transaction); + Transaction updateTransaction(Long id, TransactionUpdateRequest request); - // fetch all transactions with pagination Page getAllTransactions( - TransactionCategory category, - TransactionType type, + TransactionFilter filter, Pageable pageable ); - } diff --git a/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java b/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java index 4b3b048..fbbf11e 100644 --- a/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java +++ b/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java @@ -1,10 +1,11 @@ package com.SkillsForge.expensetracker.service; -import com.SkillsForge.expensetracker.app.enums.TransactionCategory; -import com.SkillsForge.expensetracker.app.enums.TransactionType; +import com.SkillsForge.expensetracker.app.dto.TransactionUpdateRequest; +import com.SkillsForge.expensetracker.app.filter.TransactionFilter; import com.SkillsForge.expensetracker.persistence.entity.Transaction; import com.SkillsForge.expensetracker.persistence.repository.TransactionRepository; +import com.SkillsForge.expensetracker.persistence.specification.TransactionSpecification; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -23,41 +24,31 @@ public class TransactionServiceImpl implements TransactionService{ private final TransactionRepository transactionRepository; @Override - public Transaction updateTransaction(Long id, Transaction transaction) { + public Transaction updateTransaction(Long id, TransactionUpdateRequest request) { - // fetch existing transaction by id or throw error - Transaction existingTransaction = transactionRepository.findById(id) + Transaction existing = transactionRepository.findById(id) .orElseThrow(() -> new RuntimeException("Transaction not found")); - // update only allowed fields - existingTransaction - .setDescription(transaction.getDescription()) - .setAmount(transaction.getAmount()) - .setCategory(transaction.getCategory()) - .setType(transaction.getType()) - .setDate(transaction.getDate()); + existing.setDescription(request.getDescription()); + existing.setCategory(request.getCategory()); + existing.setType(request.getType()); + existing.setAmount(request.getAmount()); + existing.setDate(request.getDate()); + existing.setUpdatedAt(LocalDateTime.now()); - // update the edited fields - existingTransaction.setUpdatedAt(LocalDateTime.now()); - - // save back to db - return transactionRepository.save(existingTransaction); + return transactionRepository.save(existing); } @Override public Page getAllTransactions( - TransactionCategory category, - TransactionType type, + TransactionFilter filter, Pageable pageable ) { - - if (category != null && type != null) { - return transactionRepository.findByCategoryAndType(category, type, pageable); - } - - return transactionRepository.findAll(pageable); + return transactionRepository.findAll( + TransactionSpecification.withFilters(filter), + pageable + ); } - } From 8eaaf021ebb7e7a1fb702a53a033ac7fa294c6b2 Mon Sep 17 00:00:00 2001 From: Adeleke Emmanuel Date: Sun, 1 Feb 2026 22:15:15 +0100 Subject: [PATCH 3/4] refactor transactions to use DTOs and inline specifications --- .../app/dto/TransactionDto.java | 22 ++++++++ .../controller/TransactionController.java | 11 ++-- .../repository/TransactionRepository.java | 3 +- .../TransactionSpecification.java | 33 ------------ .../service/TransactionService.java | 12 ++--- .../service/TransactionServiceImpl.java | 52 ++++++++++++++----- 6 files changed, 72 insertions(+), 61 deletions(-) create mode 100644 src/main/java/com/SkillsForge/expensetracker/app/dto/TransactionDto.java delete mode 100644 src/main/java/com/SkillsForge/expensetracker/persistence/specification/TransactionSpecification.java diff --git a/src/main/java/com/SkillsForge/expensetracker/app/dto/TransactionDto.java b/src/main/java/com/SkillsForge/expensetracker/app/dto/TransactionDto.java new file mode 100644 index 0000000..fad9835 --- /dev/null +++ b/src/main/java/com/SkillsForge/expensetracker/app/dto/TransactionDto.java @@ -0,0 +1,22 @@ +package com.SkillsForge.expensetracker.app.dto; + +import com.SkillsForge.expensetracker.app.enums.TransactionCategory; +import com.SkillsForge.expensetracker.app.enums.TransactionType; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@Builder +public class TransactionDto { + + private Long id; + private String description; + private TransactionCategory category; + private TransactionType type; + private Long amount; + private LocalDate date; + private LocalDateTime createdAt; +} diff --git a/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java b/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java index 53e3ba7..55092a6 100644 --- a/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java +++ b/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java @@ -1,14 +1,11 @@ package com.SkillsForge.expensetracker.controller; +import com.SkillsForge.expensetracker.app.dto.TransactionDto; import com.SkillsForge.expensetracker.app.dto.TransactionUpdateRequest; -import com.SkillsForge.expensetracker.app.enums.TransactionCategory; -import com.SkillsForge.expensetracker.app.enums.TransactionType; import com.SkillsForge.expensetracker.app.filter.TransactionFilter; -import com.SkillsForge.expensetracker.persistence.entity.Transaction; import com.SkillsForge.expensetracker.service.TransactionService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.*; @@ -20,7 +17,7 @@ public class TransactionController { private final TransactionService transactionService; @PutMapping("/{id}") - public Transaction updateTransaction( + public TransactionDto updateTransaction( @PathVariable Long id, @RequestBody TransactionUpdateRequest request ) { @@ -28,10 +25,10 @@ public Transaction updateTransaction( } @GetMapping - public Page getAllTransactions( + public Page getAllTransactions( @ModelAttribute TransactionFilter filter, Pageable pageable ) { return transactionService.getAllTransactions(filter, pageable); } -} \ No newline at end of file +} diff --git a/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java b/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java index 6856687..b97dd32 100644 --- a/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java +++ b/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java @@ -7,5 +7,4 @@ public interface TransactionRepository extends JpaRepository, JpaSpecificationExecutor { - -} \ No newline at end of file +} diff --git a/src/main/java/com/SkillsForge/expensetracker/persistence/specification/TransactionSpecification.java b/src/main/java/com/SkillsForge/expensetracker/persistence/specification/TransactionSpecification.java deleted file mode 100644 index 9ed4a5e..0000000 --- a/src/main/java/com/SkillsForge/expensetracker/persistence/specification/TransactionSpecification.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.SkillsForge.expensetracker.persistence.specification; - -import com.SkillsForge.expensetracker.app.filter.TransactionFilter; -import com.SkillsForge.expensetracker.persistence.entity.Transaction; -import org.springframework.data.jpa.domain.Specification; - -public class TransactionSpecification { - - public static Specification withFilters(TransactionFilter filter) { - - return (root, query, cb) -> { - - var predicate = cb.conjunction(); - - if (filter.getCategory() != null) { - predicate = cb.and( - predicate, - cb.equal(root.get("category"), filter.getCategory()) - ); - } - - if (filter.getType() != null) { - predicate = cb.and( - predicate, - cb.equal(root.get("type"), filter.getType()) - ); - } - return predicate; - }; - - } -} - diff --git a/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java b/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java index 2c8bfc9..79053bc 100644 --- a/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java +++ b/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java @@ -1,17 +1,17 @@ package com.SkillsForge.expensetracker.service; +import com.SkillsForge.expensetracker.app.dto.TransactionDto; import com.SkillsForge.expensetracker.app.dto.TransactionUpdateRequest; import com.SkillsForge.expensetracker.app.filter.TransactionFilter; -import com.SkillsForge.expensetracker.persistence.entity.Transaction; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; public interface TransactionService { - Transaction updateTransaction(Long id, TransactionUpdateRequest request); - Page getAllTransactions( - TransactionFilter filter, - Pageable pageable - ); + TransactionDto updateTransaction(Long id, TransactionUpdateRequest request); + + Page getAllTransactions(TransactionFilter filter, Pageable pageable); + + } diff --git a/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java b/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java index fbbf11e..70fb0e8 100644 --- a/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java +++ b/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java @@ -1,30 +1,31 @@ package com.SkillsForge.expensetracker.service; - +import com.SkillsForge.expensetracker.app.dto.TransactionDto; import com.SkillsForge.expensetracker.app.dto.TransactionUpdateRequest; import com.SkillsForge.expensetracker.app.filter.TransactionFilter; import com.SkillsForge.expensetracker.persistence.entity.Transaction; import com.SkillsForge.expensetracker.persistence.repository.TransactionRepository; -import com.SkillsForge.expensetracker.persistence.specification.TransactionSpecification; +import jakarta.persistence.criteria.Predicate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; - -// Implementation class of transaction service @Service @Slf4j @RequiredArgsConstructor -public class TransactionServiceImpl implements TransactionService{ +public class TransactionServiceImpl implements TransactionService { private final TransactionRepository transactionRepository; @Override - public Transaction updateTransaction(Long id, TransactionUpdateRequest request) { + public TransactionDto updateTransaction(Long id, TransactionUpdateRequest request) { Transaction existing = transactionRepository.findById(id) .orElseThrow(() -> new RuntimeException("Transaction not found")); @@ -36,19 +37,44 @@ public Transaction updateTransaction(Long id, TransactionUpdateRequest request) existing.setDate(request.getDate()); existing.setUpdatedAt(LocalDateTime.now()); - return transactionRepository.save(existing); + Transaction saved = transactionRepository.save(existing); + + return mapToDto(saved); } @Override - public Page getAllTransactions( + public Page getAllTransactions( TransactionFilter filter, Pageable pageable ) { - return transactionRepository.findAll( - TransactionSpecification.withFilters(filter), - pageable - ); - } + Specification spec = (root, query, cb) -> { + List predicates = new ArrayList<>(); + + if (filter.getCategory() != null) { + predicates.add(cb.equal(root.get("category"), filter.getCategory())); + } + if (filter.getType() != null) { + predicates.add(cb.equal(root.get("type"), filter.getType())); + } + + return cb.and(predicates.toArray(new Predicate[0])); + }; + + return transactionRepository.findAll(spec, pageable) + .map(this::mapToDto); + } + + private TransactionDto mapToDto(Transaction transaction) { + return TransactionDto.builder() + .id(transaction.getId()) + .description(transaction.getDescription()) + .category(transaction.getCategory()) + .type(transaction.getType()) + .amount(transaction.getAmount()) + .date(transaction.getDate()) + .createdAt(transaction.getCreatedAt()) + .build(); + } } From ac2c03d57485b9308d65db597b73182438308056 Mon Sep 17 00:00:00 2001 From: Adeleke Emmanuel Date: Mon, 2 Feb 2026 00:46:07 +0100 Subject: [PATCH 4/4] Resolved all conflict --- .../controller/TransactionController.java | 3 +- .../dto/TransactionUpdateRequest.java | 2 +- .../service/TransactionService.java | 10 +- .../service/TransactionServiceImpl.java | 105 +++++++++--------- 4 files changed, 58 insertions(+), 62 deletions(-) diff --git a/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java b/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java index be8ded3..2391e98 100644 --- a/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java +++ b/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java @@ -2,9 +2,8 @@ import com.SkillsForge.expensetracker.dto.CreateTransactionRequest; import com.SkillsForge.expensetracker.dto.TransactionDto; -import com.SkillsForge.expensetracker.app.dto.TransactionDto; -import com.SkillsForge.expensetracker.app.dto.TransactionUpdateRequest; import com.SkillsForge.expensetracker.app.filter.TransactionFilter; +import com.SkillsForge.expensetracker.dto.TransactionUpdateRequest; import com.SkillsForge.expensetracker.service.TransactionService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; diff --git a/src/main/java/com/SkillsForge/expensetracker/dto/TransactionUpdateRequest.java b/src/main/java/com/SkillsForge/expensetracker/dto/TransactionUpdateRequest.java index 97461fb..c7564f0 100644 --- a/src/main/java/com/SkillsForge/expensetracker/dto/TransactionUpdateRequest.java +++ b/src/main/java/com/SkillsForge/expensetracker/dto/TransactionUpdateRequest.java @@ -1,4 +1,4 @@ -package com.SkillsForge.expensetracker.app.dto; +package com.SkillsForge.expensetracker.dto; import com.SkillsForge.expensetracker.app.enums.TransactionCategory; import com.SkillsForge.expensetracker.app.enums.TransactionType; diff --git a/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java b/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java index b730d85..70850cf 100644 --- a/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java +++ b/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java @@ -1,23 +1,21 @@ package com.SkillsForge.expensetracker.service; -import com.SkillsForge.expensetracker.app.dto.TransactionDto; -import com.SkillsForge.expensetracker.app.dto.TransactionUpdateRequest; +import com.SkillsForge.expensetracker.dto.TransactionDto; +import com.SkillsForge.expensetracker.dto.TransactionUpdateRequest; import com.SkillsForge.expensetracker.app.filter.TransactionFilter; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import com.SkillsForge.expensetracker.dto.CreateTransactionRequest; -import com.SkillsForge.expensetracker.dto.TransactionDto; public interface TransactionService { TransactionDto createTransaction(CreateTransactionRequest request); TransactionDto getTransactionById(Long id); + TransactionDto updateTransaction(Long id, TransactionUpdateRequest request); - TransactionDto updateTransaction(Long id, TransactionUpdateRequest request); - - Page getAllTransactions(TransactionFilter filter, Pageable pageable); + Page getAllTransactions(TransactionFilter filter, Pageable pageable); } diff --git a/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java b/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java index 8f19906..ed23fd7 100644 --- a/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java +++ b/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java @@ -1,8 +1,18 @@ package com.SkillsForge.expensetracker.service; - +import com.SkillsForge.expensetracker.app.filter.TransactionFilter; +import com.SkillsForge.expensetracker.dto.CreateTransactionRequest; +import com.SkillsForge.expensetracker.dto.TransactionDto; +import com.SkillsForge.expensetracker.dto.TransactionUpdateRequest; +import com.SkillsForge.expensetracker.exception.ResourceNotFoundException; +import com.SkillsForge.expensetracker.persistence.entity.Transaction; +import com.SkillsForge.expensetracker.persistence.repository.TransactionRepository; +import jakarta.persistence.criteria.Predicate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -15,45 +25,45 @@ @RequiredArgsConstructor public class TransactionServiceImpl implements TransactionService { - private final TransactionRepository transactionRepository; - - @Override - @Transactional - public TransactionDto createTransaction(CreateTransactionRequest request) { - LocalDateTime now = LocalDateTime.now(); - - Transaction transaction = new Transaction(request); - transaction.setCreatedAt(now); - transaction.setUpdatedAt(now); - - Transaction savedTransaction = transactionRepository.save(transaction); - log.info("Transaction created successfully with ID: {}", savedTransaction.getId()); - return TransactionDto.fromEntity(savedTransaction); - } - - @Override - @Transactional(readOnly = true) - public TransactionDto getTransactionById(Long id) { - log.info("Fetching transaction with ID: {}", id); - - return TransactionDto.fromEntity( - transactionRepository - .findById(id) - .orElseThrow( - () -> { - log.error("Transaction not found with ID: {}", id); - return new ResourceNotFoundException("Transaction not found with ID: " + id); - })); - } -public class TransactionServiceImpl implements TransactionService { - private final TransactionRepository transactionRepository; + // ================= CREATE ================= + @Override + @Transactional + public TransactionDto createTransaction(CreateTransactionRequest request) { + LocalDateTime now = LocalDateTime.now(); + + Transaction transaction = new Transaction(request); + transaction.setCreatedAt(now); + transaction.setUpdatedAt(now); + + Transaction saved = transactionRepository.save(transaction); + log.info("Transaction created successfully with ID: {}", saved.getId()); + + return TransactionDto.fromEntity(saved); + } + + // ================= GET BY ID ================= + @Override + @Transactional(readOnly = true) + public TransactionDto getTransactionById(Long id) { + log.info("Fetching transaction with ID: {}", id); + + Transaction transaction = transactionRepository.findById(id) + .orElseThrow(() -> + new ResourceNotFoundException("Transaction not found with ID: " + id)); + + return TransactionDto.fromEntity(transaction); + } + + // ================= UPDATE ================= @Override + @Transactional public TransactionDto updateTransaction(Long id, TransactionUpdateRequest request) { Transaction existing = transactionRepository.findById(id) - .orElseThrow(() -> new RuntimeException("Transaction not found")); + .orElseThrow(() -> + new ResourceNotFoundException("Transaction not found with ID: " + id)); existing.setDescription(request.getDescription()); existing.setCategory(request.getCategory()); @@ -63,15 +73,15 @@ public TransactionDto updateTransaction(Long id, TransactionUpdateRequest reques existing.setUpdatedAt(LocalDateTime.now()); Transaction saved = transactionRepository.save(existing); + log.info("Transaction updated successfully with ID: {}", saved.getId()); - return mapToDto(saved); + return TransactionDto.fromEntity(saved); } + // ================= GET ALL (FILTERED) ================= @Override - public Page getAllTransactions( - TransactionFilter filter, - Pageable pageable - ) { + @Transactional(readOnly = true) + public Page getAllTransactions(TransactionFilter filter, Pageable pageable) { Specification spec = (root, query, cb) -> { List predicates = new ArrayList<>(); @@ -87,19 +97,8 @@ public Page getAllTransactions( return cb.and(predicates.toArray(new Predicate[0])); }; - return transactionRepository.findAll(spec, pageable) - .map(this::mapToDto); - } - - private TransactionDto mapToDto(Transaction transaction) { - return TransactionDto.builder() - .id(transaction.getId()) - .description(transaction.getDescription()) - .category(transaction.getCategory()) - .type(transaction.getType()) - .amount(transaction.getAmount()) - .date(transaction.getDate()) - .createdAt(transaction.getCreatedAt()) - .build(); + return transactionRepository + .findAll(spec, pageable) + .map(TransactionDto::fromEntity); } }