diff --git a/docker-compose.yml b/docker-compose.yml
index b387e1a6..b5bd37b5 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,15 +1,47 @@
version: '3.1'
services:
stats-server:
+ build: statistics-service/stats-server
+ image: statistics-server
+ container_name: stats-server-container
ports:
- "9090:9090"
+ depends_on:
+ - stats-db
+ environment:
+ - SPRING_DATASOURCE_URL=jdbc:postgresql://stats-db:5432/ewm-stats
+ - SPRING_DATASOURCE_USERNAME=root
+ - SPRING_DATASOURCE_PASSWORD=root
stats-db:
image: postgres:14-alpine
+ container_name: stats-db-container
+ ports:
+ - "6541:5432"
+ environment:
+ - POSTGRES_DB=ewm-stats
+ - POSTGRES_USER=root
+ - POSTGRES_PASSWORD=root
ewm-service:
+ build: main-service
+ image: ewm-main-service
+ container_name: ewm-main-service-container
ports:
- "8080:8080"
+ depends_on:
+ - ewm-db
+ environment:
+ - SPRING_DATASOURCE_URL=jdbc:postgresql://ewm-db:5432/ewm-db
+ - SPRING_DATASOURCE_USERNAME=root
+ - SPRING_DATASOURCE_PASSWORD=root
ewm-db:
image: postgres:14-alpine
+ container_name: ewm-db-container
+ ports:
+ - "6542:5432"
+ environment:
+ - POSTGRES_DB=ewm-db
+ - POSTGRES_USER=root
+ - POSTGRES_PASSWORD=root
\ No newline at end of file
diff --git a/main-service/Dockerfile b/main-service/Dockerfile
new file mode 100644
index 00000000..4049bfc8
--- /dev/null
+++ b/main-service/Dockerfile
@@ -0,0 +1,3 @@
+FROM amazoncorretto:11
+COPY target/*.jar main-service.jar
+ENTRYPOINT ["java", "-jar", "/main-service.jar"]
\ No newline at end of file
diff --git a/main-service/pom.xml b/main-service/pom.xml
new file mode 100644
index 00000000..9c8cb4c9
--- /dev/null
+++ b/main-service/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+
+ ru.practicum
+ explore-with-me
+ 0.0.1-SNAPSHOT
+
+
+ main-service
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+
\ No newline at end of file
diff --git a/main-service/src/main/java/ru/practicum/MainService.java b/main-service/src/main/java/ru/practicum/MainService.java
new file mode 100644
index 00000000..b7b5fed9
--- /dev/null
+++ b/main-service/src/main/java/ru/practicum/MainService.java
@@ -0,0 +1,11 @@
+package ru.practicum;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class MainService {
+ public static void main(String[] args) {
+ SpringApplication.run(MainService.class, args);
+ }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index ad386209..9153f90e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,6 +1,6 @@
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
@@ -11,6 +11,10 @@
Explore With Me
+
+ main-service
+ stats-service
+
ru.practicum
explore-with-me
@@ -19,6 +23,8 @@
11
+ 11
+ 11
UTF-8
diff --git a/stats-service/pom.xml b/stats-service/pom.xml
new file mode 100644
index 00000000..07d5cfe2
--- /dev/null
+++ b/stats-service/pom.xml
@@ -0,0 +1,38 @@
+
+
+ 4.0.0
+
+
+ explore-with-me
+ ru.practicum
+ 0.0.1-SNAPSHOT
+
+
+ stats-service
+ 0.0.1-SNAPSHOT
+ pom
+
+
+ stats-dto
+ stats-server
+ stats-client
+
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
\ No newline at end of file
diff --git a/stats-service/stats-client/pom.xml b/stats-service/stats-client/pom.xml
new file mode 100644
index 00000000..66ee0fb3
--- /dev/null
+++ b/stats-service/stats-client/pom.xml
@@ -0,0 +1,42 @@
+
+
+
+ stats-service
+ ru.practicum
+ 0.0.1-SNAPSHOT
+
+ 4.0.0
+
+ stats-client
+ 0.0.1-SNAPSHOT
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+ ru.practicum
+ stats-dto
+ 0.0.1-SNAPSHOT
+ compile
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
\ No newline at end of file
diff --git a/stats-service/stats-client/src/main/java/ru/practicum/StatsClient.java b/stats-service/stats-client/src/main/java/ru/practicum/StatsClient.java
new file mode 100644
index 00000000..4a922812
--- /dev/null
+++ b/stats-service/stats-client/src/main/java/ru/practicum/StatsClient.java
@@ -0,0 +1,45 @@
+package ru.practicum;
+
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import java.util.List;
+
+@Service
+public class StatsClient {
+ private final WebClient client;
+
+ public StatsClient(String serverUrl) {
+ client = WebClient.create(serverUrl);
+ }
+
+ public ResponseEntity> getStats(String start, String end, String[] uris, Boolean unique) {
+ return client.get()
+ .uri(uriBuilder -> uriBuilder
+ .path("/stats")
+ .queryParam("start", start)
+ .queryParam("end", end)
+ .queryParam("uris", uris)
+ .queryParam("unique", unique)
+ .build())
+ .accept(MediaType.APPLICATION_JSON)
+ .retrieve()
+ .toEntityList(ViewStatsDto.class)
+ .block();
+ }
+
+ public void addHit(EndpointHitDto endpointHitDto) {
+ client.post()
+ .uri("/hit")
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON)
+ .bodyValue(endpointHitDto)
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ }
+
+
+}
\ No newline at end of file
diff --git a/stats-service/stats-dto/pom.xml b/stats-service/stats-dto/pom.xml
new file mode 100644
index 00000000..8a5f360b
--- /dev/null
+++ b/stats-service/stats-dto/pom.xml
@@ -0,0 +1,45 @@
+
+
+
+ stats-service
+ ru.practicum
+ 0.0.1-SNAPSHOT
+
+ 4.0.0
+
+ stats-dto
+ 0.0.1-SNAPSHOT
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework
+ spring-context
+
+
+ org.springframework.boot
+ spring-boot-starter-json
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
\ No newline at end of file
diff --git a/stats-service/stats-dto/src/main/java/ru/practicum/EndpointHitDto.java b/stats-service/stats-dto/src/main/java/ru/practicum/EndpointHitDto.java
new file mode 100644
index 00000000..b0033507
--- /dev/null
+++ b/stats-service/stats-dto/src/main/java/ru/practicum/EndpointHitDto.java
@@ -0,0 +1,27 @@
+package ru.practicum;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class EndpointHitDto {
+ @NotBlank(message = "IP-адрес пользователя не может быть пустым")
+ private String ip;
+ @NotBlank(message = "ID сервиса не может быть пустым")
+ private String app;
+ @NotBlank(message = "URI не может быть пустым")
+ private String uri;
+ @NotNull
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime timestamp;
+}
\ No newline at end of file
diff --git a/stats-service/stats-dto/src/main/java/ru/practicum/ViewStatsDto.java b/stats-service/stats-dto/src/main/java/ru/practicum/ViewStatsDto.java
new file mode 100644
index 00000000..338b9ca0
--- /dev/null
+++ b/stats-service/stats-dto/src/main/java/ru/practicum/ViewStatsDto.java
@@ -0,0 +1,16 @@
+package ru.practicum;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ViewStatsDto {
+ private String app;
+ private String uri;
+ private Long hits;
+}
\ No newline at end of file
diff --git a/stats-service/stats-server/Dockerfile b/stats-service/stats-server/Dockerfile
new file mode 100644
index 00000000..2118327f
--- /dev/null
+++ b/stats-service/stats-server/Dockerfile
@@ -0,0 +1,3 @@
+FROM amazoncorretto:11-alpine-jdk
+COPY target/*.jar app.jar
+ENTRYPOINT ["java","-jar","/app.jar"]
\ No newline at end of file
diff --git a/stats-service/stats-server/pom.xml b/stats-service/stats-server/pom.xml
new file mode 100644
index 00000000..e6a45162
--- /dev/null
+++ b/stats-service/stats-server/pom.xml
@@ -0,0 +1,88 @@
+
+
+
+ stats-service
+ ru.practicum
+ 0.0.1-SNAPSHOT
+
+ 4.0.0
+
+ stats-server
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+ ru.practicum
+ stats-dto
+ 0.0.1-SNAPSHOT
+ compile
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+ coverage
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+
+
+
+
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/java/ru/practicum/StatsServerApplication.java b/stats-service/stats-server/src/main/java/ru/practicum/StatsServerApplication.java
new file mode 100644
index 00000000..f4d45f7f
--- /dev/null
+++ b/stats-service/stats-server/src/main/java/ru/practicum/StatsServerApplication.java
@@ -0,0 +1,11 @@
+package ru.practicum;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class StatsServerApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(StatsServerApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/java/ru/practicum/controller/StatsController.java b/stats-service/stats-server/src/main/java/ru/practicum/controller/StatsController.java
new file mode 100644
index 00000000..c67ddd9e
--- /dev/null
+++ b/stats-service/stats-server/src/main/java/ru/practicum/controller/StatsController.java
@@ -0,0 +1,38 @@
+package ru.practicum.controller;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.EndpointHitDto;
+import ru.practicum.service.StatsService;
+
+import javax.validation.Valid;
+import java.time.LocalDateTime;
+import java.util.List;
+
+import ru.practicum.ViewStatsDto;
+
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+public class StatsController {
+ private final StatsService statsService;
+
+ @PostMapping("/hit")
+ @ResponseStatus(HttpStatus.CREATED)
+ public void createHit(@Valid @RequestBody EndpointHitDto endpointHitDto) {
+ log.info("Получен post запрос на сохранение информации : {}", endpointHitDto);
+ statsService.createHit(endpointHitDto);
+ }
+
+ @GetMapping("/stats")
+ public List getStats(@RequestParam(name = "start") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime start,
+ @RequestParam(name = "end") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime end,
+ @RequestParam(name = "uris", required = false) String[] uris,
+ @RequestParam(name = "unique", defaultValue = "false") Boolean unique) {
+ log.info("Получен get запрос на получение статистики по посещениям с {} по {}", start, end);
+ return statsService.getStats(start, end, uris, unique);
+ }
+}
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/java/ru/practicum/exception/BadRequestException.java b/stats-service/stats-server/src/main/java/ru/practicum/exception/BadRequestException.java
new file mode 100644
index 00000000..8f6d2085
--- /dev/null
+++ b/stats-service/stats-server/src/main/java/ru/practicum/exception/BadRequestException.java
@@ -0,0 +1,7 @@
+package ru.practicum.exception;
+
+public class BadRequestException extends RuntimeException {
+ public BadRequestException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/java/ru/practicum/exception/ErrorHandler.java b/stats-service/stats-server/src/main/java/ru/practicum/exception/ErrorHandler.java
new file mode 100644
index 00000000..5b4ccafc
--- /dev/null
+++ b/stats-service/stats-server/src/main/java/ru/practicum/exception/ErrorHandler.java
@@ -0,0 +1,28 @@
+package ru.practicum.exception;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.util.Map;
+
+@Slf4j
+@RestControllerAdvice
+public class ErrorHandler {
+
+ @ExceptionHandler
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public Map handleValidationException(MethodArgumentNotValidException e) {
+ return Map.of("error", "Ошибка валидации",
+ "errorMessage", e.getMessage());
+ }
+
+ @ExceptionHandler
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ public Map handleThrowableException(final Throwable e) {
+ return Map.of("error", "Internal Server Error");
+ }
+}
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/java/ru/practicum/mapper/EndpointHitMapper.java b/stats-service/stats-server/src/main/java/ru/practicum/mapper/EndpointHitMapper.java
new file mode 100644
index 00000000..7d1802aa
--- /dev/null
+++ b/stats-service/stats-server/src/main/java/ru/practicum/mapper/EndpointHitMapper.java
@@ -0,0 +1,19 @@
+package ru.practicum.mapper;
+
+import lombok.experimental.UtilityClass;
+import ru.practicum.EndpointHitDto;
+import ru.practicum.model.EndpointHit;
+
+
+@UtilityClass
+public class EndpointHitMapper {
+
+ public static EndpointHit fromEndpointHitDto(EndpointHitDto endpointHitDto) {
+ return EndpointHit.builder()
+ .app(endpointHitDto.getApp())
+ .uri(endpointHitDto.getUri())
+ .ip(endpointHitDto.getIp())
+ .created(endpointHitDto.getTimestamp())
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/java/ru/practicum/mapper/ViewStatsMapper.java b/stats-service/stats-server/src/main/java/ru/practicum/mapper/ViewStatsMapper.java
new file mode 100644
index 00000000..b256e5ab
--- /dev/null
+++ b/stats-service/stats-server/src/main/java/ru/practicum/mapper/ViewStatsMapper.java
@@ -0,0 +1,18 @@
+package ru.practicum.mapper;
+
+import lombok.experimental.UtilityClass;
+import ru.practicum.ViewStatsDto;
+import ru.practicum.model.ViewStats;
+
+
+@UtilityClass
+public class ViewStatsMapper {
+
+ public static ViewStatsDto toViewStatsDto(ViewStats viewStats) {
+ return ViewStatsDto.builder()
+ .app(viewStats.getApp())
+ .uri(viewStats.getUri())
+ .hits(viewStats.getHits())
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/java/ru/practicum/model/EndpointHit.java b/stats-service/stats-server/src/main/java/ru/practicum/model/EndpointHit.java
new file mode 100644
index 00000000..e43e77db
--- /dev/null
+++ b/stats-service/stats-server/src/main/java/ru/practicum/model/EndpointHit.java
@@ -0,0 +1,28 @@
+package ru.practicum.model;
+
+import lombok.*;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Table(name = "endpoint_hit")
+public class EndpointHit {
+ @Id
+ @Column(name = "id")
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ @Column(name = "app")
+ private String app;
+ @Column(name = "uri")
+ private String uri;
+ @Column(name = "ip")
+ private String ip;
+ @Column(name = "created")
+ private LocalDateTime created;
+}
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/java/ru/practicum/model/ViewStats.java b/stats-service/stats-server/src/main/java/ru/practicum/model/ViewStats.java
new file mode 100644
index 00000000..cdc0d8a3
--- /dev/null
+++ b/stats-service/stats-server/src/main/java/ru/practicum/model/ViewStats.java
@@ -0,0 +1,9 @@
+package ru.practicum.model;
+
+public interface ViewStats {
+ String getApp();
+
+ String getUri();
+
+ Long getHits();
+}
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/java/ru/practicum/repository/EndpointHitsRepository.java b/stats-service/stats-server/src/main/java/ru/practicum/repository/EndpointHitsRepository.java
new file mode 100644
index 00000000..9624e666
--- /dev/null
+++ b/stats-service/stats-server/src/main/java/ru/practicum/repository/EndpointHitsRepository.java
@@ -0,0 +1,45 @@
+package ru.practicum.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+import ru.practicum.model.EndpointHit;
+import ru.practicum.model.ViewStats;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Repository
+public interface EndpointHitsRepository extends JpaRepository {
+
+ String SELECT_PREFIX = "SELECT h.app as app, h.uri as uri,";
+ String GROUP_BY_SUFFIX = "GROUP BY h.app, h.uri";
+
+ @Query(SELECT_PREFIX + " COUNT(DISTINCT h.ip) as hits " +
+ "FROM EndpointHit AS h " +
+ "WHERE h.created BETWEEN ?1 AND ?2 AND h.uri IN ?3 " +
+ GROUP_BY_SUFFIX +
+ " ORDER BY COUNT(DISTINCT h.ip) DESC")
+ List getStatsUnique(LocalDateTime start, LocalDateTime end, String[] uris);
+
+ @Query(SELECT_PREFIX + " COUNT(DISTINCT h.ip) as hits " +
+ "FROM EndpointHit AS h " +
+ "WHERE h.created BETWEEN ?1 AND ?2 " +
+ GROUP_BY_SUFFIX +
+ " ORDER BY COUNT(DISTINCT h.ip) DESC")
+ List getStatsUnique(LocalDateTime start, LocalDateTime end);
+
+ @Query(SELECT_PREFIX + " COUNT(h.id) as hits " +
+ "FROM EndpointHit AS h " +
+ "WHERE h.created BETWEEN ?1 AND ?2 AND h.uri IN ?3 " +
+ GROUP_BY_SUFFIX +
+ " ORDER BY COUNT(h.id) DESC")
+ List getAll(LocalDateTime start, LocalDateTime end, String[] uris);
+
+ @Query(SELECT_PREFIX + " COUNT(h.id) as hits " +
+ "FROM EndpointHit AS h " +
+ "WHERE h.created BETWEEN ?1 AND ?2 " +
+ GROUP_BY_SUFFIX +
+ " ORDER BY COUNT(h.id) DESC")
+ List getAll(LocalDateTime start, LocalDateTime end);
+}
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/java/ru/practicum/service/StatsService.java b/stats-service/stats-server/src/main/java/ru/practicum/service/StatsService.java
new file mode 100644
index 00000000..00ac4bf7
--- /dev/null
+++ b/stats-service/stats-server/src/main/java/ru/practicum/service/StatsService.java
@@ -0,0 +1,16 @@
+package ru.practicum.service;
+
+
+import ru.practicum.EndpointHitDto;
+import ru.practicum.ViewStatsDto;
+
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public interface StatsService {
+
+ void createHit(EndpointHitDto endpointHitDto);
+
+ List getStats(LocalDateTime start, LocalDateTime end, String[] uris, Boolean unique);
+}
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/java/ru/practicum/service/StatsServiceImpl.java b/stats-service/stats-server/src/main/java/ru/practicum/service/StatsServiceImpl.java
new file mode 100644
index 00000000..b753524d
--- /dev/null
+++ b/stats-service/stats-server/src/main/java/ru/practicum/service/StatsServiceImpl.java
@@ -0,0 +1,63 @@
+package ru.practicum.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.EndpointHitDto;
+import ru.practicum.ViewStatsDto;
+import ru.practicum.exception.BadRequestException;
+import ru.practicum.model.EndpointHit;
+import ru.practicum.mapper.EndpointHitMapper;
+import ru.practicum.model.ViewStats;
+import ru.practicum.mapper.ViewStatsMapper;
+import ru.practicum.repository.EndpointHitsRepository;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StatsServiceImpl implements StatsService {
+ private final EndpointHitsRepository endpointHitsRepository;
+ public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ @Override
+ @Transactional
+ public void createHit(EndpointHitDto endpointHitDto) {
+ EndpointHit endpointHit = EndpointHitMapper.fromEndpointHitDto(endpointHitDto);
+ endpointHitsRepository.save(endpointHit);
+ }
+
+ @Override
+ public List getStats(LocalDateTime start, LocalDateTime end, String[] uris, Boolean unique) {
+ validateDateRange(start, end);
+ List result = unique ? getStatsUnique(start, end, uris) : getAll(start, end, uris);
+ return result.stream()
+ .map(ViewStatsMapper::toViewStatsDto)
+ .collect(Collectors.toList());
+ }
+
+
+ private List getStatsUnique(LocalDateTime start, LocalDateTime end, String[] uris) {
+ return (uris == null) ?
+ endpointHitsRepository.getStatsUnique(start, end) :
+ endpointHitsRepository.getStatsUnique(start, end, uris);
+ }
+
+ private void validateDateRange(LocalDateTime start, LocalDateTime end) {
+ if (end.isBefore(start)) {
+ log.error("Дата окончания не может быть раньше даты начала");
+ throw new BadRequestException("Дата окончания не может быть раньше даты начала");
+ }
+ }
+
+ private List getAll(LocalDateTime start, LocalDateTime end, String[] uris) {
+ return (uris == null) ?
+ endpointHitsRepository.getAll(start, end) :
+ endpointHitsRepository.getAll(start, end, uris);
+ }
+}
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/resources/application.properties b/stats-service/stats-server/src/main/resources/application.properties
new file mode 100644
index 00000000..777fdee7
--- /dev/null
+++ b/stats-service/stats-server/src/main/resources/application.properties
@@ -0,0 +1,24 @@
+server.port=9090
+logging.level.com.zaxxer.hikari=TRACE
+
+logging.level.org.springframework.orm.jpa=INFO
+logging.level.org.springframework.transaction=INFO
+logging.level.org.springframework.transaction.interceptor=TRACE
+logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG
+
+spring.jpa.hibernate.ddl-auto=none
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL10Dialect
+spring.jpa.properties.hibernate.format_sql=true
+spring.sql.init.mode=always
+#---
+#spring.datasource.driverClassName=org.postgresql.Driver
+#spring.datasource.url=jdbc:postgresql://localhost:6541/ewm-stats
+#spring.datasource.username=root
+#spring.datasource.password=root
+
+spring.config.activate.on-profile=ci,test
+spring.datasource.driverClassName=org.h2.Driver
+spring.datasource.url=jdbc:h2:mem:ewm-stats
+spring.datasource.username=test
+spring.datasource.password=test
+spring.h2.console.enabled=true
\ No newline at end of file
diff --git a/stats-service/stats-server/src/main/resources/schema.sql b/stats-service/stats-server/src/main/resources/schema.sql
new file mode 100644
index 00000000..aa634691
--- /dev/null
+++ b/stats-service/stats-server/src/main/resources/schema.sql
@@ -0,0 +1,9 @@
+DROP TABLE IF EXISTS endpoint_hit CASCADE;
+
+CREATE TABLE IF NOT EXISTS endpoint_hit (
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ app VARCHAR(255) NOT NULL,
+ uri VARCHAR(512) NOT NULL,
+ ip VARCHAR(16) NOT NULL,
+ created TIMESTAMP WITHOUT TIME ZONE NOT NULL
+);
\ No newline at end of file