Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [PagoPa-1797] paging and caching #70

Merged
merged 15 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
4 changes: 4 additions & 0 deletions helm/values-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ microservice-chart:
GENERATE_PDF_RETRY_MAX_DELAY: "10000"
GENERATE_PDF_RETRY_MAX_ATTEMPTS: "1"
CONNECTION_TIMEOUT: "10000"
REDIS_PORT: "6380"
REDIS_TTL: "5" # 5 minutes
OTEL_SERVICE_NAME: "pagopabizeventsservice"
OTEL_RESOURCE_ATTRIBUTES: "service.name=pagopareceiptspdfserviceotl,deployment.environment=dev"
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317"
Expand All @@ -99,6 +101,8 @@ microservice-chart:
COSMOS_DB_PRIMARY_KEY: 'cosmos-d-biz-key'
PDF_RECEIPT_SUBSCRIPTION_KEY: "bizevent-d-receiptpdfservice-subscription-key"
PDF_GENERATE_RECEIPT_SUBSCRIPTION_KEY: "bizevent-d-generatepdfservice-subscription-key"
REDIS_PWD: 'redis-password'
REDIS_HOST: 'redis-hostname'
OTEL_EXPORTER_OTLP_HEADERS: 'elastic-otl-secret-token'
keyvault:
name: "pagopa-d-bizevents-kv"
Expand Down
4 changes: 4 additions & 0 deletions helm/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ microservice-chart:
GENERATE_PDF_RETRY_MAX_DELAY: "10000"
GENERATE_PDF_RETRY_MAX_ATTEMPTS: "1"
CONNECTION_TIMEOUT: "10000"
REDIS_PORT: "6380"
REDIS_TTL: "20" # 20 minutes
OTEL_SERVICE_NAME: "pagopabizeventsservice"
OTEL_RESOURCE_ATTRIBUTES: "service.name=pagopareceiptspdfserviceotl,deployment.environment=prod"
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317"
Expand All @@ -99,6 +101,8 @@ microservice-chart:
COSMOS_DB_PRIMARY_KEY: 'cosmos-p-biz-key'
PDF_RECEIPT_SUBSCRIPTION_KEY: "bizevent-p-receiptpdfservice-subscription-key"
PDF_GENERATE_RECEIPT_SUBSCRIPTION_KEY: "bizevent-p-generatepdfservice-subscription-key"
REDIS_PWD: 'redis-password'
REDIS_HOST: 'redis-hostname'
OTEL_EXPORTER_OTLP_HEADERS: 'elastic-otl-secret-token'
keyvault:
name: "pagopa-p-bizevents-kv"
Expand Down
4 changes: 4 additions & 0 deletions helm/values-uat.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ microservice-chart:
GENERATE_PDF_RETRY_MAX_DELAY: "10000"
GENERATE_PDF_RETRY_MAX_ATTEMPTS: "1"
CONNECTION_TIMEOUT: "10000"
REDIS_PORT: "6380"
REDIS_TTL: "20" # 20 minutes
OTEL_SERVICE_NAME: "pagopabizeventsservice"
OTEL_RESOURCE_ATTRIBUTES: "service.name=pagopareceiptspdfserviceotl,deployment.environment=uat"
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.elastic-system.svc:4317"
Expand All @@ -99,6 +101,8 @@ microservice-chart:
COSMOS_DB_PRIMARY_KEY: 'cosmos-u-biz-key'
PDF_RECEIPT_SUBSCRIPTION_KEY: "bizevent-u-receiptpdfservice-subscription-key"
PDF_GENERATE_RECEIPT_SUBSCRIPTION_KEY: "bizevent-u-generatepdfservice-subscription-key"
REDIS_PWD: 'redis-password'
REDIS_HOST: 'redis-hostname'
OTEL_EXPORTER_OTLP_HEADERS: 'elastic-otl-secret-token'
keyvault:
name: "pagopa-u-bizevents-kv"
Expand Down
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- AZURE -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
public class Application {

public static void main(String[] args) {
// to avoid java.lang.ClassCastException for objects fetched from the REDIS cache
System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(Application.class, args);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package it.gov.pagopa.bizeventsservice.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

@Value("${spring.redis.host}")
private String redisHost;

@Value("${spring.redis.port}")
private int redisPort;

@Value("${spring.redis.pwd}")
private String redisPwd;

@Bean
public ObjectMapper objectMapper() {
final var objectMapper = new ObjectMapper().findAndRegisterModules();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
return objectMapper;
}

@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisConfiguration =
new RedisStandaloneConfiguration(redisHost, redisPort);
redisConfiguration.setPassword(redisPwd);
LettuceClientConfiguration lettuceConfig =
LettuceClientConfiguration.builder().useSsl().build();
return new LettuceConnectionFactory(redisConfiguration, lettuceConfig);
}

@Bean
@Qualifier("object")
public RedisTemplate<String, byte[]> redisObjectTemplate(
final LettuceConnectionFactory connectionFactory, ObjectMapper objectMapper) {
RedisTemplate<String, byte[]> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public interface ITransactionController {
String X_CONTINUATION_TOKEN = "x-continuation-token";
String X_FISCAL_CODE = "x-fiscal-code";
String PAGE_SIZE = "size";
String PAGE_NUMBER = "page";

/**
* recovers biz-event data for the transaction list
Expand All @@ -60,6 +61,30 @@ ResponseEntity<TransactionListWrapResponse> getTransactionList(
@RequestParam(name = PAGE_SIZE, required = false, defaultValue = "10") Integer size

);

/**
* recovers biz-event data for the transaction list
*
* @param fiscalCode tokenized user fiscal code
* @param page optional parameter defining page number, default to 0 (first page)
* @param size optional parameter defining page size, defaults to 10
* @return the transaction list
*/
@GetMapping(value = "/cached", produces = MediaType.APPLICATION_JSON_VALUE)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Obtained transaction list.",
content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(name = "TransactionListWrapResponse", implementation = TransactionListWrapResponse.class))),
@ApiResponse(responseCode = "401", description = "Wrong or missing function key.", content = @Content(schema = @Schema())),
@ApiResponse(responseCode = "404", description = "Not found the transaction.", content = @Content(schema = @Schema(implementation = ProblemJson.class))),
@ApiResponse(responseCode = "429", description = "Too many requests.", content = @Content(schema = @Schema())),
@ApiResponse(responseCode = "500", description = "Service unavailable.", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ProblemJson.class)))})
@Operation(summary = "Retrieve the paged transaction list from biz events.", security = {
@SecurityRequirement(name = "ApiKey")}, operationId = "getTransactionList")
ResponseEntity<TransactionListWrapResponse> getCachedTransactionList(
@RequestHeader(name = X_FISCAL_CODE) String fiscalCode,
@RequestParam(name = PAGE_NUMBER, required = false, defaultValue = "0") Integer page,
@RequestParam(name = PAGE_SIZE, required = false, defaultValue = "10") Integer size
);

@Operation(summary = "Retrieve the transaction details given its id.", security = {
@SecurityRequirement(name = "ApiKey")}, operationId = "getTransactionDetails")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ public ResponseEntity<TransactionListWrapResponse> getTransactionList(
.header(X_CONTINUATION_TOKEN, transactionListResponse.getContinuationToken())
.body(TransactionListWrapResponse.builder().transactions(transactionListResponse.getTransactionList()).build());
}

@Override
public ResponseEntity<TransactionListWrapResponse> getCachedTransactionList(String fiscalCode,
Integer page, Integer size) {
TransactionListResponse transactionListResponse = transactionService.getCachedTransactionList(fiscalCode, page, size);
return ResponseEntity.ok()
.body(TransactionListWrapResponse.builder()
.transactions(transactionListResponse.getTransactionList())
.pageInfo(transactionListResponse.getPageInfo())
.build());
}

@Override
public ResponseEntity<TransactionDetailResponse> getTransactionDetails(String fiscalCode, String eventReference) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package it.gov.pagopa.bizeventsservice.entity.view;

import java.io.Serializable;

import com.azure.spring.data.cosmos.core.mapping.Container;
import com.azure.spring.data.cosmos.core.mapping.GeneratedValue;
import com.azure.spring.data.cosmos.core.mapping.PartitionKey;
Expand All @@ -14,8 +16,13 @@
@Getter
@Setter
@Builder
public class BizEventsViewUser {
@GeneratedValue
public class BizEventsViewUser implements Serializable {
/**
*
*/
private static final long serialVersionUID = -4997399615775767480L;

@GeneratedValue
private String id;
@PartitionKey
private String taxCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@ public static TransactionListItem convertTransactionListItem(BizEventsViewUser v
totalAmount.updateAndGet(v -> v.add(amountExtracted));
}

// check if the cart contains an item in which the user is a debtor
boolean userHasDebtorItemInTheCart = listOfCartViews.stream().anyMatch(x -> viewUser.getTaxCode().equals(x.getDebtor().getTaxCode()));

return TransactionListItem.builder()
.transactionId(viewUser.getTransactionId())
.payeeName(listOfCartViews.size() > 1 ? payeeCartName : listOfCartViews.get(0).getPayee().getName())
Expand All @@ -98,7 +95,7 @@ public static TransactionListItem convertTransactionListItem(BizEventsViewUser v
.transactionDate(dateFormatZoned(viewUser.getTransactionDate()))
.isCart(listOfCartViews.size() > 1)
.isPayer(BooleanUtils.isTrue(viewUser.getIsPayer()))
.isDebtor(userHasDebtorItemInTheCart ? userHasDebtorItemInTheCart: BooleanUtils.isTrue(viewUser.getIsDebtor()))
.isDebtor(BooleanUtils.isTrue(viewUser.getIsDebtor()))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package it.gov.pagopa.bizeventsservice.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.validation.constraints.Positive;
import javax.validation.constraints.PositiveOrZero;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class PageInfo {

@JsonProperty("page")
@Schema(description = "Page number", required = true)
@PositiveOrZero
Integer page;

@JsonProperty("limit")
@Schema(description = "Required number of items per page", required = true)
@Positive
Integer limit;

@JsonProperty("items_found")
@Schema(description = "Number of items found. (The last page may have fewer elements than required)", required = true)
@PositiveOrZero
Integer itemsFound;

@JsonProperty("total_pages")
@Schema(description = "Total number of pages", required = true)
@PositiveOrZero
Integer totalPages;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@

import java.util.List;

import it.gov.pagopa.bizeventsservice.model.PageInfo;

@Builder
@Getter
public class TransactionListResponse {
private List<TransactionListItem> transactionList;
private String continuationToken;
private PageInfo pageInfo;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@

import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

import it.gov.pagopa.bizeventsservice.model.PageInfo;

@Builder
@Getter
@JsonInclude(Include.NON_NULL)
public class TransactionListWrapResponse {
private List<TransactionListItem> transactions;
@JsonProperty("page_info")
private PageInfo pageInfo;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
public interface BizEventsViewUserRepository extends CosmosRepository<BizEventsViewUser, String> {
@Query("select * from c where c.taxCode = @taxCode and c.hidden = false")
Page<BizEventsViewUser> getBizEventsViewUserByTaxCode(@Param("taxCode") String taxCode, Pageable pageable);

@Query("select * from c where c.taxCode = @taxCode and c.hidden = false")
List<BizEventsViewUser> getBizEventsViewUserByTaxCode(@Param("taxCode") String taxCode);

@Query("select * from c where c.transactionId=@transactionId and c.taxCode = @fiscalCode and c.hidden = false")
List<BizEventsViewUser> getBizEventsViewUserByTaxCodeAndTransactionId(String fiscalCode, String transactionId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package it.gov.pagopa.bizeventsservice.repository.redis;

import java.time.Duration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;


@Component
public class RedisRepository {

@Autowired
@Qualifier("object")
private RedisTemplate<String, byte[]> redisTemplateObj;

public void save(String key, byte[] value, long ttl) {
redisTemplateObj.opsForValue().set(key, value, Duration.ofMinutes(ttl));
}

public byte[] get(String key) {
return redisTemplateObj.opsForValue().get(key);
}

public void remove(String key) {
redisTemplateObj.delete(key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public interface ITransactionService {
* @return transaction list
*/
TransactionListResponse getTransactionList(String fiscalCode, String continuationToken, Integer size);
TransactionListResponse getCachedTransactionList(String fiscalCode, Integer page, Integer size);
TransactionDetailResponse getTransactionDetails(String fiscalCode, String transactionId);

void disableTransaction(String fiscalCode, String transactionId);
Expand Down
Loading
Loading