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
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@
<dependency>
<groupId>com.peralta.cashflow</groupId>
<artifactId>cashflow-commons</artifactId>
<version>0.0.3</version>
<version>0.0.5</version>
</dependency>
<dependency>
<groupId>com.peralta.cashflow</groupId>
Expand All @@ -182,6 +182,11 @@
<artifactId>cashflow-exceptions</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>com.peralta.cashflow</groupId>
<artifactId>cashflow-cache</artifactId>
<version>1.0.0</version>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.cashflow.coredata.config;

import com.cashflow.coredata.utils.constants.cache.CacheNames;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;
import java.util.Map;

@Configuration
public class CacheDurationConfig {

@Value("${cache.duration.categories}")
private Long categoriesCacheDuration;

@Bean
public Map<String, Duration> cacheDurations() {
return Map.of(
CacheNames.CATEGORIES, Duration.ofMinutes(categoriesCacheDuration)
);
}

}
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package com.cashflow.coredata.controller.category;

import org.springframework.web.bind.annotation.*;

import com.cashflow.auth.core.utils.AuthUtils;
import com.cashflow.commons.core.dto.request.BaseRequest;
import com.cashflow.commons.core.dto.request.PageRequest;
import com.cashflow.commons.core.dto.response.PageResponse;
import com.cashflow.coredata.domain.dto.request.category.CategoryCreationRequest;
import com.cashflow.coredata.domain.dto.response.CategoryResponse;
import com.cashflow.coredata.service.category.ICategoryService;
import com.cashflow.exception.core.CashFlowException;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.Locale;

Expand All @@ -34,16 +36,20 @@ public CategoryController(final ICategoryService categoryService) {
@PostMapping
public CategoryResponse registerCategory(CategoryCreationRequest request, Locale language) throws CashFlowException {
log.info("Received request to register a new category with name: {}", request.name());
return categoryService.registerCategory(new BaseRequest<>(language, request));
return categoryService.registerCategory(
new BaseRequest<>(language, request),
AuthUtils.getUserIdFromSecurityContext()
);
}

@Override
@GetMapping("/list")
public Page<CategoryResponse> listCategories(Locale language, int pageNumber, int pageSize, String search) {
public PageResponse<CategoryResponse> listCategories(Locale language, int pageNumber, int pageSize, String search) {
log.info("Received request to list categories..");
return categoryService.listCategories(new PageRequest<>(
pageNumber, pageSize, language, search
));
return categoryService.listCategories(
new PageRequest<>(pageNumber, pageSize, language, search),
AuthUtils.getUserIdFromSecurityContext()
);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cashflow.coredata.controller.category;

import com.cashflow.commons.core.dto.response.PageResponse;
import com.cashflow.coredata.domain.dto.request.category.CategoryCreationRequest;
import com.cashflow.coredata.domain.dto.response.CategoryResponse;
import com.cashflow.exception.core.CashFlowException;
Expand All @@ -13,7 +14,6 @@
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
Expand Down Expand Up @@ -72,7 +72,7 @@ CategoryResponse registerCategory(
@ApiResponse(responseCode = "200", description = "List of categories retrieved",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = CategoryResponse.class)))
})
Page<CategoryResponse> listCategories(
PageResponse<CategoryResponse> listCategories(
@RequestHeader(name = "Accept-Language", required = false, defaultValue = "en") Locale language,
@RequestParam(name = "pageNumber", required = false, defaultValue = "0") int pageNumber,
@RequestParam(name = "pageSize", required = false, defaultValue = "10") int pageSize,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,53 @@
package com.cashflow.coredata.service.category;

import com.cashflow.auth.core.utils.AuthUtils;
import com.cashflow.cache.service.CacheService;
import com.cashflow.commons.core.dto.request.BaseRequest;
import com.cashflow.commons.core.dto.request.PageRequest;
import com.cashflow.commons.core.dto.response.PageResponse;
import com.cashflow.coredata.domain.dto.request.category.CategoryCreationRequest;
import com.cashflow.coredata.domain.dto.response.CategoryResponse;
import com.cashflow.coredata.domain.entities.Category;
import com.cashflow.coredata.domain.mapper.category.CategoryMapper;
import com.cashflow.coredata.domain.validator.category.CategoryValidator;
import com.cashflow.coredata.repository.category.CategoryRepository;
import com.cashflow.coredata.utils.constants.cache.CacheNames;
import com.cashflow.exception.core.CashFlowException;
import jakarta.transaction.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.MessageSource;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;

@Service
public class CategoryService implements ICategoryService {

Logger log = LoggerFactory.getLogger(CategoryService.class);
@Value("${cache.key-prefix}")
private String cacheKeyPrefix;

private final Logger log = LoggerFactory.getLogger(CategoryService.class);

private final CategoryRepository categoryRepository;

private final MessageSource messageSource;

private final CacheService cacheService;

public CategoryService(final CategoryRepository categoryRepository,
final MessageSource messageSource) {
final MessageSource messageSource,
final CacheService cacheService) {
this.categoryRepository = categoryRepository;
this.messageSource = messageSource;
this.cacheService = cacheService;
}

@Override
public CategoryResponse registerCategory(BaseRequest<CategoryCreationRequest> baseRequest) throws CashFlowException {
@Transactional
public CategoryResponse registerCategory(BaseRequest<CategoryCreationRequest> baseRequest, Long userId) throws CashFlowException {

CategoryCreationRequest request = baseRequest.getRequest();
Long userId = AuthUtils.getUserIdFromSecurityContext();

CategoryValidator.validateCategoryCreation(
categoryExistsByName(request.name(), userId),
Expand All @@ -50,6 +62,10 @@ public CategoryResponse registerCategory(BaseRequest<CategoryCreationRequest> ba

log.info("Category created successfully!");

cacheService.invalidateCacheByPattern(
cacheKeyPrefix + CacheNames.CATEGORIES + CacheNames.SEPARATOR + userId + "-*"
);

return CategoryMapper.mapToResponse(category);

}
Expand All @@ -59,9 +75,12 @@ private boolean categoryExistsByName(String name, Long userId) {
}

@Override
public Page<CategoryResponse> listCategories(PageRequest<Void> request) {
@Cacheable(
value = CacheNames.CATEGORIES,
key = "#userId + '-' + #request.search + '-' + #request.pageable.pageNumber + '-' + #request.pageable.pageSize"
)
public PageResponse<CategoryResponse> listCategories(PageRequest<Void> request, Long userId) {

Long userId = AuthUtils.getUserIdFromSecurityContext();
String search = request.getSearch();

log.info("Searching user: {} categories with search: {}", userId, search);
Expand All @@ -70,7 +89,13 @@ public Page<CategoryResponse> listCategories(PageRequest<Void> request) {

log.info("Found {} categories!", response.getTotalElements());

return response;
return new PageResponse<>(
response.getContent(),
response.getPageable().getPageNumber(),
response.getPageable().getPageSize(),
response.getTotalElements(),
response.getTotalPages()
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import com.cashflow.commons.core.dto.request.BaseRequest;
import com.cashflow.commons.core.dto.request.PageRequest;
import com.cashflow.commons.core.dto.response.PageResponse;
import com.cashflow.coredata.domain.dto.request.category.CategoryCreationRequest;
import com.cashflow.coredata.domain.dto.response.CategoryResponse;
import com.cashflow.exception.core.CashFlowException;
import org.springframework.data.domain.Page;

public interface ICategoryService {

CategoryResponse registerCategory(BaseRequest<CategoryCreationRequest> baseRequest) throws CashFlowException;
Page<CategoryResponse> listCategories(PageRequest<Void> request);
CategoryResponse registerCategory(BaseRequest<CategoryCreationRequest> baseRequest, Long userId) throws CashFlowException;
PageResponse<CategoryResponse> listCategories(PageRequest<Void> request, Long userId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.cashflow.coredata.utils.constants.cache;

public class CacheNames {

private CacheNames() {}

public static final String SEPARATOR = "::";
public static final String CATEGORIES = "categories";

}
10 changes: 10 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,23 @@ server:
# Spring Config

spring:
# DataBase
datasource:
url: ${DATABASE_CONNECTION_STRING}
username: ${DATABASE_USERNAME}
password: ${DATABASE_PASSWORD}
autoconfigure:
exclude:
- org.springframework.boot.security.autoconfigure.UserDetailsServiceAutoConfiguration
# Cache
data:
redis:
url: ${REDIS_CONNECTION_STRING}

cache:
key-prefix: "cashflow-core-data::"
duration:
categories: ${CACHE_DURATION_CATEGORIES}

management:
endpoints:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
package com.cashflow.coredata.controller.category;

import com.cashflow.auth.core.domain.authentication.CashFlowAuthentication;
import com.cashflow.commons.core.dto.response.PageResponse;
import com.cashflow.coredata.domain.dto.response.CategoryResponse;
import com.cashflow.coredata.service.category.ICategoryService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.http.MediaType;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import templates.category.CategoryTemplates;
import templates.security.AuthenticationTemplates;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -36,20 +40,28 @@ class CategoryControllerTest {

private static final String BASE_REQUEST_URL = "/core/category";
private final CategoryResponse categoryResponse = CategoryTemplates.categoryResponse();
private final CashFlowAuthentication authentication = AuthenticationTemplates.cashFlowAuthentication();

@Autowired
CategoryControllerTest(final MockMvc mockMvc) {
this.mockMvc = mockMvc;
this.objectMapper = new ObjectMapper();
}

@BeforeEach
void setup() {
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}

@Test
@SneakyThrows
void givenCategoryCreationRequest_whenRegisterCategory_thenCategoryResponseIsReturned() {

String jsonRequest = objectMapper.writeValueAsString(CategoryTemplates.categoryCreationRequest());

when(categoryService.registerCategory(any())).thenReturn(categoryResponse);
when(categoryService.registerCategory(any(), any())).thenReturn(categoryResponse);

mockMvc.perform(MockMvcRequestBuilders.post(BASE_REQUEST_URL)
.contentType(MediaType.APPLICATION_JSON)
Expand All @@ -62,9 +74,9 @@ void givenCategoryCreationRequest_whenRegisterCategory_thenCategoryResponseIsRet
@SneakyThrows
void givenParameter_whenListCategories_thenReturnCategoryResponsePage() {

Page<CategoryResponse> response = new PageImpl<>(new ArrayList<>(List.of(categoryResponse)));
PageResponse<CategoryResponse> response = new PageResponse<>(new ArrayList<>(List.of(categoryResponse)), 1, 0, 10, 1);

when(categoryService.listCategories(any())).thenReturn(response);
when(categoryService.listCategories(any(), any())).thenReturn(response);

mockMvc.perform(MockMvcRequestBuilders.get(BASE_REQUEST_URL + "/list")
.contentType(MediaType.APPLICATION_JSON))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.cashflow.coredata.repository.category;

import com.cashflow.cache.service.CacheService;
import com.cashflow.commons.core.dto.request.PageRequest;
import com.cashflow.coredata.domain.dto.response.CategoryResponse;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;

import java.util.Locale;

Expand All @@ -20,6 +22,9 @@ class CategoryRepositoryTest {
@Autowired
private CategoryRepository categoryRepository;

@MockitoBean
private CacheService cacheService;

private final PageRequest<Void> pageRequest = new PageRequest<>(0, 2, Locale.ENGLISH, "");

@Test
Expand Down
Loading