Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
@Getter
@Setter
public class TransactionFilter {
private TransactionCategory category;
private TransactionType type;
private TransactionCategory category;
private TransactionType type;
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package com.SkillsForge.expensetracker.controller;

import com.SkillsForge.expensetracker.app.filter.TransactionFilter;
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.*;

Expand All @@ -22,31 +19,26 @@ public class TransactionController {
private final TransactionService transactionService;

@PostMapping
public ResponseEntity<TransactionDto> createTransaction(
@ResponseStatus(HttpStatus.CREATED)
public TransactionDto createTransaction(
@RequestBody @Validated CreateTransactionRequest request) {
TransactionDto response = transactionService.createTransaction(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
return transactionService.createTransaction(request);
}

@GetMapping("/{id}")
public ResponseEntity<TransactionDto> getTransactionById(@PathVariable Long id) {
TransactionDto response = transactionService.getTransactionById(id);
return ResponseEntity.ok(response);
public TransactionDto getTransactionById(@PathVariable Long id) {
return transactionService.getTransactionById(id);
}

@PutMapping("/{id}")
public TransactionDto updateTransaction(
@PathVariable Long id,
@RequestBody TransactionUpdateRequest request
) {
@PathVariable Long id, @RequestBody TransactionUpdateRequest request) {
return transactionService.updateTransaction(id, request);
}

@GetMapping
public Page<TransactionDto> getAllTransactions(
@ModelAttribute TransactionFilter filter,
Pageable pageable
) {
@ModelAttribute TransactionFilter filter, Pageable pageable) {
return transactionService.getAllTransactions(filter, pageable);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@

import com.SkillsForge.expensetracker.app.enums.TransactionCategory;
import com.SkillsForge.expensetracker.app.enums.TransactionType;
import java.time.LocalDate;
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;
private String description;
private TransactionCategory category;
private TransactionType type;
private Long amount;
private LocalDate date;
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ public class Transaction extends BaseEntity {
private LocalDate date; // the date the transaction was made

@Column(nullable = false)
private Long
amount; // We save amount in kobo value so 1.50 naira will be saved as 150
private Long amount; // We save amount in kobo value so 1.50 naira will be saved as 150

// constructor to accept dto
public Transaction(CreateTransactionRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,4 @@
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface TransactionRepository
extends JpaRepository<Transaction, Long>,
JpaSpecificationExecutor<Transaction> {
}
extends JpaRepository<Transaction, Long>, JpaSpecificationExecutor<Transaction> {}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
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.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);

Expand All @@ -16,6 +15,4 @@ public interface TransactionService {
TransactionDto updateTransaction(Long id, TransactionUpdateRequest request);

Page<TransactionDto> getAllTransactions(TransactionFilter filter, Pageable pageable);


}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import com.SkillsForge.expensetracker.persistence.entity.Transaction;
import com.SkillsForge.expensetracker.persistence.repository.TransactionRepository;
import jakarta.persistence.criteria.Predicate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
Expand All @@ -16,89 +19,88 @@
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;

// ================= 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<TransactionDto> getAllTransactions(TransactionFilter filter, Pageable pageable) {

Specification<Transaction> spec = (root, query, cb) -> {
List<Predicate> 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]));
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<TransactionDto> getAllTransactions(TransactionFilter filter, Pageable pageable) {

Specification<Transaction> spec =
(root, query, cb) -> {
List<Predicate> 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);
}
return transactionRepository.findAll(spec, pageable).map(TransactionDto::fromEntity);
}
}