diff --git a/docker-compose.yml b/docker-compose.yml
index be96142..c1d5b2a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,14 +1,65 @@
+version: '3.8'
+
services:
+
stats-server:
+ container_name: stats-server
+ image: stats-server
+ build: ./stat/stat-server
ports:
- "9090:9090"
+ depends_on:
+ - stats-db
+ environment:
+ SPRING_PROFILES_ACTIVE: docker
+ SPRING_DATASOURCE_URL: jdbc:postgresql://stats-db:5432/stats-service
+ SPRING_DATASOURCE_USERNAME: user
+ SPRING_DATASOURCE_PASSWORD: 12345
stats-db:
image: postgres:16.1
+ container_name: postgres-stat
+ ports:
+ - "6541:5432"
+ environment:
+ POSTGRES_USER: user
+ POSTGRES_PASSWORD: 12345
+ POSTGRES_DB: stats-service
+ healthcheck:
+ test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER
+ interval: 5s
+ timeout: 5s
+ retries: 10
ewm-service:
+ build:
+ context: ./evm-service
+ dockerfile: Dockerfile
+ image: explore-with-me-service
+ container_name: explore-with-me-service
ports:
- "8080:8080"
+ environment:
+ SPRING_PROFILES_ACTIVE: docker
+ SPRING_DATASOURCE_URL: jdbc:postgresql://ewm-db:5432/ewm-service
+ SPRING_DATASOURCE_USERNAME: user
+ SPRING_DATASOURCE_PASSWORD: 12345
+ depends_on:
+ - ewm-db
+ - stats-server
ewm-db:
image: postgres:16.1
+ container_name: postgres-service
+ ports:
+ - "6542:5432"
+ environment:
+ POSTGRES_USER: user
+ POSTGRES_PASSWORD: 12345
+ POSTGRES_DB: ewm-service
+ healthcheck:
+ test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER
+ interval: 5s
+ timeout: 5s
+ retries: 10
+
diff --git a/evm-service/Dockerfile b/evm-service/Dockerfile
new file mode 100644
index 0000000..0ff1817
--- /dev/null
+++ b/evm-service/Dockerfile
@@ -0,0 +1,5 @@
+FROM eclipse-temurin:21-jre-jammy
+VOLUME /tmp
+ARG JAR_FILE=target/*.jar
+COPY ${JAR_FILE} app.jar
+ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]
\ No newline at end of file
diff --git a/evm-service/pom.xml b/evm-service/pom.xml
new file mode 100644
index 0000000..96ad8a3
--- /dev/null
+++ b/evm-service/pom.xml
@@ -0,0 +1,154 @@
+
+
+ 4.0.0
+
+ ru.practicum
+ explore-with-me
+ 0.0.1-SNAPSHOT
+
+
+ evm-service
+
+
+ 5.0.0
+ 1.6.3
+ 0.2.0
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.mapstruct
+ mapstruct
+ 1.6.3
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ ${lombok-mapstruct-binding.version}
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+ ru.practicum
+ stat-dto
+ 0.0.1-SNAPSHOT
+
+
+ ru.practicum
+ stat-client
+ 0.0.1-SNAPSHOT
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 1.8
+ 1.8
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ ${lombok-mapstruct-binding.version}
+
+
+ true
+
+
+ -Amapstruct.suppressGeneratorTimestamp=true
+
+
+ -Amapstruct.suppressGeneratorVersionInfoComment=true
+
+
+ -Amapstruct.verbose=true
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/evm-service/src/main/java/ru/practicum/Category/Controller/CategoryAdminController.java b/evm-service/src/main/java/ru/practicum/Category/Controller/CategoryAdminController.java
new file mode 100644
index 0000000..6864f84
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Category/Controller/CategoryAdminController.java
@@ -0,0 +1,35 @@
+package ru.practicum.Category.Controller;
+
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.Category.DTO.CategoryDTO;
+import ru.practicum.Category.DTO.NewCategoryDTO;
+import ru.practicum.Category.Service.Admin.CategoryAdminServiceImpl;
+
+@RestController
+@RequestMapping("/admin/categories")
+@RequiredArgsConstructor
+public class CategoryAdminController {
+ private final CategoryAdminServiceImpl categoryService;
+
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ public CategoryDTO createCategory(@RequestBody @Valid NewCategoryDTO category) {
+ return categoryService.addCategory(category);
+ }
+
+ @DeleteMapping("/{catId}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void deleteCategory(@PathVariable Integer catId) {
+ categoryService.deleteCategory(catId);
+ }
+
+ @PatchMapping("/{catId}")
+ @ResponseStatus(HttpStatus.OK)
+ public CategoryDTO updateCategory(@PathVariable Integer catId, @RequestBody @Valid CategoryDTO category) {
+ return categoryService.updateCategory(catId, category);
+ }
+
+}
diff --git a/evm-service/src/main/java/ru/practicum/Category/Controller/CategoryPublicController.java b/evm-service/src/main/java/ru/practicum/Category/Controller/CategoryPublicController.java
new file mode 100644
index 0000000..65cbea9
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Category/Controller/CategoryPublicController.java
@@ -0,0 +1,28 @@
+package ru.practicum.Category.Controller;
+
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.Category.DTO.CategoryDTO;
+import ru.practicum.Category.Service.Public.CategoryPublicService;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/categories")
+@RequiredArgsConstructor
+public class CategoryPublicController {
+ private final CategoryPublicService categoryService;
+
+ @GetMapping("/{catId}")
+ public CategoryDTO getCategory(@PathVariable Integer catId) {
+ return categoryService.getCategory(catId);
+ }
+
+ @GetMapping
+ public List getCategories(@RequestParam(defaultValue = "0") @PositiveOrZero Integer from,
+ @RequestParam(defaultValue = "10") @Positive Integer size) {
+ return categoryService.getCategories(from, size);
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Category/DTO/CategoryDTO.java b/evm-service/src/main/java/ru/practicum/Category/DTO/CategoryDTO.java
new file mode 100644
index 0000000..fdc9e3d
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Category/DTO/CategoryDTO.java
@@ -0,0 +1,18 @@
+package ru.practicum.Category.DTO;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class CategoryDTO {
+ private Integer id;
+
+ @NotBlank
+ @Size(max = 50)
+ private String name;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Category/DTO/NewCategoryDTO.java b/evm-service/src/main/java/ru/practicum/Category/DTO/NewCategoryDTO.java
new file mode 100644
index 0000000..0e0dbfc
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Category/DTO/NewCategoryDTO.java
@@ -0,0 +1,16 @@
+package ru.practicum.Category.DTO;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class NewCategoryDTO {
+ @NotBlank
+ @Size(max = 50)
+ private String name;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Category/Mapper/CategoryMapper.java b/evm-service/src/main/java/ru/practicum/Category/Mapper/CategoryMapper.java
new file mode 100644
index 0000000..a1d82dd
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Category/Mapper/CategoryMapper.java
@@ -0,0 +1,15 @@
+package ru.practicum.Category.Mapper;
+
+import org.mapstruct.Mapper;
+import ru.practicum.Category.DTO.CategoryDTO;
+import ru.practicum.Category.DTO.NewCategoryDTO;
+import ru.practicum.Category.Model.Category;
+
+@Mapper(componentModel = "spring")
+public interface CategoryMapper {
+ Category toCategory(NewCategoryDTO categoryDTO);
+
+ CategoryDTO toCategoryDTO(Category category);
+
+ Category toCategory(CategoryDTO categoryDTO);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Category/Model/Category.java b/evm-service/src/main/java/ru/practicum/Category/Model/Category.java
new file mode 100644
index 0000000..92b44d1
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Category/Model/Category.java
@@ -0,0 +1,22 @@
+package ru.practicum.Category.Model;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "categories")
+public class Category {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Integer id;
+
+ @Column(name = "name", length = 50)
+ private String name;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Category/Repository/CategoryRepository.java b/evm-service/src/main/java/ru/practicum/Category/Repository/CategoryRepository.java
new file mode 100644
index 0000000..f1634f8
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Category/Repository/CategoryRepository.java
@@ -0,0 +1,16 @@
+package ru.practicum.Category.Repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import ru.practicum.Category.Model.Category;
+
+import java.util.Optional;
+
+public interface CategoryRepository extends JpaRepository {
+ Category save(Category category);
+
+ void deleteById(Integer id);
+
+ Optional findById(Integer id);
+
+
+}
diff --git a/evm-service/src/main/java/ru/practicum/Category/Service/Admin/CategoryAdminService.java b/evm-service/src/main/java/ru/practicum/Category/Service/Admin/CategoryAdminService.java
new file mode 100644
index 0000000..4609760
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Category/Service/Admin/CategoryAdminService.java
@@ -0,0 +1,12 @@
+package ru.practicum.Category.Service.Admin;
+
+import ru.practicum.Category.DTO.CategoryDTO;
+import ru.practicum.Category.DTO.NewCategoryDTO;
+
+public interface CategoryAdminService {
+ CategoryDTO addCategory(NewCategoryDTO categoryDTO);
+
+ void deleteCategory(Integer catId);
+
+ CategoryDTO updateCategory(int id, CategoryDTO categoryDTO);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Category/Service/Admin/CategoryAdminServiceImpl.java b/evm-service/src/main/java/ru/practicum/Category/Service/Admin/CategoryAdminServiceImpl.java
new file mode 100644
index 0000000..3e13534
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Category/Service/Admin/CategoryAdminServiceImpl.java
@@ -0,0 +1,60 @@
+package ru.practicum.Category.Service.Admin;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import ru.practicum.Category.DTO.CategoryDTO;
+import ru.practicum.Category.DTO.NewCategoryDTO;
+import ru.practicum.Category.Mapper.CategoryMapper;
+import ru.practicum.Category.Model.Category;
+import ru.practicum.Category.Repository.CategoryRepository;
+import ru.practicum.Event.Model.Event;
+import ru.practicum.Event.Repository.EventRepository;
+import ru.practicum.Exception.ConflictException;
+import ru.practicum.Exception.NotFoundException;
+
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class CategoryAdminServiceImpl implements CategoryAdminService {
+ private final CategoryMapper categoryMapper;
+ private final CategoryRepository repository;
+ private final EventRepository eventRepository;
+
+ @Override
+ public CategoryDTO addCategory(NewCategoryDTO newCategory) {
+ Category category = categoryMapper.toCategory(newCategory);
+ return categoryMapper.toCategoryDTO(repository.save(category));
+ }
+
+ @Override
+ public void deleteCategory(Integer catId) {
+ Category category = repository.findById(catId)
+ .orElseThrow(() -> new NotFoundException("Category with id " + catId + " not found"));
+
+ List events = eventRepository.findEventsByCategory_Id(catId);
+ if (!events.isEmpty()) {
+ throw new ConflictException("Category with id " + catId + " has connected events");
+ }
+
+ // Integer categoryEvents =
+
+ repository.deleteById(catId);
+ }
+
+ @Override
+ public CategoryDTO updateCategory(int id, CategoryDTO newCategory) {
+ Category category = repository.findById(id)
+ .orElseThrow(() -> new NotFoundException("Category with id " + id + " not found"));
+
+ if (category.getName().equals(newCategory.getName())) {
+ return categoryMapper.toCategoryDTO(category);
+ } else {
+ return categoryMapper.toCategoryDTO(repository.save(categoryMapper.toCategory(newCategory)));
+ }
+
+
+ }
+
+
+}
diff --git a/evm-service/src/main/java/ru/practicum/Category/Service/Public/CategoryPublicService.java b/evm-service/src/main/java/ru/practicum/Category/Service/Public/CategoryPublicService.java
new file mode 100644
index 0000000..7d614ec
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Category/Service/Public/CategoryPublicService.java
@@ -0,0 +1,11 @@
+package ru.practicum.Category.Service.Public;
+
+import ru.practicum.Category.DTO.CategoryDTO;
+
+import java.util.List;
+
+public interface CategoryPublicService {
+ CategoryDTO getCategory(Integer catId);
+
+ List getCategories(Integer from, Integer size);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Category/Service/Public/CategoryPublicServiceImpl.java b/evm-service/src/main/java/ru/practicum/Category/Service/Public/CategoryPublicServiceImpl.java
new file mode 100644
index 0000000..fb00324
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Category/Service/Public/CategoryPublicServiceImpl.java
@@ -0,0 +1,40 @@
+package ru.practicum.Category.Service.Public;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+import ru.practicum.Category.DTO.CategoryDTO;
+import ru.practicum.Category.Mapper.CategoryMapper;
+import ru.practicum.Category.Model.Category;
+import ru.practicum.Category.Repository.CategoryRepository;
+import ru.practicum.Exception.NotFoundException;
+
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class CategoryPublicServiceImpl implements CategoryPublicService {
+ private final CategoryRepository repository;
+ private final CategoryMapper categoryMapper;
+
+ @Override
+ public CategoryDTO getCategory(Integer catId) {
+ Category category = repository.findById(catId)
+ .orElseThrow(() -> new NotFoundException("Category with id " + catId + " not found"));
+ return categoryMapper.toCategoryDTO(category);
+ }
+
+
+
+ @Override
+ public List getCategories(Integer from, Integer size) {
+ Pageable pageable = PageRequest.of(from, size, Sort.by("id").ascending());
+ Page categories = repository.findAll(pageable);
+ return categories.getContent().stream()
+ .map(categoryMapper::toCategoryDTO)
+ .toList();
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/CompilationDTO/CompilationDTO.java b/evm-service/src/main/java/ru/practicum/Compilation/CompilationDTO/CompilationDTO.java
new file mode 100644
index 0000000..062239e
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/CompilationDTO/CompilationDTO.java
@@ -0,0 +1,26 @@
+package ru.practicum.Compilation.CompilationDTO;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.Event.DTO.EventShortDTO;
+
+import java.util.Set;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class CompilationDTO {
+ @NotNull
+ private Integer id;
+
+ @NotNull
+ private Boolean pinned;
+
+ @NotBlank
+ private String title;
+
+ private Set events;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/CompilationDTO/CompilationsListRequestParams.java b/evm-service/src/main/java/ru/practicum/Compilation/CompilationDTO/CompilationsListRequestParams.java
new file mode 100644
index 0000000..02ca7bc
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/CompilationDTO/CompilationsListRequestParams.java
@@ -0,0 +1,16 @@
+package ru.practicum.Compilation.CompilationDTO;
+
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.Data;
+
+@Data
+public class CompilationsListRequestParams {
+ private Boolean pinned;
+
+ @PositiveOrZero
+ private Integer from = 0;
+
+ @Positive
+ private Integer size = 10;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/CompilationDTO/NewCompilationDTO.java b/evm-service/src/main/java/ru/practicum/Compilation/CompilationDTO/NewCompilationDTO.java
new file mode 100644
index 0000000..6286f29
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/CompilationDTO/NewCompilationDTO.java
@@ -0,0 +1,21 @@
+package ru.practicum.Compilation.CompilationDTO;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class NewCompilationDTO {
+ private ArrayList events;
+ private Boolean pinned = false;
+
+ @NotBlank
+ @Size(min = 1, max = 50)
+ private String title;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/CompilationDTO/UpdateCompilationRequest.java b/evm-service/src/main/java/ru/practicum/Compilation/CompilationDTO/UpdateCompilationRequest.java
new file mode 100644
index 0000000..9339d0e
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/CompilationDTO/UpdateCompilationRequest.java
@@ -0,0 +1,20 @@
+package ru.practicum.Compilation.CompilationDTO;
+
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+import java.util.ArrayList;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UpdateCompilationRequest {
+ private ArrayList events;
+ private Boolean pinned;
+
+ @Size(min = 1, max = 50)
+ private String title;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/Controller/CompilationAdminController.java b/evm-service/src/main/java/ru/practicum/Compilation/Controller/CompilationAdminController.java
new file mode 100644
index 0000000..24c1cf8
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/Controller/CompilationAdminController.java
@@ -0,0 +1,37 @@
+package ru.practicum.Compilation.Controller;
+
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.Compilation.CompilationDTO.CompilationDTO;
+import ru.practicum.Compilation.CompilationDTO.NewCompilationDTO;
+import ru.practicum.Compilation.CompilationDTO.UpdateCompilationRequest;
+import ru.practicum.Compilation.Service.Admin.CompilationAdminServiceImpl;
+
+@RestController
+@RequestMapping("/admin/compilations")
+@RequiredArgsConstructor
+public class CompilationAdminController {
+ private final CompilationAdminServiceImpl compilationService;
+
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ public CompilationDTO addCompilation(@RequestBody @Valid NewCompilationDTO compilationDTO) {
+ return compilationService.addCompilation(compilationDTO);
+
+ }
+
+ @DeleteMapping("/{compId}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void deleteCompilation(@PathVariable Integer compId) {
+ compilationService.deleteCompilation(compId);
+ }
+
+ @PatchMapping("/{compId}")
+ public CompilationDTO updateCompilation(@PathVariable Integer compId,
+ @RequestBody @Valid UpdateCompilationRequest compilationDTO) {
+ return compilationService.updateCompilation(compId, compilationDTO);
+ }
+
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/Controller/CompilationPublicController.java b/evm-service/src/main/java/ru/practicum/Compilation/Controller/CompilationPublicController.java
new file mode 100644
index 0000000..81fc643
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/Controller/CompilationPublicController.java
@@ -0,0 +1,26 @@
+package ru.practicum.Compilation.Controller;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.Compilation.CompilationDTO.CompilationDTO;
+import ru.practicum.Compilation.CompilationDTO.CompilationsListRequestParams;
+import ru.practicum.Compilation.Service.Public.CompilationPublicServiceImpl;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/compilations")
+@RequiredArgsConstructor
+public class CompilationPublicController {
+ private final CompilationPublicServiceImpl compilationService;
+
+ @GetMapping("/{compId}")
+ public CompilationDTO getCompilation(@PathVariable Integer compId) {
+ return compilationService.getCompilation(compId);
+ }
+
+ @GetMapping
+ public List getCompilations(@ModelAttribute CompilationsListRequestParams params) {
+ return compilationService.getCompilations(params);
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/Mapper/CompilationMapper.java b/evm-service/src/main/java/ru/practicum/Compilation/Mapper/CompilationMapper.java
new file mode 100644
index 0000000..6020c2c
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/Mapper/CompilationMapper.java
@@ -0,0 +1,18 @@
+package ru.practicum.Compilation.Mapper;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import ru.practicum.Compilation.CompilationDTO.CompilationDTO;
+import ru.practicum.Compilation.CompilationDTO.NewCompilationDTO;
+import ru.practicum.Compilation.Model.Compilation;
+
+@Mapper(componentModel = "spring")
+public interface CompilationMapper {
+ CompilationDTO toDto(Compilation compilation);
+
+ Compilation toCompilation(CompilationDTO dto);
+
+ @Mapping(target = "events", ignore = true)
+ @Mapping(target = "id", ignore = true)
+ Compilation toCompilation(NewCompilationDTO compilation);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/Model/Compilation.java b/evm-service/src/main/java/ru/practicum/Compilation/Model/Compilation.java
new file mode 100644
index 0000000..221ebbe
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/Model/Compilation.java
@@ -0,0 +1,35 @@
+package ru.practicum.Compilation.Model;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.Event.Model.Event;
+
+import java.util.Set;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "compilations")
+public class Compilation {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Integer id;
+
+ @ManyToMany
+ @JoinTable(
+ name = "compilation_events",
+ joinColumns = @JoinColumn(name = "compilation_id"),
+ inverseJoinColumns = @JoinColumn(name = "event_id")
+ )
+ private Set events;
+
+ @Column(name = "pinned")
+ private Boolean pinned;
+
+ @Column(name = "title")
+ private String title;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/Repository/CompilationRepository.java b/evm-service/src/main/java/ru/practicum/Compilation/Repository/CompilationRepository.java
new file mode 100644
index 0000000..8a4bc5c
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/Repository/CompilationRepository.java
@@ -0,0 +1,17 @@
+package ru.practicum.Compilation.Repository;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.jpa.repository.JpaRepository;
+import ru.practicum.Compilation.Model.Compilation;
+
+import org.springframework.data.domain.Pageable;
+import java.util.Optional;
+
+public interface CompilationRepository extends JpaRepository {
+
+ Optional findCompilationById(Integer compId);
+
+ Compilation save(Compilation compilation);
+
+ Page findCompilationByPinned(Boolean pinned, Pageable pageable);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/Service/Admin/CompilationAdminService.java b/evm-service/src/main/java/ru/practicum/Compilation/Service/Admin/CompilationAdminService.java
new file mode 100644
index 0000000..71b3a9e
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/Service/Admin/CompilationAdminService.java
@@ -0,0 +1,14 @@
+package ru.practicum.Compilation.Service.Admin;
+
+import ru.practicum.Compilation.CompilationDTO.CompilationDTO;
+import ru.practicum.Compilation.CompilationDTO.NewCompilationDTO;
+import ru.practicum.Compilation.CompilationDTO.UpdateCompilationRequest;
+
+
+public interface CompilationAdminService {
+ CompilationDTO addCompilation(NewCompilationDTO compilationDTO);
+
+ void deleteCompilation(Integer compId);
+
+ CompilationDTO updateCompilation(Integer compId, UpdateCompilationRequest compilationDTO);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/Service/Admin/CompilationAdminServiceImpl.java b/evm-service/src/main/java/ru/practicum/Compilation/Service/Admin/CompilationAdminServiceImpl.java
new file mode 100644
index 0000000..764c182
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/Service/Admin/CompilationAdminServiceImpl.java
@@ -0,0 +1,66 @@
+package ru.practicum.Compilation.Service.Admin;
+
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import ru.practicum.Compilation.CompilationDTO.CompilationDTO;
+import ru.practicum.Compilation.CompilationDTO.NewCompilationDTO;
+import ru.practicum.Compilation.CompilationDTO.UpdateCompilationRequest;
+import ru.practicum.Compilation.Mapper.CompilationMapper;
+import ru.practicum.Compilation.Model.Compilation;
+import ru.practicum.Compilation.Repository.CompilationRepository;
+import ru.practicum.Event.Mapper.EventMapper;
+import ru.practicum.Event.Model.Event;
+import ru.practicum.Event.Repository.EventRepository;
+import ru.practicum.Exception.NotFoundException;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@Service
+@RequiredArgsConstructor
+public class CompilationAdminServiceImpl implements CompilationAdminService {
+ private final CompilationRepository compilationRepository;
+ private final EventRepository eventRepository;
+ private final CompilationMapper compilationMapper;
+ private final EventMapper eventMapper;
+
+ @Override
+ public CompilationDTO addCompilation(NewCompilationDTO compilationDTO) {
+ Compilation compilation = compilationMapper.toCompilation(compilationDTO);
+
+ if (compilationDTO.getEvents() != null) {
+ Set events = setEvents(compilationDTO.getEvents());
+ compilation.setEvents(events);
+ }
+
+ return compilationMapper.toDto(compilationRepository.save(compilation));
+ }
+
+ @Override
+ public void deleteCompilation(Integer compId) {
+ compilationRepository.delete(checkCompilation(compId));
+ }
+
+ @Override
+ public CompilationDTO updateCompilation(Integer compId, UpdateCompilationRequest compilationDTO) {
+ Compilation compilation = checkCompilation(compId);
+
+ if (compilationDTO.getEvents() != null) {
+ Set events = setEvents(compilationDTO.getEvents());
+ compilation.setEvents(events);
+ }
+
+ return compilationMapper.toDto(compilationRepository.save(compilation));
+ }
+
+ private Compilation checkCompilation(Integer compId) {
+ return compilationRepository.findCompilationById(compId)
+ .orElseThrow(() -> new NotFoundException("Compilation with id " + compId + " not found"));
+ }
+
+ private Set setEvents(List eventIds) {
+ return new HashSet<>(eventRepository.findEventsByIdIn(eventIds));
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/Service/Public/CompilationPublicService.java b/evm-service/src/main/java/ru/practicum/Compilation/Service/Public/CompilationPublicService.java
new file mode 100644
index 0000000..3b76595
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/Service/Public/CompilationPublicService.java
@@ -0,0 +1,12 @@
+package ru.practicum.Compilation.Service.Public;
+
+import ru.practicum.Compilation.CompilationDTO.CompilationDTO;
+import ru.practicum.Compilation.CompilationDTO.CompilationsListRequestParams;
+
+import java.util.List;
+
+public interface CompilationPublicService {
+ CompilationDTO getCompilation(Integer compId);
+
+ List getCompilations(CompilationsListRequestParams params);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Compilation/Service/Public/CompilationPublicServiceImpl.java b/evm-service/src/main/java/ru/practicum/Compilation/Service/Public/CompilationPublicServiceImpl.java
new file mode 100644
index 0000000..f8a14b9
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Compilation/Service/Public/CompilationPublicServiceImpl.java
@@ -0,0 +1,116 @@
+package ru.practicum.Compilation.Service.Public;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.stereotype.Service;
+import ru.practicum.Compilation.CompilationDTO.CompilationDTO;
+import ru.practicum.Compilation.CompilationDTO.CompilationsListRequestParams;
+import ru.practicum.Compilation.Mapper.CompilationMapper;
+import ru.practicum.Compilation.Model.Compilation;
+import ru.practicum.Compilation.Repository.CompilationRepository;
+import ru.practicum.Event.Mapper.EventMapper;
+import ru.practicum.Event.Model.Event;
+import ru.practicum.Event.Repository.EventRepository;
+import ru.practicum.Exception.NotFoundException;
+import ru.practicum.StatsClient;
+import ru.practicum.ViewStatsDTO;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+import static java.lang.Integer.parseInt;
+
+@Service
+@RequiredArgsConstructor
+public class CompilationPublicServiceImpl implements CompilationPublicService {
+ private final CompilationRepository compilationRepository;
+ private final CompilationMapper compilationMapper;
+ private final EventRepository eventRepository;
+ private final StatsClient statsClient;
+ private final EventMapper eventMapper;
+
+ @Override
+ public CompilationDTO getCompilation(Integer compId) {
+ Compilation compilation = checkCompilation(compId);
+ CompilationDTO compilationDTO = compilationMapper.toDto(compilation);
+
+ List events = compilation.getEvents().stream().toList();
+
+ if ((events != null) && (!events.isEmpty())) {
+ Map views = getEvents(events);
+ compilationDTO.getEvents().stream()
+ .peek(eventShortDTO -> eventShortDTO.setViews(views.get(eventShortDTO.getId())));
+ }
+
+ return compilationDTO;
+ }
+
+ @Override
+ public List getCompilations(CompilationsListRequestParams params) {
+ int page = (int) Math.floor((double) params.getFrom() / params.getSize());
+ Pageable pageable = PageRequest.of(page, params.getSize());
+ Page compilations = params.getPinned() == null
+ ? compilationRepository.findAll(pageable)
+ : compilationRepository.findCompilationByPinned(params.getPinned(), pageable);
+
+ List events = compilations.stream()
+ .flatMap(c -> c.getEvents().stream())
+ .distinct()
+ .toList();
+
+ List compilationDtos = compilations.getContent().stream()
+ .map(compilationMapper::toDto)
+ .toList();
+
+
+ if ((events != null) && (!events.isEmpty())) {
+ Map views = getEvents(events);
+ compilationDtos = compilationDtos.stream().peek(compilationDTO -> {
+ compilationDTO.getEvents().stream()
+ .peek(eventShortDTO -> eventShortDTO.setViews(views.get(eventShortDTO.getId())
+ ));
+ }
+ ).toList();
+ }
+
+ return compilationDtos;
+
+ }
+
+ private Compilation checkCompilation(Integer compId) {
+ return compilationRepository.findCompilationById(compId)
+ .orElseThrow(() -> new NotFoundException("Compilation with id " + compId + " not found"));
+ }
+
+ private Map getEvents(List events) {
+ if (events == null) {
+ return Collections.emptyMap();
+ }
+
+ List ids = events.stream()
+ .map(Event::getId)
+ .toList();
+ List events1 = eventRepository.findEventsByIdIn(ids);
+
+ List uris = events.stream()
+ .map(event -> "/events/" + event.getId())
+ .distinct()
+ .toList();
+ LocalDateTime start = LocalDateTime.now().minusMonths(6);
+ LocalDateTime end = LocalDateTime.now().plusMinutes(1);
+ List viewStatsDTOS = statsClient.viewStats(start, end, uris, true);
+
+ Map views = new HashMap<>();
+ if (!viewStatsDTOS.isEmpty()) {
+ for (ViewStatsDTO viewStatsDTO : viewStatsDTOS) {
+ int eventId = parseInt(viewStatsDTO.getUri().substring("/events/".length()));
+ views.put(eventId, viewStatsDTO.getHits());
+ }
+ }
+
+ return views;
+ }
+}
+
diff --git a/evm-service/src/main/java/ru/practicum/Event/Controller/EventAdminController.java b/evm-service/src/main/java/ru/practicum/Event/Controller/EventAdminController.java
new file mode 100644
index 0000000..24f166c
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Controller/EventAdminController.java
@@ -0,0 +1,32 @@
+package ru.practicum.Event.Controller;
+
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.Event.DTO.EventFullDTO;
+import ru.practicum.Event.DTO.SearchEventsDTO;
+import ru.practicum.Event.DTO.UpdateEventAdminRequest;
+import ru.practicum.Event.Service.Admin.EventAdminService;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/admin/events")
+@RequiredArgsConstructor
+public class EventAdminController {
+ private final EventAdminService eventAdminService;
+
+ @GetMapping
+ public List getEvents(@ModelAttribute @Valid SearchEventsDTO dto) {
+ return eventAdminService.getEvents(dto);
+ }
+
+ @PatchMapping("/{eventId}")
+ @ResponseStatus(HttpStatus.OK)
+ public EventFullDTO updateEvent(@PathVariable Integer eventId,
+ @RequestBody @Valid UpdateEventAdminRequest request) {
+ return eventAdminService.updateEvent(eventId, request);
+ }
+
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/Controller/EventPrivateController.java b/evm-service/src/main/java/ru/practicum/Event/Controller/EventPrivateController.java
new file mode 100644
index 0000000..fb82c2c
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Controller/EventPrivateController.java
@@ -0,0 +1,59 @@
+package ru.practicum.Event.Controller;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.Event.DTO.*;
+import ru.practicum.Event.Service.Private.EventPrivateServiceImpl;
+import ru.practicum.Request.RequestDTO.ParticipationRequestDto;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/users/{userId}/events")
+@RequiredArgsConstructor
+public class EventPrivateController {
+ private final EventPrivateServiceImpl eventPrivateService;
+
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ public EventFullDTO addEvent(@PathVariable Integer userId, @RequestBody @Valid NewEventDTO event) {
+ return eventPrivateService.addEvent(userId, event);
+ }
+
+ @GetMapping
+ public List getEvents(@PathVariable Integer userId,
+ @RequestParam(defaultValue = "0") @PositiveOrZero Integer from,
+ @RequestParam(defaultValue = "10") @Positive Integer size) {
+ return eventPrivateService.getEvents(userId, from, size);
+ }
+
+ @GetMapping("/{eventId}")
+ public EventFullDTO getEvent(@PathVariable Integer userId, @PathVariable Integer eventId) {
+ return eventPrivateService.getEvent(userId, eventId);
+ }
+
+ @PatchMapping("/{eventId}")
+ public EventFullDTO updateEvent(@PathVariable Integer userId,
+ @PathVariable Integer eventId,
+ @RequestBody @Valid UpdateEventUserRequest event) {
+ return eventPrivateService.updateEvent(userId, eventId, event);
+ }
+
+ @GetMapping("/{eventId}/requests")
+ public List getUserRequests(@PathVariable Integer userId,
+ @PathVariable Integer eventId) {
+ return eventPrivateService.getUserRequests(userId, eventId);
+ }
+
+ @PatchMapping("/{eventId}/requests")
+ public EventRequestStatusUpdateResult updateRequests(@RequestBody @Valid EventRequestStatusUpdateRequest request,
+ @PathVariable Integer userId,
+ @PathVariable Integer eventId) {
+ return eventPrivateService.updateRequests(request, userId, eventId);
+ }
+
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/Controller/EventPublicController.java b/evm-service/src/main/java/ru/practicum/Event/Controller/EventPublicController.java
new file mode 100644
index 0000000..d559408
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Controller/EventPublicController.java
@@ -0,0 +1,31 @@
+package ru.practicum.Event.Controller;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.Event.DTO.EventFullDTO;
+import ru.practicum.Event.DTO.EventPublicParams;
+import ru.practicum.Event.DTO.EventShortDTO;
+import ru.practicum.Event.Service.Public.EventPublicServiceImpl;
+
+import java.util.List;
+
+
+@RestController
+@RequestMapping("/events")
+@RequiredArgsConstructor
+public class EventPublicController {
+ private final EventPublicServiceImpl eventPublicService;
+
+ @GetMapping("/{id}")
+ public EventFullDTO findEventById(@PathVariable Integer id, HttpServletRequest request) {
+ return eventPublicService.findEventById(id, request);
+ }
+
+ @GetMapping
+ public List findAllEvents(@ModelAttribute @Valid EventPublicParams params,
+ HttpServletRequest request) {
+ return eventPublicService.findAllEvents(params, request);
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/DTO/EventFullDTO.java b/evm-service/src/main/java/ru/practicum/Event/DTO/EventFullDTO.java
new file mode 100644
index 0000000..71d8c1e
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/DTO/EventFullDTO.java
@@ -0,0 +1,53 @@
+package ru.practicum.Event.DTO;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.Category.DTO.CategoryDTO;
+import ru.practicum.StatsRequestDTO;
+import ru.practicum.User.DTO.UserShortDTO;
+import ru.practicum.Event.Model.Location;
+import ru.practicum.Event.Model.State;
+
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class EventFullDTO {
+ private Integer id;
+
+ private String annotation;
+
+ private CategoryDTO category;
+
+ private Integer confirmedRequests;
+
+ @JsonFormat(pattern = StatsRequestDTO.DATE_FORMAT)
+ private LocalDateTime createdOn;
+
+ private String description;
+
+ @JsonFormat(pattern = StatsRequestDTO.DATE_FORMAT)
+ private LocalDateTime eventDate;
+
+ private UserShortDTO initiator;
+
+ private Location location;
+
+ private Boolean paid;
+
+ private Integer participantLimit;
+
+ @JsonFormat(pattern = StatsRequestDTO.DATE_FORMAT)
+ private LocalDateTime publishedOn = LocalDateTime.now();
+
+ private Boolean requestModeration;
+
+ private State state;
+
+ private String title;
+
+ private Integer views;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/DTO/EventPublicParams.java b/evm-service/src/main/java/ru/practicum/Event/DTO/EventPublicParams.java
new file mode 100644
index 0000000..0224c91
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/DTO/EventPublicParams.java
@@ -0,0 +1,37 @@
+package ru.practicum.Event.DTO;
+
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.format.annotation.DateTimeFormat;
+import ru.practicum.StatsRequestDTO;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class EventPublicParams {
+ private String text;
+ public ArrayList categories;
+ private Boolean paid;
+
+ @DateTimeFormat(pattern = StatsRequestDTO.DATE_FORMAT)
+ private LocalDateTime rangeStart;
+
+ @DateTimeFormat(pattern = StatsRequestDTO.DATE_FORMAT)
+ private LocalDateTime rangeEnd;
+
+ private Boolean onlyAvailable = false;
+
+ private String sort;
+
+ @PositiveOrZero
+ private Integer from = 0;
+
+ @Positive
+ private Integer size = 10;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/DTO/EventRequestStatusUpdateRequest.java b/evm-service/src/main/java/ru/practicum/Event/DTO/EventRequestStatusUpdateRequest.java
new file mode 100644
index 0000000..b7bade6
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/DTO/EventRequestStatusUpdateRequest.java
@@ -0,0 +1,24 @@
+package ru.practicum.Event.DTO;
+
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.Event.Model.State;
+
+import java.util.ArrayList;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class EventRequestStatusUpdateRequest {
+
+ @NotNull
+ @NotEmpty
+ private ArrayList requestIds;
+
+ @NotNull
+ private State status;
+
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/DTO/EventRequestStatusUpdateResult.java b/evm-service/src/main/java/ru/practicum/Event/DTO/EventRequestStatusUpdateResult.java
new file mode 100644
index 0000000..215b2be
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/DTO/EventRequestStatusUpdateResult.java
@@ -0,0 +1,16 @@
+package ru.practicum.Event.DTO;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.Request.RequestDTO.ParticipationRequestDto;
+
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class EventRequestStatusUpdateResult {
+ private List confirmedRequests;
+ private List rejectedRequests;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/DTO/EventShortDTO.java b/evm-service/src/main/java/ru/practicum/Event/DTO/EventShortDTO.java
new file mode 100644
index 0000000..b268a70
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/DTO/EventShortDTO.java
@@ -0,0 +1,43 @@
+package ru.practicum.Event.DTO;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.Category.DTO.CategoryDTO;
+import ru.practicum.StatsRequestDTO;
+import ru.practicum.User.DTO.UserShortDTO;
+
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class EventShortDTO {
+ private Integer id;
+
+ @NotBlank
+ private String annotation;
+
+ @NotNull
+ private CategoryDTO category;
+
+ private Integer confirmedRequests;
+
+ @NotNull
+ @JsonFormat(pattern = StatsRequestDTO.DATE_FORMAT)
+ private LocalDateTime eventDate;
+
+ @NotNull
+ private UserShortDTO initiator;
+
+ @NotNull
+ private Boolean paid;
+
+ @NotBlank
+ private String title;
+
+ private Integer views;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/DTO/NewEventDTO.java b/evm-service/src/main/java/ru/practicum/Event/DTO/NewEventDTO.java
new file mode 100644
index 0000000..8d840c2
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/DTO/NewEventDTO.java
@@ -0,0 +1,48 @@
+package ru.practicum.Event.DTO;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.*;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.Event.Model.Location;
+import ru.practicum.StatsRequestDTO;
+
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class NewEventDTO {
+ @NotBlank
+ @Size(min = 20, max = 2000)
+ private String annotation;
+
+ @NotNull
+ @JsonProperty("category")
+ private Integer categoryId;
+
+ @NotBlank
+ @Size(min = 20, max = 7000)
+ private String description;
+
+ @NotNull
+ @Future
+ @JsonFormat(pattern = StatsRequestDTO.DATE_FORMAT)
+ private LocalDateTime eventDate;
+
+ @NotNull
+ private Location location;
+
+ private Boolean paid = false;
+
+ @PositiveOrZero
+ private Integer participantLimit = 0;
+
+ private Boolean requestModeration = true;
+
+ @NotBlank
+ @Size(min = 3, max = 120)
+ private String title;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/DTO/SearchEventsDTO.java b/evm-service/src/main/java/ru/practicum/Event/DTO/SearchEventsDTO.java
new file mode 100644
index 0000000..f037783
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/DTO/SearchEventsDTO.java
@@ -0,0 +1,34 @@
+package ru.practicum.Event.DTO;
+
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.format.annotation.DateTimeFormat;
+import ru.practicum.Event.Model.State;
+import ru.practicum.StatsRequestDTO;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class SearchEventsDTO {
+ private List users;
+ private List states;
+ private List categories;
+
+ @DateTimeFormat(pattern = StatsRequestDTO.DATE_FORMAT)
+ private LocalDateTime rangeStart = LocalDateTime.now();
+
+ @DateTimeFormat(pattern = StatsRequestDTO.DATE_FORMAT)
+ private LocalDateTime rangeEnd;
+
+ @PositiveOrZero
+ private Integer from = 0;
+
+ @Positive
+ private Integer size = 10;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/DTO/UpdateEventAdminRequest.java b/evm-service/src/main/java/ru/practicum/Event/DTO/UpdateEventAdminRequest.java
new file mode 100644
index 0000000..52d08f6
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/DTO/UpdateEventAdminRequest.java
@@ -0,0 +1,45 @@
+package ru.practicum.Event.DTO;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.validation.constraints.Future;
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.Event.Model.Location;
+import ru.practicum.Event.Model.State;
+import ru.practicum.StatsRequestDTO;
+
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UpdateEventAdminRequest {
+
+ @Size(min = 20, max = 2000)
+ private String annotation;
+
+ private Integer category;
+
+ @Size(min = 20, max = 7000)
+ private String description;
+
+ @Future
+ @JsonFormat(pattern = StatsRequestDTO.DATE_FORMAT)
+ private LocalDateTime eventDate;
+
+ private Location location;
+
+ private Boolean paid;
+
+ private Integer participantLimit;
+
+ private Boolean requestModeration;
+
+ private State stateAction;
+
+ @Size(min = 3, max = 120)
+ private String title;
+
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/DTO/UpdateEventUserRequest.java b/evm-service/src/main/java/ru/practicum/Event/DTO/UpdateEventUserRequest.java
new file mode 100644
index 0000000..b605ab0
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/DTO/UpdateEventUserRequest.java
@@ -0,0 +1,46 @@
+package ru.practicum.Event.DTO;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.validation.constraints.Future;
+import jakarta.validation.constraints.PositiveOrZero;
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.Event.Model.Location;
+import ru.practicum.Event.Model.State;
+import ru.practicum.StatsRequestDTO;
+
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UpdateEventUserRequest {
+
+ @Size(min = 20, max = 2000)
+ private String annotation;
+
+ private Integer categoryId;
+
+ @Size(min = 20, max = 7000)
+ private String description;
+
+ @JsonFormat(pattern = StatsRequestDTO.DATE_FORMAT)
+ @Future
+ private LocalDateTime eventDate;
+
+ private Location location;
+
+ private Boolean paid;
+
+ @PositiveOrZero
+ private Integer participantLimit;
+
+ private Boolean requestModeration;
+
+ private State stateAction;
+
+ @Size(min = 3, max = 120)
+ private String title;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/Mapper/EventMapper.java b/evm-service/src/main/java/ru/practicum/Event/Mapper/EventMapper.java
new file mode 100644
index 0000000..74afdec
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Mapper/EventMapper.java
@@ -0,0 +1,21 @@
+package ru.practicum.Event.Mapper;
+
+import org.mapstruct.*;
+import ru.practicum.Event.DTO.EventFullDTO;
+import ru.practicum.Event.DTO.EventShortDTO;
+import ru.practicum.Event.DTO.NewEventDTO;
+import ru.practicum.Event.DTO.UpdateEventAdminRequest;
+import ru.practicum.Event.Model.Event;
+
+@Mapper(componentModel = "spring")
+public interface EventMapper {
+ Event toEvent(NewEventDTO event);
+
+ EventShortDTO toEventShortDTO(Event event);
+
+ EventFullDTO toEventFullDTO(Event event);
+
+ @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
+ @Mapping(target = "category", ignore = true)
+ Event toEvent(UpdateEventAdminRequest request, @MappingTarget Event event);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/Model/Event.java b/evm-service/src/main/java/ru/practicum/Event/Model/Event.java
new file mode 100644
index 0000000..2b81c22
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Model/Event.java
@@ -0,0 +1,70 @@
+package ru.practicum.Event.Model;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.Category.Model.Category;
+import ru.practicum.User.Model.User;
+
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "events")
+public class Event {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Integer id;
+
+ @Column(name = "title", length = 200)
+ private String title;
+
+ @Column(name = "annotation", length = 2000)
+ private String annotation;
+
+ @ManyToOne(fetch = FetchType.EAGER)
+ private Category category;
+
+ @Column(name = "confirmed_requests")
+ private Integer confirmedRequests = 0;
+
+ @Column(name = "created_on")
+ private LocalDateTime createdOn = LocalDateTime.now();
+
+ @Column(name = "description", length = 7000)
+ private String description;
+
+ @Column(name = "event_date")
+ private LocalDateTime eventDate;
+
+ @OneToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "initiator_id")
+ private User initiator;
+
+ @Embedded
+ private Location location;
+
+ @Column(name = "paid")
+ private Boolean paid;
+
+ @Column(name = "participant_limit")
+ private Integer participantLimit;
+
+ @Column(name = "published_on")
+ private LocalDateTime publishedOn;
+
+ @Column(name = "request_moderation")
+ private Boolean requestModeration;
+
+ @Column(name = "state_id")
+ @Enumerated(EnumType.ORDINAL)
+ private State state;
+
+ @Column(name = "views")
+ private Integer views = 0;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/Model/Location.java b/evm-service/src/main/java/ru/practicum/Event/Model/Location.java
new file mode 100644
index 0000000..f97e67e
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Model/Location.java
@@ -0,0 +1,17 @@
+package ru.practicum.Event.Model;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Embeddable
+public class Location {
+
+ private Float lat;
+
+ private Float lon;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/Model/State.java b/evm-service/src/main/java/ru/practicum/Event/Model/State.java
new file mode 100644
index 0000000..33b5d70
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Model/State.java
@@ -0,0 +1,15 @@
+package ru.practicum.Event.Model;
+
+
+public enum State {
+ PUBLISHED,
+ CONFIRMED,
+ REJECTED,
+ PENDING,
+ PUBLISH_EVENT,
+ REJECT_EVENT,
+ CANCEL_REVIEW,
+ SEND_TO_REVIEW,
+ CANCELED
+
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/Repository/EventRepository.java b/evm-service/src/main/java/ru/practicum/Event/Repository/EventRepository.java
new file mode 100644
index 0000000..22782e2
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Repository/EventRepository.java
@@ -0,0 +1,26 @@
+package ru.practicum.Event.Repository;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.data.jpa.repository.JpaRepository;
+import ru.practicum.Event.Model.Event;
+import ru.practicum.Event.Model.State;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface EventRepository extends JpaRepository {
+ Optional findEventById(Integer eventId);
+
+ Optional findEventsByIdAndState(Integer id, State state);
+
+ Page getEventByInitiator_Id(Integer initiatorId, Pageable pageable);
+
+ List findEventsByIdIn(List ids);
+
+ List findEventsByCategory_Id(Integer categoryId);
+
+ Page findAll(Specification spec, Pageable pageable);
+
+}
\ No newline at end of file
diff --git a/evm-service/src/main/java/ru/practicum/Event/Service/Admin/EventAdminService.java b/evm-service/src/main/java/ru/practicum/Event/Service/Admin/EventAdminService.java
new file mode 100644
index 0000000..1b4a0c9
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Service/Admin/EventAdminService.java
@@ -0,0 +1,13 @@
+package ru.practicum.Event.Service.Admin;
+
+import ru.practicum.Event.DTO.EventFullDTO;
+import ru.practicum.Event.DTO.SearchEventsDTO;
+import ru.practicum.Event.DTO.UpdateEventAdminRequest;
+
+import java.util.List;
+
+public interface EventAdminService {
+ List getEvents(SearchEventsDTO searchEventsDTO);
+
+ EventFullDTO updateEvent(Integer eventId, UpdateEventAdminRequest request);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/Service/Admin/EventAdminServiceImpl.java b/evm-service/src/main/java/ru/practicum/Event/Service/Admin/EventAdminServiceImpl.java
new file mode 100644
index 0000000..b5200e9
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Service/Admin/EventAdminServiceImpl.java
@@ -0,0 +1,123 @@
+package ru.practicum.Event.Service.Admin;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import ru.practicum.Category.Repository.CategoryRepository;
+import ru.practicum.Event.DTO.EventFullDTO;
+import ru.practicum.Event.DTO.SearchEventsDTO;
+import ru.practicum.Event.DTO.UpdateEventAdminRequest;
+import ru.practicum.Event.Mapper.EventMapper;
+import ru.practicum.Event.Model.Event;
+import ru.practicum.Event.Model.State;
+import ru.practicum.Event.Repository.EventRepository;
+import ru.practicum.Exception.ConflictException;
+import ru.practicum.Exception.ValidationException;
+import ru.practicum.User.Repository.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+
+@Service
+@RequiredArgsConstructor
+public class EventAdminServiceImpl implements EventAdminService {
+ private final EventRepository eventRepository;
+ private final EventMapper eventMapper;
+ private final CategoryRepository categoryRepository;
+ private final UserRepository userRepository;
+
+ @Override
+ public List getEvents(SearchEventsDTO dto) {
+ if (dto.getRangeStart() != null && dto.getRangeEnd() != null) {
+ if (dto.getRangeStart().isAfter(dto.getRangeEnd())) {
+ throw new ValidationException("Range start is after range end");
+ }
+ }
+
+ Specification spec = getEventSpecification(dto);
+ Pageable pageable = PageRequest.of(dto.getFrom(), dto.getSize());
+
+ List events = eventRepository.findAll(spec, pageable).getContent();
+
+ return events.stream()
+ .map(eventMapper::toEventFullDTO)
+ .toList();
+ }
+
+ @Override
+ public EventFullDTO updateEvent(Integer eventId, UpdateEventAdminRequest request) {
+ Event event = eventRepository.findEventById(eventId)
+ .orElseThrow(() -> new IllegalArgumentException("Event not found"));
+
+ if (request.getEventDate() != null) {
+ if (event.getEventDate().isBefore(LocalDateTime.now().plusHours(1))) {
+ throw new ConflictException("Event date should be 1 horse after creation date");
+ }
+ }
+
+ if (request.getStateAction() != null) {
+ switch (request.getStateAction()) {
+ case PUBLISH_EVENT -> {
+ if (event.getState().equals(State.PENDING)) {
+ event.setState(State.PUBLISHED);
+ event.setPublishedOn(LocalDateTime.now());
+ eventRepository.save(event);
+ } else {
+ throw new ConflictException("Event state should be PENDING");
+ }
+ }
+
+ case REJECT_EVENT -> {
+ if (!event.getState().equals(State.PUBLISHED)) {
+ event.setState(State.REJECTED);
+ eventRepository.save(event);
+ } else {
+ throw new ConflictException("Event state should be not PUBLISHED");
+ }
+ }
+ }
+ }
+
+ Event updatedEvent = eventMapper.toEvent(request, event);
+
+ return eventMapper.toEventFullDTO(eventRepository.save(updatedEvent));
+ }
+
+ private Specification getEventSpecification(SearchEventsDTO event) {
+ Specification spec = Specification.where(null);
+
+ if (Objects.nonNull(event.getUsers()) && !event.getUsers().isEmpty()) {
+ spec = spec.and((root, query, cb) -> root.get("initiator").get("id").in(
+ event.getUsers().stream().filter(Objects::nonNull).toList()));
+ }
+
+ if (Objects.nonNull(event.getCategories()) && !event.getCategories().isEmpty()) {
+ spec = spec.and((root, query, builder) ->
+ root.get("category").get("id").in(event.getCategories().stream()
+ .filter(Objects::nonNull)
+ .toList()));
+ }
+
+ if (Objects.nonNull(event.getStates()) && !event.getStates().isEmpty()) {
+ spec = spec.and((root, query, cb) ->
+ root.get("state").in(event.getStates().stream()
+ .filter(Objects::nonNull)
+ .toList()));
+ }
+
+ if (Objects.nonNull(event.getRangeStart())) {
+ spec = spec.and((root, query, cb) ->
+ cb.greaterThanOrEqualTo(root.get("eventDate"), event.getRangeStart()));
+ }
+
+ if (Objects.nonNull(event.getRangeEnd())) {
+ spec = spec.and((root, query, cb) ->
+ cb.lessThanOrEqualTo(root.get("eventDate"), event.getRangeEnd()));
+ }
+
+ return spec;
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/Service/Private/EventPrivateService.java b/evm-service/src/main/java/ru/practicum/Event/Service/Private/EventPrivateService.java
new file mode 100644
index 0000000..5ac76da
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Service/Private/EventPrivateService.java
@@ -0,0 +1,20 @@
+package ru.practicum.Event.Service.Private;
+
+import ru.practicum.Event.DTO.*;
+import ru.practicum.Request.RequestDTO.ParticipationRequestDto;
+
+import java.util.List;
+
+public interface EventPrivateService {
+ EventFullDTO addEvent(Integer userId, NewEventDTO event);
+
+ EventFullDTO getEvent(Integer userId, Integer eventId);
+
+ EventFullDTO updateEvent(Integer userId, Integer eventId, UpdateEventUserRequest event);
+
+ List getUserRequests(Integer userId, Integer eventId);
+
+ EventRequestStatusUpdateResult updateRequests(EventRequestStatusUpdateRequest request, Integer userId, Integer eventId);
+
+ List getEvents(Integer userId, Integer from, Integer size);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/Service/Private/EventPrivateServiceImpl.java b/evm-service/src/main/java/ru/practicum/Event/Service/Private/EventPrivateServiceImpl.java
new file mode 100644
index 0000000..d5c52bb
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Service/Private/EventPrivateServiceImpl.java
@@ -0,0 +1,176 @@
+package ru.practicum.Event.Service.Private;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import ru.practicum.Category.Model.Category;
+import ru.practicum.Category.Repository.CategoryRepository;
+import ru.practicum.Event.DTO.*;
+import ru.practicum.Event.Mapper.EventMapper;
+import ru.practicum.Event.Model.Event;
+import ru.practicum.Event.Repository.EventRepository;
+import ru.practicum.Exception.ConflictException;
+import ru.practicum.Exception.NotFoundException;
+import ru.practicum.Exception.ValidationException;
+import ru.practicum.Request.Mapper.RequestMapper;
+import ru.practicum.Request.Model.Request;
+import ru.practicum.Request.Repository.RequestRepository;
+import ru.practicum.Request.RequestDTO.ParticipationRequestDto;
+import ru.practicum.User.Model.User;
+import ru.practicum.User.Repository.UserRepository;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static ru.practicum.Event.Model.State.*;
+
+@Service
+@RequiredArgsConstructor
+public class EventPrivateServiceImpl implements EventPrivateService {
+ private final EventRepository eventRepository;
+ private final EventMapper eventMapper;
+ private final UserRepository userRepository;
+ private final CategoryRepository categoryRepository;
+ private final RequestRepository requestRepository;
+ private final RequestMapper requestMapper;
+
+ @Override
+ public EventFullDTO addEvent(Integer userId, NewEventDTO event) {
+ User user = checkUser(userId);
+ Category category = checkCategory(event.getCategoryId());
+
+ Event newEvent = eventMapper.toEvent(event);
+ checkDates(newEvent.getCreatedOn(), event.getEventDate());
+ newEvent.setInitiator(user);
+ newEvent.setCategory(category);
+ newEvent.setState(PENDING);
+ return eventMapper.toEventFullDTO(eventRepository.save(newEvent));
+ }
+
+ @Override
+ public EventFullDTO getEvent(Integer userId, Integer eventId) {
+ Event event = checkEvent(eventId);
+ int confirmedRequests = requestRepository.findRequestsByEvent_IdAndStatus(eventId, CONFIRMED).size();
+ return eventMapper.toEventFullDTO(event);
+ }
+
+ @Override
+ public EventFullDTO updateEvent(Integer userId, Integer eventId, UpdateEventUserRequest event) {
+ Event eventFound = checkEvent(eventId);
+ User user = checkUser(userId);
+
+ if (eventFound.getState().equals(PUBLISHED)) {
+ throw new ConflictException("Event with id " + eventId + " could not be changed");
+ }
+
+ if (event.getStateAction() != null) {
+ switch (event.getStateAction()) {
+ case CANCEL_REVIEW -> eventFound.setState(CANCELED);
+ case SEND_TO_REVIEW -> eventFound.setState(PENDING);
+ }
+ }
+
+ if (!userId.equals(eventFound.getInitiator().getId())) {
+ throw new ConflictException("Initiator id mismatch");
+ }
+
+ if (event.getCategoryId() != null) {
+ Category category = checkCategory(event.getCategoryId());
+ eventFound.setCategory(category);
+ }
+
+ if (event.getEventDate() != null) {
+ checkDates(eventFound.getCreatedOn(), event.getEventDate());
+ eventFound.setEventDate(event.getEventDate());
+ }
+
+ return eventMapper.toEventFullDTO(eventRepository.save(eventFound));
+ }
+
+ @Override
+ public List getUserRequests(Integer userId, Integer eventId) {
+ checkUser(userId);
+ checkEvent(eventId);
+
+ return requestRepository.findRequestsByEvent_IdAndEvent_Initiator_Id(eventId, userId).stream()
+ .map(requestMapper::toParticipationRequestDto)
+ .toList();
+ }
+
+ @Override
+ public EventRequestStatusUpdateResult updateRequests(EventRequestStatusUpdateRequest request,
+ Integer userId, Integer eventId) {
+ checkUser(userId);
+ Event event = checkEvent(eventId);
+ List requests = requestRepository.findAllById(request.getRequestIds());
+
+ if (Objects.equals(event.getParticipantLimit(), event.getConfirmedRequests())) {
+ throw new ConflictException("ConfirmedRequests limit exceeded");
+ }
+
+ List confirmedRequests = new ArrayList<>();
+ List rejectedRequests = new ArrayList<>();
+
+ for (Request request1 : requests) {
+ if (!request1.getStatus().equals(PENDING)) {
+ throw new ConflictException("States should be PENDING");
+ }
+
+ if (request.getStatus().equals(CONFIRMED)) {
+ if (event.getParticipantLimit() > event.getConfirmedRequests()) {
+ request1.setStatus(CONFIRMED);
+ requestRepository.save(request1);
+ confirmedRequests.add(requestMapper.toParticipationRequestDto(request1));
+ event.setConfirmedRequests(event.getConfirmedRequests() + 1);
+ eventRepository.save(event);
+ } else {
+ request1.setStatus(REJECTED);
+ requestRepository.save(request1);
+ rejectedRequests.add(requestMapper.toParticipationRequestDto(request1));
+ }
+ } else {
+ request1.setStatus(REJECTED);
+ requestRepository.save(request1);
+ rejectedRequests.add(requestMapper.toParticipationRequestDto(request1));
+ }
+ }
+
+ return new EventRequestStatusUpdateResult(confirmedRequests, rejectedRequests);
+ }
+
+ @Override
+ public List getEvents(Integer userId, Integer from, Integer size) {
+ checkUser(userId);
+ Pageable pageable = PageRequest.of(from, size);
+ Page events = eventRepository.getEventByInitiator_Id(userId, pageable);
+ return events.getContent().stream()
+ .map(eventMapper::toEventShortDTO)
+ .toList();
+ }
+
+ private void checkDates(LocalDateTime createdOn, LocalDateTime eventDate) {
+ if (eventDate.isBefore(createdOn) ||
+ eventDate.minusHours(2L).isBefore(createdOn)) {
+ throw new ValidationException("Event date should be 2 horse after creation date");
+ }
+ }
+
+ private User checkUser(Integer userId) {
+ return userRepository.findById(userId)
+ .orElseThrow(() -> new NotFoundException("User with id " + userId + " not found"));
+ }
+
+ private Category checkCategory(Integer categoryId) {
+ return categoryRepository.findById(categoryId)
+ .orElseThrow(() -> new NotFoundException("Category with id " + categoryId + " not found"));
+ }
+
+ private Event checkEvent(Integer eventId) {
+ return eventRepository.findEventById(eventId)
+ .orElseThrow(() -> new NotFoundException("Event with id " + eventId + " not found"));
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/Service/Public/EventPublicService.java b/evm-service/src/main/java/ru/practicum/Event/Service/Public/EventPublicService.java
new file mode 100644
index 0000000..db38b40
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Service/Public/EventPublicService.java
@@ -0,0 +1,14 @@
+package ru.practicum.Event.Service.Public;
+
+import jakarta.servlet.http.HttpServletRequest;
+import ru.practicum.Event.DTO.EventFullDTO;
+import ru.practicum.Event.DTO.EventPublicParams;
+import ru.practicum.Event.DTO.EventShortDTO;
+
+import java.util.List;
+
+public interface EventPublicService {
+ EventFullDTO findEventById(Integer id, HttpServletRequest request);
+
+ List findAllEvents(EventPublicParams params, HttpServletRequest request);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Event/Service/Public/EventPublicServiceImpl.java b/evm-service/src/main/java/ru/practicum/Event/Service/Public/EventPublicServiceImpl.java
new file mode 100644
index 0000000..044d22f
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Event/Service/Public/EventPublicServiceImpl.java
@@ -0,0 +1,168 @@
+package ru.practicum.Event.Service.Public;
+
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import ru.practicum.Category.Repository.CategoryRepository;
+import ru.practicum.Event.DTO.EventFullDTO;
+import ru.practicum.Event.DTO.EventPublicParams;
+import ru.practicum.Event.DTO.EventShortDTO;
+import ru.practicum.Event.Mapper.EventMapper;
+import ru.practicum.Event.Model.Event;
+import ru.practicum.Event.Model.State;
+import ru.practicum.Event.Repository.EventRepository;
+import ru.practicum.Exception.NotFoundException;
+import ru.practicum.Exception.ValidationException;
+import ru.practicum.Request.Repository.RequestRepository;
+import ru.practicum.StatsClient;
+import ru.practicum.ViewStatsDTO;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+import static java.lang.Integer.parseInt;
+
+
+@Service
+@RequiredArgsConstructor
+public class EventPublicServiceImpl implements EventPublicService {
+ private final EventRepository eventRepository;
+ private final EventMapper eventMapper;
+ private final CategoryRepository categoryRepository;
+ private final RequestRepository requestRepository;
+ private final StatsClient statsClient;
+
+ @Override
+ public EventFullDTO findEventById(Integer id, HttpServletRequest request) {
+ Event event = eventRepository.findEventsByIdAndState(id, State.PUBLISHED)
+ .orElseThrow(() -> new NotFoundException("Event with id " + id + " not found"));
+
+ statsClient.createHit(request, "ewm-main-service");
+
+ List uris = List.of(request.getRequestURI());
+ Map views = getStats(uris, request);
+ event.setViews(views.get(event.getId()));
+
+ return eventMapper.toEventFullDTO(event);
+ }
+
+ @Override
+ public List findAllEvents(EventPublicParams params, HttpServletRequest request) {
+ statsClient.createHit(request, "ewm-main-service");
+
+ if (params.getRangeStart() != null || params.getRangeEnd() != null) {
+ if (params.getRangeStart().isAfter(params.getRangeEnd())) {
+ throw new ValidationException("Range start is after range end");
+ }
+ }
+
+ Specification spec = eventSpecification(params);
+
+ Sort sort = null;
+ if (params.getSort() != null) {
+ switch (params.getSort()) {
+ case "EVENT_DATE" -> sort = Sort.by(Sort.Direction.DESC, "eventDate");
+ case "VIEWS" -> sort = Sort.by(Sort.Direction.ASC, "views");
+ }
+ } else {
+ sort = Sort.by(Sort.Direction.DESC, "eventDate");
+ }
+
+
+ Pageable pageable = PageRequest.of(params.getFrom(), params.getSize(), sort);
+
+ List events = eventRepository.findAll(spec, pageable).getContent().stream()
+ .map(eventMapper::toEventShortDTO)
+ .toList();
+
+ List uris = events.stream()
+ .map(event -> {
+ return request.getRequestURI() + "/" + event.getId();
+ })
+ .toList();
+
+ if (!uris.isEmpty()) {
+ Map views = getStats(uris, request);
+ events.stream()
+ .peek(event -> {
+ if (views.containsKey(event.getId())) {
+ event.setViews(views.get(event.getId()));
+ }
+ })
+ .toList();
+ }
+
+ return events;
+ }
+
+ private Map getStats(List uris, HttpServletRequest request) {
+ if (uris.isEmpty()) {
+ return Collections.emptyMap();
+ }
+
+ LocalDateTime start = LocalDateTime.now().minusMonths(6);
+ LocalDateTime end = LocalDateTime.now().plusMinutes(1);
+ List viewStatsDTOS = statsClient.viewStats(start, end, uris, true);
+
+ Map eventViews = new HashMap<>();
+
+ for (ViewStatsDTO viewStatsDTO : viewStatsDTOS) {
+ int index = viewStatsDTO.getUri().lastIndexOf('/') + 1;
+ int id = parseInt(viewStatsDTO.getUri().substring(index));
+ eventViews.put(id, viewStatsDTO.getHits());
+ }
+
+ return eventViews;
+ }
+
+ private Specification eventSpecification(EventPublicParams params) {
+ Specification spec = Specification.where(null);
+
+ if (Objects.nonNull(params.getText()) && !params.getText().isBlank()) {
+ String search = "%" + params.getText().toLowerCase() + "%";
+ spec = spec.and((root, query, cb) ->
+ cb.or(
+ cb.like(cb.lower(root.get("annotation")), search),
+ cb.like(cb.lower(root.get("description")), search)
+ ));
+ }
+
+ if (Objects.nonNull(params.getCategories()) && !params.getCategories().isEmpty()) {
+ spec = spec.and((root, query, cb) ->
+ root.get("category").get("id").in(
+ params.getCategories()));
+ }
+
+ if (Objects.nonNull(params.getPaid())) {
+ spec = spec.and((root, query, cb) ->
+ cb.equal(root.get("paid"), params.getPaid()));
+ }
+
+ if (Objects.nonNull(params.getOnlyAvailable())) {
+ spec = spec.and((root, query, cb) ->
+ cb.or(
+ cb.equal(root.get("participantLimit"), 0),
+ cb.lessThan(
+ root.get("confirmedRequests"),
+ root.get("participantLimit")
+ )
+ ));
+ }
+
+ if (Objects.nonNull(params.getRangeStart())) {
+ spec = spec.and((root, query, cb) ->
+ cb.greaterThanOrEqualTo(root.get("eventDate"), params.getRangeStart()));
+ }
+
+ if (Objects.nonNull(params.getRangeEnd())) {
+ spec = spec.and((root, query, cb) ->
+ cb.lessThanOrEqualTo(root.get("eventDate"), params.getRangeEnd()));
+ }
+
+ return spec;
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/EvmServiceApp.java b/evm-service/src/main/java/ru/practicum/EvmServiceApp.java
new file mode 100644
index 0000000..78f5ae5
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/EvmServiceApp.java
@@ -0,0 +1,11 @@
+package ru.practicum;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class EvmServiceApp {
+ public static void main(String[] args) {
+ SpringApplication.run(EvmServiceApp.class, args);
+ }
+}
\ No newline at end of file
diff --git a/evm-service/src/main/java/ru/practicum/Exception/ConflictException.java b/evm-service/src/main/java/ru/practicum/Exception/ConflictException.java
new file mode 100644
index 0000000..cd46f5b
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Exception/ConflictException.java
@@ -0,0 +1,11 @@
+package ru.practicum.Exception;
+
+public class ConflictException extends RuntimeException {
+ public ConflictException(String message) {
+ super(message);
+ }
+
+ public ConflictException() {
+ super();
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Exception/ErrorResponse.java b/evm-service/src/main/java/ru/practicum/Exception/ErrorResponse.java
new file mode 100644
index 0000000..eefbe00
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Exception/ErrorResponse.java
@@ -0,0 +1,19 @@
+package ru.practicum.Exception;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+import java.time.LocalDateTime;
+
+@Getter
+@AllArgsConstructor
+public class ErrorResponse {
+ private HttpStatus status;
+ private String message;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime timestamp;
+
+}
diff --git a/evm-service/src/main/java/ru/practicum/Exception/EvmErrorHandler.java b/evm-service/src/main/java/ru/practicum/Exception/EvmErrorHandler.java
new file mode 100644
index 0000000..7d23cde
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Exception/EvmErrorHandler.java
@@ -0,0 +1,64 @@
+package ru.practicum.Exception;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.time.LocalDateTime;
+
+@RestControllerAdvice
+@Slf4j
+public class EvmErrorHandler {
+
+ @ExceptionHandler
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ public ErrorResponse handleException(final RuntimeException e) {
+ log.error("500 {}", e.getMessage(), e);
+ LocalDateTime timestamp = LocalDateTime.now();
+ return new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), timestamp);
+ }
+
+ @ExceptionHandler(ValidationException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ErrorResponse handleValidationException(final ValidationException e) {
+ log.error("400 {}", e.getMessage(), e);
+ LocalDateTime timestamp = LocalDateTime.now();
+ return new ErrorResponse(HttpStatus.BAD_REQUEST, e.getMessage(), timestamp);
+ }
+
+ @ExceptionHandler(NotFoundException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public ErrorResponse handleNotFoundException(final NotFoundException e) {
+ log.error("404 {}", e.getMessage(), e);
+ LocalDateTime timestamp = LocalDateTime.now();
+ return new ErrorResponse(HttpStatus.NOT_FOUND, e.getMessage(), timestamp);
+ }
+
+ @ExceptionHandler(ConflictException.class)
+ @ResponseStatus(HttpStatus.CONFLICT)
+ public ErrorResponse handleConflictException(final ConflictException e) {
+ log.error("409 {}", e.getMessage(), e);
+ LocalDateTime timestamp = LocalDateTime.now();
+ return new ErrorResponse(HttpStatus.CONFLICT, e.getMessage(), timestamp);
+ }
+
+ @ExceptionHandler(MissingServletRequestParameterException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ErrorResponse handleMissingPathVariableException(final MissingServletRequestParameterException e) {
+ log.error("400 {}", e.getMessage(), e);
+ LocalDateTime timestamp = LocalDateTime.now();
+ return new ErrorResponse(HttpStatus.BAD_REQUEST, e.getMessage(), timestamp);
+ }
+
+ @ExceptionHandler(DataIntegrityViolationException.class)
+ @ResponseStatus(HttpStatus.CONFLICT)
+ public ErrorResponse handSQLException(final DataIntegrityViolationException e) {
+ log.error("409 {}", e.getMessage(), e);
+ LocalDateTime timestamp = LocalDateTime.now();
+ return new ErrorResponse(HttpStatus.CONFLICT, e.getMessage(), timestamp);
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Exception/NotFoundException.java b/evm-service/src/main/java/ru/practicum/Exception/NotFoundException.java
new file mode 100644
index 0000000..982e60a
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Exception/NotFoundException.java
@@ -0,0 +1,11 @@
+package ru.practicum.Exception;
+
+public class NotFoundException extends RuntimeException {
+ public NotFoundException(String message) {
+ super(message);
+ }
+
+ public NotFoundException() {
+ super();
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Exception/UniqueException.java b/evm-service/src/main/java/ru/practicum/Exception/UniqueException.java
new file mode 100644
index 0000000..ca6b3a6
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Exception/UniqueException.java
@@ -0,0 +1,11 @@
+package ru.practicum.Exception;
+
+public class UniqueException extends RuntimeException {
+ public UniqueException(final String message) {
+ super(message);
+ }
+
+ public UniqueException() {
+ super();
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Exception/ValidationException.java b/evm-service/src/main/java/ru/practicum/Exception/ValidationException.java
new file mode 100644
index 0000000..06bd106
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Exception/ValidationException.java
@@ -0,0 +1,11 @@
+package ru.practicum.Exception;
+
+public class ValidationException extends RuntimeException {
+ public ValidationException() {
+ super();
+ }
+
+ public ValidationException(String message) {
+ super(message);
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Request/Controller/RequestPrivateController.java b/evm-service/src/main/java/ru/practicum/Request/Controller/RequestPrivateController.java
new file mode 100644
index 0000000..8c25286
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Request/Controller/RequestPrivateController.java
@@ -0,0 +1,35 @@
+package ru.practicum.Request.Controller;
+
+import jakarta.validation.constraints.Positive;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.Request.RequestDTO.ParticipationRequestDto;
+import ru.practicum.Request.Service.RequestPrivateServiceImpl;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/users/{userId}/requests")
+@RequiredArgsConstructor
+public class RequestPrivateController {
+ private final RequestPrivateServiceImpl requestService;
+
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ public ParticipationRequestDto addRequest(@PathVariable @Positive Integer userId,
+ @RequestParam @Positive Integer eventId) {
+ return requestService.addRequest(userId, eventId);
+ }
+
+ @PatchMapping("/{requestId}/cancel")
+ public ParticipationRequestDto cancelRequest(@PathVariable @Positive Integer userId,
+ @PathVariable @Positive Integer requestId) {
+ return requestService.cancelRequest(userId, requestId);
+ }
+
+ @GetMapping
+ List getRequests(@PathVariable @Positive Integer userId) {
+ return requestService.getRequests(userId);
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/Request/Mapper/RequestMapper.java b/evm-service/src/main/java/ru/practicum/Request/Mapper/RequestMapper.java
new file mode 100644
index 0000000..e232dad
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Request/Mapper/RequestMapper.java
@@ -0,0 +1,18 @@
+package ru.practicum.Request.Mapper;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import ru.practicum.Request.Model.Request;
+import ru.practicum.Request.RequestDTO.ParticipationRequestDto;
+
+@Mapper(componentModel = "spring")
+public interface RequestMapper {
+
+ @Mapping(target = "event", source = "event.id")
+ @Mapping(target = "requester", source = "requester.id")
+ ParticipationRequestDto toParticipationRequestDto(Request request);
+
+ @Mapping(target = "event", ignore = true)
+ @Mapping(target = "requester", ignore = true)
+ Request toRequest(ParticipationRequestDto participationRequestDto);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Request/Model/Request.java b/evm-service/src/main/java/ru/practicum/Request/Model/Request.java
new file mode 100644
index 0000000..3e5b1d5
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Request/Model/Request.java
@@ -0,0 +1,37 @@
+package ru.practicum.Request.Model;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.Event.Model.Event;
+import ru.practicum.Event.Model.State;
+import ru.practicum.User.Model.User;
+
+import java.time.LocalDateTime;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@Table(name = "requests")
+public class Request {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Integer id;
+
+ @Column(name = "created")
+ private final LocalDateTime created = LocalDateTime.now();
+
+ @OneToOne
+ private Event event;
+
+ @OneToOne(fetch = FetchType.EAGER)
+ private User requester;
+
+ @Column(name = "state_id")
+ @Enumerated(EnumType.ORDINAL)
+ private State status;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Request/Repository/RequestRepository.java b/evm-service/src/main/java/ru/practicum/Request/Repository/RequestRepository.java
new file mode 100644
index 0000000..43d0400
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Request/Repository/RequestRepository.java
@@ -0,0 +1,20 @@
+package ru.practicum.Request.Repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import ru.practicum.Event.Model.State;
+import ru.practicum.Request.Model.Request;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface RequestRepository extends JpaRepository {
+ Optional findRequestById(Integer requestId);
+
+ List findRequestsByRequester_Id(Integer requesterId);
+
+ List findRequestsByEvent_IdAndEvent_Initiator_Id(Integer eventId, Integer eventInitiatorId);
+
+ List findRequestsByEvent_IdAndStatus(Integer eventId, State status);
+
+ Request findRequestsByEvent_IdAndRequester_Id(Integer eventId, Integer requesterId);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Request/RequestDTO/ParticipationRequestDto.java b/evm-service/src/main/java/ru/practicum/Request/RequestDTO/ParticipationRequestDto.java
new file mode 100644
index 0000000..1e3edc8
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Request/RequestDTO/ParticipationRequestDto.java
@@ -0,0 +1,24 @@
+package ru.practicum.Request.RequestDTO;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.Event.Model.State;
+
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ParticipationRequestDto {
+
+ private Integer id;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private final LocalDateTime created = LocalDateTime.now();
+
+ private Integer event;
+ private Integer requester;
+ private State status;
+}
diff --git a/evm-service/src/main/java/ru/practicum/Request/Service/RequestPrivateService.java b/evm-service/src/main/java/ru/practicum/Request/Service/RequestPrivateService.java
new file mode 100644
index 0000000..29be91e
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Request/Service/RequestPrivateService.java
@@ -0,0 +1,13 @@
+package ru.practicum.Request.Service;
+
+import ru.practicum.Request.RequestDTO.ParticipationRequestDto;
+
+import java.util.List;
+
+public interface RequestPrivateService {
+ ParticipationRequestDto addRequest(Integer userId, Integer eventId);
+
+ ParticipationRequestDto cancelRequest(Integer userId, Integer requestId);
+
+ List getRequests(Integer userId);
+}
diff --git a/evm-service/src/main/java/ru/practicum/Request/Service/RequestPrivateServiceImpl.java b/evm-service/src/main/java/ru/practicum/Request/Service/RequestPrivateServiceImpl.java
new file mode 100644
index 0000000..7cfbc06
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/Request/Service/RequestPrivateServiceImpl.java
@@ -0,0 +1,88 @@
+package ru.practicum.Request.Service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import ru.practicum.Event.Model.Event;
+import ru.practicum.Event.Model.State;
+import ru.practicum.Event.Repository.EventRepository;
+import ru.practicum.Exception.ConflictException;
+import ru.practicum.Exception.NotFoundException;
+import ru.practicum.Request.Mapper.RequestMapper;
+import ru.practicum.Request.Model.Request;
+import ru.practicum.Request.Repository.RequestRepository;
+import ru.practicum.Request.RequestDTO.ParticipationRequestDto;
+import ru.practicum.User.Model.User;
+import ru.practicum.User.Repository.UserRepository;
+
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class RequestPrivateServiceImpl implements RequestPrivateService {
+ private final RequestRepository requestRepository;
+ private final EventRepository eventRepository;
+ private final UserRepository userRepository;
+ private final RequestMapper requestMapper;
+
+ @Override
+ public ParticipationRequestDto addRequest(Integer userId, Integer eventId) {
+ Request request = new Request();
+
+ Event event = eventRepository.findEventById(eventId)
+ .orElseThrow(() -> new NotFoundException("Event not found"));
+
+ User user = checkUser(userId);
+
+ if (requestRepository.findRequestsByEvent_IdAndRequester_Id(eventId, userId) != null) {
+ throw new ConflictException("Request already exists");
+ }
+
+ if (!event.getState().equals(State.PUBLISHED)) {
+ throw new ConflictException("Event is not published");
+ } else if (event.getInitiator().getId().equals(userId)) {
+ throw new ConflictException("Requester and initiator could not be the same.");
+ }
+
+ if (event.getParticipantLimit().equals(event.getConfirmedRequests()) && event.getParticipantLimit() != 0) {
+ throw new ConflictException("Request limit exceeded");
+ }
+
+ if (!(event.getRequestModeration()) || (event.getParticipantLimit() == 0)) {
+ request.setStatus(State.CONFIRMED);
+ event.setConfirmedRequests(event.getConfirmedRequests() + 1);
+ eventRepository.save(event);
+ } else {
+ request.setStatus(State.PENDING);
+ }
+
+ request.setEvent(event);
+ request.setRequester(user);
+
+ return requestMapper.toParticipationRequestDto(requestRepository.save(request));
+ }
+
+ @Override
+ public ParticipationRequestDto cancelRequest(Integer userId, Integer requestId) {
+ Request request = requestRepository.findRequestById(requestId)
+ .orElseThrow(() -> new NotFoundException("Request with id " + requestId + " not found"));
+
+ checkUser(userId);
+
+ request.setStatus(State.CANCELED);
+
+ return requestMapper.toParticipationRequestDto(requestRepository.save(request));
+ }
+
+ @Override
+ public List getRequests(Integer userId) {
+ checkUser(userId);
+ return requestRepository.findRequestsByRequester_Id(userId).stream()
+ .map(requestMapper::toParticipationRequestDto)
+ .toList();
+ }
+
+ private User checkUser(Integer userId) {
+ return userRepository.findUserById(userId)
+ .orElseThrow(() -> new NotFoundException("User with id " + userId + " not found"));
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/User/Controller/UserAdminController.java b/evm-service/src/main/java/ru/practicum/User/Controller/UserAdminController.java
new file mode 100644
index 0000000..3ee9657
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/User/Controller/UserAdminController.java
@@ -0,0 +1,47 @@
+package ru.practicum.User.Controller;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.User.DTO.NewUserRequest;
+import ru.practicum.User.DTO.UserDTO;
+import ru.practicum.User.DTO.UsersListRequest;
+import ru.practicum.User.Service.UserAdminServiceImpl;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/admin/users")
+@RequiredArgsConstructor
+public class UserAdminController {
+ private final UserAdminServiceImpl userAdminService;
+
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ public UserDTO addUser(@RequestBody @Valid NewUserRequest newUser) {
+ return userAdminService.addUser(newUser);
+ }
+
+ @GetMapping("/{userId}")
+ @ResponseStatus(HttpStatus.OK)
+ public UserDTO getUserById(@PathVariable Integer userId) {
+ return userAdminService.getUserById(userId);
+ }
+
+ @GetMapping
+ @ResponseStatus(HttpStatus.OK)
+ public List getUsers(@RequestParam(required = false) List ids,
+ @RequestParam(defaultValue = "0") @PositiveOrZero int from,
+ @RequestParam(defaultValue = "10") @Positive int size) {
+ return userAdminService.getUsers(new UsersListRequest(ids, from, size));
+ }
+
+ @DeleteMapping("/{userId}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void deleteUser(@PathVariable Integer userId) {
+ userAdminService.deleteUserById(userId);
+ }
+}
diff --git a/evm-service/src/main/java/ru/practicum/User/DTO/NewUserRequest.java b/evm-service/src/main/java/ru/practicum/User/DTO/NewUserRequest.java
new file mode 100644
index 0000000..9e2e7f4
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/User/DTO/NewUserRequest.java
@@ -0,0 +1,23 @@
+package ru.practicum.User.DTO;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class NewUserRequest {
+ @Email
+ @NotBlank
+ @Size(min = 6, max = 254)
+ private String email;
+
+ @NotBlank
+ @Size(min = 2, max = 250)
+ private String name;
+
+}
diff --git a/evm-service/src/main/java/ru/practicum/User/DTO/UserDTO.java b/evm-service/src/main/java/ru/practicum/User/DTO/UserDTO.java
new file mode 100644
index 0000000..552d020
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/User/DTO/UserDTO.java
@@ -0,0 +1,21 @@
+package ru.practicum.User.DTO;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserDTO {
+ private Integer id;
+
+ @NotBlank
+ @Email
+ private String email;
+
+ @NotBlank
+ private String name;
+}
diff --git a/evm-service/src/main/java/ru/practicum/User/DTO/UserShortDTO.java b/evm-service/src/main/java/ru/practicum/User/DTO/UserShortDTO.java
new file mode 100644
index 0000000..6f8c646
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/User/DTO/UserShortDTO.java
@@ -0,0 +1,18 @@
+package ru.practicum.User.DTO;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserShortDTO {
+ @NotNull
+ private Integer id;
+
+ @NotBlank
+ private String name;
+}
diff --git a/evm-service/src/main/java/ru/practicum/User/DTO/UsersListRequest.java b/evm-service/src/main/java/ru/practicum/User/DTO/UsersListRequest.java
new file mode 100644
index 0000000..c194ba8
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/User/DTO/UsersListRequest.java
@@ -0,0 +1,9 @@
+package ru.practicum.User.DTO;
+
+import java.util.List;
+
+public record UsersListRequest(
+ List ids,
+ Integer from,
+ Integer size) {
+}
diff --git a/evm-service/src/main/java/ru/practicum/User/Mapper/UserMapper.java b/evm-service/src/main/java/ru/practicum/User/Mapper/UserMapper.java
new file mode 100644
index 0000000..6ee6e70
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/User/Mapper/UserMapper.java
@@ -0,0 +1,16 @@
+package ru.practicum.User.Mapper;
+
+import org.mapstruct.Mapper;
+import ru.practicum.User.DTO.NewUserRequest;
+import ru.practicum.User.DTO.UserDTO;
+import ru.practicum.User.DTO.UserShortDTO;
+import ru.practicum.User.Model.User;
+
+@Mapper(componentModel = "spring")
+public interface UserMapper {
+ User toUser(NewUserRequest user);
+
+ UserDTO toUserDTO(User user);
+
+ UserShortDTO toUserShortDTO(User user);
+}
diff --git a/evm-service/src/main/java/ru/practicum/User/Model/User.java b/evm-service/src/main/java/ru/practicum/User/Model/User.java
new file mode 100644
index 0000000..9e08ef5
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/User/Model/User.java
@@ -0,0 +1,24 @@
+package ru.practicum.User.Model;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "users")
+public class User {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Integer id;
+
+ @Column(name = "email", length = 500)
+ private String email;
+
+ @Column(name = "name", length = 250)
+ private String name;
+}
diff --git a/evm-service/src/main/java/ru/practicum/User/Repository/UserRepository.java b/evm-service/src/main/java/ru/practicum/User/Repository/UserRepository.java
new file mode 100644
index 0000000..4f34f21
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/User/Repository/UserRepository.java
@@ -0,0 +1,17 @@
+package ru.practicum.User.Repository;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import ru.practicum.User.Model.User;
+
+import java.util.Collection;
+import java.util.Optional;
+
+public interface UserRepository extends JpaRepository {
+ User save(User newUser);
+
+ Optional findUserById(Integer id);
+
+ Page findByIdIn(Collection ids, Pageable pageable);
+}
diff --git a/evm-service/src/main/java/ru/practicum/User/Service/UserAdminService.java b/evm-service/src/main/java/ru/practicum/User/Service/UserAdminService.java
new file mode 100644
index 0000000..75a8beb
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/User/Service/UserAdminService.java
@@ -0,0 +1,17 @@
+package ru.practicum.User.Service;
+
+import ru.practicum.User.DTO.NewUserRequest;
+import ru.practicum.User.DTO.UserDTO;
+import ru.practicum.User.DTO.UsersListRequest;
+
+import java.util.List;
+
+public interface UserAdminService {
+ UserDTO addUser(NewUserRequest newUser);
+
+ void deleteUserById(Integer id);
+
+ UserDTO getUserById(Integer id);
+
+ List getUsers(UsersListRequest request);
+}
diff --git a/evm-service/src/main/java/ru/practicum/User/Service/UserAdminServiceImpl.java b/evm-service/src/main/java/ru/practicum/User/Service/UserAdminServiceImpl.java
new file mode 100644
index 0000000..378cbf0
--- /dev/null
+++ b/evm-service/src/main/java/ru/practicum/User/Service/UserAdminServiceImpl.java
@@ -0,0 +1,59 @@
+package ru.practicum.User.Service;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+import ru.practicum.Exception.NotFoundException;
+import ru.practicum.User.DTO.NewUserRequest;
+import ru.practicum.User.DTO.UserDTO;
+import ru.practicum.User.DTO.UsersListRequest;
+import ru.practicum.User.Mapper.UserMapper;
+import ru.practicum.User.Model.User;
+import ru.practicum.User.Repository.UserRepository;
+
+import java.util.List;
+
+
+@Service
+@RequiredArgsConstructor
+public class UserAdminServiceImpl implements UserAdminService {
+ private final UserRepository userRepository;
+ private final UserMapper userMapper;
+
+ @Override
+ public UserDTO addUser(NewUserRequest newUser) {
+ return userMapper.toUserDTO(userRepository.save(userMapper.toUser(newUser)));
+ }
+
+ @Override
+ public void deleteUserById(Integer id) {
+ User user = userRepository.findById(id)
+ .orElseThrow(() -> new NotFoundException("User with id " + id + " not found"));
+
+ userRepository.delete(user);
+ }
+
+ @Override
+ public UserDTO getUserById(Integer id) {
+ User user = userRepository.findById(id)
+ .orElseThrow(() -> new NotFoundException("User with id " + id + " not found"));
+
+ return userMapper.toUserDTO(user);
+ }
+
+ @Override
+ public List getUsers(UsersListRequest request) {
+ int page = request.from() / request.size();
+ Pageable pageable = PageRequest.of(page, request.size(), Sort.by("id").ascending());
+
+ List users = request.ids() == null || request.ids().isEmpty()
+ ? userRepository.findAll(pageable).getContent()
+ : userRepository.findByIdIn(request.ids(), pageable).getContent();
+
+ return users.stream()
+ .map(userMapper::toUserDTO)
+ .toList();
+ }
+}
diff --git a/evm-service/src/main/resources/application-docker.yml b/evm-service/src/main/resources/application-docker.yml
new file mode 100644
index 0000000..14693c9
--- /dev/null
+++ b/evm-service/src/main/resources/application-docker.yml
@@ -0,0 +1,30 @@
+server:
+ port: 8080
+
+stat-server:
+ url: http://stats-server:9090
+
+spring:
+ jpa:
+ hibernate.ddl-auto: none
+ properties.hibernate.format_sql: true
+
+ sql:
+ init:
+ mode: always
+ datasource:
+ driverClassName: org.postgresql.Driver
+ url: jdbc:postgresql://ewm-service:5432/ewm-service
+ username: user
+ password: 12345
+ application:
+ name: evm-service
+
+logging:
+ level:
+ org:
+ springframework:
+ orm.jpa: INFO
+ transaction: INFO
+ interceptor: TRACE
+ JpaTransactionManager: DEBUG
\ No newline at end of file
diff --git a/evm-service/src/main/resources/application.yml b/evm-service/src/main/resources/application.yml
new file mode 100644
index 0000000..e2e1cd1
--- /dev/null
+++ b/evm-service/src/main/resources/application.yml
@@ -0,0 +1,30 @@
+server:
+ port: 8080
+
+stat-server:
+ url: http://localhost:9090
+
+spring:
+ jpa:
+ hibernate.ddl-auto: none
+ properties.hibernate.format_sql: true
+
+ sql:
+ init:
+ mode: always
+ datasource:
+ driverClassName: org.postgresql.Driver
+ url: jdbc:postgresql://localhost:5432/ewm-service
+ username: user
+ password: 12345
+ application:
+ name: evm-service
+
+logging:
+ level:
+ org:
+ springframework:
+ orm.jpa: INFO
+ transaction: INFO
+ interceptor: TRACE
+ JpaTransactionManager: DEBUG
diff --git a/evm-service/src/main/resources/schema.sql b/evm-service/src/main/resources/schema.sql
new file mode 100644
index 0000000..97956c5
--- /dev/null
+++ b/evm-service/src/main/resources/schema.sql
@@ -0,0 +1,66 @@
+DROP TABLE IF EXISTS users, categories, events, requests, compilations, compilation_events;
+
+CREATE TABLE IF NOT EXISTS users (
+ id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ name VARCHAR(250) NOT NULL,
+ email VARCHAR(500) NOT NULL UNIQUE,
+ CONSTRAINT UQ_USER_EMAIL UNIQUE (email)
+ );
+
+
+CREATE TABLE IF NOT EXISTS categories (
+ id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ name VARCHAR(50) UNIQUE NOT NULL,
+ CONSTRAINT UQ_CATEGORY_NAME UNIQUE (name)
+);
+
+CREATE TABLE IF NOT EXISTS state (
+ id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ name VARCHAR(50)
+);
+
+CREATE TABLE IF NOT EXISTS events (
+ id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ title VARCHAR(205) NOT NULL,
+ annotation VARCHAR(2000) NOT NULL,
+ category_id INT NOT NULL,
+ confirmed_requests INT,
+ created_on TIMESTAMP WITHOUT TIME ZONE,
+ description VARCHAR(7000),
+ event_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ initiator_id INT NOT NULL,
+ lat INT NOT NULL,
+ lon INT NOT NULL,
+ paid BOOLEAN,
+ participant_limit INT,
+ published_on TIMESTAMP WITHOUT TIME ZONE,
+ request_moderation BOOLEAN,
+ state_id INT,
+ views INT,
+ CONSTRAINT fk_events_to_categories FOREIGN KEY (category_id) REFERENCES categories (id) ON DELETE CASCADE,
+ CONSTRAINT fk_events_to_users FOREIGN KEY (initiator_id) REFERENCES users (id) ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS compilations (
+ id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ pinned BOOLEAN,
+ title VARCHAR(150) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS requests (
+ id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ created TIMESTAMP WITHOUT TIME ZONE,
+ event_id INT NOT NULL,
+ requester_id INT NOT NULL,
+ state_id INT,
+ CONSTRAINT fk_requests_to_events FOREIGN KEY (event_id) REFERENCES events (id) ON DELETE CASCADE,
+ CONSTRAINT fk_requests_to_users FOREIGN KEY (requester_id) REFERENCES users (id) ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS compilation_events (
+ compilation_id INT NOT NULL,
+ event_id INT NOT NULL,
+ CONSTRAINT fk_compilation_events_to_compilations FOREIGN KEY (compilation_id) REFERENCES compilations (id) ON DELETE CASCADE,
+ CONSTRAINT fk_compilation_events_to_events FOREIGN KEY (event_id) REFERENCES events (id) ON DELETE CASCADE
+);
+
diff --git a/pom.xml b/pom.xml
index b15acb2..c2e49de 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,13 +12,19 @@
Explore With Me
- ru.practicum
+
+ evm-service
+ stat
+
+
+ ru.practicum
explore-with-me
0.0.1-SNAPSHOT
pom
21
+ 21
UTF-8
diff --git a/stat/Dockerfile b/stat/Dockerfile
new file mode 100644
index 0000000..0ff1817
--- /dev/null
+++ b/stat/Dockerfile
@@ -0,0 +1,5 @@
+FROM eclipse-temurin:21-jre-jammy
+VOLUME /tmp
+ARG JAR_FILE=target/*.jar
+COPY ${JAR_FILE} app.jar
+ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]
\ No newline at end of file
diff --git a/stat/pom.xml b/stat/pom.xml
new file mode 100644
index 0000000..81221fe
--- /dev/null
+++ b/stat/pom.xml
@@ -0,0 +1,21 @@
+
+
+ 4.0.0
+
+ ru.practicum
+ explore-with-me
+ 0.0.1-SNAPSHOT
+
+
+ stat
+ pom
+
+
+ stat-client
+ stat-dto
+ stat-server
+
+
+
\ No newline at end of file
diff --git a/stat/stat-client/Dockerfile b/stat/stat-client/Dockerfile
new file mode 100644
index 0000000..0ff1817
--- /dev/null
+++ b/stat/stat-client/Dockerfile
@@ -0,0 +1,5 @@
+FROM eclipse-temurin:21-jre-jammy
+VOLUME /tmp
+ARG JAR_FILE=target/*.jar
+COPY ${JAR_FILE} app.jar
+ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]
\ No newline at end of file
diff --git a/stat/stat-client/pom.xml b/stat/stat-client/pom.xml
new file mode 100644
index 0000000..b3c1366
--- /dev/null
+++ b/stat/stat-client/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+ ru.practicum
+ stat
+ 0.0.1-SNAPSHOT
+
+
+ stat-client
+
+
+
+ ru.practicum
+ stat-dto
+ 0.0.1-SNAPSHOT
+ compile
+
+
+
+
\ No newline at end of file
diff --git a/stat/stat-client/src/main/java/ru/practicum/StatsClient.java b/stat/stat-client/src/main/java/ru/practicum/StatsClient.java
new file mode 100644
index 0000000..fef47fe
--- /dev/null
+++ b/stat/stat-client/src/main/java/ru/practicum/StatsClient.java
@@ -0,0 +1,102 @@
+package ru.practicum;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.micrometer.common.lang.Nullable;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class StatsClient {
+ private final String serverUrl;
+ private final RestTemplate restTemplate;
+
+ @Autowired
+ public StatsClient(@Value(value = "${stat-server.url:http://stat-server:9090}") String serverUrl,
+ RestTemplateBuilder builder) {
+ this.serverUrl = serverUrl;
+ this.restTemplate = builder.build();
+ }
+
+ public ResponseEntity