diff --git a/core/main-service/Dockerfile b/core/main-service/Dockerfile
new file mode 100644
index 0000000..16eef7a
--- /dev/null
+++ b/core/main-service/Dockerfile
@@ -0,0 +1,4 @@
+FROM amazoncorretto:21-alpine
+LABEL authors="Слава"
+COPY target/*.jar app.jar
+ENTRYPOINT ["java", "-jar", "app.jar"]
diff --git a/core/main-service/pom.xml b/core/main-service/pom.xml
new file mode 100644
index 0000000..dbb723f
--- /dev/null
+++ b/core/main-service/pom.xml
@@ -0,0 +1,137 @@
+
+
+ 4.0.0
+
+
+ ru.practicum
+ core
+ 0.0.1-SNAPSHOT
+
+
+ main-service
+
+
+
+
+
+
+ ru.practicum
+ stats-client
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ javax.validation
+ validation-api
+ 2.0.1.Final
+
+
+
+
+
+ org.postgresql
+ postgresql
+
+
+
+ com.h2database
+ h2
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-client
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-config
+
+
+
+ org.springframework.retry
+ spring-retry
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
+
diff --git a/core/main-service/src/main/java/ru/practicum/Main.java b/core/main-service/src/main/java/ru/practicum/Main.java
new file mode 100644
index 0000000..52735de
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/Main.java
@@ -0,0 +1,14 @@
+package ru.practicum;
+
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@EnableScheduling
+@SpringBootApplication
+public class Main {
+ public static void main(String[] args) {
+ SpringApplication.run(Main.class, args);
+ }
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java b/core/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java
new file mode 100644
index 0000000..8d5b8d7
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java
@@ -0,0 +1,53 @@
+package ru.practicum.category.controller;
+
+import jakarta.validation.constraints.Positive;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.category.dto.CategoryDto;
+import ru.practicum.category.service.CategoryAdminService;
+import ru.practicum.validation.CreateOrUpdateValidator;
+
+@RestController
+@RequiredArgsConstructor
+@Validated
+@RequestMapping(path = "/admin/categories")
+@Slf4j
+public class CategoryAdminController {
+
+ private final CategoryAdminService categoryAdminService;
+
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ public CategoryDto addCategory(
+ @RequestBody @Validated(CreateOrUpdateValidator.Create.class)
+ CategoryDto requestCategory,
+ BindingResult bindingResult
+ ) {
+ log.info("Calling the POST request to /admin/categories endpoint");
+ if (bindingResult.hasErrors()) {
+ log.error("Validation error with category name");
+ throw new IllegalArgumentException("Validation failed");
+ }
+ return categoryAdminService.createCategory(requestCategory);
+ }
+
+ @DeleteMapping("/{catId}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void deleteCategories(@PathVariable @Positive Long catId) {
+ log.info("Calling the DELETE request to /admin/categories/{catId} endpoint");
+ categoryAdminService.deleteCategory(catId);
+ }
+
+ @PatchMapping("/{catId}")
+ public CategoryDto updateCategory(
+ @PathVariable Long catId,
+ @RequestBody @Validated(CreateOrUpdateValidator.Update.class) CategoryDto categoryDto
+ ) {
+ log.info("Calling the PATCH request to /admin/categories/{catId} endpoint");
+ return categoryAdminService.updateCategory(catId, categoryDto);
+ }
+}
diff --git a/core/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java b/core/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java
new file mode 100644
index 0000000..8ee6349
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java
@@ -0,0 +1,40 @@
+package ru.practicum.category.controller;
+
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.category.dto.CategoryDto;
+import ru.practicum.category.service.CategoryPublicService;
+
+import java.util.List;
+
+@RestController
+@RequiredArgsConstructor
+@Validated
+@RequestMapping(path = "/categories")
+@Slf4j
+public class CategoryPublicController {
+
+ private final CategoryPublicService service;
+
+ @GetMapping
+ public ResponseEntity> readAllCategories(
+ @RequestParam(defaultValue = "0") @PositiveOrZero int from,
+ @RequestParam(defaultValue = "10") @Positive int size
+ ) {
+ log.info("Calling the POST request to - /categories - endpoint");
+ return ResponseEntity.ok(service.readAllCategories(from, size));
+ }
+
+ @GetMapping("/{catId}")
+ public ResponseEntity readCategoryById(
+ @PathVariable Long catId
+ ) {
+ log.info("Calling the GET request to - /categories/{catId} - endpoint");
+ return ResponseEntity.ok(service.readCategoryById(catId));
+ }
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/category/dto/CategoryDto.java b/core/main-service/src/main/java/ru/practicum/category/dto/CategoryDto.java
new file mode 100644
index 0000000..7fffa9e
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/category/dto/CategoryDto.java
@@ -0,0 +1,22 @@
+package ru.practicum.category.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.validation.CreateOrUpdateValidator;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CategoryDto {
+
+ private Long id;
+
+ @NotBlank(groups = {CreateOrUpdateValidator.Create.class, CreateOrUpdateValidator.Update.class})
+ @Size(min = 1, max = 50, groups = {CreateOrUpdateValidator.Create.class, CreateOrUpdateValidator.Update.class})
+ private String name;
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/category/mapper/CategoryMapper.java b/core/main-service/src/main/java/ru/practicum/category/mapper/CategoryMapper.java
new file mode 100644
index 0000000..e675a57
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/category/mapper/CategoryMapper.java
@@ -0,0 +1,28 @@
+package ru.practicum.category.mapper;
+
+import ru.practicum.category.dto.CategoryDto;
+import ru.practicum.category.model.Category;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CategoryMapper {
+
+ public static CategoryDto toCategoryDto(Category category) {
+ return CategoryDto.builder()
+ .id(category.getId())
+ .name(category.getName())
+ .build();
+ }
+
+ public static Category toCategories(CategoryDto categoryDto) {
+ return Category.builder()
+ .name(categoryDto.getName())
+ .build();
+ }
+
+ public static List toListCategoriesDto(List list) {
+ return list.stream().map(CategoryMapper::toCategoryDto).collect(Collectors.toList());
+ }
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/category/model/Category.java b/core/main-service/src/main/java/ru/practicum/category/model/Category.java
new file mode 100644
index 0000000..6e4439c
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/category/model/Category.java
@@ -0,0 +1,34 @@
+package ru.practicum.category.model;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.Size;
+import lombok.*;
+
+@Getter
+@Setter
+@Builder
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Table(name = "categories")
+public class Category {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @Column(name = "cat_name")
+ @Size(min = 1, max = 50)
+ @NotEmpty
+ private String name;
+
+ @Override
+ public String toString() {
+ return "Categories{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/category/repository/CategoryRepository.java b/core/main-service/src/main/java/ru/practicum/category/repository/CategoryRepository.java
new file mode 100644
index 0000000..539b9c8
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/category/repository/CategoryRepository.java
@@ -0,0 +1,10 @@
+package ru.practicum.category.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import ru.practicum.category.model.Category;
+
+public interface CategoryRepository extends JpaRepository {
+
+ boolean existsByName(String name);
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/category/service/CategoryAdminService.java b/core/main-service/src/main/java/ru/practicum/category/service/CategoryAdminService.java
new file mode 100644
index 0000000..e5b1c18
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/category/service/CategoryAdminService.java
@@ -0,0 +1,13 @@
+package ru.practicum.category.service;
+
+import ru.practicum.category.dto.CategoryDto;
+
+public interface CategoryAdminService {
+
+ CategoryDto createCategory(CategoryDto requestCategory);
+
+ void deleteCategory(Long catId);
+
+ CategoryDto updateCategory(Long catId, CategoryDto categoryDto);
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/category/service/CategoryAdminServiceImpl.java b/core/main-service/src/main/java/ru/practicum/category/service/CategoryAdminServiceImpl.java
new file mode 100644
index 0000000..4a9b694
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/category/service/CategoryAdminServiceImpl.java
@@ -0,0 +1,65 @@
+package ru.practicum.category.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+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.event.repository.EventRepository;
+import ru.practicum.exception.ConflictException;
+import ru.practicum.exception.NotFoundException;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+@Transactional(readOnly = false)
+public class CategoryAdminServiceImpl implements CategoryAdminService {
+
+ private final CategoryRepository categoryRepository;
+
+ private final EventRepository eventRepository;
+
+ @Override
+ public CategoryDto createCategory(CategoryDto requestCategory) {
+ log.info("createCategories - invoked");
+ if (categoryRepository.existsByName(requestCategory.getName())) {
+ log.error("Category name not unique {}", requestCategory.getName());
+ throw new ConflictException("Category with this name already exists");
+ }
+ Category result = categoryRepository.saveAndFlush(CategoryMapper.toCategories(requestCategory));
+ log.info("Result: category - {} - saved", result.getName());
+ return CategoryMapper.toCategoryDto(result);
+ }
+
+ @Override
+ public void deleteCategory(Long catId) {
+ log.info("deleteCategories - invoked");
+ if (!categoryRepository.existsById(catId)) {
+ log.error("Category with this id does not exist {}", catId);
+ throw new NotFoundException("Category with this id does not exist");
+ }
+ if (eventRepository.existsByCategoryId(catId)) {
+ throw new ConflictException("Can't delete a category with associated events");
+ }
+ log.info("Result: category with id - {} - deleted", catId);
+ categoryRepository.deleteById(catId);
+ }
+
+ @Override
+ public CategoryDto updateCategory(Long catId, CategoryDto categoryDto) {
+ log.info("updateCategories - invoked");
+ Category category = categoryRepository.findById(catId).orElseThrow(()
+ -> new NotFoundException("This Category not found"));
+ if (!category.getName().equals(categoryDto.getName()) &&
+ categoryRepository.existsByName(categoryDto.getName())) {
+ log.error("Category with this name not unique: {}", categoryDto.getName());
+ throw new ConflictException("Category with this name not unique: " + categoryDto.getName());
+ }
+ category.setName(categoryDto.getName());
+ log.info("Result: category - {} updated", category.getName());
+ return CategoryMapper.toCategoryDto(category);
+ }
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/category/service/CategoryPublicService.java b/core/main-service/src/main/java/ru/practicum/category/service/CategoryPublicService.java
new file mode 100644
index 0000000..de4cdd2
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/category/service/CategoryPublicService.java
@@ -0,0 +1,13 @@
+package ru.practicum.category.service;
+
+import ru.practicum.category.dto.CategoryDto;
+
+import java.util.List;
+
+public interface CategoryPublicService {
+
+ List readAllCategories(Integer from, Integer size);
+
+ CategoryDto readCategoryById(Long catId);
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/category/service/CategoryPublicServiceImpl.java b/core/main-service/src/main/java/ru/practicum/category/service/CategoryPublicServiceImpl.java
new file mode 100644
index 0000000..b5d6fe6
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/category/service/CategoryPublicServiceImpl.java
@@ -0,0 +1,44 @@
+package ru.practicum.category.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+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;
+
+import static ru.practicum.util.Util.createPageRequestAsc;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class CategoryPublicServiceImpl implements CategoryPublicService {
+
+ private final CategoryRepository repository;
+
+ @Override
+ public List readAllCategories(Integer from, Integer size) {
+ log.info("readAllCategories - invoked");
+ Page page = repository.findAll(createPageRequestAsc(from, size));
+ List cat = page.getContent();
+ log.info("Result: categories size = {}", cat.size());
+ return CategoryMapper.toListCategoriesDto(cat);
+ }
+
+ @Override
+ public CategoryDto readCategoryById(Long catId) {
+ log.info("readCategoryById - invoked");
+ Category category = repository.findById(catId).orElseThrow(() -> {
+ log.error("Category with id = {} not exist", catId);
+ return new NotFoundException("Category not found");
+ });
+ log.info("Result: received a category - {}", category.getName());
+ return CategoryMapper.toCategoryDto(category);
+ }
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java b/core/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java
new file mode 100644
index 0000000..1be0a1b
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java
@@ -0,0 +1,63 @@
+package ru.practicum.comment.controller;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Positive;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.comment.dto.CommentDto;
+import ru.practicum.comment.service.CommentAdminService;
+
+import java.util.List;
+
+@RestController
+@RequestMapping(path = "/admin")
+@RequiredArgsConstructor
+@Slf4j
+public class CommentAdminController {
+
+ private final CommentAdminService service;
+
+ @GetMapping("/comments/search")
+ public ResponseEntity> search(@RequestParam @NotBlank String text,
+ @RequestParam(defaultValue = "0") int from,
+ @RequestParam(defaultValue = "10") int size) {
+ log.info("Calling the GET request to /admin/comment/search endpoint");
+ return ResponseEntity.ok(service.search(text, from, size));
+ }
+
+ @GetMapping("users/{userId}/comments")
+ public ResponseEntity> get(@PathVariable @Positive Long userId,
+ @RequestParam(defaultValue = "0") int from,
+ @RequestParam(defaultValue = "10") int size) {
+ log.info("Calling the GET request to admin/users/{userId}/comment endpoint");
+ return ResponseEntity.ok(service.findAllByUserId(userId, from, size));
+ }
+
+ @DeleteMapping("comments/{comId}")
+ public ResponseEntity delete(@PathVariable @Positive Long comId) {
+ log.info("Calling the GET request to admin/comment/{comId} endpoint");
+ service.delete(comId);
+ return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
+ }
+
+ @PatchMapping("/comments/{comId}/approve")
+ public ResponseEntity approveComment(@PathVariable @Positive Long comId) {
+ log.info("Calling the PATCH request to /admin/comment/{comId}/approve endpoint");
+ CommentDto commentDto = service.approveComment(comId);
+ return ResponseEntity
+ .status(HttpStatus.OK)
+ .body(commentDto);
+ }
+
+ @PatchMapping("/comments/{comId}/reject")
+ public ResponseEntity rejectComment(@PathVariable @Positive Long comId) {
+ log.info("Calling the PATCH request to /admin/comment/{comId}/reject endpoint");
+ CommentDto commentDto = service.rejectComment(comId);
+ return ResponseEntity
+ .status(HttpStatus.OK)
+ .body(commentDto);
+ }
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java b/core/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java
new file mode 100644
index 0000000..bac14f2
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java
@@ -0,0 +1,49 @@
+package ru.practicum.comment.controller;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Positive;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.comment.dto.CommentCreateDto;
+import ru.practicum.comment.dto.CommentDto;
+import ru.practicum.comment.service.CommentPrivateService;
+
+@RestController
+@Validated
+@RequiredArgsConstructor
+@Slf4j
+public class CommentPrivateController {
+
+ private final CommentPrivateService service;
+
+ @PostMapping("/users/{userId}/events/{eventId}/comments")
+ public ResponseEntity create(@PathVariable @Positive Long userId,
+ @PathVariable @Positive Long eventId,
+ @RequestBody @Valid CommentCreateDto commentCreateDto) {
+ log.info("Calling the GET request to /users/{userId}/events/{eventId}/comment endpoint");
+ return ResponseEntity.status(HttpStatus.CREATED)
+ .body(service.createComment(userId, eventId, commentCreateDto));
+ }
+
+ @DeleteMapping("/users/{userId}/comments/{comId}")
+ public ResponseEntity delete(@PathVariable @Positive Long userId,
+ @PathVariable @Positive Long comId) {
+ log.info("Calling the GET request to /users/{userId}/comment/{comId} endpoint");
+ service.deleteComment(userId, comId);
+ return ResponseEntity
+ .status(HttpStatus.NO_CONTENT)
+ .body("Comment deleted by user: " + comId);
+ }
+
+ @PatchMapping("/users/{userId}/comments/{comId}")
+ public ResponseEntity patch(@PathVariable @Positive Long userId,
+ @PathVariable @Positive Long comId,
+ @RequestBody @Valid CommentCreateDto commentCreateDto) {
+ log.info("Calling the PATCH request to users/{userId}/comment/{comId} endpoint");
+ return ResponseEntity.ok(service.patchComment(userId, comId, commentCreateDto));
+ }
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java b/core/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java
new file mode 100644
index 0000000..5a97d7b
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java
@@ -0,0 +1,45 @@
+package ru.practicum.comment.controller;
+
+import jakarta.validation.constraints.Positive;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import ru.practicum.comment.dto.CommentDto;
+import ru.practicum.comment.dto.CommentShortDto;
+import ru.practicum.comment.service.CommentPublicService;
+
+import java.util.List;
+
+@RestController
+@RequiredArgsConstructor
+@Slf4j
+public class CommentPublicController {
+
+ private final CommentPublicService service;
+
+ @GetMapping("/comments/{comId}")
+ public ResponseEntity getById(@PathVariable @Positive Long comId) {
+ log.info("Calling the GET request to /comments/{comId} endpoint");
+ return ResponseEntity.ok(service.getComment(comId));
+ }
+
+ @GetMapping("/events/{eventId}/comments")
+ public ResponseEntity> getByEventId(@PathVariable @Positive Long eventId,
+ @RequestParam(defaultValue = "0") int from,
+ @RequestParam(defaultValue = "10") int size) {
+ log.info("Calling the GET request to /events/{eventId}/comments");
+ return ResponseEntity.ok(service.getCommentsByEvent(eventId, from, size));
+ }
+
+ @GetMapping("/events/{eventId}/comments/{commentId}")
+ public ResponseEntity getByEventAndCommentId(@PathVariable @Positive Long eventId,
+ @PathVariable @Positive Long commentId) {
+ log.info("Calling the GET request to /events/{eventId}/comments/{commentId}");
+ return ResponseEntity.ok(service.getCommentByEventAndCommentId(eventId, commentId));
+ }
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/dto/CommentCountDto.java b/core/main-service/src/main/java/ru/practicum/comment/dto/CommentCountDto.java
new file mode 100644
index 0000000..fe16256
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/dto/CommentCountDto.java
@@ -0,0 +1,17 @@
+package ru.practicum.comment.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class CommentCountDto {
+
+ private Long eventId;
+ private Long commentCount;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/dto/CommentCreateDto.java b/core/main-service/src/main/java/ru/practicum/comment/dto/CommentCreateDto.java
new file mode 100644
index 0000000..4182509
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/dto/CommentCreateDto.java
@@ -0,0 +1,19 @@
+package ru.practicum.comment.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class CommentCreateDto {
+
+ @NotBlank
+ @Size(min = 1, max = 1000)
+ private String text;
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/dto/CommentDto.java b/core/main-service/src/main/java/ru/practicum/comment/dto/CommentDto.java
new file mode 100644
index 0000000..1de7f27
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/dto/CommentDto.java
@@ -0,0 +1,35 @@
+package ru.practicum.comment.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.event.dto.EventCommentDto;
+import ru.practicum.user.dto.UserDto;
+
+import java.time.LocalDateTime;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class CommentDto {
+
+ private Long id;
+
+ private String text;
+
+ private UserDto author;
+
+ private EventCommentDto event;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime createTime;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime patchTime;
+
+ private Boolean approved;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/dto/CommentShortDto.java b/core/main-service/src/main/java/ru/practicum/comment/dto/CommentShortDto.java
new file mode 100644
index 0000000..a32bd4c
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/dto/CommentShortDto.java
@@ -0,0 +1,25 @@
+package ru.practicum.comment.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.user.dto.UserDto;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class CommentShortDto {
+
+ private Long id;
+
+ private String text;
+
+ private UserDto author;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private String createTime;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java b/core/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java
new file mode 100644
index 0000000..b666a10
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java
@@ -0,0 +1,49 @@
+package ru.practicum.comment.mapper;
+
+import ru.practicum.comment.dto.CommentCreateDto;
+import ru.practicum.comment.dto.CommentDto;
+import ru.practicum.comment.dto.CommentShortDto;
+import ru.practicum.comment.model.Comment;
+import ru.practicum.event.mapper.EventMapper;
+import ru.practicum.user.mapper.UserMapper;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CommentMapper {
+
+ public static Comment toComment(CommentCreateDto commentDto) {
+ return Comment.builder()
+ .text(commentDto.getText())
+ .build();
+ }
+
+ public static CommentDto toCommentDto(Comment comment) {
+ return CommentDto.builder()
+ .id(comment.getId())
+ .author(UserMapper.toDto(comment.getAuthor()))
+ .event(EventMapper.toEventComment(comment.getEvent()))
+ .createTime(comment.getCreateTime())
+ .text(comment.getText())
+ .approved(comment.getApproved())
+ .build();
+ }
+
+ public static List toListCommentDto(List list) {
+ return list.stream().map(CommentMapper::toCommentDto).collect(Collectors.toList());
+ }
+
+ public static CommentShortDto toCommentShortDto(Comment comment) {
+ return CommentShortDto.builder()
+ .author(UserMapper.toDto(comment.getAuthor()))
+ .createTime(comment.getText())
+ .id(comment.getId())
+ .text(comment.getText())
+ .build();
+ }
+
+ public static List toListCommentShortDto(List list) {
+ return list.stream().map(CommentMapper::toCommentShortDto).collect(Collectors.toList());
+ }
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/model/Comment.java b/core/main-service/src/main/java/ru/practicum/comment/model/Comment.java
new file mode 100644
index 0000000..76cdb2f
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/model/Comment.java
@@ -0,0 +1,47 @@
+package ru.practicum.comment.model;
+
+import jakarta.persistence.*;
+import lombok.*;
+import ru.practicum.event.model.Event;
+import ru.practicum.user.model.User;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@Builder
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Table(name = "comments")
+public class Comment {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "textual_content", length = 1000, nullable = false)
+ private String text;
+
+ @ManyToOne
+ @JoinColumn(name = "author_id", nullable = false)
+ private User author;
+
+ @ManyToOne
+ @JoinColumn(name = "event_id", nullable = false)
+ private Event event;
+
+ @Column(name = "create_time", nullable = false)
+ private LocalDateTime createTime;
+
+ @Column(name = "patch_time")
+ private LocalDateTime patchTime;
+
+ @Column(name = "approved", nullable = false)
+ private Boolean approved;
+
+ public boolean isApproved() {
+ return approved;
+ }
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java b/core/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java
new file mode 100644
index 0000000..21bbc6d
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java
@@ -0,0 +1,31 @@
+package ru.practicum.comment.repository;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import ru.practicum.comment.dto.CommentCountDto;
+import ru.practicum.comment.model.Comment;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface CommentRepository extends JpaRepository {
+
+ Page findAllByEventId(Long eventId, Pageable pageable);
+
+ @Query("select new ru.practicum.comment.dto.CommentCountDto(c.event.id, count(c.id)) " +
+ "from Comment as c " +
+ "where c.event.id in ?1 " +
+ "group by c.event.id")
+ List findAllCommentCount(List listEventId);
+
+ @Query("select c " +
+ "from Comment as c " +
+ "where c.text ilike concat('%', ?1, '%')")
+ Page findAllByText(String text, Pageable pageable);
+
+ Page findAllByAuthorId(Long userId, Pageable pageable);
+
+ Optional findByEventIdAndId(Long eventId, Long commentId);
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java b/core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java
new file mode 100644
index 0000000..707f1d1
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java
@@ -0,0 +1,18 @@
+package ru.practicum.comment.service;
+
+import ru.practicum.comment.dto.CommentDto;
+
+import java.util.List;
+
+public interface CommentAdminService {
+
+ void delete(Long comId);
+
+ List search(String text, int from, int size);
+
+ List findAllByUserId(Long userId, int from, int size);
+
+ CommentDto approveComment(Long comId);
+
+ CommentDto rejectComment(Long comId);
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java b/core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java
new file mode 100644
index 0000000..38e9dd4
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java
@@ -0,0 +1,85 @@
+package ru.practicum.comment.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.comment.dto.CommentDto;
+import ru.practicum.comment.mapper.CommentMapper;
+import ru.practicum.comment.model.Comment;
+import ru.practicum.comment.repository.CommentRepository;
+import ru.practicum.exception.NotFoundException;
+import ru.practicum.user.repository.UserRepository;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class CommentAdminServiceImpl implements CommentAdminService {
+
+ private final CommentRepository repository;
+ private final UserRepository userRepository;
+
+ @Override
+ @Transactional
+ public void delete(Long comId) {
+ log.info("admin delete - invoked for comment ID: {}", comId);
+ if (!repository.existsById(comId)) {
+ log.error("Comment with id = {} not found", comId);
+ throw new NotFoundException("Comment not found");
+ }
+ log.info("Result: comment with id = {} deleted", comId);
+ repository.deleteById(comId);
+ }
+
+ @Override
+ public List search(String text, int from, int size) {
+ log.info("admin search - invoked with text='{}', from={}, size={}", text, from, size);
+ Pageable pageable = PageRequest.of(from / size, size);
+ Page page = repository.findAllByText(text, pageable);
+ List list = page.getContent();
+ log.info("Result: found {} comments for search query '{}'", list.size(), text);
+ return CommentMapper.toListCommentDto(list);
+ }
+
+ @Override
+ public List findAllByUserId(Long userId, int from, int size) {
+ log.info("admin findAllByUserId - invoked for user ID: {}, from={}, size={}", userId, from, size);
+ if (!userRepository.existsById(userId)) {
+ log.error("User with id = {} not found", userId);
+ throw new NotFoundException("User not found");
+ }
+ Pageable pageable = PageRequest.of(from / size, size);
+ Page page = repository.findAllByAuthorId(userId, pageable);
+ List list = page.getContent();
+ log.info("Result: user ID {} has {} comments", userId, list.size());
+ return CommentMapper.toListCommentDto(list);
+ }
+
+ @Override
+ @Transactional
+ public CommentDto approveComment(Long comId) {
+ log.info("approveComment - invoked for comment ID: {}", comId);
+ Comment comment = repository.findById(comId)
+ .orElseThrow(() -> new NotFoundException("Comment not found"));
+ comment.setApproved(true);
+ repository.save(comment);
+ log.info("Result: comment with id = {} approved successfully", comId);
+ return CommentMapper.toCommentDto(comment);
+ }
+
+ @Override
+ @Transactional
+ public CommentDto rejectComment(Long comId) {
+ log.info("rejectComment - invoked for comment ID: {}", comId);
+ Comment comment = repository.findById(comId)
+ .orElseThrow(() -> new NotFoundException("Comment not found"));
+ comment.setApproved(false);
+ repository.save(comment);
+ log.info("Result: comment with id = {} rejected successfully", comId);
+ return CommentMapper.toCommentDto(comment);
+ }
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateService.java b/core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateService.java
new file mode 100644
index 0000000..1bf8dbd
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateService.java
@@ -0,0 +1,13 @@
+package ru.practicum.comment.service;
+
+import ru.practicum.comment.dto.CommentCreateDto;
+import ru.practicum.comment.dto.CommentDto;
+
+public interface CommentPrivateService {
+
+ CommentDto createComment(Long userId, Long eventId, CommentCreateDto commentDto);
+
+ void deleteComment(Long userId, Long comId);
+
+ CommentDto patchComment(Long userId, Long comId, CommentCreateDto commentCreateDto);
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateServiceImpl.java b/core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateServiceImpl.java
new file mode 100644
index 0000000..c5e47c7
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateServiceImpl.java
@@ -0,0 +1,110 @@
+package ru.practicum.comment.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.comment.dto.CommentCreateDto;
+import ru.practicum.comment.dto.CommentDto;
+import ru.practicum.comment.mapper.CommentMapper;
+import ru.practicum.comment.model.Comment;
+import ru.practicum.comment.repository.CommentRepository;
+import ru.practicum.event.dto.State;
+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.user.model.User;
+import ru.practicum.user.repository.UserRepository;
+
+import java.time.LocalDateTime;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class CommentPrivateServiceImpl implements CommentPrivateService {
+
+ private final CommentRepository repository;
+ private final UserRepository userRepository;
+ private final EventRepository eventRepository;
+
+ @Override
+ @Transactional
+ public CommentDto createComment(Long userId, Long eventId, CommentCreateDto commentDto) {
+ log.info("createComment - invoked for user ID: {}, event ID: {}", userId, eventId);
+
+ Comment comment = CommentMapper.toComment(commentDto);
+
+ User author = userRepository.findById(userId)
+ .orElseThrow(() -> {
+ log.error("User with id = {} not registered", userId);
+ return new NotFoundException("Please register first then you can comment");
+ });
+
+ Event event = eventRepository.findById(eventId)
+ .orElseThrow(() -> {
+ log.error("Event with id = {} does not exist", eventId);
+ return new NotFoundException("Event not found");
+ });
+
+ if (!event.getState().equals(State.PUBLISHED)) {
+ log.error("Event ID {} has state = {}, expected PUBLISHED", eventId, event.getState());
+ throw new ConflictException("Event not published, you can't comment it");
+ }
+
+ comment.setAuthor(author);
+ comment.setEvent(event);
+ comment.setApproved(true);
+ comment.setCreateTime(LocalDateTime.now().withNano(0));
+
+ log.info("Result: new comment created for user ID: {}, event ID: {}, comment ID: {}",
+ userId, eventId, comment.getId());
+
+ return CommentMapper.toCommentDto(repository.save(comment));
+ }
+
+ @Override
+ @Transactional
+ public void deleteComment(Long userId, Long comId) {
+ log.info("deleteComment - invoked by user ID: {}, for comment ID: {}", userId, comId);
+
+ Comment comment = repository.findById(comId)
+ .orElseThrow(() -> {
+ log.error("Comment with id = {} does not exist", comId);
+ return new NotFoundException("Comment not found");
+ });
+
+ if (!comment.getAuthor().getId().equals(userId)) {
+ log.error("Unauthorized access: user ID {} tried to delete comment ID {}, but author is ID {}",
+ userId, comId, comment.getAuthor().getId());
+ throw new ConflictException("You didn't write this comment and can't delete it");
+ }
+
+ log.info("Result: comment with id = {} deleted by user ID {}", comId, userId);
+ repository.deleteById(comId);
+ }
+
+ @Override
+ @Transactional
+ public CommentDto patchComment(Long userId, Long comId, CommentCreateDto commentCreateDto) {
+ log.info("patchComment - invoked by user ID: {}, for comment ID: {}", userId, comId);
+
+ Comment comment = repository.findById(comId)
+ .orElseThrow(() -> {
+ log.error("Comment with id = {} does not exist", comId);
+ return new NotFoundException("Comment not found");
+ });
+
+ if (!comment.getAuthor().getId().equals(userId)) {
+ log.error("Unauthorized access: user ID {} tried to patch comment ID {}, but author is ID {}",
+ userId, comId, comment.getAuthor().getId());
+ throw new ConflictException("You didn't write this comment and can't patch it");
+ }
+
+ comment.setText(commentCreateDto.getText());
+ comment.setPatchTime(LocalDateTime.now().withNano(0));
+
+ log.info("Result: comment with id = {} updated by user ID {}", comId, userId);
+ return CommentMapper.toCommentDto(comment);
+ }
+}
diff --git a/core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicService.java b/core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicService.java
new file mode 100644
index 0000000..2762467
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicService.java
@@ -0,0 +1,15 @@
+package ru.practicum.comment.service;
+
+import ru.practicum.comment.dto.CommentDto;
+import ru.practicum.comment.dto.CommentShortDto;
+
+import java.util.List;
+
+public interface CommentPublicService {
+
+ CommentDto getComment(Long comId);
+
+ List getCommentsByEvent(Long eventId, int from, int size);
+
+ CommentDto getCommentByEventAndCommentId(Long eventId, Long commentId);
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicServiceImpl.java b/core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicServiceImpl.java
new file mode 100644
index 0000000..94a8949
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicServiceImpl.java
@@ -0,0 +1,102 @@
+package ru.practicum.comment.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.comment.dto.CommentDto;
+import ru.practicum.comment.dto.CommentShortDto;
+import ru.practicum.comment.mapper.CommentMapper;
+import ru.practicum.comment.model.Comment;
+import ru.practicum.comment.repository.CommentRepository;
+import ru.practicum.event.repository.EventRepository;
+import ru.practicum.exception.ForbiddenException;
+import ru.practicum.exception.NotFoundException;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static ru.practicum.util.Util.createPageRequestAsc;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class CommentPublicServiceImpl implements CommentPublicService {
+
+ private final CommentRepository repository;
+ private final EventRepository eventRepository;
+
+ @Override
+ public CommentDto getComment(Long comId) {
+ log.info("getComment - invoked for comment ID: {}", comId);
+
+ Comment comment = repository.findById(comId)
+ .orElseThrow(() -> {
+ log.error("Comment with ID {} not found", comId);
+ return new NotFoundException("Comment not found");
+ });
+
+ if (!comment.isApproved()) {
+ log.warn("Comment with ID {} is not approved (current state: {})",
+ comId, comment.isApproved());
+ throw new ForbiddenException("Comment is not approved");
+ }
+
+ log.info("Result: successfully retrieved approved comment with ID {}", comId);
+ return CommentMapper.toCommentDto(comment);
+ }
+
+ @Override
+ public List getCommentsByEvent(Long eventId, int from, int size) {
+ log.info("getCommentsByEvent - invoked for event ID: {}, from: {}, size: {}",
+ eventId, from, size);
+
+ if (!eventRepository.existsById(eventId)) {
+ log.error("Event with ID {} does not exist", eventId);
+ throw new NotFoundException("Event not found");
+ }
+
+ Pageable pageable = createPageRequestAsc("createTime", from, size);
+ Page commentsPage = repository.findAllByEventId(eventId, pageable);
+ List comments = commentsPage.getContent();
+
+ List approvedComments = comments.stream()
+ .filter(Comment::isApproved)
+ .collect(Collectors.toList());
+
+ log.info("Result: retrieved {} approved comments for event ID {} (requested {} items, offset {})",
+ approvedComments.size(), eventId, size, from);
+
+ return CommentMapper.toListCommentShortDto(approvedComments);
+ }
+
+ @Override
+ public CommentDto getCommentByEventAndCommentId(Long eventId, Long commentId) {
+ log.info("getCommentByEventAndCommentId - invoked for event ID: {}, comment ID: {}",
+ eventId, commentId);
+
+ Comment comment = repository.findById(commentId)
+ .orElseThrow(() -> {
+ log.error("Comment with ID {} not found", commentId);
+ return new NotFoundException("Comment not found");
+ });
+
+ if (!comment.getEvent().getId().equals(eventId)) {
+ log.error("Comment ID {} does not belong to event ID {} (belongs to event ID {})",
+ commentId, eventId, comment.getEvent().getId());
+ throw new NotFoundException("Comment not found for the specified event");
+ }
+
+ if (!comment.isApproved()) {
+ log.warn("Comment ID {} is not approved (cannot be accessed)", commentId);
+ throw new ForbiddenException("Comment is not approved");
+ }
+
+ log.info("Result: successfully retrieved comment ID {} for event ID {}",
+ commentId, eventId);
+
+ return CommentMapper.toCommentDto(comment);
+ }
+}
diff --git a/core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationAdminController.java b/core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationAdminController.java
new file mode 100644
index 0000000..1ecb2a9
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationAdminController.java
@@ -0,0 +1,55 @@
+package ru.practicum.compilation.controller;
+
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.compilation.dto.CompilationDto;
+import ru.practicum.compilation.dto.NewCompilationDto;
+import ru.practicum.compilation.dto.UpdateCompilationDto;
+import ru.practicum.compilation.service.CompilationAdminService;
+
+@RestController
+@Validated
+@RequestMapping("/admin/compilations")
+@RequiredArgsConstructor
+@Slf4j
+public class CompilationAdminController {
+
+ private final CompilationAdminService compilationAdminService;
+
+ @PostMapping
+ public ResponseEntity postCompilations(
+ @RequestBody @Valid NewCompilationDto newCompilationDto
+ ) {
+ log.info("Calling the POST request to /admin/compilations endpoint");
+ return ResponseEntity
+ .status(HttpStatus.CREATED)
+ .body(compilationAdminService.createCompilation(newCompilationDto));
+ }
+
+ @DeleteMapping("/{compId}")
+ public ResponseEntity deleteCompilation(
+ @PathVariable Long compId
+ ) {
+ log.info("Calling the DELETE request to /admin/endpoint/{compId}");
+ compilationAdminService.deleteCompilation(compId);
+ return ResponseEntity
+ .status(HttpStatus.NO_CONTENT)
+ .body("Compilation deleted: " + compId);
+ }
+
+ @PatchMapping("/{compId}")
+ public ResponseEntity patchCompilation(
+ @PathVariable Long compId,
+ @RequestBody @Valid UpdateCompilationDto updateCompilationDto
+ ) {
+ log.info("Calling the PATCH request to /admin/compilations/{compId} endpoint");
+ return ResponseEntity
+ .status(HttpStatus.OK)
+ .body(compilationAdminService.updateCompilation(compId, updateCompilationDto));
+ }
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationPublicController.java b/core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationPublicController.java
new file mode 100644
index 0000000..3021f0a
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationPublicController.java
@@ -0,0 +1,44 @@
+package ru.practicum.compilation.controller;
+
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.compilation.dto.CompilationDto;
+import ru.practicum.compilation.service.CompilationPublicService;
+
+import java.util.List;
+
+@RestController
+@Validated
+@RequestMapping("/compilations")
+@RequiredArgsConstructor
+@Slf4j
+public class CompilationPublicController {
+
+ private final CompilationPublicService compilationPublicService;
+
+ @GetMapping
+ public ResponseEntity> getCompilation(
+ @RequestParam(required = false) Boolean pinned,
+ @RequestParam(defaultValue = "0") @PositiveOrZero int from,
+ @RequestParam(defaultValue = "10") @Positive int size
+ ) {
+ log.info("Calling the GET request to /compilations endpoint");
+ List list = compilationPublicService.readAllCompilations(pinned, from, size);
+ return ResponseEntity.ok(list);
+ }
+
+ @GetMapping("/{compId}")
+ public ResponseEntity getCompilationById(
+ @PathVariable Long compId
+ ) {
+ log.info("Calling the GET request to /compilations/{compId} endpoint");
+ CompilationDto response = compilationPublicService.readCompilationById(compId);
+ return ResponseEntity.ok(response);
+ }
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/compilation/dto/CompilationDto.java b/core/main-service/src/main/java/ru/practicum/compilation/dto/CompilationDto.java
new file mode 100644
index 0000000..8078f28
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/compilation/dto/CompilationDto.java
@@ -0,0 +1,25 @@
+package ru.practicum.compilation.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.event.dto.EventShortDto;
+
+import java.util.List;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class CompilationDto {
+
+ private List events;
+
+ private Long id;
+
+ private Boolean pinned;
+
+ private String title;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/compilation/dto/NewCompilationDto.java b/core/main-service/src/main/java/ru/practicum/compilation/dto/NewCompilationDto.java
new file mode 100644
index 0000000..3dfbc8b
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/compilation/dto/NewCompilationDto.java
@@ -0,0 +1,29 @@
+package ru.practicum.compilation.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class NewCompilationDto {
+
+ @Builder.Default
+ private Set events = new HashSet<>();
+
+ @Builder.Default
+ private Boolean pinned = false;
+
+ @NotBlank
+ @Size(min = 1, max = 50)
+ private String title;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/compilation/dto/UpdateCompilationDto.java b/core/main-service/src/main/java/ru/practicum/compilation/dto/UpdateCompilationDto.java
new file mode 100644
index 0000000..b655cf2
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/compilation/dto/UpdateCompilationDto.java
@@ -0,0 +1,30 @@
+package ru.practicum.compilation.dto;
+
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.validation.AtLeastOneNotNull;
+import ru.practicum.validation.NotBlankButNullAllowed;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@AtLeastOneNotNull(fields = {"events", "pinned", "title"}, message = "DTO has only null data fields")
+public class UpdateCompilationDto {
+
+ @Builder.Default
+ private Set events = new HashSet<>();
+
+ private Boolean pinned;
+
+ @NotBlankButNullAllowed
+ @Size(min = 1, max = 50)
+ private String title;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/compilation/mapper/CompilationMapper.java b/core/main-service/src/main/java/ru/practicum/compilation/mapper/CompilationMapper.java
new file mode 100644
index 0000000..9635192
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/compilation/mapper/CompilationMapper.java
@@ -0,0 +1,33 @@
+package ru.practicum.compilation.mapper;
+
+import ru.practicum.compilation.dto.CompilationDto;
+import ru.practicum.compilation.model.Compilation;
+import ru.practicum.event.dto.EventShortDto;
+import ru.practicum.event.mapper.EventMapper;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CompilationMapper {
+
+ public static CompilationDto toCompilationDto(Compilation compilation) {
+ List eventShortDtoList = compilation.getEvents().stream()
+ .map(event ->
+ EventMapper.toEventShortDto(event, 0L, 0L)
+ ).collect(Collectors.toList());
+
+ return CompilationDto.builder()
+ .id(compilation.getId())
+ .pinned(compilation.getPinned())
+ .title(compilation.getTitle())
+ .events(eventShortDtoList)
+ .build();
+ }
+
+ public static List toCompilationDtoList(List compilations) {
+ return compilations.stream()
+ .map(CompilationMapper::toCompilationDto)
+ .collect(Collectors.toList());
+ }
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/compilation/model/Compilation.java b/core/main-service/src/main/java/ru/practicum/compilation/model/Compilation.java
new file mode 100644
index 0000000..e4e4004
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/compilation/model/Compilation.java
@@ -0,0 +1,49 @@
+package ru.practicum.compilation.model;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.Size;
+import lombok.*;
+import ru.practicum.event.model.Event;
+
+import java.util.Set;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "compilations")
+public class Compilation {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @Column(name = "pinned")
+ private Boolean pinned;
+
+ @Column(name = "title")
+ @Size(min = 1, max = 50)
+ @NotEmpty
+ private String title;
+
+ @ManyToMany
+ @JoinTable(name = "compilations_events",
+ joinColumns = @JoinColumn(name = "compilations_id"),
+ inverseJoinColumns = @JoinColumn(name = "events_id"))
+ private Set events;
+
+ @Override
+ public String toString() {
+ return "Compilations{" +
+ "id=" + id +
+ ", pinned=" + pinned +
+ ", title='" + title + '\'' +
+ ", events=" + events +
+ '}';
+ }
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/compilation/repository/CompilationRepository.java b/core/main-service/src/main/java/ru/practicum/compilation/repository/CompilationRepository.java
new file mode 100644
index 0000000..2d9e074
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/compilation/repository/CompilationRepository.java
@@ -0,0 +1,19 @@
+package ru.practicum.compilation.repository;
+
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import ru.practicum.compilation.model.Compilation;
+
+import java.util.List;
+
+public interface CompilationRepository extends JpaRepository {
+
+ @Query("SELECT c " +
+ "FROM Compilation c " +
+ "WHERE c.pinned = ?1")
+ List findAllByPinned(Boolean pinned, Pageable pageable);
+
+ boolean existsByTitle(String title);
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminService.java b/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminService.java
new file mode 100644
index 0000000..ad82c21
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminService.java
@@ -0,0 +1,15 @@
+package ru.practicum.compilation.service;
+
+import ru.practicum.compilation.dto.CompilationDto;
+import ru.practicum.compilation.dto.NewCompilationDto;
+import ru.practicum.compilation.dto.UpdateCompilationDto;
+
+public interface CompilationAdminService {
+
+ CompilationDto createCompilation(NewCompilationDto request);
+
+ void deleteCompilation(Long compId);
+
+ CompilationDto updateCompilation(Long compId, UpdateCompilationDto updateCompilationDto);
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminServiceImpl.java b/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminServiceImpl.java
new file mode 100644
index 0000000..69d6ed6
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminServiceImpl.java
@@ -0,0 +1,98 @@
+package ru.practicum.compilation.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import ru.practicum.compilation.dto.CompilationDto;
+import ru.practicum.compilation.dto.NewCompilationDto;
+import ru.practicum.compilation.dto.UpdateCompilationDto;
+import ru.practicum.compilation.mapper.CompilationMapper;
+import ru.practicum.compilation.model.Compilation;
+import ru.practicum.compilation.repository.CompilationRepository;
+import ru.practicum.event.model.Event;
+import ru.practicum.event.repository.EventRepository;
+import ru.practicum.exception.NotFoundException;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Service
+@Transactional
+@RequiredArgsConstructor
+@Slf4j
+public class CompilationAdminServiceImpl implements CompilationAdminService {
+
+ private final CompilationRepository compilationRepository;
+ private final EventRepository eventRepository;
+
+ @Override
+ public CompilationDto createCompilation(NewCompilationDto request) {
+ log.info("createCompilation - invoked. Title: '{}', pinned: {}, eventCount: {}",
+ request.getTitle(), request.getPinned(),
+ (request.getEvents() != null ? request.getEvents().size() : 0));
+
+ Set events = (request.getEvents() != null && !request.getEvents().isEmpty())
+ ? new HashSet<>(eventRepository.findAllById(request.getEvents()))
+ : new HashSet<>();
+
+ Compilation compilation = Compilation.builder()
+ .pinned(request.getPinned() != null && request.getPinned())
+ .title(request.getTitle())
+ .events(events)
+ .build();
+
+ Compilation savedCompilation = compilationRepository.save(compilation);
+ log.info("Result: compilation created with ID: {}, title: '{}'",
+ savedCompilation.getId(), savedCompilation.getTitle());
+ return CompilationMapper.toCompilationDto(savedCompilation);
+ }
+
+ @Override
+ public void deleteCompilation(Long compId) {
+ log.info("deleteCompilation - invoked for compilation ID: {}", compId);
+
+ if (!compilationRepository.existsById(compId)) {
+ log.error("Compilation with ID {} not found", compId);
+ throw new NotFoundException("Compilation not found");
+ }
+
+ compilationRepository.deleteById(compId);
+ log.info("Result: compilation with ID {} deleted successfully", compId);
+ }
+
+ @Override
+ public CompilationDto updateCompilation(Long compId, UpdateCompilationDto updateCompilationDto) {
+ log.info("updateCompilation - invoked for ID: {}. Changes - title: {}, pinned: {}, eventCount: {}",
+ compId,
+ updateCompilationDto.getTitle(),
+ updateCompilationDto.getPinned(),
+ (updateCompilationDto.getEvents() != null ? updateCompilationDto.getEvents().size() : "unchanged"));
+
+ Compilation compilation = compilationRepository.findById(compId)
+ .orElseThrow(() -> {
+ log.error("Compilation with ID {} not found", compId);
+ return new NotFoundException("Compilation not found");
+ });
+
+ if (updateCompilationDto.getTitle() != null) {
+ compilation.setTitle(updateCompilationDto.getTitle());
+ }
+ if (updateCompilationDto.getPinned() != null) {
+ compilation.setPinned(updateCompilationDto.getPinned());
+ }
+ if (updateCompilationDto.getEvents() != null && !updateCompilationDto.getEvents().isEmpty()) {
+ HashSet events = new HashSet<>(eventRepository.findAllById(updateCompilationDto.getEvents()));
+ compilation.setEvents(events);
+ }
+
+ Compilation updatedCompilation = compilationRepository.save(compilation);
+ log.info("Result: compilation ID {} updated successfully. New title: '{}', pinned: {}, eventCount: {}",
+ updatedCompilation.getId(),
+ updatedCompilation.getTitle(),
+ updatedCompilation.getPinned(),
+ updatedCompilation.getEvents().size());
+
+ return CompilationMapper.toCompilationDto(updatedCompilation);
+ }
+}
diff --git a/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicService.java b/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicService.java
new file mode 100644
index 0000000..ab3283e
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicService.java
@@ -0,0 +1,13 @@
+package ru.practicum.compilation.service;
+
+import ru.practicum.compilation.dto.CompilationDto;
+
+import java.util.List;
+
+public interface CompilationPublicService {
+
+ CompilationDto readCompilationById(Long compId);
+
+ List readAllCompilations(Boolean pinned, int from, int size);
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicServiceImpl.java b/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicServiceImpl.java
new file mode 100644
index 0000000..f4ef0e0
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicServiceImpl.java
@@ -0,0 +1,56 @@
+package ru.practicum.compilation.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+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.compilation.dto.CompilationDto;
+import ru.practicum.compilation.mapper.CompilationMapper;
+import ru.practicum.compilation.model.Compilation;
+import ru.practicum.compilation.repository.CompilationRepository;
+import ru.practicum.exception.NotFoundException;
+
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class CompilationPublicServiceImpl implements CompilationPublicService {
+
+ private final CompilationRepository compilationRepository;
+
+ @Override
+ public CompilationDto readCompilationById(Long compId) {
+ log.info("readCompilationById - invoked for compilation ID: {}", compId);
+
+ Compilation compilation = compilationRepository.findById(compId)
+ .orElseThrow(() -> {
+ log.error("Compilation with ID {} not found", compId);
+ return new NotFoundException("Compilation not found");
+ });
+
+ log.info("Result: compilation ID {} retrieved successfully (title: '{}')",
+ compId, compilation.getTitle());
+
+ return CompilationMapper.toCompilationDto(compilation);
+ }
+
+ @Override
+ public List readAllCompilations(Boolean pinned, int from, int size) {
+ log.info("readAllCompilations - invoked. Pinned: {}, from: {}, size: {}",
+ pinned, from, size);
+
+ Pageable pageable = PageRequest.of(from, size, Sort.Direction.ASC, "id");
+ List compilations = (pinned == null)
+ ? compilationRepository.findAll(pageable).getContent()
+ : compilationRepository.findAllByPinned(pinned, pageable);
+
+ int resultSize = compilations.size();
+ log.info("Result: retrieved {} compilations (pinned={}, from={}, size={})",
+ resultSize, pinned, from, size);
+
+ return CompilationMapper.toCompilationDtoList(compilations);
+ }
+}
diff --git a/core/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java b/core/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java
new file mode 100644
index 0000000..6097494
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java
@@ -0,0 +1,66 @@
+package ru.practicum.event.controller;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.event.dto.EventAdminParams;
+import ru.practicum.event.dto.EventFullDto;
+import ru.practicum.event.dto.State;
+import ru.practicum.event.dto.UpdateEventDto;
+import ru.practicum.event.service.EventAdminService;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.List;
+
+@RestController
+@RequestMapping("/admin/events")
+@RequiredArgsConstructor
+@Slf4j
+@Validated
+public class EventAdminController {
+
+ private final EventAdminService eventAdminService;
+
+ // Поиск событий
+ @GetMapping
+ Collection getAllEventsByParams(
+ @RequestParam(required = false) List users,
+ @RequestParam(required = false) List states,
+ @RequestParam(required = false) List categories,
+ @RequestParam(required = false) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart,
+ @RequestParam(required = false) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd,
+ @RequestParam(defaultValue = "0") @PositiveOrZero Long from,
+ @RequestParam(defaultValue = "10") @Positive Long size
+ ) {
+ EventAdminParams params = EventAdminParams.builder()
+ .users(users)
+ .states(states)
+ .categories(categories)
+ .rangeStart(rangeStart)
+ .rangeEnd(rangeEnd)
+ .from(from)
+ .size(size)
+ .build();
+
+ log.info("Calling to endpoint /admin/events GetMapping for params: " + params.toString());
+ return eventAdminService.getAllEventsByParams(params);
+ }
+
+ // Редактирование данных события и его статуса (отклонение/публикация).
+ @PatchMapping("/{eventId}")
+ EventFullDto updateEventByAdmin(
+ @PathVariable Long eventId,
+ @RequestBody @Valid UpdateEventDto updateEventDto
+ ) {
+ log.info("Calling to endpoint /admin/events/{eventId} PatchMapping for eventId: " + eventId + "."
+ + " UpdateEvent: " + updateEventDto.toString());
+ return eventAdminService.updateEventByAdmin(eventId, updateEventDto);
+ }
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java b/core/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java
new file mode 100644
index 0000000..ac70da1
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java
@@ -0,0 +1,74 @@
+package ru.practicum.event.controller;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Positive;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.event.dto.EventFullDto;
+import ru.practicum.event.dto.EventShortDto;
+import ru.practicum.event.dto.NewEventDto;
+import ru.practicum.event.dto.UpdateEventDto;
+import ru.practicum.event.service.EventPrivateService;
+
+import java.util.Collection;
+
+@RestController
+@RequestMapping("/users/{userId}/events")
+@RequiredArgsConstructor
+@Slf4j
+@Validated
+public class EventPrivateController {
+
+ private final EventPrivateService eventPrivateService;
+
+ // Добавление нового события
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED)
+ EventFullDto addNewEventByUser(
+ @PathVariable @Positive Long userId,
+ @Valid @RequestBody NewEventDto newEventDto
+ ) {
+ log.info("Calling to endpoint /users/{userId}/events PostMapping for userId: " + userId);
+ return eventPrivateService.addEvent(userId, newEventDto);
+ }
+
+ // Получение событий, добавленных текущим пользователем
+ @GetMapping
+ Collection getAllEventsByUserId(
+ @PathVariable @Positive Long userId,
+ @RequestParam(defaultValue = "0") Long from,
+ @RequestParam(defaultValue = "10") Long size
+ ) {
+ log.info("Calling to endpoint /users/{userId}/events GetMapping for userId: " + userId);
+ return eventPrivateService.getEventsByUserId(userId, from, size);
+ }
+
+ // Получение полной информации о событии добавленном текущим пользователем
+ @GetMapping("/{eventId}")
+ EventFullDto getEventByUserIdAndEventId(
+ @PathVariable @Positive Long userId,
+ @PathVariable @Positive Long eventId
+ ) {
+ log.info("Calling to endpoint /users/{userId}/events/{eventId} GetMapping for userId: "
+ + userId + " and eventId: " + eventId);
+ return eventPrivateService.getEventByUserIdAndEventId(userId, eventId);
+ }
+
+ // Изменение события добавленного текущим пользователем
+ @PatchMapping("/{eventId}")
+ EventFullDto updateEventByUserIdAndEventId(
+ @PathVariable @Positive Long userId,
+ @PathVariable @Positive Long eventId,
+ @Valid @RequestBody UpdateEventDto updateEventDto
+ ) {
+ log.info("Calling to endpoint /users/{userId}/events/{eventId} PatchMapping for userId: " + userId
+ + " and eventId: " + eventId + "."
+ + "Information by eventDto: " + updateEventDto.toString());
+ return eventPrivateService.updateEventByUserIdAndEventId(userId, eventId, updateEventDto);
+ }
+
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java b/core/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java
new file mode 100644
index 0000000..7a6eeb9
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java
@@ -0,0 +1,65 @@
+package ru.practicum.event.controller;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.constraints.Positive;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import ru.practicum.event.dto.EventFullDto;
+import ru.practicum.event.dto.EventParams;
+import ru.practicum.event.dto.EventShortDto;
+import ru.practicum.event.dto.EventSort;
+import ru.practicum.event.service.EventPublicService;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/events")
+@Slf4j
+public class EventPublicController {
+
+ private final EventPublicService eventPublicService;
+
+ // Получение событий с возможностью фильтрации
+ @GetMapping
+ List getAllEventsByParams(
+ @RequestParam(required = false) String text,
+ @RequestParam(required = false) List categories,
+ @RequestParam(required = false) Boolean paid,
+ @RequestParam(required = false) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart,
+ @RequestParam(required = false) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd,
+ @RequestParam(defaultValue = "false") Boolean onlyAvailable,
+ @RequestParam(defaultValue = "EVENT_DATE") EventSort eventSort,
+ @RequestParam(defaultValue = "0") Long from,
+ @RequestParam(defaultValue = "10") Long size,
+ HttpServletRequest request
+ ) {
+ EventParams params = EventParams.builder()
+ .text(text)
+ .categories(categories)
+ .paid(paid)
+ .rangeStart(rangeStart)
+ .rangeEnd(rangeEnd)
+ .onlyAvailable(onlyAvailable)
+ .eventSort(eventSort)
+ .from(from)
+ .size(size)
+ .build();
+ log.info("Calling to endpoint /events GetMapping for params: " + params.toString());
+ return eventPublicService.getAllEventsByParams(params, request);
+ }
+
+ // Получение подробной информации об опубликованном событии по его идентификатору
+ @GetMapping("/{id}")
+ EventFullDto getInformationAboutEventByEventId(
+ @PathVariable @Positive Long id,
+ HttpServletRequest request
+ ) {
+ log.info("Calling to endpoint /events/{id} GetMapping for eventId: " + id);
+ return eventPublicService.getEventById(id, request);
+ }
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventAdminParams.java b/core/main-service/src/main/java/ru/practicum/event/dto/EventAdminParams.java
new file mode 100644
index 0000000..d09fe38
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/EventAdminParams.java
@@ -0,0 +1,31 @@
+package ru.practicum.event.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class EventAdminParams {
+
+ private List users;
+
+ private List states;
+
+ private List categories;
+
+ private LocalDateTime rangeStart;
+
+ private LocalDateTime rangeEnd;
+
+ private Long from;
+
+ private Long size;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventCommentDto.java b/core/main-service/src/main/java/ru/practicum/event/dto/EventCommentDto.java
new file mode 100644
index 0000000..d8aeb73
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/EventCommentDto.java
@@ -0,0 +1,17 @@
+package ru.practicum.event.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class EventCommentDto {
+
+ private Long id;
+
+ private String title;
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java b/core/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java
new file mode 100644
index 0000000..e892531
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java
@@ -0,0 +1,50 @@
+package ru.practicum.event.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.category.dto.CategoryDto;
+import ru.practicum.comment.dto.CommentShortDto;
+import ru.practicum.user.dto.UserShortDto;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class EventFullDto {
+
+ private Long id;
+
+ private UserShortDto initiator;
+ private CategoryDto category;
+
+ private String title;
+ private String annotation;
+ private String description;
+
+ private State state;
+
+ private LocationDto location;
+
+ private Long participantLimit;
+ private Boolean requestModeration;
+ private Boolean paid;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime eventDate;
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime publishedOn;
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime createdOn;
+
+ private Long confirmedRequests;
+ private Long views;
+
+ private List comments;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventParams.java b/core/main-service/src/main/java/ru/practicum/event/dto/EventParams.java
new file mode 100644
index 0000000..411ce9c
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/EventParams.java
@@ -0,0 +1,35 @@
+package ru.practicum.event.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class EventParams {
+
+ private String text;
+
+ private List categories;
+
+ private Boolean paid;
+
+ private LocalDateTime rangeStart;
+
+ private LocalDateTime rangeEnd;
+
+ private Boolean onlyAvailable;
+
+ private EventSort eventSort;
+
+ private Long from;
+
+ private Long size;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventRequestStatusUpdateRequest.java b/core/main-service/src/main/java/ru/practicum/event/dto/EventRequestStatusUpdateRequest.java
new file mode 100644
index 0000000..3fbc1a3
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/EventRequestStatusUpdateRequest.java
@@ -0,0 +1,14 @@
+package ru.practicum.event.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class EventRequestStatusUpdateRequest {
+
+ private List requestIds;
+
+ private State state;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventRequestStatusUpdateResult.java b/core/main-service/src/main/java/ru/practicum/event/dto/EventRequestStatusUpdateResult.java
new file mode 100644
index 0000000..4f5c254
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/EventRequestStatusUpdateResult.java
@@ -0,0 +1,15 @@
+package ru.practicum.event.dto;
+
+import lombok.Data;
+import ru.practicum.request.dto.ParticipationRequestDto;
+
+import java.util.List;
+
+@Data
+public class EventRequestStatusUpdateResult {
+
+ private List confirmedRequests;
+
+ private List rejectedRequests;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java b/core/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java
new file mode 100644
index 0000000..8b3a31a
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java
@@ -0,0 +1,35 @@
+package ru.practicum.event.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.category.dto.CategoryDto;
+import ru.practicum.user.dto.UserShortDto;
+
+import java.time.LocalDateTime;
+
+@AllArgsConstructor
+@Builder
+@Data
+@NoArgsConstructor
+public class EventShortDto {
+
+ private Long id;
+
+ private UserShortDto initiator;
+ private CategoryDto category;
+
+ private String title;
+ private String annotation;
+
+ private Boolean paid;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime eventDate;
+
+ private Long confirmedRequests;
+ private Long views;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventSort.java b/core/main-service/src/main/java/ru/practicum/event/dto/EventSort.java
new file mode 100644
index 0000000..56e6dc6
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/EventSort.java
@@ -0,0 +1,5 @@
+package ru.practicum.event.dto;
+
+public enum EventSort {
+ EVENT_DATE, VIEWS
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/LocationDto.java b/core/main-service/src/main/java/ru/practicum/event/dto/LocationDto.java
new file mode 100644
index 0000000..687d4ee
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/LocationDto.java
@@ -0,0 +1,17 @@
+package ru.practicum.event.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class LocationDto {
+
+ private Float lat;
+ private Float lon;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java b/core/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java
new file mode 100644
index 0000000..09e385d
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java
@@ -0,0 +1,42 @@
+package ru.practicum.event.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.validation.constraints.*;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class NewEventDto {
+
+ @NotNull
+ @Positive
+ private Long category;
+
+ @NotBlank
+ @Size(min = 3, max = 120)
+ private String title;
+
+ @NotBlank
+ @Size(min = 20, max = 2000)
+ private String annotation;
+
+ @NotBlank
+ @Size(min = 20, max = 7000)
+ private String description;
+
+ private LocationDto location;
+
+ private Boolean requestModeration = true;
+
+ private Boolean paid = false;
+
+ @PositiveOrZero
+ private Long participantLimit = 0L;
+
+ @NotNull
+ @Future(message = "Event should be in future")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime eventDate;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/State.java b/core/main-service/src/main/java/ru/practicum/event/dto/State.java
new file mode 100644
index 0000000..8d04843
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/State.java
@@ -0,0 +1,5 @@
+package ru.practicum.event.dto;
+
+public enum State {
+ PENDING, PUBLISHED, CANCELED
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/StateAction.java b/core/main-service/src/main/java/ru/practicum/event/dto/StateAction.java
new file mode 100644
index 0000000..b6ee34d
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/StateAction.java
@@ -0,0 +1,8 @@
+package ru.practicum.event.dto;
+
+public enum StateAction {
+ SEND_TO_REVIEW,
+ CANCEL_REVIEW,
+ PUBLISH_EVENT,
+ REJECT_EVENT
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/UpdateEventDto.java b/core/main-service/src/main/java/ru/practicum/event/dto/UpdateEventDto.java
new file mode 100644
index 0000000..f73ee20
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/dto/UpdateEventDto.java
@@ -0,0 +1,51 @@
+package ru.practicum.event.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.validation.constraints.Future;
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.PositiveOrZero;
+import jakarta.validation.constraints.Size;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import ru.practicum.validation.AtLeastOneNotNull;
+
+import java.time.LocalDateTime;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@AtLeastOneNotNull(fields = {"category", "title", "annotation", "description", "location", "paid",
+ "participantLimit", "requestModeration", "stateAction", "eventDate"})
+public class UpdateEventDto {
+
+ @Positive
+ private Long category;
+
+ @Size(min = 3, max = 120)
+ private String title;
+
+ @Size(min = 20, max = 2000)
+ private String annotation;
+
+ @Size(min = 20, max = 7000)
+ private String description;
+
+ private LocationDto location;
+
+ private Boolean paid;
+
+ @PositiveOrZero
+ private Long participantLimit;
+
+ private Boolean requestModeration;
+
+ private StateAction stateAction;
+
+ @Future(message = "Event should be in future")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime eventDate;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java b/core/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java
new file mode 100644
index 0000000..c57915b
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java
@@ -0,0 +1,87 @@
+package ru.practicum.event.mapper;
+
+import ru.practicum.category.mapper.CategoryMapper;
+import ru.practicum.category.model.Category;
+import ru.practicum.event.dto.*;
+import ru.practicum.event.model.Event;
+import ru.practicum.user.mapper.UserMapper;
+import ru.practicum.user.model.User;
+
+import java.time.LocalDateTime;
+
+public class EventMapper {
+
+ public static Event toEvent(
+ NewEventDto newEventDto,
+ User initiator,
+ Category category
+ ) {
+ return Event.builder()
+ .initiator(initiator)
+ .category(category)
+ .title(newEventDto.getTitle())
+ .annotation(newEventDto.getAnnotation())
+ .description(newEventDto.getDescription())
+ .state(State.PENDING)
+ .location(LocationMapper.toEntity(newEventDto.getLocation()))
+ .participantLimit(newEventDto.getParticipantLimit())
+ .requestModeration(newEventDto.getRequestModeration())
+ .paid(newEventDto.getPaid())
+ .eventDate(newEventDto.getEventDate())
+ .createdOn(LocalDateTime.now())
+ .build();
+ }
+
+
+ public static EventFullDto toEventFullDto(
+ Event event,
+ Long confirmedRequests,
+ Long views
+ ) {
+ if (confirmedRequests == null) confirmedRequests = 0L;
+ return EventFullDto.builder()
+ .id(event.getId())
+ .initiator(UserMapper.toUserShortDto(event.getInitiator()))
+ .category(CategoryMapper.toCategoryDto(event.getCategory()))
+ .title(event.getTitle())
+ .annotation(event.getAnnotation())
+ .description(event.getDescription())
+ .state(event.getState())
+ .location(LocationMapper.toDto(event.getLocation()))
+ .participantLimit(event.getParticipantLimit())
+ .requestModeration(event.getRequestModeration())
+ .paid(event.getPaid())
+ .eventDate(event.getEventDate())
+ .publishedOn(event.getPublishedOn())
+ .createdOn(event.getCreatedOn())
+ .confirmedRequests(confirmedRequests)
+ .views(views)
+ .build();
+ }
+
+ public static EventShortDto toEventShortDto(
+ Event event,
+ Long confirmedRequests,
+ Long views
+ ) {
+ if (confirmedRequests == null) confirmedRequests = 0L;
+ return EventShortDto.builder()
+ .id(event.getId())
+ .initiator(UserMapper.toUserShortDto(event.getInitiator()))
+ .category(CategoryMapper.toCategoryDto(event.getCategory()))
+ .title(event.getTitle())
+ .annotation(event.getAnnotation())
+ .paid(event.getPaid())
+ .eventDate(event.getEventDate())
+ .confirmedRequests(confirmedRequests)
+ .views(views)
+ .build();
+ }
+
+ public static EventCommentDto toEventComment(Event event) {
+ return EventCommentDto.builder()
+ .id(event.getId())
+ .title(event.getTitle())
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/mapper/LocationMapper.java b/core/main-service/src/main/java/ru/practicum/event/mapper/LocationMapper.java
new file mode 100644
index 0000000..7d4f61a
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/mapper/LocationMapper.java
@@ -0,0 +1,24 @@
+package ru.practicum.event.mapper;
+
+import ru.practicum.event.dto.LocationDto;
+import ru.practicum.event.model.Location;
+
+public class LocationMapper {
+
+ public static Location toEntity(LocationDto dto) {
+ if (dto == null) return null;
+ return Location.builder()
+ .lat(dto.getLat())
+ .lon(dto.getLon())
+ .build();
+ }
+
+ public static LocationDto toDto(Location location) {
+ if (location == null) return null;
+ return LocationDto.builder()
+ .lat(location.getLat())
+ .lon(location.getLon())
+ .build();
+ }
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/model/Event.java b/core/main-service/src/main/java/ru/practicum/event/model/Event.java
new file mode 100644
index 0000000..fb8ea7c
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/model/Event.java
@@ -0,0 +1,76 @@
+package ru.practicum.event.model;
+
+import jakarta.persistence.*;
+import lombok.*;
+import ru.practicum.category.model.Category;
+import ru.practicum.comment.model.Comment;
+import ru.practicum.event.dto.State;
+import ru.practicum.request.model.Request;
+import ru.practicum.user.model.User;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "events")
+public class Event {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @ManyToOne
+ @JoinColumn(name = "initiator", nullable = false)
+ private User initiator;
+
+ @ManyToOne
+ @JoinColumn(name = "categories_id", nullable = false)
+ private Category category;
+
+ @Column(name = "title", length = 120, nullable = false)
+ private String title;
+
+ @Column(name = "annotation", length = 2000, nullable = false)
+ private String annotation;
+
+ @Column(name = "description", length = 7000, nullable = false)
+ private String description;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "state", length = 20, nullable = false)
+ private State state;
+
+ @Embedded
+ private Location location;
+
+ @Column(name = "participant_limit", nullable = false)
+ private Long participantLimit;
+
+ @Column(name = "request_moderation", nullable = false)
+ private Boolean requestModeration;
+
+ @Column(name = "paid", nullable = false)
+ private Boolean paid;
+
+ @Column(name = "event_date", nullable = false)
+ private LocalDateTime eventDate;
+
+ @Column(name = "published_on")
+ private LocalDateTime publishedOn;
+
+ @Column(name = "created_on", nullable = false)
+ private LocalDateTime createdOn;
+
+ @OneToMany(mappedBy = "event", fetch = FetchType.LAZY)
+ private List requests;
+
+ @OneToMany(mappedBy = "event", fetch = FetchType.LAZY)
+ private List comments;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/model/Location.java b/core/main-service/src/main/java/ru/practicum/event/model/Location.java
new file mode 100644
index 0000000..44cbad3
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/model/Location.java
@@ -0,0 +1,18 @@
+package ru.practicum.event.model;
+
+import jakarta.persistence.Embeddable;
+import lombok.*;
+
+@Getter
+@Setter
+@Embeddable
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class Location {
+
+ private Float lat;
+
+ private Float lon;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/model/View.java b/core/main-service/src/main/java/ru/practicum/event/model/View.java
new file mode 100644
index 0000000..0945701
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/model/View.java
@@ -0,0 +1,27 @@
+package ru.practicum.event.model;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+@Getter
+@Setter
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@Table(name = "views")
+public class View {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @ManyToOne
+ @JoinColumn(name = "event_id")
+ private Event event;
+
+ @Column(name = "ip", length = 15, nullable = false)
+ private String ip;
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java b/core/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java
new file mode 100644
index 0000000..9ccec1d
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java
@@ -0,0 +1,22 @@
+package ru.practicum.event.repository;
+
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import ru.practicum.event.dto.State;
+import ru.practicum.event.model.Event;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface EventRepository extends JpaRepository, JpaSpecificationExecutor {
+
+ Optional findByIdAndInitiatorId(Long eventId, Long initiatorId);
+
+ List findByInitiatorId(Long initiatorId, Pageable pageable);
+
+ Optional findByIdAndState(Long id, State state);
+
+ boolean existsByCategoryId(Long catId);
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/repository/JpaSpecifications.java b/core/main-service/src/main/java/ru/practicum/event/repository/JpaSpecifications.java
new file mode 100644
index 0000000..4ca46d0
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/repository/JpaSpecifications.java
@@ -0,0 +1,78 @@
+package ru.practicum.event.repository;
+
+import jakarta.persistence.criteria.Join;
+import jakarta.persistence.criteria.JoinType;
+import jakarta.persistence.criteria.Predicate;
+import org.springframework.data.jpa.domain.Specification;
+import ru.practicum.event.dto.EventAdminParams;
+import ru.practicum.event.dto.EventParams;
+import ru.practicum.event.model.Event;
+import ru.practicum.request.dto.ParticipationRequestStatus;
+import ru.practicum.request.model.Request;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class JpaSpecifications {
+
+ public static Specification adminFilters(EventAdminParams params) {
+ return (root, query, cb) -> {
+ List predicates = new ArrayList<>();
+
+ if (params.getUsers() != null && !params.getUsers().isEmpty())
+ predicates.add(root.get("initiator").get("id").in(params.getUsers()));
+
+ if (params.getStates() != null && !params.getStates().isEmpty())
+ predicates.add(root.get("state").in(params.getStates()));
+
+ if (params.getCategories() != null && !params.getCategories().isEmpty())
+ predicates.add(root.get("category").get("id").in(params.getCategories()));
+
+ if (params.getRangeStart() != null)
+ predicates.add(cb.greaterThanOrEqualTo(root.get("eventDate"), params.getRangeStart()));
+
+ if (params.getRangeEnd() != null)
+ predicates.add(cb.lessThanOrEqualTo(root.get("eventDate"), params.getRangeEnd()));
+
+ return cb.and(predicates.toArray(new Predicate[0]));
+ };
+ }
+
+ public static Specification publicFilters(EventParams params) {
+ return (root, query, cb) -> {
+ List predicates = new ArrayList<>();
+
+ if (params.getText() != null && !params.getText().isEmpty()) {
+ String searchPattern = "%" + params.getText().toLowerCase() + "%";
+ Predicate annotationPredicate = cb.like(cb.lower(root.get("annotation")), searchPattern);
+ Predicate descriptionPredicate = cb.like(cb.lower(root.get("description")), searchPattern);
+ predicates.add(cb.or(annotationPredicate, descriptionPredicate));
+ }
+
+ if (params.getCategories() != null && !params.getCategories().isEmpty())
+ predicates.add(root.get("category").get("id").in(params.getCategories()));
+
+ if (params.getPaid() != null) predicates.add(cb.equal(root.get("paid"), params.getPaid()));
+
+ if (params.getRangeStart() != null)
+ predicates.add(cb.greaterThanOrEqualTo(root.get("eventDate"), params.getRangeStart()));
+
+ if (params.getRangeEnd() != null)
+ predicates.add(cb.lessThanOrEqualTo(root.get("eventDate"), params.getRangeEnd()));
+
+ if (params.getOnlyAvailable() == true) {
+ Join requestJoin = root.join("requests", JoinType.LEFT);
+ requestJoin.on(cb.equal(requestJoin.get("status"), ParticipationRequestStatus.CONFIRMED));
+ query.groupBy(root.get("id"));
+
+ Predicate unlimitedPredicate = cb.equal(root.get("participantLimit"), 0);
+ Predicate hasFreeSeatsPredicate = cb.greaterThan(root.get("participantLimit"), cb.count(requestJoin));
+ query.having(cb.or(unlimitedPredicate, hasFreeSeatsPredicate));
+ }
+
+ return cb.and(predicates.toArray(new Predicate[0]));
+ };
+ }
+
+
+}
\ No newline at end of file
diff --git a/core/main-service/src/main/java/ru/practicum/event/repository/ViewRepository.java b/core/main-service/src/main/java/ru/practicum/event/repository/ViewRepository.java
new file mode 100644
index 0000000..f94c538
--- /dev/null
+++ b/core/main-service/src/main/java/ru/practicum/event/repository/ViewRepository.java
@@ -0,0 +1,26 @@
+package ru.practicum.event.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import ru.practicum.event.model.View;
+
+import java.util.List;
+
+public interface ViewRepository extends JpaRepository {
+
+ long countByEventId(Long eventId);
+
+ @Query("""
+ SELECT v.event.id, count(v)
+ FROM View v
+ WHERE v.event.id IN :eventIds
+ GROUP BY v.event.id
+ """)
+ List