diff --git a/docker-compose.yml b/docker-compose.yml index b387e1a6..19196d1b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,50 @@ version: '3.1' services: - stats-server: + server: + build: stat-service/server + image: server + container_name: server ports: - "9090:9090" + - "9091:9091" + depends_on: + - statdb + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://statdb:5432/statdb + - SPRING_DATASOURCE_USERNAME=ewm + - SPRING_DATASOURCE_PASSWORD=ewm - stats-db: - image: postgres:14-alpine + statdb: + image: postgres:13.7-alpine + container_name: statdb + ports: + - "5432:5432" + environment: + - POSTGRES_PASSWORD=ewm + - POSTGRES_USER=ewm + - POSTGRES_DB=statdb - ewm-service: + main-service: + build: main-service + image: main-service + container_name: main-service ports: - "8080:8080" + depends_on: + - main-db + - server + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://main-db:5432/main-db + - SPRING_DATASOURCE_USERNAME=ewm + - SPRING_DATASOURCE_PASSWORD=ewm + - STATS_SERVER_URL=http://server:9090 - ewm-db: - image: postgres:14-alpine + main-db: + image: postgres:13.7-alpine + container_name: main-db + ports: + - "6542:5432" + environment: + - POSTGRES_PASSWORD=ewm + - POSTGRES_USER=ewm + - POSTGRES_DB=main-db \ No newline at end of file diff --git a/ewm-main-service-spec.json b/ewm-main-service-spec.json index f28d1413..65c4182d 100644 --- a/ewm-main-service-spec.json +++ b/ewm-main-service-spec.json @@ -2833,4 +2833,4 @@ } } } -} +} \ No newline at end of file diff --git a/ewm-stats-service-spec.json b/ewm-stats-service-spec.json index 436892a8..96f7408b 100644 --- a/ewm-stats-service-spec.json +++ b/ewm-stats-service-spec.json @@ -167,4 +167,4 @@ } } } -} +} \ No newline at end of file diff --git a/main-service/Dockerfile b/main-service/Dockerfile new file mode 100644 index 00000000..2118327f --- /dev/null +++ b/main-service/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/main-service/pom.xml b/main-service/pom.xml new file mode 100644 index 00000000..54d1ac03 --- /dev/null +++ b/main-service/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + ru.practicum + explore-with-me + 0.0.1-SNAPSHOT + + + main-service + + + + ru.practicum + client + ${project.version} + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + 11 + 11 + UTF-8 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/main-service/src/main/java/ru/practicum/ewm/EWMMainServiceApp.java b/main-service/src/main/java/ru/practicum/ewm/EWMMainServiceApp.java new file mode 100644 index 00000000..413a7bae --- /dev/null +++ b/main-service/src/main/java/ru/practicum/ewm/EWMMainServiceApp.java @@ -0,0 +1,11 @@ +package ru.practicum.ewm; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class EWMMainServiceApp { + public static void main(String[] args) { + SpringApplication.run(EWMMainServiceApp.class, args); + } +} \ No newline at end of file diff --git a/main-service/src/resources/application.properties b/main-service/src/resources/application.properties new file mode 100644 index 00000000..67e50e5f --- /dev/null +++ b/main-service/src/resources/application.properties @@ -0,0 +1,24 @@ +server.port=8080 +STAT_SERVER_URL=http://localhost:9090 + +spring.application.name=ewm-main-service +spring.jpa.hibernate.ddl-auto=none +spring.jpa.properties.hibernate.jdbc.time_zone=UTC +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:6542/main-db +spring.datasource.username=ewm +spring.datasource.password=ewm +#--- +spring.config.activate.on-profile=local +spring.datasource.url=jdbc:postgresql://localhost:6542/postgres +stats-server.url=http://localhost:9090 +#--- +spring.config.activate.on-profile=ci,test +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:main-db +spring.datasource.username=ewm +spring.datasource.password=ewm \ No newline at end of file diff --git a/pom.xml b/pom.xml index ad386209..b83f48f4 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 + + stat-service + main-service + ru.practicum explore-with-me @@ -183,5 +187,14 @@ + + + + com.fasterxml.jackson.core + jackson-databind + 2.14.1 + + - + + \ No newline at end of file diff --git a/stat-service/client/pom.xml b/stat-service/client/pom.xml new file mode 100644 index 00000000..a6eaf073 --- /dev/null +++ b/stat-service/client/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + ru.practicum + stat-service + 0.0.1-SNAPSHOT + ../pom.xml + + + client + + + + ru.practicum + dto + ${project.version} + + + + org.springframework.boot + spring-boot-starter-web + + + + + 11 + 11 + UTF-8 + + + \ No newline at end of file diff --git a/stat-service/client/src/main/java/ru/practicum/ewm/BaseClient.java b/stat-service/client/src/main/java/ru/practicum/ewm/BaseClient.java new file mode 100644 index 00000000..194d7d84 --- /dev/null +++ b/stat-service/client/src/main/java/ru/practicum/ewm/BaseClient.java @@ -0,0 +1,59 @@ +package ru.practicum.ewm; + +import org.springframework.http.*; +import org.springframework.lang.Nullable; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.Map; + +public class BaseClient { + protected final RestTemplate restTemplate; + + public BaseClient(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + protected ResponseEntity post(T body) { + return makeAndSendRequest(HttpMethod.POST, "/hit", null, body); + } + + protected ResponseEntity get(String path, @Nullable Map parameters) { + return makeAndSendRequest(HttpMethod.GET, path, parameters, null); + } + + private ResponseEntity makeAndSendRequest(HttpMethod method, String path, @Nullable Map parameters, @Nullable T body) { + HttpEntity requestEntity = new HttpEntity<>(body, defaultHeaders()); + + ResponseEntity exploreWithMeServerResponse; + try { + if (parameters != null) { + exploreWithMeServerResponse = restTemplate.exchange(path, method, requestEntity, Object.class, parameters); + } else { + exploreWithMeServerResponse = restTemplate.exchange(path, method, requestEntity, Object.class); + } + } catch (HttpStatusCodeException e) { + return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); + } + return prepareResponse(exploreWithMeServerResponse); + } + + private HttpHeaders defaultHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + return headers; + } + + private static ResponseEntity prepareResponse(ResponseEntity response) { + if (response.getStatusCode().is2xxSuccessful()) { + return response; + } + ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.status(response.getStatusCode()); + if (response.hasBody()) { + return responseBuilder.body(response.getBody()); + } + return responseBuilder.build(); + } +} \ No newline at end of file diff --git a/stat-service/client/src/main/java/ru/practicum/ewm/StatsClient.java b/stat-service/client/src/main/java/ru/practicum/ewm/StatsClient.java new file mode 100644 index 00000000..d89f9013 --- /dev/null +++ b/stat-service/client/src/main/java/ru/practicum/ewm/StatsClient.java @@ -0,0 +1,57 @@ +package ru.practicum.ewm; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.util.DefaultUriBuilderFactory; + +import javax.servlet.http.HttpServletRequest; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; + +public class StatsClient extends BaseClient { + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(Constants.DATE_FORMAT); + + @Value("${server.application.name:ewm-main-service}") + private String applicationName; + + public StatsClient(@Value("${server.url}") String serverUrl, RestTemplateBuilder builder) { + super( + builder + .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl)) + .requestFactory(HttpComponentsClientHttpRequestFactory::new) + .build() + ); + } + + public ResponseEntity saveHit(HttpServletRequest request) { + final EndpointHit hit = EndpointHit.builder() + .app(applicationName) + .uri(request.getRequestURI()) + .ip(request.getRemoteAddr()) + .timestamp(Timestamp.from(Instant.now()).toLocalDateTime()) + .build(); + return post(hit); + } + + public ResponseEntity getHit(LocalDateTime start, LocalDateTime end, List uris, Boolean unique) { + StringBuilder uriBuilder = new StringBuilder("/stats?start={start}&end={end}"); + Map parameters = Map.of( + "start", start.format(formatter), + "end", end.format(formatter) + ); + + if (uris != null) { + parameters.put("uris", String.join(",", uris)); + } + if (unique) { + parameters.put("unique", true); + } + return get(uriBuilder.toString(), parameters); + } +} \ No newline at end of file diff --git a/stat-service/dto/pom.xml b/stat-service/dto/pom.xml new file mode 100644 index 00000000..f481ab51 --- /dev/null +++ b/stat-service/dto/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + ru.practicum + stat-service + 0.0.1-SNAPSHOT + ../pom.xml + + + dto + + + + com.fasterxml.jackson.core + jackson-databind + + + + + 11 + 11 + UTF-8 + + \ No newline at end of file diff --git a/stat-service/dto/src/main/java/ru/practicum/ewm/Constants.java b/stat-service/dto/src/main/java/ru/practicum/ewm/Constants.java new file mode 100644 index 00000000..011ea4d3 --- /dev/null +++ b/stat-service/dto/src/main/java/ru/practicum/ewm/Constants.java @@ -0,0 +1,5 @@ +package ru.practicum.ewm; + +public class Constants { + public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; +} \ No newline at end of file diff --git a/stat-service/dto/src/main/java/ru/practicum/ewm/EndpointHit.java b/stat-service/dto/src/main/java/ru/practicum/ewm/EndpointHit.java new file mode 100644 index 00000000..f3d6bf41 --- /dev/null +++ b/stat-service/dto/src/main/java/ru/practicum/ewm/EndpointHit.java @@ -0,0 +1,22 @@ +package ru.practicum.ewm; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class EndpointHit { + private int id; + private String app; + private String uri; + private String ip; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timestamp; +} \ No newline at end of file diff --git a/stat-service/dto/src/main/java/ru/practicum/ewm/ViewStats.java b/stat-service/dto/src/main/java/ru/practicum/ewm/ViewStats.java new file mode 100644 index 00000000..422e202a --- /dev/null +++ b/stat-service/dto/src/main/java/ru/practicum/ewm/ViewStats.java @@ -0,0 +1,14 @@ +package ru.practicum.ewm; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Builder +@Getter +@ToString +public class ViewStats { + private String app; + private String uri; + private int hits; +} \ No newline at end of file diff --git a/stat-service/dto/src/main/java/ru/practicum/ewm/ViewsStatsRequest.java b/stat-service/dto/src/main/java/ru/practicum/ewm/ViewsStatsRequest.java new file mode 100644 index 00000000..cd57d391 --- /dev/null +++ b/stat-service/dto/src/main/java/ru/practicum/ewm/ViewsStatsRequest.java @@ -0,0 +1,21 @@ +package ru.practicum.ewm; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@ToString +@Builder(toBuilder = true) +public class ViewsStatsRequest { + private List uris; + @Builder.Default + private LocalDateTime start = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0); + @Builder.Default + private LocalDateTime end = LocalDateTime.now(); + private boolean unique; + private String application; +} \ No newline at end of file diff --git a/stat-service/pom.xml b/stat-service/pom.xml new file mode 100644 index 00000000..da2938b5 --- /dev/null +++ b/stat-service/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + ru.practicum + explore-with-me + 0.0.1-SNAPSHOT + + + stat-service + pom + + + server + dto + client + + + + + org.projectlombok + lombok + provided + + + + \ No newline at end of file diff --git a/stat-service/server/Dockerfile b/stat-service/server/Dockerfile new file mode 100644 index 00000000..9783921b --- /dev/null +++ b/stat-service/server/Dockerfile @@ -0,0 +1,4 @@ +FROM amazoncorretto:11-alpine-jdk +ENV JAVA_TOOL_OPTIONS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:9091 +COPY target/*.jar app.jar +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/stat-service/server/pom.xml b/stat-service/server/pom.xml new file mode 100644 index 00000000..4175540b --- /dev/null +++ b/stat-service/server/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + ru.practicum + stat-service + 0.0.1-SNAPSHOT + ../pom.xml + + + server + + + + org.springframework.boot + spring-boot-starter-web + + + + ru.practicum + dto + ${project.version} + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.postgresql + postgresql + + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + + com.h2database + h2 + runtime + + + + + 11 + 11 + UTF-8 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/stat-service/server/src/main/java/ru/practicum/ewm/EWMStatServiceApp.java b/stat-service/server/src/main/java/ru/practicum/ewm/EWMStatServiceApp.java new file mode 100644 index 00000000..419f9b60 --- /dev/null +++ b/stat-service/server/src/main/java/ru/practicum/ewm/EWMStatServiceApp.java @@ -0,0 +1,11 @@ +package ru.practicum.ewm; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class EWMStatServiceApp { + public static void main(String[] args) { + SpringApplication.run(EWMStatServiceApp.class, args); + } +} \ No newline at end of file diff --git a/stat-service/server/src/main/java/ru/practicum/ewm/controller/StatsController.java b/stat-service/server/src/main/java/ru/practicum/ewm/controller/StatsController.java new file mode 100644 index 00000000..15cd0bb9 --- /dev/null +++ b/stat-service/server/src/main/java/ru/practicum/ewm/controller/StatsController.java @@ -0,0 +1,51 @@ +package ru.practicum.ewm.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.EndpointHit; +import ru.practicum.ewm.ViewStats; +import ru.practicum.ewm.ViewsStatsRequest; +import ru.practicum.ewm.service.StatsService; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) +public class StatsController { + private final StatsService service; + + @PostMapping("/hit") + @ResponseStatus(code = HttpStatus.CREATED) + public void hit(@RequestBody EndpointHit hit) { + log.info("POST request to save information."); + service.saveHit(hit); + } + + @GetMapping("/stats") + public List getStats(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime start, + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime end, + @RequestParam(required = false) List uris, + @RequestParam(defaultValue = "false") boolean unique) { + log.info("GET request to get all statistic."); + if (uris == null) { + uris = Collections.emptyList(); + } + List results = service.getViewStatsList( + ViewsStatsRequest.builder() + .start(start) + .end(end) + .uris(uris) + .unique(unique) + .build() + ); + return results; + } +} \ No newline at end of file diff --git a/stat-service/server/src/main/java/ru/practicum/ewm/exceptions/ErrorHandler.java b/stat-service/server/src/main/java/ru/practicum/ewm/exceptions/ErrorHandler.java new file mode 100644 index 00000000..d4db30e3 --- /dev/null +++ b/stat-service/server/src/main/java/ru/practicum/ewm/exceptions/ErrorHandler.java @@ -0,0 +1,43 @@ +package ru.practicum.ewm.exceptions; + +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; + + +@Slf4j +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNotFoundException(final NotFoundException e) { + log.warn("Получен статус 404 NOT_FOUND {}", e.getMessage(), e); + return new ErrorResponse( + e.getMessage() + + ); + } + + @ExceptionHandler({MethodArgumentNotValidException.class}) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleValidationException(final Exception e) { + log.warn("Получен статус 400 BAD_REQUEST {}", e.getMessage(), e); + return new ErrorResponse( + e.getMessage() + ); + } + + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleOtherException(final Throwable e) { + log.warn("Получен статус 500 SERVER_ERROR {}", e.getMessage(), e); + return new ErrorResponse( + e.getMessage() + ); + } +} \ No newline at end of file diff --git a/stat-service/server/src/main/java/ru/practicum/ewm/exceptions/ErrorResponse.java b/stat-service/server/src/main/java/ru/practicum/ewm/exceptions/ErrorResponse.java new file mode 100644 index 00000000..49b51280 --- /dev/null +++ b/stat-service/server/src/main/java/ru/practicum/ewm/exceptions/ErrorResponse.java @@ -0,0 +1,10 @@ +package ru.practicum.ewm.exceptions; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ErrorResponse { + private final String error; +} \ No newline at end of file diff --git a/stat-service/server/src/main/java/ru/practicum/ewm/exceptions/NotFoundException.java b/stat-service/server/src/main/java/ru/practicum/ewm/exceptions/NotFoundException.java new file mode 100644 index 00000000..dc7d4adf --- /dev/null +++ b/stat-service/server/src/main/java/ru/practicum/ewm/exceptions/NotFoundException.java @@ -0,0 +1,8 @@ +package ru.practicum.ewm.exceptions; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } + +} \ No newline at end of file diff --git a/stat-service/server/src/main/java/ru/practicum/ewm/repository/StatsRepository.java b/stat-service/server/src/main/java/ru/practicum/ewm/repository/StatsRepository.java new file mode 100644 index 00000000..ba5edd51 --- /dev/null +++ b/stat-service/server/src/main/java/ru/practicum/ewm/repository/StatsRepository.java @@ -0,0 +1,15 @@ +package ru.practicum.ewm.repository; + +import ru.practicum.ewm.EndpointHit; +import ru.practicum.ewm.ViewStats; +import ru.practicum.ewm.ViewsStatsRequest; + +import java.util.List; + +public interface StatsRepository { + void saveHit(EndpointHit hit); + + List getStats(ViewsStatsRequest request); + + List getUniqueStats(ViewsStatsRequest request); +} \ No newline at end of file diff --git a/stat-service/server/src/main/java/ru/practicum/ewm/repository/StatsRepositoryImpl.java b/stat-service/server/src/main/java/ru/practicum/ewm/repository/StatsRepositoryImpl.java new file mode 100644 index 00000000..10b4734c --- /dev/null +++ b/stat-service/server/src/main/java/ru/practicum/ewm/repository/StatsRepositoryImpl.java @@ -0,0 +1,51 @@ +package ru.practicum.ewm.repository; + +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; +import ru.practicum.ewm.EndpointHit; +import ru.practicum.ewm.ViewStats; +import ru.practicum.ewm.ViewsStatsRequest; + +import java.sql.Timestamp; +import java.util.List; + +@Component +@RequiredArgsConstructor +public class StatsRepositoryImpl implements StatsRepository { + private final JdbcTemplate jdbcTemplate; + private final ViewStatsMapper viewStatsMapper; + + @Override + public void saveHit(EndpointHit hit) { + jdbcTemplate.update("INSERT INTO stats (app, uri, ip, created) VALUES (?, ?, ?, ?)", + hit.getApp(), hit.getUri(), hit.getIp(), Timestamp.valueOf(hit.getTimestamp())); + } + + @Override + public List getStats(ViewsStatsRequest request) { + String query = "SELECT app, uri, COUNT (ip) AS hits FROM stats WHERE (created >= ? AND created <= ?) "; + if (!request.getUris().isEmpty()) { + query += createUrisQuery(request.getUris()); + } + query += " GROUP BY app, uri ORDER BY hits DESC"; + return jdbcTemplate.query(query, viewStatsMapper, request.getStart(), request.getEnd()); + } + + @Override + public List getUniqueStats(ViewsStatsRequest request) { + String query = "SELECT app, uri, COUNT (DISTINCT ip) AS hits FROM stats WHERE (created >= ? AND created <= ?) "; + if (!request.getUris().isEmpty()) { + query += createUrisQuery(request.getUris()); + } + query += " GROUP BY app, uri ORDER BY hits DESC"; + return jdbcTemplate.query(query, viewStatsMapper, request.getStart(), request.getEnd()); + } + + + private String createUrisQuery(List uris) { + StringBuilder result = new StringBuilder("AND uri IN ('"); + result.append(String.join("', '", uris)); + return result.append("') ").toString(); + } +} \ No newline at end of file diff --git a/stat-service/server/src/main/java/ru/practicum/ewm/repository/ViewStatsMapper.java b/stat-service/server/src/main/java/ru/practicum/ewm/repository/ViewStatsMapper.java new file mode 100644 index 00000000..bb813d6b --- /dev/null +++ b/stat-service/server/src/main/java/ru/practicum/ewm/repository/ViewStatsMapper.java @@ -0,0 +1,22 @@ +package ru.practicum.ewm.repository; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import ru.practicum.ewm.ViewStats; + +import java.sql.ResultSet; +import java.sql.SQLException; + +@Component +public class ViewStatsMapper implements RowMapper { + + @Override + public ViewStats mapRow(ResultSet rs, int rowNum) throws SQLException { + return ViewStats.builder() + .app(rs.getString("app")) + .uri(rs.getString("uri")) + .hits(rs.getInt("hits")) + .build(); + } +} + diff --git a/stat-service/server/src/main/java/ru/practicum/ewm/service/StatsService.java b/stat-service/server/src/main/java/ru/practicum/ewm/service/StatsService.java new file mode 100644 index 00000000..dba0e8de --- /dev/null +++ b/stat-service/server/src/main/java/ru/practicum/ewm/service/StatsService.java @@ -0,0 +1,13 @@ +package ru.practicum.ewm.service; + +import ru.practicum.ewm.EndpointHit; +import ru.practicum.ewm.ViewStats; +import ru.practicum.ewm.ViewsStatsRequest; + +import java.util.List; + +public interface StatsService { + void saveHit(EndpointHit hit); + + List getViewStatsList(ViewsStatsRequest request); +} \ No newline at end of file diff --git a/stat-service/server/src/main/java/ru/practicum/ewm/service/StatsServiceImpl.java b/stat-service/server/src/main/java/ru/practicum/ewm/service/StatsServiceImpl.java new file mode 100644 index 00000000..64e3195d --- /dev/null +++ b/stat-service/server/src/main/java/ru/practicum/ewm/service/StatsServiceImpl.java @@ -0,0 +1,30 @@ +package ru.practicum.ewm.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.practicum.ewm.EndpointHit; +import ru.practicum.ewm.ViewStats; +import ru.practicum.ewm.ViewsStatsRequest; +import ru.practicum.ewm.repository.StatsRepository; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class StatsServiceImpl implements StatsService { + private final StatsRepository statRepository; + + @Override + public void saveHit(EndpointHit hit) { + statRepository.saveHit(hit); + } + + @Override + public List getViewStatsList(ViewsStatsRequest request) { + if (request.isUnique()) { + return statRepository.getUniqueStats(request); + } + + return statRepository.getStats(request); + } +} \ No newline at end of file diff --git a/stat-service/server/src/main/resources/application.properties b/stat-service/server/src/main/resources/application.properties new file mode 100644 index 00000000..e6cefba8 --- /dev/null +++ b/stat-service/server/src/main/resources/application.properties @@ -0,0 +1,23 @@ +server.port=9090 +logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG +spring.jpa.hibernate.ddl-auto=none +spring.jpa.properties.hibernate.jdbc.time_zone=UTC +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:5432/statdb +spring.datasource.username=ewm +spring.datasource.password=ewm +#--- +spring.config.activate.on-profile=local +spring.datasource.url=jdbc:postgresql://localhost:5432/postgres +#--- +spring.config.activate.on-profile=ci,test +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:statdb +spring.datasource.username=ewm +spring.datasource.password=ewm +#--- +server.application.name=ewm-main-service \ No newline at end of file diff --git a/stat-service/server/src/main/resources/schema.sql b/stat-service/server/src/main/resources/schema.sql new file mode 100644 index 00000000..df5b158c --- /dev/null +++ b/stat-service/server/src/main/resources/schema.sql @@ -0,0 +1,9 @@ +DROP TABLE IF EXISTS stats; + +CREATE TABLE IF NOT EXISTS stats ( + id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + app VARCHAR(255), + uri VARCHAR(255), + ip VARCHAR(255), + created TIMESTAMP WITHOUT TIME ZONE + ); \ No newline at end of file