diff --git a/backend/codiki-application/src/main/java/org/codiki/application/traffic/TrafficTraceUseCases.java b/backend/codiki-application/src/main/java/org/codiki/application/traffic/TrafficTraceUseCases.java new file mode 100644 index 0000000..3d2a612 --- /dev/null +++ b/backend/codiki-application/src/main/java/org/codiki/application/traffic/TrafficTraceUseCases.java @@ -0,0 +1,42 @@ +package org.codiki.application.traffic; + +import jakarta.annotation.Nullable; +import org.codiki.domain.traffic.exception.TrafficTraceCreationException; +import org.codiki.domain.traffic.model.TrafficEndpoint; +import org.codiki.domain.traffic.model.TrafficTrace; +import org.codiki.domain.traffic.port.TrafficTracePort; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.time.Clock; +import java.time.ZonedDateTime; +import java.util.UUID; + +import static java.util.Objects.isNull; +import static org.codiki.domain.traffic.model.TrafficTrace.aTrafficTrace; + +@Component +public class TrafficTraceUseCases { + private final TrafficTracePort trafficTracePort; + private final Clock clock; + + public TrafficTraceUseCases(TrafficTracePort trafficTracePort, Clock clock) { + this.trafficTracePort = trafficTracePort; + this.clock = clock; + } + + @Async + public void saveNewTrace(TrafficEndpoint trafficEndpoint, @Nullable String correlationId) { + if (isNull(trafficEndpoint)) { + throw new TrafficTraceCreationException("Traffic endpoint should not be null."); + } + + TrafficTrace newTrace = aTrafficTrace() + .withId(UUID.randomUUID()) + .withDateTime(ZonedDateTime.now(clock)) + .withEndpoint(trafficEndpoint) + .withCorrelationId(correlationId) + .build(); + trafficTracePort.save(newTrace); + } +} diff --git a/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/exception/TrafficTraceCreationException.java b/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/exception/TrafficTraceCreationException.java new file mode 100644 index 0000000..c4d11ce --- /dev/null +++ b/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/exception/TrafficTraceCreationException.java @@ -0,0 +1,9 @@ +package org.codiki.domain.traffic.exception; + +import org.codiki.domain.exception.FunctionnalException; + +public class TrafficTraceCreationException extends FunctionnalException { + public TrafficTraceCreationException(String message) { + super(message); + } +} diff --git a/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/model/HttpMethod.java b/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/model/HttpMethod.java new file mode 100644 index 0000000..42269b9 --- /dev/null +++ b/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/model/HttpMethod.java @@ -0,0 +1,14 @@ +package org.codiki.domain.traffic.model; + +import java.util.Arrays; +import java.util.Optional; + +public enum HttpMethod { + GET, POST, PUT, DELETE; + + public static Optional fromString(String methodAsString) { + return Arrays.stream(values()) + .filter(method -> method.name().equals(methodAsString)) + .findFirst(); + } +} diff --git a/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/model/TrafficEndpoint.java b/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/model/TrafficEndpoint.java new file mode 100644 index 0000000..9270a08 --- /dev/null +++ b/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/model/TrafficEndpoint.java @@ -0,0 +1,6 @@ +package org.codiki.domain.traffic.model; + +public record TrafficEndpoint( + HttpMethod method, + String path +) {} diff --git a/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/model/TrafficTrace.java b/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/model/TrafficTrace.java new file mode 100644 index 0000000..aecd9df --- /dev/null +++ b/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/model/TrafficTrace.java @@ -0,0 +1,48 @@ +package org.codiki.domain.traffic.model; + +import java.time.ZonedDateTime; +import java.util.UUID; + +public record TrafficTrace( + UUID id, + ZonedDateTime dateTime, + TrafficEndpoint endpoint, + String correlationId +) { + public static Builder aTrafficTrace() { + return new Builder(); + } + + public static class Builder { + private UUID id; + private ZonedDateTime dateTime; + private TrafficEndpoint endpoint; + private String correlationId; + + private Builder() {} + + public Builder withId(UUID id) { + this.id = id; + return this; + } + + public Builder withDateTime(ZonedDateTime dateTime) { + this.dateTime = dateTime; + return this; + } + + public Builder withEndpoint(TrafficEndpoint endpoint) { + this.endpoint = endpoint; + return this; + } + + public Builder withCorrelationId(String correlationId) { + this.correlationId = correlationId; + return this; + } + + public TrafficTrace build() { + return new TrafficTrace(id, dateTime, endpoint, correlationId); + } + } +} diff --git a/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/port/TrafficTracePort.java b/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/port/TrafficTracePort.java new file mode 100644 index 0000000..7e7c235 --- /dev/null +++ b/backend/codiki-domain/src/main/java/org/codiki/domain/traffic/port/TrafficTracePort.java @@ -0,0 +1,14 @@ +package org.codiki.domain.traffic.port; + +import org.codiki.domain.traffic.model.TrafficTrace; + +import java.time.ZonedDateTime; +import java.util.List; + +public interface TrafficTracePort { + void save(TrafficTrace trace); + List getAllInPeriod(ZonedDateTime startDate, ZonedDateTime endDate); + List getAllByCorrelationId(String correlationId); + Integer countAllInPeriod(ZonedDateTime startDate, ZonedDateTime endDate); + Integer countByCorrelationId(String correlationId); +} diff --git a/backend/codiki-exposition/pom.xml b/backend/codiki-exposition/pom.xml index 392fa00..f6869d7 100644 --- a/backend/codiki-exposition/pom.xml +++ b/backend/codiki-exposition/pom.xml @@ -25,6 +25,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-aop + org.projectlombok lombok @@ -33,28 +37,5 @@ org.apache.tika tika-core - - - - - - - - - - - - - - - - - - - - - - - diff --git a/backend/codiki-exposition/src/main/java/org/codiki/exposition/configuration/TrafficTraceConfiguration.java b/backend/codiki-exposition/src/main/java/org/codiki/exposition/configuration/TrafficTraceConfiguration.java new file mode 100644 index 0000000..a55d8fb --- /dev/null +++ b/backend/codiki-exposition/src/main/java/org/codiki/exposition/configuration/TrafficTraceConfiguration.java @@ -0,0 +1,12 @@ +package org.codiki.exposition.configuration; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableAsync; + +@Configuration +@EnableAspectJAutoProxy +@EnableAsync +public class TrafficTraceConfiguration { + +} \ No newline at end of file diff --git a/backend/codiki-exposition/src/main/java/org/codiki/exposition/traffic/ApiCallsLoggerAspect.java b/backend/codiki-exposition/src/main/java/org/codiki/exposition/traffic/ApiCallsLoggerAspect.java new file mode 100644 index 0000000..78c2f3f --- /dev/null +++ b/backend/codiki-exposition/src/main/java/org/codiki/exposition/traffic/ApiCallsLoggerAspect.java @@ -0,0 +1,61 @@ +package org.codiki.exposition.traffic; + +import jakarta.servlet.http.HttpServletRequest; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.codiki.application.traffic.TrafficTraceUseCases; +import org.codiki.domain.traffic.model.HttpMethod; +import org.codiki.domain.traffic.model.TrafficEndpoint; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Optional; + +@Component +@Aspect +public class ApiCallsLoggerAspect { + private static final String HTTP_HEADER_CORRELATION_ID = "x-correlation-id"; + + private final TrafficTraceUseCases trafficTraceUseCases; + + public ApiCallsLoggerAspect(TrafficTraceUseCases trafficTraceUseCases) { + this.trafficTraceUseCases = trafficTraceUseCases; + } + + @Before("@annotation(org.springframework.web.bind.annotation.GetMapping)") + public void logGetApiCall(JoinPoint joinPoint) { + logApiCall(); + } + + @Before("@annotation(org.springframework.web.bind.annotation.PostMapping)") + public void logPostApiCall(JoinPoint joinPoint) { + logApiCall(); + } + @Before("@annotation(org.springframework.web.bind.annotation.PutMapping)") + public void logPutApiCall(JoinPoint joinPoint) { + logApiCall(); + } + @Before("@annotation(org.springframework.web.bind.annotation.DeleteMapping)") + public void logDeleteApiCall(JoinPoint joinPoint) { + logApiCall(); + } + + private void logApiCall() { + Optional.ofNullable(RequestContextHolder.getRequestAttributes()) + .filter(ServletRequestAttributes.class::isInstance) + .map(ServletRequestAttributes.class::cast) + .map(ServletRequestAttributes::getRequest) + .ifPresent(request -> + Optional.of(request.getMethod()) + .flatMap(HttpMethod::fromString) + .ifPresent(queryHttpMethod -> { + String queryUriPath = request.getRequestURI(); + String correlationId = request.getHeader(HTTP_HEADER_CORRELATION_ID); + TrafficEndpoint endpoint = new TrafficEndpoint(queryHttpMethod, queryUriPath); + trafficTraceUseCases.saveNewTrace(endpoint, correlationId); + }) + ); + } +} diff --git a/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/traffic/TrafficTraceInMemoryAdapter.java b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/traffic/TrafficTraceInMemoryAdapter.java new file mode 100644 index 0000000..84ba866 --- /dev/null +++ b/backend/codiki-infrastructure/src/main/java/org/codiki/infrastructure/traffic/TrafficTraceInMemoryAdapter.java @@ -0,0 +1,39 @@ +package org.codiki.infrastructure.traffic; + +import org.codiki.domain.traffic.model.TrafficTrace; +import org.codiki.domain.traffic.port.TrafficTracePort; +import org.springframework.stereotype.Component; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; + +@Component +public class TrafficTraceInMemoryAdapter implements TrafficTracePort { + private final List traces = new ArrayList<>(); + + @Override + public void save(TrafficTrace trace) { + traces.add(trace); + } + + @Override + public List getAllInPeriod(ZonedDateTime startDate, ZonedDateTime endDate) { + return List.of(); + } + + @Override + public List getAllByCorrelationId(String correlationId) { + return List.of(); + } + + @Override + public Integer countAllInPeriod(ZonedDateTime startDate, ZonedDateTime endDate) { + return 0; + } + + @Override + public Integer countByCorrelationId(String correlationId) { + return 0; + } +} diff --git a/backend/pom.xml b/backend/pom.xml index 2281a94..50eb8f0 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -15,11 +15,11 @@ 21 21 21 - 6.0.0 + 6.1.0 4.4.0 - 42.7.0 2.9.0 - 3.14.0 + 42.7.4 + 3.17.0 @@ -35,7 +35,7 @@ org.springframework.boot spring-boot-dependencies - 3.2.0 + 3.3.4 pom import @@ -84,8 +84,6 @@ commons-lang3 ${commons-lang3.version} - -