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
5 changes: 5 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ services:
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: redis
ports:
- "6379:6379"

app:
build: .
Expand Down
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
Expand Down
34 changes: 34 additions & 0 deletions src/main/java/com/guilherme/finance_api/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.guilherme.finance_api.config;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.time.Duration;

@Configuration
@EnableCaching
public class RedisConfig {

@Bean
@Primary
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()
)
);

return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.guilherme.finance_api.dto.TransactionFilter;
import com.guilherme.finance_api.dto.TransactionRequest;
import com.guilherme.finance_api.dto.TransactionResponse;
import com.guilherme.finance_api.dto.TransactionSummary;
import com.guilherme.finance_api.entity.TransactionType;
import com.guilherme.finance_api.service.TransactionService;
import jakarta.validation.Valid;
Expand All @@ -13,6 +14,7 @@
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -60,6 +62,15 @@ public ResponseEntity<TransactionResponse> save(@Valid @RequestBody TransactionR
return ResponseEntity.status(HttpStatus.CREATED).body(savedTransaction);
}

@GetMapping("/summary")
public ResponseEntity<TransactionSummary> getSummary(
@RequestParam(required = false) String month) {
String email = SecurityContextHolder.getContext()
.getAuthentication()
.getName();
return ResponseEntity.ok(transactionService.getSummary(month, email));
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
transactionService.delete(id);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.guilherme.finance_api.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TransactionSummary {
private BigDecimal totalIncome;
private BigDecimal totalExpense;
private BigDecimal balance;
private String month;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.guilherme.finance_api.dto.TransactionFilter;
import com.guilherme.finance_api.dto.TransactionRequest;
import com.guilherme.finance_api.dto.TransactionResponse;
import com.guilherme.finance_api.dto.TransactionSummary;
import com.guilherme.finance_api.entity.Category;
import com.guilherme.finance_api.entity.Transaction;
import com.guilherme.finance_api.entity.TransactionType;
import com.guilherme.finance_api.entity.User;
import com.guilherme.finance_api.event.TransactionEvent;
import com.guilherme.finance_api.event.TransactionPublisher;
Expand All @@ -13,15 +15,23 @@
import com.guilherme.finance_api.repository.TransactionRepository;
import com.guilherme.finance_api.repository.UserRepository;
import com.guilherme.finance_api.specification.TransactionSpecification;
import org.springframework.cache.annotation.Cacheable;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import org.springframework.data.domain.Pageable;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.List;


@Data
@Service
Expand Down Expand Up @@ -80,6 +90,7 @@ public TransactionResponse update(Long id, TransactionRequest request) {
return toResponse(savedTransaction);
}

@CacheEvict(value = "transaction-summary", allEntries = true)
public TransactionResponse save(TransactionRequest request) {
Transaction transaction = new Transaction();
transaction.setDescription(request.getDescription());
Expand Down Expand Up @@ -114,6 +125,42 @@ public TransactionResponse save(TransactionRequest request) {
return toResponse(savedTransaction);
}

@Cacheable(value = "transaction-summary", key = "#month ?: 'current' + '::' + #email")
public TransactionSummary getSummary(String month, String email) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));

YearMonth yearMonth = month != null
? YearMonth.parse(month)
: YearMonth.now();

LocalDate start = yearMonth.atDay(1);
LocalDate end = yearMonth.atEndOfMonth();

Specification<Transaction> spec = Specification.<Transaction>unrestricted()
.and(TransactionSpecification.hasUser(user.getId()))
.and(TransactionSpecification.dateBetween(start, end));

List<Transaction> transactions = transactionRepository.findAll(spec);

BigDecimal totalIncome = transactions.stream()
.filter(t -> t.getType() == TransactionType.INCOME)
.map(Transaction::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);

BigDecimal totalExpense = transactions.stream()
.filter(t -> t.getType() == TransactionType.EXPENSE)
.map(Transaction::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);

return new TransactionSummary(
totalIncome,
totalExpense,
totalIncome.subtract(totalExpense),
yearMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))
);
}

public void delete(Long id) {
if (!transactionRepository.existsById(id)) {
throw new ResourceNotFoundException("Transaction not found");
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ spring.rabbitmq.username=${RABBITMQ_USER:guest}
spring.rabbitmq.password=${RABBITMQ_PASSWORD:guest}
rabbitmq.exchange=transactions.exchange
rabbitmq.routing-key=transactions.created
spring.data.redis.host=${REDIS_HOST:localhost}
spring.data.redis.port=${REDIS_PORT:6379}
Loading