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 7a5674d..2391e98 100644 --- a/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java +++ b/src/main/java/com/SkillsForge/expensetracker/controller/TransactionController.java @@ -2,18 +2,22 @@ import com.SkillsForge.expensetracker.dto.CreateTransactionRequest; import com.SkillsForge.expensetracker.dto.TransactionDto; +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; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.*; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -@Slf4j @RequiredArgsConstructor @RestController -@RequestMapping(value = "/api/v1/transaction") +@RequestMapping("/api/v1/transactions") public class TransactionController { private final TransactionService transactionService; @@ -29,4 +33,20 @@ public ResponseEntity getTransactionById(@PathVariable Long id) TransactionDto response = transactionService.getTransactionById(id); return ResponseEntity.ok(response); } + + @PutMapping("/{id}") + public TransactionDto updateTransaction( + @PathVariable Long id, + @RequestBody TransactionUpdateRequest request + ) { + return transactionService.updateTransaction(id, request); + } + + @GetMapping + public Page getAllTransactions( + @ModelAttribute TransactionFilter filter, + Pageable pageable + ) { + return transactionService.getAllTransactions(filter, pageable); + } } diff --git a/src/main/java/com/SkillsForge/expensetracker/dto/TransactionUpdateRequest.java b/src/main/java/com/SkillsForge/expensetracker/dto/TransactionUpdateRequest.java new file mode 100644 index 0000000..c7564f0 --- /dev/null +++ b/src/main/java/com/SkillsForge/expensetracker/dto/TransactionUpdateRequest.java @@ -0,0 +1,19 @@ +package com.SkillsForge.expensetracker.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/persistence/repository/TransactionRepository.java b/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java index 2f4330b..b97dd32 100644 --- a/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java +++ b/src/main/java/com/SkillsForge/expensetracker/persistence/repository/TransactionRepository.java @@ -2,5 +2,9 @@ import com.SkillsForge.expensetracker.persistence.entity.Transaction; 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 { +} diff --git a/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java b/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java index 25dfe2f..70850cf 100644 --- a/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java +++ b/src/main/java/com/SkillsForge/expensetracker/service/TransactionService.java @@ -1,10 +1,21 @@ package com.SkillsForge.expensetracker.service; -import com.SkillsForge.expensetracker.dto.CreateTransactionRequest; 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; public interface TransactionService { TransactionDto createTransaction(CreateTransactionRequest request); TransactionDto getTransactionById(Long id); + + 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 04d38ff..ed23fd7 100644 --- a/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java +++ b/src/main/java/com/SkillsForge/expensetracker/service/TransactionServiceImpl.java @@ -1,49 +1,104 @@ 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 java.time.LocalDateTime; +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; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + @Service @Slf4j @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); - })); - } + 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 ResourceNotFoundException("Transaction not found with ID: " + id)); + + existing.setDescription(request.getDescription()); + existing.setCategory(request.getCategory()); + existing.setType(request.getType()); + existing.setAmount(request.getAmount()); + existing.setDate(request.getDate()); + existing.setUpdatedAt(LocalDateTime.now()); + + Transaction saved = transactionRepository.save(existing); + log.info("Transaction updated successfully with ID: {}", saved.getId()); + + return TransactionDto.fromEntity(saved); + } + + // ================= GET ALL (FILTERED) ================= + @Override + @Transactional(readOnly = true) + public Page getAllTransactions(TransactionFilter filter, Pageable 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(TransactionDto::fromEntity); + } }