From 57118293a865ca399b5aaf82c174ebb02ed5c6a5 Mon Sep 17 00:00:00 2001 From: PavelSpectr Date: Tue, 17 Oct 2023 06:11:17 +0800 Subject: [PATCH] =?UTF-8?q?stat=5Fsvc=20->=20=D0=A0=D0=B5=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=D0=B5=D1=80=D0=B2?= =?UTF-8?q?=D0=B8=D1=81=D0=B0=20=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 32 +++++++ main-service/Dockerfile | 3 + main-service/pom.xml | 30 +++++++ .../main/java/ru/practicum/MainService.java | 11 +++ pom.xml | 8 +- stats-service/pom.xml | 38 ++++++++ stats-service/stats-client/pom.xml | 42 +++++++++ .../main/java/ru/practicum/StatsClient.java | 45 ++++++++++ stats-service/stats-dto/pom.xml | 45 ++++++++++ .../java/ru/practicum/EndpointHitDto.java | 27 ++++++ .../main/java/ru/practicum/ViewStatsDto.java | 16 ++++ stats-service/stats-server/Dockerfile | 3 + stats-service/stats-server/pom.xml | 88 +++++++++++++++++++ .../ru/practicum/StatsServerApplication.java | 11 +++ .../practicum/controller/StatsController.java | 38 ++++++++ .../exception/BadRequestException.java | 7 ++ .../ru/practicum/exception/ErrorHandler.java | 28 ++++++ .../practicum/mapper/EndpointHitMapper.java | 19 ++++ .../ru/practicum/mapper/ViewStatsMapper.java | 18 ++++ .../java/ru/practicum/model/EndpointHit.java | 28 ++++++ .../java/ru/practicum/model/ViewStats.java | 9 ++ .../repository/EndpointHitsRepository.java | 45 ++++++++++ .../ru/practicum/service/StatsService.java | 16 ++++ .../practicum/service/StatsServiceImpl.java | 63 +++++++++++++ .../src/main/resources/application.properties | 24 +++++ .../src/main/resources/schema.sql | 9 ++ 26 files changed, 702 insertions(+), 1 deletion(-) create mode 100644 main-service/Dockerfile create mode 100644 main-service/pom.xml create mode 100644 main-service/src/main/java/ru/practicum/MainService.java create mode 100644 stats-service/pom.xml create mode 100644 stats-service/stats-client/pom.xml create mode 100644 stats-service/stats-client/src/main/java/ru/practicum/StatsClient.java create mode 100644 stats-service/stats-dto/pom.xml create mode 100644 stats-service/stats-dto/src/main/java/ru/practicum/EndpointHitDto.java create mode 100644 stats-service/stats-dto/src/main/java/ru/practicum/ViewStatsDto.java create mode 100644 stats-service/stats-server/Dockerfile create mode 100644 stats-service/stats-server/pom.xml create mode 100644 stats-service/stats-server/src/main/java/ru/practicum/StatsServerApplication.java create mode 100644 stats-service/stats-server/src/main/java/ru/practicum/controller/StatsController.java create mode 100644 stats-service/stats-server/src/main/java/ru/practicum/exception/BadRequestException.java create mode 100644 stats-service/stats-server/src/main/java/ru/practicum/exception/ErrorHandler.java create mode 100644 stats-service/stats-server/src/main/java/ru/practicum/mapper/EndpointHitMapper.java create mode 100644 stats-service/stats-server/src/main/java/ru/practicum/mapper/ViewStatsMapper.java create mode 100644 stats-service/stats-server/src/main/java/ru/practicum/model/EndpointHit.java create mode 100644 stats-service/stats-server/src/main/java/ru/practicum/model/ViewStats.java create mode 100644 stats-service/stats-server/src/main/java/ru/practicum/repository/EndpointHitsRepository.java create mode 100644 stats-service/stats-server/src/main/java/ru/practicum/service/StatsService.java create mode 100644 stats-service/stats-server/src/main/java/ru/practicum/service/StatsServiceImpl.java create mode 100644 stats-service/stats-server/src/main/resources/application.properties create mode 100644 stats-service/stats-server/src/main/resources/schema.sql 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