From 3b34ad896b2b2a8d8356015b2aabe56cc0fe0d78 Mon Sep 17 00:00:00 2001 From: pavelrk97 Date: Thu, 11 Sep 2025 14:26:29 +0300 Subject: [PATCH 1/7] =?UTF-8?q?=D0=BA=D0=B0=D1=82=D0=B5=D0=B3=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ewm-main-svc/pom.xml | 89 ++++++++++++++----- .../ewm/category/CategoryRepository.java | 10 +++ .../controller/AdminCategoryController.java | 41 +++++++++ .../controller/PublicCategoryController.java | 33 +++++++ .../ewm/category/dto/CategoryDto.java | 14 +++ .../ewm/category/dto/NewCategoryDto.java | 17 ++++ .../ewm/category/dto/UpdateCategoryDto.java | 15 ++++ .../ewm/category/mapper/CategoryMapper.java | 21 +++++ .../ewm/category/model/Category.java | 21 +++++ .../ewm/category/service/CategoryService.java | 20 +++++ .../category/service/CategoryServiceImpl.java | 84 +++++++++++++++++ 11 files changed, 343 insertions(+), 22 deletions(-) create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/category/CategoryRepository.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/category/controller/AdminCategoryController.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/category/controller/PublicCategoryController.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/category/dto/CategoryDto.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/category/dto/NewCategoryDto.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/category/dto/UpdateCategoryDto.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/category/mapper/CategoryMapper.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/category/model/Category.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryService.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryServiceImpl.java diff --git a/ewm-main-svc/pom.xml b/ewm-main-svc/pom.xml index d400a70..84e5682 100644 --- a/ewm-main-svc/pom.xml +++ b/ewm-main-svc/pom.xml @@ -10,6 +10,7 @@ ewm-main-svc + 0.0.1-SNAPSHOT 22 @@ -19,32 +20,76 @@ - - org.apache.httpcomponents.client5 - httpclient5 - + + ru.practicum + stat-client + 0.0.1-SNAPSHOT + compile + - - org.springframework.boot - spring-boot-starter-actuator - + + org.postgresql + postgresql + runtime + - - org.springframework.boot - spring-boot-starter-web - + + com.h2database + h2 + runtime + - - org.springframework.boot - spring-boot-configuration-processor - true - + + org.apache.httpcomponents.client5 + httpclient5 + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + - - org.projectlombok - lombok - true - + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.projectlombok + lombok + true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/CategoryRepository.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/CategoryRepository.java new file mode 100644 index 0000000..3d94a9f --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/CategoryRepository.java @@ -0,0 +1,10 @@ +package ru.practicum.ewm.category; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.ewm.category.model.Category; + +public interface CategoryRepository extends JpaRepository { + + boolean existsByName(String name); + +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/controller/AdminCategoryController.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/controller/AdminCategoryController.java new file mode 100644 index 0000000..1a9228f --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/controller/AdminCategoryController.java @@ -0,0 +1,41 @@ +package ru.practicum.ewm.category.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.category.dto.CategoryDto; +import ru.practicum.ewm.category.dto.NewCategoryDto; +import ru.practicum.ewm.category.dto.UpdateCategoryDto; +import ru.practicum.ewm.category.service.CategoryService; + +@Slf4j +@RestController +@RequestMapping("/admin/categories") +@RequiredArgsConstructor +public class AdminCategoryController { + + private final CategoryService service; + + @ResponseStatus(HttpStatus.CREATED) + @PostMapping + public CategoryDto create(@RequestBody @Valid NewCategoryDto newCategoryDto) { + log.info("POST запрос на создание категории: {}", newCategoryDto); + return service.create(newCategoryDto); + } + + @PatchMapping("/{catId}") + public CategoryDto update(@PathVariable Long catId, + @RequestBody @Valid UpdateCategoryDto updateCategoryDto) { + log.info("PATCH запрос на обновление категории c id: {}", catId); + return service.update(catId, updateCategoryDto); + } + + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping("/{catId}") + public void delete(@PathVariable Long catId) { + log.info("DELETE запрос на удаление категории с id: {}", catId); + service.delete(catId); + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/controller/PublicCategoryController.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/controller/PublicCategoryController.java new file mode 100644 index 0000000..ea4b50f --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/controller/PublicCategoryController.java @@ -0,0 +1,33 @@ +package ru.practicum.ewm.category.controller; + +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.category.dto.CategoryDto; +import ru.practicum.ewm.category.service.CategoryService; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/categories") +@RequiredArgsConstructor +public class PublicCategoryController { + + private final CategoryService service; + + @GetMapping("/{catId}") + public CategoryDto getCategoryById(@PathVariable Long catId) { + log.info("GET запрос на получение категории c id: {}", catId); + return service.getCategoryById(catId); + } + + @GetMapping + public List getAllCategories(@RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { + log.info("GET запрос на получение списка всех категорий."); + return service.getAllCategories(from, size); + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/dto/CategoryDto.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/dto/CategoryDto.java new file mode 100644 index 0000000..f237175 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/dto/CategoryDto.java @@ -0,0 +1,14 @@ +package ru.practicum.ewm.category.dto; + +import lombok.*; +import lombok.experimental.FieldDefaults; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CategoryDto { + Long id; + String name; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/dto/NewCategoryDto.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/dto/NewCategoryDto.java new file mode 100644 index 0000000..0a2dd3c --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/dto/NewCategoryDto.java @@ -0,0 +1,17 @@ +package ru.practicum.ewm.category.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.*; +import lombok.experimental.FieldDefaults; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class NewCategoryDto { + @NotBlank(message = "Название категории должно быть указано!") + @Size(min = 1, max = 50, message = "Длина name должна составлять от 1 до 50 символов!") + String name; +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/dto/UpdateCategoryDto.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/dto/UpdateCategoryDto.java new file mode 100644 index 0000000..d33e0ad --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/dto/UpdateCategoryDto.java @@ -0,0 +1,15 @@ +package ru.practicum.ewm.category.dto; + +import jakarta.validation.constraints.Size; +import lombok.*; +import lombok.experimental.FieldDefaults; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UpdateCategoryDto { + @Size(min = 1, max = 50, message = "Длина name должна составлять от 1 до 50 символов!") + String name; +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/mapper/CategoryMapper.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/mapper/CategoryMapper.java new file mode 100644 index 0000000..30fbc2c --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/mapper/CategoryMapper.java @@ -0,0 +1,21 @@ +package ru.practicum.ewm.category.mapper; + +import ru.practicum.ewm.category.dto.CategoryDto; +import ru.practicum.ewm.category.dto.NewCategoryDto; +import ru.practicum.ewm.category.model.Category; + + +public class CategoryMapper { + public static Category toNewCategoryFromDto(NewCategoryDto newCategoryDto) { + return Category.builder() + .name(newCategoryDto.getName()) + .build(); + } + + public static CategoryDto toCategoryDto(Category category) { + return CategoryDto.builder() + .id(category.getId()) + .name(category.getName()) + .build(); + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/model/Category.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/model/Category.java new file mode 100644 index 0000000..efabdb7 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/model/Category.java @@ -0,0 +1,21 @@ +package ru.practicum.ewm.category.model; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import jakarta.persistence.*; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "categories") +public class Category { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @Column(nullable = false, unique = true, length = 50) + String name; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryService.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryService.java new file mode 100644 index 0000000..5c52700 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryService.java @@ -0,0 +1,20 @@ +package ru.practicum.ewm.category.service; + +import ru.practicum.ewm.category.dto.CategoryDto; +import ru.practicum.ewm.category.dto.NewCategoryDto; +import ru.practicum.ewm.category.dto.UpdateCategoryDto; + +import java.util.List; + +public interface CategoryService { + CategoryDto create(NewCategoryDto newCategoryDto); + + void delete(Long id); + + CategoryDto update(Long catId, UpdateCategoryDto updateCategoryDto); + + CategoryDto getCategoryById(Long catId); + + List getAllCategories(int from, int size); + +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryServiceImpl.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryServiceImpl.java new file mode 100644 index 0000000..f97dc79 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryServiceImpl.java @@ -0,0 +1,84 @@ +package ru.practicum.ewm.category.service; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +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.ewm.category.CategoryRepository; +import ru.practicum.ewm.category.dto.CategoryDto; +import ru.practicum.ewm.category.dto.NewCategoryDto; +import ru.practicum.ewm.category.dto.UpdateCategoryDto; +import ru.practicum.ewm.category.mapper.CategoryMapper; +import ru.practicum.ewm.category.model.Category; +import ru.practicum.ewm.event.EventRepository; +import ru.practicum.ewm.exception.ConflictException; +import ru.practicum.ewm.exception.DuplicatedDataException; +import ru.practicum.ewm.exception.NotFoundException; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class CategoryServiceImpl implements CategoryService { + + CategoryRepository categoryRepository; + EventRepository eventRepository; + + @Override + public CategoryDto create(NewCategoryDto newCategoryDto) { + log.info("Попытка создать категорию с именем: {}", newCategoryDto.getName()); + if (categoryRepository.existsByName(newCategoryDto.getName())) { + throw new DuplicatedDataException("Название категории уже зарегистрировано: " + newCategoryDto.getName()); + } + Category category = CategoryMapper.toNewCategoryFromDto(newCategoryDto); + Category createdCategory = categoryRepository.save(category); + log.info("Категория успешно создана с ID: {}", createdCategory.getId()); + return CategoryMapper.toCategoryDto(createdCategory); + } + + @Override + public void delete(Long id) { + if (eventRepository.existsByCategoryId(id)) { + throw new ConflictException("Невозможно удалить категорию, так как с ней связаны события."); + } + categoryRepository.deleteById(id); + } + + @Override + public CategoryDto update(Long catId, UpdateCategoryDto updateCategoryDto) { + log.info("Попытка обновить категорию с ID: {}", catId); + Category category = categoryRepository.findById(catId) + .orElseThrow(() -> new NotFoundException("Категория с ID " + catId + " не найдена!")); + + String newName = updateCategoryDto.getName().trim(); + category.setName(newName); + + categoryRepository.save(category); + log.info("Категория с ID {} успешно обновлена.", catId); + return CategoryMapper.toCategoryDto(category); + } + + @Override + @Transactional(readOnly = true) + public CategoryDto getCategoryById(Long catId) { + Category category = categoryRepository.findById(catId) + .orElseThrow(() -> new NotFoundException("Категория с ID " + catId + " не найдена!")); + return CategoryMapper.toCategoryDto(category); + } + + @Override + @Transactional(readOnly = true) + public List getAllCategories(int from, int size) { + Pageable pageable = PageRequest.of(from / size, size); + return categoryRepository.findAll(pageable) + .stream().map(CategoryMapper::toCategoryDto).collect(Collectors.toList()); + } +} \ No newline at end of file From 8ad63bd699434781f201cd18aa251bb579566323 Mon Sep 17 00:00:00 2001 From: pavelrk97 Date: Thu, 11 Sep 2025 14:34:21 +0300 Subject: [PATCH 2/7] =?UTF-8?q?=D0=BA=D0=B0=D1=82=D0=B5=D0=B3=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=B8=D0=B2=D0=B5=D0=BD=D1=82=20=D0=B8=20=D0=BE?= =?UTF-8?q?=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practicum/ewm/event/EventRepository.java | 43 ++ .../controller/AdminEventController.java | 38 ++ .../controller/PrivateEventController.java | 61 +++ .../controller/PublicEventController.java | 42 ++ .../practicum/ewm/event/dto/EventFullDto.java | 40 ++ .../ewm/event/dto/EventSearchParams.java | 34 ++ .../ewm/event/dto/EventShortDto.java | 30 ++ .../practicum/ewm/event/dto/NewEventDto.java | 39 ++ .../event/dto/UpdateEventAdminRequest.java | 38 ++ .../ewm/event/dto/UpdateEventUserRequest.java | 38 ++ .../ewm/event/mapper/EventMapper.java | 58 +++ .../ru/practicum/ewm/event/model/Event.java | 63 +++ .../ewm/event/service/EventService.java | 25 ++ .../ewm/event/service/EventServiceImpl.java | 423 ++++++++++++++++++ .../ru/practicum/ewm/exception/ApiError.java | 15 + .../ewm/exception/ConflictException.java | 11 + .../exception/DuplicatedDataException.java | 11 + .../practicum/ewm/exception/ErrorHandler.java | 97 ++++ .../ewm/exception/ErrorResponse.java | 11 + .../ewm/exception/ForbiddenException.java | 11 + .../exception/IncorrectRequestException.java | 7 + .../ewm/exception/NotFoundException.java | 11 + .../ewm/exception/ValidationException.java | 11 + 23 files changed, 1157 insertions(+) create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/EventRepository.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/controller/AdminEventController.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/controller/PrivateEventController.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/controller/PublicEventController.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/EventFullDto.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/EventSearchParams.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/EventShortDto.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/NewEventDto.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/UpdateEventAdminRequest.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/UpdateEventUserRequest.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/mapper/EventMapper.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/model/Event.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/service/EventService.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/event/service/EventServiceImpl.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ApiError.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ConflictException.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/exception/DuplicatedDataException.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ErrorHandler.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ErrorResponse.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ForbiddenException.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/exception/IncorrectRequestException.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/exception/NotFoundException.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ValidationException.java diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/EventRepository.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/EventRepository.java new file mode 100644 index 0000000..9427c6c --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/EventRepository.java @@ -0,0 +1,43 @@ +package ru.practicum.ewm.event; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import ru.practicum.ewm.enums.EventState; +import ru.practicum.ewm.event.model.Event; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +public interface EventRepository extends JpaRepository { + @Query(""" + SELECT e + FROM Event AS e + WHERE (?1 IS NULL or e.initiator.id IN ?1) + AND (?2 IS NULL or e.state IN ?2) + AND (?3 IS NULL or e.category.id in ?3) + AND (CAST(?4 AS timestamp) IS NULL or e.eventDate >= ?4) + AND (CAST(?5 AS timestamp) IS NULL or e.eventDate < ?5) + """) + List findAllByAdmin( + List users, + List states, + List categories, + LocalDateTime rangeStart, + LocalDateTime rangeEnd, + Pageable pageable + ); + + List findAllByInitiatorId(Long initiatorId, Pageable pageable); + + List findAllByIdIn(List eventIds); + + boolean existsByCategoryId(Long id); + + Optional findByIdAndInitiatorId(Long eventId, Long userId); + + Page findAll(Specification spec, Pageable pageable); + } \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/controller/AdminEventController.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/controller/AdminEventController.java new file mode 100644 index 0000000..cddede9 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/controller/AdminEventController.java @@ -0,0 +1,38 @@ +package ru.practicum.ewm.event.controller; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.event.dto.EventFullDto; +import ru.practicum.ewm.event.dto.EventSearchParams; +import ru.practicum.ewm.event.dto.UpdateEventAdminRequest; +import ru.practicum.ewm.event.service.EventService; + +import java.util.Collection; + +@Slf4j +@RestController +@RequestMapping("/admin/events") +@RequiredArgsConstructor +public class AdminEventController { + private final EventService eventService; + + @GetMapping + public Collection findAllByAdmin(@Valid EventSearchParams searchEventParams, + HttpServletRequest request) { + log.info("GET запрос на получения событий с фильтром"); + Collection events = eventService.findAllByAdmin(searchEventParams, request); + log.info("Отправлен ответ GET /admin/events с телом: {}", events); + return events; + } + + @PatchMapping("/{eventId}") + public EventFullDto update(@PathVariable Long eventId, @RequestBody @Valid UpdateEventAdminRequest eventDto) { + log.info("PATCH запрос /admin/events/{} с телом {}", eventId, eventDto); + EventFullDto event = eventService.updateEventByAdmin(eventId, eventDto); + log.info("Отправлен ответ PATCH /admin/events/{} с телом: {}", eventId, event); + return event; + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/controller/PrivateEventController.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/controller/PrivateEventController.java new file mode 100644 index 0000000..56c630a --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/controller/PrivateEventController.java @@ -0,0 +1,61 @@ +package ru.practicum.ewm.event.controller; + +import jakarta.servlet.http.HttpServletRequest; +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.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.event.dto.EventFullDto; +import ru.practicum.ewm.event.dto.EventShortDto; +import ru.practicum.ewm.event.dto.NewEventDto; +import ru.practicum.ewm.event.dto.UpdateEventUserRequest; +import ru.practicum.ewm.event.service.EventService; + +import java.util.Collection; + +@Slf4j +@RestController +@RequestMapping("/users/{userId}/events") +@RequiredArgsConstructor +public class PrivateEventController { + + private final EventService eventService; + + @ResponseStatus(HttpStatus.CREATED) + @PostMapping + public EventFullDto create(@PathVariable Long userId, @RequestBody @Valid NewEventDto eventDto) { + log.info("POST запрос /users/{}/events с телом {}", userId, eventDto); + EventFullDto event = eventService.create(userId, eventDto); + log.info("Отправлен ответ POST /users/{}/events с телом: {}", userId, event); + return event; + } + + @PatchMapping("/{eventId}") + public EventFullDto update(@PathVariable Long userId, @PathVariable Long eventId, @RequestBody @Valid UpdateEventUserRequest eventDto) { + log.info("PATCH запрос /users/{}/events/{} с телом {}", userId, eventId, eventDto); + EventFullDto event = eventService.updateEventByPrivate(userId, eventId, eventDto); + log.info("Отправлен ответ PATCH /users/{}/events/{} с телом: {}", userId, eventId, event); + return event; + } + + @GetMapping("/{eventId}") + public EventFullDto getEventOfUser(@PathVariable Long userId, @PathVariable Long eventId) { + log.info("GET запрос на получение списка всех событий пользователя с id: {}", userId); + return eventService.getEventOfUser(userId, eventId); + } + + @GetMapping + public Collection findAllByPrivate( + @PathVariable Long userId, + @RequestParam(required = false, defaultValue = "0") @PositiveOrZero Integer from, + @RequestParam(required = false, defaultValue = "10") @Positive Integer size, HttpServletRequest request + ) { + log.info("GET запрос /users/{}/events?from={}&size={}", userId, from, size); + Collection events = eventService.findAllByPrivate(userId, from, size,request); + log.info("Отправлен ответ GET /users/{}/events?from={}&size={} с телом: {}", userId, from, size, events); + return events; + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/controller/PublicEventController.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/controller/PublicEventController.java new file mode 100644 index 0000000..d779a1d --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/controller/PublicEventController.java @@ -0,0 +1,42 @@ +package ru.practicum.ewm.event.controller; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.ewm.event.dto.EventFullDto; +import ru.practicum.ewm.event.dto.EventSearchParams; +import ru.practicum.ewm.event.dto.EventShortDto; +import ru.practicum.ewm.event.service.EventService; + +import java.util.Collection; + +@Slf4j +@RestController +@RequestMapping("/events") +@RequiredArgsConstructor +public class PublicEventController { + + private final EventService eventService; + + @GetMapping + public Collection findAllByPublic(@Valid EventSearchParams searchEventParams, + HttpServletRequest request) { + log.info("GET запрос на получения событий с фильтром"); + Collection events = eventService.findAllByPublic(searchEventParams, request); + log.info("Отправлен ответ GET /events с телом: {}", events); + return events; + } + + @GetMapping("/{eventId}") + public EventFullDto findEventById(@PathVariable Long eventId, HttpServletRequest request) { + log.info("GET запрос /events/{}", eventId); + EventFullDto event = eventService.findEventById(eventId, request); + log.info("Отправлен ответ GET /events/{} с телом: {}", eventId, event); + return event; + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/EventFullDto.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/EventFullDto.java new file mode 100644 index 0000000..138f774 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/EventFullDto.java @@ -0,0 +1,40 @@ +package ru.practicum.ewm.event.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.category.dto.CategoryDto; +import ru.practicum.ewm.enums.EventState; +import ru.practicum.ewm.location.dto.LocationDto; +import ru.practicum.ewm.user.dto.UserShortDto; + +import java.time.LocalDateTime; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EventFullDto { + Long id; + String title; + String annotation; + String description; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime eventDate; + LocationDto location; + Boolean paid; + Integer participantLimit; + Boolean requestModeration; + EventState state; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime createdOn; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime publishedOn; + UserShortDto initiator; + CategoryDto category; + @Builder.Default + long views = 0L; + @Builder.Default + long confirmedRequests = 0L; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/EventSearchParams.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/EventSearchParams.java new file mode 100644 index 0000000..09520e1 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/EventSearchParams.java @@ -0,0 +1,34 @@ +package ru.practicum.ewm.event.dto; + +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.*; +import lombok.experimental.FieldDefaults; +import org.springframework.format.annotation.DateTimeFormat; +import ru.practicum.ewm.enums.EventState; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EventSearchParams { + String text; + List categories; + Boolean paid; + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime rangeStart; + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime rangeEnd; + Boolean onlyAvailable; + String sort; + @PositiveOrZero + int from = 0; + @Positive + int size = 10; + List users; + List states; +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/EventShortDto.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/EventShortDto.java new file mode 100644 index 0000000..b17e780 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/EventShortDto.java @@ -0,0 +1,30 @@ +package ru.practicum.ewm.event.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.category.dto.CategoryDto; +import ru.practicum.ewm.user.dto.UserShortDto; + +import java.time.LocalDateTime; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EventShortDto { + Long id; + String annotation; + CategoryDto category; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime eventDate; + UserShortDto initiator; + Boolean paid; + String title; + @Builder.Default + long views = 0L; + + @Builder.Default + long confirmedRequests = 0L; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/NewEventDto.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/NewEventDto.java new file mode 100644 index 0000000..f0efffe --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/NewEventDto.java @@ -0,0 +1,39 @@ +package ru.practicum.ewm.event.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.location.dto.LocationDto; + +import java.time.LocalDateTime; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class NewEventDto { + @NotBlank + @Size(min = 20, max = 2000, message = "Длина аннотации должна не больше 2000 символов и не меньше 20") + String annotation; + Long category; + + @NotBlank + @Size(min = 20, max = 7000, message = "Длина описания должна не больше 7000 символов и не меньше 20") + String description; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime eventDate; + LocationDto location; + Boolean paid = false; + + @Min(value = 0, message = "Лимит участников не может быть отрицательным") + Integer participantLimit = 0; + Boolean requestModeration = true; + + @Size(min = 3, max = 120, message = "Длина аннотации должна не больше 120 символов и не меньше 3") + String title; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/UpdateEventAdminRequest.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/UpdateEventAdminRequest.java new file mode 100644 index 0000000..a2d9c64 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/UpdateEventAdminRequest.java @@ -0,0 +1,38 @@ +package ru.practicum.ewm.event.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Size; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.enums.StateAction; +import ru.practicum.ewm.location.model.Location; + +import java.time.LocalDateTime; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UpdateEventAdminRequest { + @Size(min = 20, max = 2000, message = "Длина аннотации должна не больше 2000 символов и не меньше 20") + String annotation; + Long category; + + @Size(min = 20, max = 7000, message = "Длина описания должна не больше 7000 символов и не меньше 20") + String description; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime eventDate; + Location location; + Boolean paid; + + @Min(value = 0, message = "Лимит участников не может быть отрицательным") + Integer participantLimit; + Boolean requestModeration; + StateAction stateAction; + + @Size(min = 3, max = 120, message = "Длина аннотации должна не больше 120 символов и не меньше 3") + String title; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/UpdateEventUserRequest.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/UpdateEventUserRequest.java new file mode 100644 index 0000000..550b70d --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/dto/UpdateEventUserRequest.java @@ -0,0 +1,38 @@ +package ru.practicum.ewm.event.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Size; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.enums.StateAction; +import ru.practicum.ewm.location.model.Location; + +import java.time.LocalDateTime; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UpdateEventUserRequest { + @Size(min = 20, max = 2000, message = "Длина аннотации должна не больше 2000 символов и не меньше 20") + String annotation; + Long category; + + @Size(min = 20, max = 7000, message = "Длина описания должна не больше 7000 символов и не меньше 20") + String description; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime eventDate; + Location location; + Boolean paid; + + @Min(value = 0, message = "Лимит участников не может быть отрицательным") + Integer participantLimit; + Boolean requestModeration; + StateAction stateAction; + + @Size(min = 3, max = 120, message = "Длина аннотации должна не больше 120 символов и не меньше 3") + String title; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/mapper/EventMapper.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/mapper/EventMapper.java new file mode 100644 index 0000000..5407d0f --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/mapper/EventMapper.java @@ -0,0 +1,58 @@ +package ru.practicum.ewm.event.mapper; + +import ru.practicum.ewm.category.mapper.CategoryMapper; +import ru.practicum.ewm.enums.EventState; +import ru.practicum.ewm.event.dto.EventFullDto; +import ru.practicum.ewm.event.dto.EventShortDto; +import ru.practicum.ewm.event.dto.NewEventDto; +import ru.practicum.ewm.event.model.Event; +import ru.practicum.ewm.location.mapper.LocationMapper; +import ru.practicum.ewm.user.mapper.UserMapper; + +public class EventMapper { + + public static Event toEvent(NewEventDto newEventDto) { + return Event.builder() + .annotation(newEventDto.getAnnotation()) + .description(newEventDto.getDescription()) + .eventDate(newEventDto.getEventDate()) + .location(LocationMapper.toLocationFromDto(newEventDto.getLocation())) + .paid(newEventDto.getPaid()) + .participantLimit(newEventDto.getParticipantLimit()) + .requestModeration(newEventDto.getRequestModeration()) + .title(newEventDto.getTitle()) + .state(EventState.PENDING) + .build(); + } + + public static EventFullDto toEventFullDto(Event event) { + return EventFullDto.builder() + .id(event.getId()) + .annotation(event.getAnnotation()) + .category(CategoryMapper.toCategoryDto(event.getCategory())) + .createdOn(event.getCreatedOn()) + .description(event.getDescription()) + .eventDate(event.getEventDate()) + .initiator(UserMapper.toUserShortDto(event.getInitiator())) + .location(LocationMapper.toLocationDto(event.getLocation())) + .paid(event.getPaid()) + .participantLimit(event.getParticipantLimit()) + .publishedOn(event.getPublishedOn()) + .requestModeration(event.getRequestModeration()) + .state(event.getState()) + .title(event.getTitle()) + .build(); + } + + public static EventShortDto toEventShortDto(Event event) { + return EventShortDto.builder() + .id(event.getId()) + .annotation(event.getAnnotation()) + .category(CategoryMapper.toCategoryDto(event.getCategory())) + .eventDate(event.getEventDate()) + .initiator(UserMapper.toUserShortDto(event.getInitiator())) + .paid(event.getPaid()) + .title(event.getTitle()) + .build(); + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/model/Event.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/model/Event.java new file mode 100644 index 0000000..64a0e61 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/model/Event.java @@ -0,0 +1,63 @@ +package ru.practicum.ewm.event.model; + +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.category.model.Category; +import ru.practicum.ewm.enums.EventState; +import ru.practicum.ewm.location.model.Location; +import ru.practicum.ewm.user.model.User; + +import java.time.LocalDateTime; + +@FieldDefaults(level = AccessLevel.PRIVATE) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "events") +public class Event { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "location_id") + Location location; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "initiator_id") + User initiator; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id") + Category category; + + @Column(length = 120) + String title; + + @Column(length = 2000) + String annotation; + + @Column(length = 7000) + String description; + + @Column(name = "event_date") + LocalDateTime eventDate; + + Boolean paid; + @Column(name = "participant_limit") + Integer participantLimit; + @Column(name = "request_moderation") + Boolean requestModeration; + + @Enumerated(EnumType.STRING) + EventState state; + + @Column(name = "created_on") + LocalDateTime createdOn; + + @Column(name = "published_on") + LocalDateTime publishedOn; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/service/EventService.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/service/EventService.java new file mode 100644 index 0000000..6d3171b --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/service/EventService.java @@ -0,0 +1,25 @@ +package ru.practicum.ewm.event.service; + +import jakarta.servlet.http.HttpServletRequest; +import ru.practicum.ewm.event.dto.*; + +import java.util.Collection; + +public interface EventService { + EventFullDto create(Long userId, NewEventDto newEventDto); + + EventFullDto updateEventByAdmin(Long eventId, UpdateEventAdminRequest adminRequest); + + EventFullDto updateEventByPrivate(Long userId, Long eventId, UpdateEventUserRequest eventUserRequest); + + EventFullDto getEventOfUser(Long userId, Long eventId); + + Collection findAllByPublic(EventSearchParams params, HttpServletRequest request); + + Collection findAllByPrivate(Long userId, Integer from, Integer size, HttpServletRequest request); + + Collection findAllByAdmin(EventSearchParams params, HttpServletRequest request); + + EventFullDto findEventById(Long eventId, HttpServletRequest request); + +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/service/EventServiceImpl.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/service/EventServiceImpl.java new file mode 100644 index 0000000..05acab7 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/service/EventServiceImpl.java @@ -0,0 +1,423 @@ +package ru.practicum.ewm.event.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.criteria.Predicate; +import jakarta.servlet.http.HttpServletRequest; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +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.data.jpa.domain.Specification; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.ewm.category.CategoryRepository; +import ru.practicum.ewm.category.mapper.CategoryMapper; +import ru.practicum.ewm.category.model.Category; +import ru.practicum.ewm.enums.EventState; +import ru.practicum.ewm.enums.RequestStatus; +import ru.practicum.ewm.enums.StateAction; +import ru.practicum.ewm.event.EventRepository; +import ru.practicum.ewm.event.dto.*; +import ru.practicum.ewm.event.mapper.EventMapper; +import ru.practicum.ewm.event.model.Event; +import ru.practicum.ewm.exception.*; +import ru.practicum.ewm.location.LocationRepository; +import ru.practicum.ewm.location.mapper.LocationMapper; +import ru.practicum.ewm.location.model.Location; +import ru.practicum.ewm.request.EventRequestRepository; +import ru.practicum.ewm.user.UserRepository; +import ru.practicum.ewm.user.mapper.UserMapper; +import ru.practicum.ewm.user.model.User; +import ru.practicum.stat.StatisticsClient; +import ru.practicum.stat.ViewStatsDto; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class EventServiceImpl implements EventService { + + static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + static int MIN_HOURS_BEFORE_EVENT = 2; + static int MIN_HOURS_BEFORE_PUBLISH = 1; + + EventRepository eventRepository; + CategoryRepository categoryRepository; + LocationRepository locationRepository; + UserRepository userRepository; + EventRequestRepository eventRequestRepository; + + StatisticsClient statClient; + ObjectMapper mapper; + + @Override + public EventFullDto create(Long userId, NewEventDto newEventDto) { + validateEventDate(newEventDto.getEventDate()); + LocalDateTime createdOn = LocalDateTime.now(); + + User user = getUserById(userId); + Category category = categoryRepository.findById(newEventDto.getCategory()) + .orElseThrow(() -> new NotFoundException("Категория с ID " + newEventDto.getCategory() + " не найдена")); + + Event event = EventMapper.toEvent(newEventDto); + event.setCreatedOn(createdOn); + event.setCategory(category); + event.setInitiator(user); + + Location location = LocationMapper.toLocationFromDto(newEventDto.getLocation()); + Location savedLocation = locationRepository.save(location); + event.setLocation(savedLocation); + + Event eventSaved = eventRepository.save(event); + + return EventMapper.toEventFullDto(eventSaved); + } + + @Override + public EventFullDto updateEventByAdmin(Long eventId, UpdateEventAdminRequest adminRequest) { + Event event = getEventById(eventId); + + LocalDateTime eventDate = (adminRequest.getEventDate() != null) ? adminRequest.getEventDate() : event.getEventDate(); + validateEventDateForAdmin(eventDate, adminRequest.getStateAction()); + validateStatusForAdmin(event.getState(), adminRequest.getStateAction()); + + updateEventFields(event, Optional.ofNullable(adminRequest.getAnnotation()), Optional.ofNullable(adminRequest.getDescription()), + eventDate, Optional.ofNullable(adminRequest.getPaid()), Optional.ofNullable(adminRequest.getParticipantLimit()), + Optional.ofNullable(adminRequest.getRequestModeration()), Optional.ofNullable(adminRequest.getTitle()), + adminRequest.getCategory()); + + processStateAction(event, adminRequest.getStateAction()); + + Event updatedEvent = eventRepository.save(event); + return EventMapper.toEventFullDto(updatedEvent); + } + + @Override + public EventFullDto updateEventByPrivate(Long userId, Long eventId, UpdateEventUserRequest eventUserRequest) { + User user = getUserById(userId); + Event event = getEventById(eventId); + + validateUser(event.getInitiator(), user); + + if (event.getState() == EventState.PUBLISHED) { + throw new ConflictException("Нельзя изменить опубликованное событие"); + } + LocalDateTime eventDate = (eventUserRequest.getEventDate() != null) ? eventUserRequest.getEventDate() : event.getEventDate(); + validateEventDate(eventDate); + + updateEventFields(event, Optional.ofNullable(eventUserRequest.getAnnotation()), Optional.ofNullable(eventUserRequest.getDescription()), + eventDate, Optional.ofNullable(eventUserRequest.getPaid()), Optional.ofNullable(eventUserRequest.getParticipantLimit()), + Optional.ofNullable(eventUserRequest.getRequestModeration()), Optional.ofNullable(eventUserRequest.getTitle()), + eventUserRequest.getCategory()); + + processStateAction(event, eventUserRequest.getStateAction()); + + Event updatedEvent = eventRepository.save(event); + return EventMapper.toEventFullDto(updatedEvent); + } + + @Override + @Transactional(readOnly = true) + public EventFullDto getEventOfUser(Long userId, Long eventId) { + log.info("Получение события от пользователя {}", userId); + User user = getUserById(userId); + Event event = getEventById(eventId); + + if (!event.getInitiator().getId().equals(userId)) { + throw new ValidationException("Пользователь не является инициатором события"); + } + return EventMapper.toEventFullDto(event); + } + + @Override + public Collection findAllByPublic(EventSearchParams params, HttpServletRequest request) { + + if (params.getRangeStart() != null && params.getRangeEnd() != null && params.getRangeStart().isAfter(params.getRangeEnd())) { + throw new IllegalArgumentException("rangeStart должен быть раньше rangeEnd"); + } + + if (params.getSort() != null && !List.of("EVENT_DATE", "VIEWS").contains(params.getSort().toUpperCase())) { + throw new IncorrectRequestException("Unknown sort type"); + } + + Pageable pageable = PageRequest.of(params.getFrom(), params.getSize()); + Specification spec = (root, query, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + predicates.add(criteriaBuilder.equal(root.get("state"), EventState.PUBLISHED)); + + + if (params.getText() != null) { + predicates.add(criteriaBuilder.or( + criteriaBuilder.like(criteriaBuilder.lower(root.get("annotation")), "%" + params.getText().toLowerCase() + "%"), + criteriaBuilder.like(criteriaBuilder.lower(root.get("description")), "%" + params.getText().toLowerCase() + "%") + )); + } + + if (params.getCategories() != null && !params.getCategories().isEmpty()) { + predicates.add(root.get("category").get("id").in(params.getCategories())); + } + + if (params.getPaid() != null) { + predicates.add(criteriaBuilder.equal(root.get("paid"), params.getPaid())); + } + + if (params.getRangeStart() == null && params.getRangeEnd() == null) { + predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("eventDate"), LocalDateTime.now())); + } else { + predicates.add(criteriaBuilder.between(root.get("eventDate"), params.getRangeStart(), params.getRangeEnd())); + } + + if (Boolean.TRUE.equals(params.getOnlyAvailable())) { + predicates.add(criteriaBuilder.or( + criteriaBuilder.equal(root.get("participantLimit"), 0), + criteriaBuilder.greaterThan(root.get("participantLimit"), root.get("confirmedRequests")) + )); + } + + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + }; + + Page eventPage = eventRepository.findAll(spec, pageable); + + sendStats(request); + + List eventShortDtos = eventPage.getContent().stream() + .map(event -> { + EventShortDto eventDto = EventMapper.toEventShortDto(event); + eventDto.setViews(getViews(event.getId(), event.getCreatedOn(), request)); + eventDto.setConfirmedRequests(eventDto.getConfirmedRequests()); + return eventDto; + }) + .collect(Collectors.toList()); + + if ("EVENT_DATE".equalsIgnoreCase(params.getSort())) { + eventShortDtos.sort(Comparator.comparing(EventShortDto::getEventDate)); + } else if ("VIEWS".equalsIgnoreCase(params.getSort())) { + eventShortDtos.sort(Comparator.comparing(EventShortDto::getViews)); + } + + return eventShortDtos; + } + + @Override + public Collection findAllByPrivate(Long userId, Integer from, Integer size, HttpServletRequest request) { + + User user = getUserById(userId); + Pageable pageable = PageRequest.of(from, size); + List events = eventRepository.findAllByInitiatorId(user.getId(), pageable); + + List eventIds = events.stream().map(Event::getId).collect(Collectors.toList()); + Map confirmedRequestsMap = getConfirmedRequestsForEvents(eventIds); + + return events.stream() + .map(event -> { + EventShortDto eventShortDto = EventMapper.toEventShortDto(event); + eventShortDto.setCategory(CategoryMapper.toCategoryDto(event.getCategory())); + eventShortDto.setInitiator(UserMapper.toUserShortDto(event.getInitiator())); + eventShortDto.setViews(getViews(event.getId(), event.getCreatedOn(), request)); + + eventShortDto.setConfirmedRequests(confirmedRequestsMap.getOrDefault(event.getId(), 0L)); + + return eventShortDto; + }) + .collect(Collectors.toCollection(ArrayList::new)); + } + + @Override + public Collection findAllByAdmin(EventSearchParams params, HttpServletRequest request) { + + Pageable pageable = PageRequest.of(params.getFrom(), params.getSize()); + + List eventList; + try { + eventList = eventRepository.findAllByAdmin(params.getUsers(), params.getStates(), params.getCategories(), params.getRangeStart(), params.getRangeEnd(), pageable); + } catch (Exception e) { + log.error("Ошибка при выполнении запроса к БД: ", e); + throw new RuntimeException("Ошибка при получении данных из базы данных", e); + } + + List eventIds = eventList.stream().map(Event::getId).collect(Collectors.toList()); + Map confirmedRequestsMap = getConfirmedRequestsForEvents(eventIds); + + return eventList.stream() + .map(event -> { + EventFullDto dto = EventMapper.toEventFullDto(event); + dto.setViews(getViews(event.getId(), event.getCreatedOn(), request)); + dto.setConfirmedRequests(confirmedRequestsMap.getOrDefault(event.getId(), 0L)); + return dto; + }) + .collect(Collectors.toList()); + } + + private Map getConfirmedRequestsForEvents(List eventIds) { + List results = eventRequestRepository.countByEventIdInAndStatus(eventIds, RequestStatus.CONFIRMED); + Map confirmedRequestsMap = new HashMap<>(); + for (Object[] result : results) { + Long eventId = (Long) result[0]; + Long count = (Long) result[1]; + confirmedRequestsMap.put(eventId, count); + } + return confirmedRequestsMap; + } + + @Override + public EventFullDto findEventById(Long eventId, HttpServletRequest request) { + Event event = getEventById(eventId); + + if (!event.getState().equals(EventState.PUBLISHED)) { + throw new NotFoundException("Событие с ID = " + eventId + " не опубликовано"); + } + + sendStats(request); + + EventFullDto eventFullDto = EventMapper.toEventFullDto(event); + eventFullDto.setViews(getViews(eventId, event.getCreatedOn(), request)); + eventFullDto.setConfirmedRequests(eventRequestRepository.countByEventIdAndStatus(eventId, RequestStatus.CONFIRMED)); + return eventFullDto; + } + + private void validateEventDate(LocalDateTime eventDate) { + LocalDateTime nowPlusMinHours = LocalDateTime.now().plusHours(MIN_HOURS_BEFORE_EVENT); + if (eventDate.isBefore(nowPlusMinHours)) { + String formattedEventDate = eventDate.format(formatter); + String formattedMinHours = nowPlusMinHours.format(formatter); + + throw new ValidationException("Дата мероприятия должна быть не ранее, чем через " + MIN_HOURS_BEFORE_EVENT + " часа(ов) от текущего момента. " + + "Указанная дата: " + formattedEventDate + ", Минимальная допустимая дата: " + formattedMinHours); + } + } + + private void validateEventDateForAdmin(LocalDateTime eventDate, StateAction stateAction) { + if (eventDate.isBefore(LocalDateTime.now().plusHours(MIN_HOURS_BEFORE_EVENT))) { + throw new ValidationException("Дата мероприятия должна быть на " + MIN_HOURS_BEFORE_EVENT + "часа раньше текущего момента"); + } + if (stateAction != null && stateAction.equals(StateAction.PUBLISH_EVENT) && eventDate.isBefore(LocalDateTime.now().plusHours(MIN_HOURS_BEFORE_PUBLISH))) { + throw new ValidationException("Дата события должна быть на " + MIN_HOURS_BEFORE_PUBLISH + " час раньше момента публикации"); + } + } + + private void validateStatusForAdmin(EventState state, StateAction stateAction) { + if (stateAction != null && !stateAction.equals(StateAction.REJECT_EVENT) && !stateAction.equals(StateAction.PUBLISH_EVENT)) { + throw new ForbiddenException("Неизвестный state action"); + } + if (!state.equals(EventState.PENDING) && stateAction.equals(StateAction.PUBLISH_EVENT)) { + throw new ConflictException("\n" + + "Не удается опубликовать незавершенное событие"); + } + if (state.equals(EventState.PUBLISHED) && stateAction.equals(StateAction.REJECT_EVENT)) { + throw new ConflictException("Невозможно отклонить уже опубликованное событие"); + } + } + + private void validateUser(User user, User initiator) { + if (!initiator.getId().equals(user.getId())) { + throw new NotFoundException("Попытка изменить информацию не от инициатора события"); + } + } + + private User getUserById(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь c ID " + userId + " не найден")); + } + + private Event getEventById(Long eventId) { + return eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Событие c ID " + eventId + " не найдено")); + } + + private void updateEventFields(Event event, Optional annotation, Optional description, + LocalDateTime eventDate, Optional paid, Optional participantLimit, + Optional requestModeration, Optional title, Long categoryId) { + + annotation.ifPresent(event::setAnnotation); + description.ifPresent(event::setDescription); + event.setEventDate(eventDate); + paid.ifPresent(event::setPaid); + participantLimit.ifPresent(event::setParticipantLimit); + requestModeration.ifPresent(event::setRequestModeration); + title.ifPresent(event::setTitle); + + if (categoryId != null) { + Category category = categoryRepository.findById(categoryId) + .orElseThrow(() -> new NotFoundException("Категория с ID " + categoryId + " не найдена")); + event.setCategory(category); + } + } + + private void processStateAction(Event event, StateAction stateAction) { + if (stateAction != null) { + switch (stateAction) { + case PUBLISH_EVENT: + event.setState(EventState.PUBLISHED); + event.setPublishedOn(LocalDateTime.now()); + break; + case REJECT_EVENT: + case CANCEL_REVIEW: + event.setState(EventState.CANCELED); + break; + case SEND_TO_REVIEW: + event.setState(EventState.PENDING); + break; + default: + throw new IllegalArgumentException("Недопустимое действие над событием: " + stateAction); + } + } + } + + private void sendStats(HttpServletRequest request) { + try { + statClient.create(request); + } catch (Exception e) { + log.error("Ошибка при отправке статистики: {}", e.getMessage()); + + } + } + + private Long getViews(Long eventId, LocalDateTime createdOn, HttpServletRequest request) { + LocalDateTime end = LocalDateTime.now(); + String uri = request.getRequestURI(); + Boolean unique = true; + Long defaultViews = 0L; + + try { + ResponseEntity statsResponse = statClient.getStats(createdOn, end, List.of(uri), unique); + log.info("Запрос к statClient: URI={}, from={}, to={}, unique={}", uri, createdOn, end, unique); + log.info("Ответ от statClient: status={}, body={}", statsResponse.getStatusCode(), statsResponse.getBody()); + if (statsResponse.getStatusCode().is2xxSuccessful() && statsResponse.hasBody()) { + Object body = statsResponse.getBody(); + if (body != null) { + try { + ViewStatsDto[] statsArray = mapper.convertValue(body, ViewStatsDto[].class); + List stats = Arrays.asList(statsArray); + + if (!stats.isEmpty()) { + return stats.getLast().getHits(); + } else { + log.info("Нет данных статистики для события {}", eventId); + } + } catch (Exception e) { + log.error("Ошибка преобразования данных статистики для события {}: {}", eventId, e.getMessage()); + return defaultViews; + } + } else { + log.warn("Тело ответа от statClient пустое для события {}", eventId); + } + } else { + log.warn("Неуспешный ответ от statClient для события {}: {}", eventId, statsResponse.getStatusCode()); + } + } catch (Exception e) { + log.error("Ошибка при получении статистики для события {}: {}", eventId, e.getMessage()); + } + return defaultViews; + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ApiError.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ApiError.java new file mode 100644 index 0000000..00fc95e --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ApiError.java @@ -0,0 +1,15 @@ +package ru.practicum.ewm.exception; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +public class ApiError { + private final int status; + private final String reason; + private final String message; + private final LocalDateTime timestamp = LocalDateTime.now(); +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ConflictException.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ConflictException.java new file mode 100644 index 0000000..aba656a --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ConflictException.java @@ -0,0 +1,11 @@ +package ru.practicum.ewm.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.CONFLICT) +public class ConflictException extends RuntimeException { + public ConflictException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/DuplicatedDataException.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/DuplicatedDataException.java new file mode 100644 index 0000000..bb001d4 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/DuplicatedDataException.java @@ -0,0 +1,11 @@ +package ru.practicum.ewm.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.CONFLICT) +public class DuplicatedDataException extends RuntimeException { + public DuplicatedDataException(String message) { + super(message); + } +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ErrorHandler.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ErrorHandler.java new file mode 100644 index 0000000..4935f92 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ErrorHandler.java @@ -0,0 +1,97 @@ +package ru.practicum.ewm.exception; + +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; + +@Slf4j +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(BAD_REQUEST) + public ErrorResponse handleConstraintViolationException(ConstraintViolationException ex) { + return new ErrorResponse("BAD_REQUEST", ex.getMessage()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + return new ErrorResponse("Ошибка валидации: ", e.getMessage()); + } + + @ExceptionHandler(ValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handlerValidationException(ValidationException e) { + return new ErrorResponse("BAD_REQUEST", e.getMessage()); + } + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(NOT_FOUND) + public ErrorResponse handleNotFoundException(NotFoundException ex) { + return new ErrorResponse("NOT_FOUND", ex.getMessage()); + } + + @ExceptionHandler(DuplicatedDataException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleDuplicatedDataException(DuplicatedDataException ex) { + log.warn("DuplicatedDataException: {}", ex.getMessage()); + return new ErrorResponse("CONFLICT", ex.getMessage()); + } + + @ExceptionHandler(ConflictException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleConflictException(ConflictException ex) { + log.warn("ConflictException: {}", ex.getMessage()); + return new ErrorResponse("CONFLICT", ex.getMessage()); + } + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(BAD_REQUEST) + public ErrorResponse handleIllegalArgumentException(IllegalArgumentException ex) { + return new ErrorResponse("BAD_REQUEST", ex.getMessage()); + } + + @ExceptionHandler(IllegalStateException.class) + @ResponseStatus(BAD_REQUEST) + public ErrorResponse handleIllegalStateException(IllegalStateException ex) { + return new ErrorResponse("BAD_REQUEST", ex.getMessage()); + } + + @ExceptionHandler(DataIntegrityViolationException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleDataIntegrityViolationException(DataIntegrityViolationException ex) { + log.warn("DataIntegrityViolationException: {}", ex.getMessage()); + return new ErrorResponse("CONFLICT", "Нарушение уникальности данных: " + ex.getMessage()); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.FORBIDDEN) + public ApiError handleConflict(final ForbiddenException e) { + log.info("403 {}", e.getMessage(), e); + return new ApiError( + HttpStatus.FORBIDDEN.value(), + "For the requested operation the conditions are not met.", + e.getMessage() + ); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiError handleIncorrectRequestException(final IncorrectRequestException e) { + log.info("400 {}", e.getMessage(), e); + return new ApiError( + HttpStatus.BAD_REQUEST.value(), + "Incorrectly made request.", + e.getMessage() + ); + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ErrorResponse.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ErrorResponse.java new file mode 100644 index 0000000..62834e0 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ErrorResponse.java @@ -0,0 +1,11 @@ +package ru.practicum.ewm.exception; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ErrorResponse { + private final String error; + private final String message; +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ForbiddenException.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ForbiddenException.java new file mode 100644 index 0000000..adf4fbc --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ForbiddenException.java @@ -0,0 +1,11 @@ +package ru.practicum.ewm.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.FORBIDDEN) +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/IncorrectRequestException.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/IncorrectRequestException.java new file mode 100644 index 0000000..db70e2b --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/IncorrectRequestException.java @@ -0,0 +1,7 @@ +package ru.practicum.ewm.exception; + +public class IncorrectRequestException extends RuntimeException { + public IncorrectRequestException(String message) { + super(message); + } +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/NotFoundException.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/NotFoundException.java new file mode 100644 index 0000000..306d37e --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/NotFoundException.java @@ -0,0 +1,11 @@ +package ru.practicum.ewm.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.NOT_FOUND) +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ValidationException.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ValidationException.java new file mode 100644 index 0000000..9bec3fe --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ValidationException.java @@ -0,0 +1,11 @@ +package ru.practicum.ewm.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} From d68b560af18977cfa8c7a4c5ed158cadc64c3941 Mon Sep 17 00:00:00 2001 From: pavelrk97 Date: Sat, 13 Sep 2025 16:42:45 +0300 Subject: [PATCH 3/7] =?UTF-8?q?=D0=BA=D0=B0=D1=82=D0=B5=D0=B3=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=B8=D0=B2=D0=B5=D0=BD=D1=82=20=D0=B8=20=D0=BE?= =?UTF-8?q?=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/practicum/ewm/enums/EventState.java | 10 + .../ru/practicum/ewm/enums/RequestStatus.java | 8 + .../ru/practicum/ewm/enums/StateAction.java | 12 ++ .../ewm/location/LocationRepository.java | 8 + .../ewm/location/dto/LocationDto.java | 17 ++ .../ewm/location/mapper/LocationMapper.java | 21 ++ .../ewm/location/model/Location.java | 20 ++ .../ewm/request/EventRequestRepository.java | 27 +++ .../EventRequestPrivateController.java | 68 +++++++ .../dto/EventRequestStatusUpdateRequest.java | 22 ++ .../dto/EventRequestStatusUpdateResult.java | 16 ++ .../request/dto/ParticipationRequestDto.java | 22 ++ .../request/mapper/EventRequestMapper.java | 19 ++ .../ewm/request/model/EventRequest.java | 35 ++++ .../request/service/EventRequestService.java | 21 ++ .../service/EventRequestServiceImpl.java | 191 ++++++++++++++++++ .../ru/practicum/ewm/user/UserRepository.java | 16 ++ .../user/controller/AdminUserController.java | 50 +++++ .../ewm/user/dto/NewUserRequest.java | 23 +++ .../ru/practicum/ewm/user/dto/UserDto.java | 15 ++ .../practicum/ewm/user/dto/UserShortDto.java | 14 ++ .../practicum/ewm/user/mapper/UserMapper.java | 31 +++ .../ru/practicum/ewm/user/model/User.java | 24 +++ .../ewm/user/service/UserService.java | 14 ++ .../ewm/user/service/UserServiceImpl.java | 55 +++++ 25 files changed, 759 insertions(+) create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/enums/EventState.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/enums/RequestStatus.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/enums/StateAction.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/location/LocationRepository.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/location/dto/LocationDto.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/location/mapper/LocationMapper.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/location/model/Location.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/request/EventRequestRepository.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/request/controller/EventRequestPrivateController.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/request/dto/EventRequestStatusUpdateRequest.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/request/dto/EventRequestStatusUpdateResult.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/request/dto/ParticipationRequestDto.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/request/mapper/EventRequestMapper.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/request/model/EventRequest.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/request/service/EventRequestService.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/request/service/EventRequestServiceImpl.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/user/UserRepository.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/user/controller/AdminUserController.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/user/dto/NewUserRequest.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/user/dto/UserDto.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/user/dto/UserShortDto.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/user/mapper/UserMapper.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/user/model/User.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/user/service/UserService.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/user/service/UserServiceImpl.java diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/enums/EventState.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/enums/EventState.java new file mode 100644 index 0000000..2c034ed --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/enums/EventState.java @@ -0,0 +1,10 @@ +package ru.practicum.ewm.enums; + +public enum EventState { + // незаконченный + PENDING, + // опубликованный + PUBLISHED, + // отмененный + CANCELED +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/enums/RequestStatus.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/enums/RequestStatus.java new file mode 100644 index 0000000..777ddd5 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/enums/RequestStatus.java @@ -0,0 +1,8 @@ +package ru.practicum.ewm.enums; + +public enum RequestStatus { + PENDING, + CONFIRMED, + REJECTED, + CANCELED +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/enums/StateAction.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/enums/StateAction.java new file mode 100644 index 0000000..f6925a9 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/enums/StateAction.java @@ -0,0 +1,12 @@ +package ru.practicum.ewm.enums; + +public enum StateAction { + // опубликовано + PUBLISH_EVENT, + //отклонено + REJECT_EVENT, + + CANCEL_REVIEW, + + SEND_TO_REVIEW +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/location/LocationRepository.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/location/LocationRepository.java new file mode 100644 index 0000000..410dbb7 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/location/LocationRepository.java @@ -0,0 +1,8 @@ +package ru.practicum.ewm.location; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.ewm.location.model.Location; + +public interface LocationRepository extends JpaRepository { + +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/location/dto/LocationDto.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/location/dto/LocationDto.java new file mode 100644 index 0000000..6ffe2af --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/location/dto/LocationDto.java @@ -0,0 +1,17 @@ +package ru.practicum.ewm.location.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.*; +import lombok.experimental.FieldDefaults; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LocationDto { + @NotNull + float lat; + @NotNull + float lon; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/location/mapper/LocationMapper.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/location/mapper/LocationMapper.java new file mode 100644 index 0000000..08eddee --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/location/mapper/LocationMapper.java @@ -0,0 +1,21 @@ +package ru.practicum.ewm.location.mapper; + +import ru.practicum.ewm.location.dto.LocationDto; +import ru.practicum.ewm.location.model.Location; + +public class LocationMapper { + + public static Location toLocationFromDto(LocationDto locationDto) { + return Location.builder() + .lat(locationDto.getLat()) + .lon(locationDto.getLon()) + .build(); + } + + public static LocationDto toLocationDto(Location location) { + return LocationDto.builder() + .lat(location.getLat()) + .lon(location.getLon()) + .build(); + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/location/model/Location.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/location/model/Location.java new file mode 100644 index 0000000..deb59b4 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/location/model/Location.java @@ -0,0 +1,20 @@ +package ru.practicum.ewm.location.model; + +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.FieldDefaults; + +@Data +@Entity +@Table(name = "locations") +@FieldDefaults(level = AccessLevel.PRIVATE) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Location { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + float lat; + float lon; +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/request/EventRequestRepository.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/EventRequestRepository.java new file mode 100644 index 0000000..b413d04 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/EventRequestRepository.java @@ -0,0 +1,27 @@ +package ru.practicum.ewm.request; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import ru.practicum.ewm.enums.RequestStatus; +import ru.practicum.ewm.event.model.Event; +import ru.practicum.ewm.request.model.EventRequest; + +import java.util.List; + +public interface EventRequestRepository extends JpaRepository { + + boolean existsByRequesterIdAndEventId(Long userId, Long eventId); + + List findByRequesterId(Long userId); + + long countByEventIdAndStatus(Long eventId, RequestStatus status); + + @Query("SELECT r.event.id, COUNT(r) FROM EventRequest r " + + "WHERE r.event.id IN :eventIds AND r.status = :status " + + "GROUP BY r.event.id") + List countByEventIdInAndStatus(@Param("eventIds") List eventIds, @Param("status") RequestStatus status); + + List findByEvent(Event event); + +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/request/controller/EventRequestPrivateController.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/controller/EventRequestPrivateController.java new file mode 100644 index 0000000..65090e5 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/controller/EventRequestPrivateController.java @@ -0,0 +1,68 @@ +package ru.practicum.ewm.request.controller; + +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.request.dto.EventRequestStatusUpdateRequest; +import ru.practicum.ewm.request.dto.EventRequestStatusUpdateResult; +import ru.practicum.ewm.request.dto.ParticipationRequestDto; +import ru.practicum.ewm.request.service.EventRequestService; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/users/{userId}") +@RequiredArgsConstructor +public class EventRequestPrivateController { + + private final EventRequestService eventRequestService; + + @PostMapping("/requests") + @ResponseStatus(HttpStatus.CREATED) + public ParticipationRequestDto create(@PathVariable @Positive Long userId, + @RequestParam @Positive Long eventId) { + log.info("POST запрос /users/{}/requests?eventId={}", userId, eventId); + ParticipationRequestDto createdRequest = eventRequestService.create(userId, eventId); + log.info("Создан запрос на участие: {}", createdRequest); + return createdRequest; + } + + @PatchMapping("/requests/{requestId}/cancel") + public ParticipationRequestDto cancel(@PathVariable @Positive Long userId, + @PathVariable @Positive Long requestId) { + log.info("PATCH запрос /users/{}/requests/{}/cancel", userId, requestId); + ParticipationRequestDto cancelledRequest = eventRequestService.cancelRequest(userId, requestId); + log.info("Запрос отменен: {}", cancelledRequest); + return cancelledRequest; + } + + @GetMapping("/requests") + public List getParticipationRequests(@PathVariable @Positive Long userId) { + log.info("GET запрос /users/{}/requests", userId); + List requests = eventRequestService.getParticipationRequests(userId); + log.info("Получены запросы на участие: {}", requests); + return requests; + } + + @GetMapping("/events/{eventId}/requests") + public List getParticipationRequestsForUserEvent(@PathVariable @Positive Long userId, + @PathVariable @Positive Long eventId) { + log.info("GET запрос /users/{}/events/{}/requests", userId, eventId); + List requests = eventRequestService.getParticipationRequestsForUserEvent(userId, eventId); + log.info("Получены запросы на участие в событии: {}", requests); + return requests; + } + + @PatchMapping("/events/{eventId}/requests") + public EventRequestStatusUpdateResult updateStatus(@PathVariable @Positive Long userId, + @PathVariable @Positive Long eventId, + @RequestBody EventRequestStatusUpdateRequest requestDto) { + log.info("PATCH запрос /users/{}/events/{}/requests с телом: {}", userId, eventId, requestDto); + EventRequestStatusUpdateResult updateResult = eventRequestService.updateStatus(userId, eventId, requestDto); + log.info("Обновлен статус запросов: {}", updateResult); + return updateResult; + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/request/dto/EventRequestStatusUpdateRequest.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/dto/EventRequestStatusUpdateRequest.java new file mode 100644 index 0000000..ee74fd6 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/dto/EventRequestStatusUpdateRequest.java @@ -0,0 +1,22 @@ +package ru.practicum.ewm.request.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.enums.RequestStatus; + +import java.util.List; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EventRequestStatusUpdateRequest { + @NotEmpty(message = "Have not requests to update") + List requestIds; + + @NotBlank(message = "Have not new status for requests to update") + RequestStatus status; +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/request/dto/EventRequestStatusUpdateResult.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/dto/EventRequestStatusUpdateResult.java new file mode 100644 index 0000000..0d6ec6e --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/dto/EventRequestStatusUpdateResult.java @@ -0,0 +1,16 @@ +package ru.practicum.ewm.request.dto; + +import lombok.*; +import lombok.experimental.FieldDefaults; + +import java.util.List; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EventRequestStatusUpdateResult { + List confirmedRequests; + List rejectedRequests; +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/request/dto/ParticipationRequestDto.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/dto/ParticipationRequestDto.java new file mode 100644 index 0000000..4fdfd23 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/dto/ParticipationRequestDto.java @@ -0,0 +1,22 @@ +package ru.practicum.ewm.request.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.enums.RequestStatus; + +import java.time.LocalDateTime; + +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ParticipationRequestDto { + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime created; + Long event; + Long id; + Long requester; + RequestStatus status; +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/request/mapper/EventRequestMapper.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/mapper/EventRequestMapper.java new file mode 100644 index 0000000..cdf4f0c --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/mapper/EventRequestMapper.java @@ -0,0 +1,19 @@ +package ru.practicum.ewm.request.mapper; + +import ru.practicum.ewm.request.dto.ParticipationRequestDto; +import ru.practicum.ewm.request.model.EventRequest; + +public class EventRequestMapper { + public static ParticipationRequestDto toRequestDto(EventRequest request) { + if (request == null) { + return null; + } + return ParticipationRequestDto.builder() + .id(request.getId()) + .event(request.getEvent().getId()) + .requester(request.getRequester().getId()) + .status(request.getStatus()) + .created(request.getCreated()) + .build(); + } +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/request/model/EventRequest.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/model/EventRequest.java new file mode 100644 index 0000000..a96a878 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/model/EventRequest.java @@ -0,0 +1,35 @@ +package ru.practicum.ewm.request.model; + +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.enums.RequestStatus; +import ru.practicum.ewm.event.model.Event; +import ru.practicum.ewm.user.model.User; + +import java.time.LocalDateTime; + +@FieldDefaults(level = AccessLevel.PRIVATE) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "requests") +public class EventRequest { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @ManyToOne + @JoinColumn(name = "event_id", nullable = false) + Event event; + + @ManyToOne + @JoinColumn(name = "requester_id", nullable = false) + User requester; + + @Enumerated(EnumType.STRING) + RequestStatus status; + LocalDateTime created; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/request/service/EventRequestService.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/service/EventRequestService.java new file mode 100644 index 0000000..48aefb8 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/service/EventRequestService.java @@ -0,0 +1,21 @@ +package ru.practicum.ewm.request.service; + +import ru.practicum.ewm.request.dto.EventRequestStatusUpdateRequest; +import ru.practicum.ewm.request.dto.EventRequestStatusUpdateResult; +import ru.practicum.ewm.request.dto.ParticipationRequestDto; + +import java.util.List; + +public interface EventRequestService { + + ParticipationRequestDto create(Long userId, Long eventId); + + ParticipationRequestDto cancelRequest(Long userId, Long requestId); + + List getParticipationRequests(Long userId); + + List getParticipationRequestsForUserEvent(Long userId, Long eventId); + + EventRequestStatusUpdateResult updateStatus(Long userId, Long eventId, + EventRequestStatusUpdateRequest dto); +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/request/service/EventRequestServiceImpl.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/service/EventRequestServiceImpl.java new file mode 100644 index 0000000..351d4a5 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/request/service/EventRequestServiceImpl.java @@ -0,0 +1,191 @@ +package ru.practicum.ewm.request.service; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.ewm.enums.EventState; +import ru.practicum.ewm.enums.RequestStatus; +import ru.practicum.ewm.event.EventRepository; +import ru.practicum.ewm.event.model.Event; +import ru.practicum.ewm.exception.ConflictException; +import ru.practicum.ewm.exception.DuplicatedDataException; +import ru.practicum.ewm.exception.ForbiddenException; +import ru.practicum.ewm.exception.NotFoundException; +import ru.practicum.ewm.request.EventRequestRepository; +import ru.practicum.ewm.request.dto.EventRequestStatusUpdateRequest; +import ru.practicum.ewm.request.dto.EventRequestStatusUpdateResult; +import ru.practicum.ewm.request.dto.ParticipationRequestDto; +import ru.practicum.ewm.request.mapper.EventRequestMapper; +import ru.practicum.ewm.request.model.EventRequest; +import ru.practicum.ewm.user.UserRepository; +import ru.practicum.ewm.user.model.User; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class EventRequestServiceImpl implements EventRequestService { + + UserRepository userRepository; + EventRepository eventRepository; + EventRequestRepository eventRequestRepository; + + @Override + public ParticipationRequestDto create(Long userId, Long eventId) { + log.info("Создание запроса на участие: userId = {}, eventId = {}", userId, eventId); + + User user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь c ID " + userId + " не найден")); + + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Событие c ID " + eventId + " не найдено")); + + if (eventRequestRepository.existsByRequesterIdAndEventId(userId, eventId)) { + throw new DuplicatedDataException("Пользователь уже подал заявку на это событие."); + } + + if (event.getInitiator().getId().equals(userId)) { + throw new DuplicatedDataException("Инициатор не может подавать заявку на своё событие."); + } + + if (event.getState() != EventState.PUBLISHED) { + throw new DuplicatedDataException("Нельзя участвовать в неопубликованном событии."); + } + + if (event.getParticipantLimit() != 0 && + eventRequestRepository.countByEventIdAndStatus(eventId, RequestStatus.CONFIRMED) >= event.getParticipantLimit()) { + throw new DuplicatedDataException("Достигнут лимит участников."); + } + + RequestStatus status = (!event.getRequestModeration() || event.getParticipantLimit() == 0) + ? RequestStatus.CONFIRMED : RequestStatus.PENDING; + + EventRequest request = EventRequest.builder() + .event(event) + .requester(user) + .created(LocalDateTime.now()) + .status(status) + .build(); + + EventRequest savedRequest = eventRequestRepository.save(request); + log.info("Создан запрос на участие с ID: {}", savedRequest.getId()); + return EventRequestMapper.toRequestDto(savedRequest); + } + + @Override + public ParticipationRequestDto cancelRequest(Long userId, Long requestId) { + log.info("Отмена запроса на участие: userId = {}, requestId = {}", userId, requestId); + + User user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь c ID " + userId + " не найден")); + + EventRequest request = eventRequestRepository.findById(requestId) + .orElseThrow(() -> new NotFoundException("Запрос не найден.")); + + if (!request.getRequester().getId().equals(userId)) { + throw new ForbiddenException("Можно отменить только собственный запрос."); + } + + request.setStatus(RequestStatus.CANCELED); + EventRequest savedRequest = eventRequestRepository.save(request); + return EventRequestMapper.toRequestDto(savedRequest); + } + + @Override + public List getParticipationRequests(Long userId) { + log.info("Получение запросов пользователя: userId = {}", userId); + + User user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь c ID " + userId + " не найден")); + + return eventRequestRepository.findByRequesterId(userId).stream() + .map(EventRequestMapper::toRequestDto) + .collect(Collectors.toList()); + } + + @Override + public List getParticipationRequestsForUserEvent(Long userId, Long eventId) { + log.info("Получение заявок на своё событие: userId = {}, eventId = {}", userId, eventId); + + User user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь c ID " + userId + " не найден")); + + Event event = eventRepository.findByIdAndInitiatorId(eventId, userId) + .orElseThrow(() -> new ForbiddenException("Пользователь не инициатор события.")); + + return eventRequestRepository.findByEvent(event).stream() + .map(EventRequestMapper::toRequestDto) + .collect(Collectors.toList()); + } + + @Override + public EventRequestStatusUpdateResult updateStatus(Long userId, Long eventId, + EventRequestStatusUpdateRequest dto) { + log.info("Изменение статуса заявок: userId = {}, eventId = {}, status = {}", userId, eventId, dto.getStatus()); + + User user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь c ID " + userId + " не найден")); + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Событие c ID " + eventId + " не найдено")); + + List requests = eventRequestRepository.findAllById(dto.getRequestIds()); + EventRequestStatusUpdateResult result = EventRequestStatusUpdateResult.builder() + .confirmedRequests(new ArrayList<>()) + .rejectedRequests(new ArrayList<>()) + .build(); + + if (requests.isEmpty()) { + return result; + } + + RequestStatus targetStatus = RequestStatus.valueOf(String.valueOf(dto.getStatus())); + + int limit = event.getParticipantLimit(); + long confirmed = eventRequestRepository.countByEventIdAndStatus(event.getId(), RequestStatus.CONFIRMED); + + if (targetStatus == RequestStatus.CONFIRMED) { + if (limit != 0 && confirmed >= limit) { + throw new ConflictException("Достигнут лимит участников."); + } + + for (EventRequest request : requests) { + if (!request.getEvent().getId().equals(eventId)) { + throw new NotFoundException("Запрос не относится к данному событию."); + } + if (request.getStatus() != RequestStatus.PENDING) { + throw new ConflictException("Изменять можно только заявки в статусе PENDING."); + } + request.setStatus(RequestStatus.CONFIRMED); + result.getConfirmedRequests().add(EventRequestMapper.toRequestDto(request)); + confirmed++; + if (confirmed == limit) { + break; + } + } + + } else if (targetStatus == RequestStatus.REJECTED) { + for (EventRequest request : requests) { + if (!request.getEvent().getId().equals(eventId)) { + throw new NotFoundException("Запрос не относится к данному событию."); + } + if (request.getStatus() != RequestStatus.PENDING) { + throw new ConflictException("Изменять можно только заявки в статусе PENDING."); + } + request.setStatus(RequestStatus.REJECTED); + result.getRejectedRequests().add(EventRequestMapper.toRequestDto(request)); + } + } + + eventRequestRepository.saveAll(requests); + return result; + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/user/UserRepository.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/UserRepository.java new file mode 100644 index 0000000..1dbd70a --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/UserRepository.java @@ -0,0 +1,16 @@ +package ru.practicum.ewm.user; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.ewm.user.model.User; + +import java.util.Collection; +import java.util.List; + +public interface UserRepository extends JpaRepository { + + boolean existsByEmail(String email); + + List findByIdIn(Collection ids, Pageable pageable); + +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/user/controller/AdminUserController.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/controller/AdminUserController.java new file mode 100644 index 0000000..b64f584 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/controller/AdminUserController.java @@ -0,0 +1,50 @@ +package ru.practicum.ewm.user.controller; + +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.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.user.dto.NewUserRequest; +import ru.practicum.ewm.user.dto.UserDto; +import ru.practicum.ewm.user.service.UserService; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/admin/users") +@RequiredArgsConstructor +public class AdminUserController { + + private final UserService service; + + @ResponseStatus(HttpStatus.CREATED) + @PostMapping + public UserDto create(@RequestBody @Valid NewUserRequest newUserRequest) { + log.info("POST запрос на создание пользователя: {}", newUserRequest); + UserDto userDto = service.create(newUserRequest); + log.info("Пользователь создан: {}", userDto); + return userDto; + } + + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + log.info("DELETE запрос на удаление пользователя с id: {}", id); + service.delete(id); + log.info("Пользователь с id {} удален", id); + } + + @GetMapping + public List getAllUsers(@RequestParam(required = false) List ids, + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { + log.info("GET запрос на получение списка пользователей с параметрами: ids={}, from={}, size={}", ids, from, size); + List users = service.getAllUsers(ids, from, size); + log.info("Возвращен список пользователей: {}", users); + return users; + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/user/dto/NewUserRequest.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/dto/NewUserRequest.java new file mode 100644 index 0000000..6c54049 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/dto/NewUserRequest.java @@ -0,0 +1,23 @@ +package ru.practicum.ewm.user.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.*; +import lombok.experimental.FieldDefaults; + +@FieldDefaults(level = AccessLevel.PRIVATE) +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class NewUserRequest { + @NotBlank(message = "Поле name не может быть пустым, заполните поле name от 2 до 250 символов!") + @Size(min = 2, max = 250, message = "Длина name должна составлять от 2 до 250 символов!") + String name; + + @NotBlank(message = "Поле email не может быть пустым!") + @Size(min = 6, max = 254, message = "Длина email должна составлять от 6 до 254 символов!") + @Email(message = "Email должен быть в подходящем формате!") + String email; +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/user/dto/UserDto.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/dto/UserDto.java new file mode 100644 index 0000000..af667e4 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/dto/UserDto.java @@ -0,0 +1,15 @@ +package ru.practicum.ewm.user.dto; + +import lombok.*; +import lombok.experimental.FieldDefaults; + +@FieldDefaults(level = AccessLevel.PRIVATE) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class UserDto { + Long id; + String name; + String email; +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/user/dto/UserShortDto.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/dto/UserShortDto.java new file mode 100644 index 0000000..6006df1 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/dto/UserShortDto.java @@ -0,0 +1,14 @@ +package ru.practicum.ewm.user.dto; + +import lombok.*; +import lombok.experimental.FieldDefaults; + +@FieldDefaults(level = AccessLevel.PRIVATE) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class UserShortDto { + Long id; + String name; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/user/mapper/UserMapper.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/mapper/UserMapper.java new file mode 100644 index 0000000..11c7ebb --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/mapper/UserMapper.java @@ -0,0 +1,31 @@ +package ru.practicum.ewm.user.mapper; + +import ru.practicum.ewm.user.dto.NewUserRequest; +import ru.practicum.ewm.user.dto.UserDto; +import ru.practicum.ewm.user.dto.UserShortDto; +import ru.practicum.ewm.user.model.User; + +public class UserMapper { + + public static User toNewUserFromRequest(NewUserRequest request) { + return User.builder() + .name(request.getName()) + .email(request.getEmail()) + .build(); + } + + public static UserDto toUserDto(User user) { + return UserDto.builder() + .id(user.getId()) + .name(user.getName()) + .email(user.getEmail()) + .build(); + } + + public static UserShortDto toUserShortDto(User user) { + return UserShortDto.builder() + .id(user.getId()) + .name(user.getName()) + .build(); + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/user/model/User.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/model/User.java new file mode 100644 index 0000000..12fe985 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/model/User.java @@ -0,0 +1,24 @@ +package ru.practicum.ewm.user.model; + +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.FieldDefaults; + +@FieldDefaults(level = AccessLevel.PRIVATE) +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @Column(nullable = false, length = 250) + String name; + + @Column(unique = true, nullable = false, length = 254) + String email; +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/user/service/UserService.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/service/UserService.java new file mode 100644 index 0000000..723513b --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/service/UserService.java @@ -0,0 +1,14 @@ +package ru.practicum.ewm.user.service; + +import ru.practicum.ewm.user.dto.NewUserRequest; +import ru.practicum.ewm.user.dto.UserDto; + +import java.util.List; + +public interface UserService { + UserDto create(NewUserRequest newUserRequest); + + void delete(Long id); + + List getAllUsers(List ids, int from, int size); +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/user/service/UserServiceImpl.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/service/UserServiceImpl.java new file mode 100644 index 0000000..44ff767 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/user/service/UserServiceImpl.java @@ -0,0 +1,55 @@ +package ru.practicum.ewm.user.service; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +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.ewm.exception.DuplicatedDataException; +import ru.practicum.ewm.user.UserRepository; +import ru.practicum.ewm.user.dto.NewUserRequest; +import ru.practicum.ewm.user.dto.UserDto; +import ru.practicum.ewm.user.mapper.UserMapper; +import ru.practicum.ewm.user.model.User; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class UserServiceImpl implements UserService { + + UserRepository userRepository; + + @Override + public UserDto create(NewUserRequest newUserRequest) { + if (userRepository.existsByEmail(newUserRequest.getEmail())) { + throw new DuplicatedDataException("Email уже зарегистрирован: " + newUserRequest.getEmail()); + } + log.info("Создание пользователя с данными: {}", newUserRequest); + User user = UserMapper.toNewUserFromRequest(newUserRequest); + User createdUser = userRepository.save(user); + return UserMapper.toUserDto(createdUser); + } + + @Override + public void delete(Long id) { + userRepository.deleteById(id); + } + + @Transactional(readOnly = true) + @Override + public List getAllUsers(List ids, int from, int size) { + int page = from > 0 ? from / size : 0; + Pageable pageable = PageRequest.of(page, size); + return (ids != null) ? userRepository.findByIdIn(ids, pageable) + .stream().map(UserMapper::toUserDto).collect(Collectors.toList()) : userRepository.findAll(pageable) + .stream().map(UserMapper::toUserDto).collect(Collectors.toList()); + } +} \ No newline at end of file From 5b62112b591de698da45b7a1682aa0f726711ee7 Mon Sep 17 00:00:00 2001 From: pavelrk97 Date: Sat, 13 Sep 2025 19:58:36 +0300 Subject: [PATCH 4/7] =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=88=D0=B5=D0=BB=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 75 +++++++---- .../main/java/ru/practicum/ewm/MainApp.java | 2 + .../compilation/CompilationRepository.java | 12 ++ .../CompilationAdminController.java | 46 +++++++ .../CompilationPublicController.java | 38 ++++++ .../ewm/compilation/dto/CompilationDto.java | 19 +++ .../compilation/dto/NewCompilationDto.java | 22 +++ .../dto/UpdateCompilationRequest.java | 21 +++ .../compilation/mapper/CompilationMapper.java | 30 +++++ .../ewm/compilation/model/Compilation.java | 31 +++++ .../service/CompilationService.java | 19 +++ .../service/CompilationServiceImpl.java | 125 ++++++++++++++++++ .../main/resources/application-h2.properties | 5 + .../resources/application-postgres.properties | 5 + .../src/main/resources/application.properties | 15 ++- ewm-main-svc/src/main/resources/schema.sql | 68 ++++++++++ pom.xml | 11 +- stat-svc/pom.xml | 4 +- .../{stat-server => stat-client}/Dockerfile | 0 stat-svc/stat-client/pom.xml | 15 ++- .../ru/practicum/stat/StatisticsClient.java | 13 +- .../ru/practicum/stat/base/BaseClient.java | 28 +++- stat-svc/stat-dto/pom.xml | 4 +- stat-svc/stats-server/Dockerfile | 5 + .../{stat-server => stats-server}/pom.xml | 6 +- .../practicum/stat/EndpointHitRepository.java | 0 .../ru/practicum/stat/StatisticsServer.java | 0 .../stat/controller/StatsController.java | 0 .../stat/exception/ErrorHandler.java | 0 .../stat/exception/ErrorResponse.java | 0 .../stat/mapper/EndpointHitMapper.java | 0 .../stat/mapper/ViewStatsMapper.java | 0 .../ru/practicum/stat/model/EndpointHit.java | 0 .../ru/practicum/stat/model/ViewStats.java | 0 .../stat/service/StatisticsService.java | 0 .../stat/service/StatisticsServiceImpl.java | 0 .../main/resources/application-h2.properties | 0 .../resources/application-postgres.properties | 0 .../src/main/resources/application.properties | 0 .../src/main/resources/schema.sql | 0 40 files changed, 560 insertions(+), 59 deletions(-) create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/CompilationRepository.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/controller/CompilationAdminController.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/controller/CompilationPublicController.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/dto/CompilationDto.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/dto/NewCompilationDto.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/dto/UpdateCompilationRequest.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/mapper/CompilationMapper.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/model/Compilation.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/service/CompilationService.java create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/service/CompilationServiceImpl.java create mode 100644 ewm-main-svc/src/main/resources/application-h2.properties create mode 100644 ewm-main-svc/src/main/resources/application-postgres.properties create mode 100644 ewm-main-svc/src/main/resources/schema.sql rename stat-svc/{stat-server => stat-client}/Dockerfile (100%) create mode 100644 stat-svc/stats-server/Dockerfile rename stat-svc/{stat-server => stats-server}/pom.xml (94%) rename stat-svc/{stat-server => stats-server}/src/main/java/ru/practicum/stat/EndpointHitRepository.java (100%) rename stat-svc/{stat-server => stats-server}/src/main/java/ru/practicum/stat/StatisticsServer.java (100%) rename stat-svc/{stat-server => stats-server}/src/main/java/ru/practicum/stat/controller/StatsController.java (100%) rename stat-svc/{stat-server => stats-server}/src/main/java/ru/practicum/stat/exception/ErrorHandler.java (100%) rename stat-svc/{stat-server => stats-server}/src/main/java/ru/practicum/stat/exception/ErrorResponse.java (100%) rename stat-svc/{stat-server => stats-server}/src/main/java/ru/practicum/stat/mapper/EndpointHitMapper.java (100%) rename stat-svc/{stat-server => stats-server}/src/main/java/ru/practicum/stat/mapper/ViewStatsMapper.java (100%) rename stat-svc/{stat-server => stats-server}/src/main/java/ru/practicum/stat/model/EndpointHit.java (100%) rename stat-svc/{stat-server => stats-server}/src/main/java/ru/practicum/stat/model/ViewStats.java (100%) rename stat-svc/{stat-server => stats-server}/src/main/java/ru/practicum/stat/service/StatisticsService.java (100%) rename stat-svc/{stat-server => stats-server}/src/main/java/ru/practicum/stat/service/StatisticsServiceImpl.java (100%) rename stat-svc/{stat-server => stats-server}/src/main/resources/application-h2.properties (100%) rename stat-svc/{stat-server => stats-server}/src/main/resources/application-postgres.properties (100%) rename stat-svc/{stat-server => stats-server}/src/main/resources/application.properties (100%) rename stat-svc/{stat-server => stats-server}/src/main/resources/schema.sql (100%) diff --git a/docker-compose.yml b/docker-compose.yml index f3b78d9..7ab4778 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,59 +1,76 @@ services: stats-server: build: - context: ./stat-svc/stat-server + context: ./stat-svc/stats-server image: stats-server - container_name: stat-server + container_name: stats-server ports: - "9090:9090" depends_on: - - statdb + statdb: + condition: service_healthy environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://statdb:5432/statdb - SPRING_DATASOURCE_USERNAME=admin - SPRING_DATASOURCE_PASSWORD=admin - - statdb: - image: postgres:16.1 - container_name: statdb - ports: - - "6541:5432" - environment: - - POSTGRES_PASSWORD=admin - - POSTGRES_USER=admin - - POSTGRES_DB=statdb + - STATS_SERVER_URL=http://stats-server:9090 healthcheck: - test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER - timeout: 5s - interval: 5s - retries: 10 + test: [ "CMD-SHELL", "curl -f http://localhost:9090/actuator/health | grep UP || exit 1" ] + interval: 10s + timeout: 10s + retries: 5 ewm-service: build: context: ./ewm-main-svc image: ewm-service container_name: ewm-service + ports: + - "8080:8080" environment: - SPRING_DATASOURCE_URL: jdbc:postgresql://ewm-db:5432/ewm-db + SPRING_DATASOURCE_URL: jdbc:postgresql://ewmdb:5432/ewmdb SPRING_DATASOURCE_USERNAME: admin SPRING_DATASOURCE_PASSWORD: admin - ports: - - "8080:8080" + SPRING_PROFILES_ACTIVE: postgres + STATS_SERVER_URL: http://stats-server:9090 depends_on: - - stats-server - - ewm-db + ewmdb: + condition: service_healthy + stats-server: + condition: service_healthy + healthcheck: + test: [ "CMD-SHELL", "curl -f http://localhost:8080/actuator/health | grep UP || exit 1" ] + interval: 10s + timeout: 10s + retries: 5 + + statdb: + image: postgres:16.1 + container_name: statdb + ports: + - "6541:5432" + environment: + - POSTGRES_PASSWORD=admin + - POSTGRES_USER=admin + - POSTGRES_DB=statdb + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U admin -d statdb" ] + interval: 10s + timeout: 10s + retries: 5 - ewm-db: + ewmdb: image: postgres:16.1 - container_name: ewm-db + container_name: ewmdb ports: - "6542:5432" environment: - - POSTGRES_DB=ewm-db + - POSTGRES_DB=ewmdb - POSTGRES_USER=admin - POSTGRES_PASSWORD=admin healthcheck: - test: pg_isready -q -d $$POSTGRES_DB -U $$POSTGRES_USER - timeout: 5s - interval: 5s - retries: 10 \ No newline at end of file + test: [ "CMD-SHELL", "pg_isready -U admin -d ewmdb" ] + interval: 10s + timeout: 10s + retries: 5 + \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/MainApp.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/MainApp.java index 908ca46..60d6014 100644 --- a/ewm-main-svc/src/main/java/ru/practicum/ewm/MainApp.java +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/MainApp.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; @SpringBootApplication +@ComponentScan({"ru.practicum.ewm", "ru.practicum.stat"}) public class MainApp { public static void main(String[] args) { SpringApplication.run(MainApp.class, args); diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/CompilationRepository.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/CompilationRepository.java new file mode 100644 index 0000000..a6cbdf6 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/CompilationRepository.java @@ -0,0 +1,12 @@ +package ru.practicum.ewm.compilation; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.ewm.compilation.model.Compilation; + +import java.util.List; + +public interface CompilationRepository extends JpaRepository { + + List findAllByPinned(Boolean pinned, Pageable pageable); +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/controller/CompilationAdminController.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/controller/CompilationAdminController.java new file mode 100644 index 0000000..d70e4c1 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/controller/CompilationAdminController.java @@ -0,0 +1,46 @@ +package ru.practicum.ewm.compilation.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.compilation.dto.CompilationDto; +import ru.practicum.ewm.compilation.dto.NewCompilationDto; +import ru.practicum.ewm.compilation.dto.UpdateCompilationRequest; +import ru.practicum.ewm.compilation.service.CompilationService; + +@Slf4j +@RestController +@RequestMapping("/admin/compilations") +@RequiredArgsConstructor +public class CompilationAdminController { + + private final CompilationService compilationService; + + @ResponseStatus(HttpStatus.CREATED) + @PostMapping + public CompilationDto create(@Valid @RequestBody NewCompilationDto newCompilationDto) { + log.info("POST запрос на добавление подборки событий - ADMIN: {}", newCompilationDto); + CompilationDto compilationDto = compilationService.create(newCompilationDto); + log.info("Подборка событий создана: {}", compilationDto); + return compilationDto; + } + + @PatchMapping("/{compId}") + public CompilationDto update(@Valid @RequestBody UpdateCompilationRequest updateCompilation, + @PathVariable Long compId) { + log.info("PATCH запрос на обновление подборки событий с id={} - ADMIN: {}", compId, updateCompilation); + CompilationDto compilationDto = compilationService.update(compId, updateCompilation); + log.info("Подборка событий с id={} обновлена: {}", compId, compilationDto); + return compilationDto; + } + + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + log.info("DELETE запрос на удаление подборки событий с id={} - ADMIN", id); + compilationService.delete(id); // Удаляем подборку + log.info("Подборка событий с id={} удалена", id); + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/controller/CompilationPublicController.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/controller/CompilationPublicController.java new file mode 100644 index 0000000..3d460a9 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/controller/CompilationPublicController.java @@ -0,0 +1,38 @@ +package ru.practicum.ewm.compilation.controller; + +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.compilation.dto.CompilationDto; +import ru.practicum.ewm.compilation.service.CompilationService; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/compilations") +@RequiredArgsConstructor +public class CompilationPublicController { + private final CompilationService compilationService; + + @GetMapping + public List getAllCompilations( + @RequestParam(required = false) Boolean pinned, + @RequestParam(defaultValue = "0")@PositiveOrZero Integer from, + @RequestParam(defaultValue = "10")@Positive Integer size) { + log.info("GET запрос на получение подборок событий. pinned={}, from={}, size={}", pinned, from, size); + List compilations = compilationService.getAllCompilations(from, size, pinned); + log.info("Возвращен список подборок событий: {}", compilations); + return compilations; + } + + @GetMapping("/{compId}") + public CompilationDto findCompilationById(@PathVariable Long compId) { + log.info("GET запрос на получение подборки событий по id = {}", compId); + CompilationDto compilationDto = compilationService.findCompilationById(compId); + log.info("Возвращена подборка событий по id = {}: {}", compId, compilationDto); + return compilationDto; + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/dto/CompilationDto.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/dto/CompilationDto.java new file mode 100644 index 0000000..b30a034 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/dto/CompilationDto.java @@ -0,0 +1,19 @@ +package ru.practicum.ewm.compilation.dto; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.event.dto.EventShortDto; + +import java.util.List; + +@FieldDefaults(level = AccessLevel.PRIVATE) +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class CompilationDto { + Long id; + List events; + Boolean pinned; + String title; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/dto/NewCompilationDto.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/dto/NewCompilationDto.java new file mode 100644 index 0000000..66abfa3 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/dto/NewCompilationDto.java @@ -0,0 +1,22 @@ +package ru.practicum.ewm.compilation.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.*; +import lombok.experimental.FieldDefaults; + +import java.util.Set; + +@FieldDefaults(level = AccessLevel.PRIVATE) +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class NewCompilationDto { + Set events; + Boolean pinned; + + @NotBlank + @Size(min = 1, max = 50) + String title; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/dto/UpdateCompilationRequest.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/dto/UpdateCompilationRequest.java new file mode 100644 index 0000000..622c766 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/dto/UpdateCompilationRequest.java @@ -0,0 +1,21 @@ +package ru.practicum.ewm.compilation.dto; + +import jakarta.validation.constraints.Size; +import lombok.*; +import lombok.experimental.FieldDefaults; + +import java.util.Set; + +@FieldDefaults(level = AccessLevel.PRIVATE) +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class UpdateCompilationRequest { + Long id; + Set events; + Boolean pinned; + + @Size(min = 1, max = 50) + String title; +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/mapper/CompilationMapper.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/mapper/CompilationMapper.java new file mode 100644 index 0000000..aa8cb57 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/mapper/CompilationMapper.java @@ -0,0 +1,30 @@ +package ru.practicum.ewm.compilation.mapper; + +import ru.practicum.ewm.compilation.dto.CompilationDto; +import ru.practicum.ewm.compilation.dto.NewCompilationDto; +import ru.practicum.ewm.compilation.model.Compilation; +import ru.practicum.ewm.event.dto.EventShortDto; +import ru.practicum.ewm.event.model.Event; + +import java.util.List; +import java.util.Set; + +public class CompilationMapper { + + public static Compilation toCompilationFromNew(NewCompilationDto newCompilationDto, Set events) { + return Compilation.builder() + .pinned(newCompilationDto.getPinned()) + .title(newCompilationDto.getTitle()) + .events(events) + .build(); + } + + public static CompilationDto toCompilationDto(Compilation compilation, List events) { + return CompilationDto.builder() + .id(compilation.getId()) + .pinned(compilation.getPinned()) + .title(compilation.getTitle()) + .events(events) + .build(); + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/model/Compilation.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/model/Compilation.java new file mode 100644 index 0000000..f3960a2 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/model/Compilation.java @@ -0,0 +1,31 @@ +package ru.practicum.ewm.compilation.model; + +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.event.model.Event; + +import java.util.Set; + +@FieldDefaults(level = AccessLevel.PRIVATE) +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Entity +@Table(name = "compilations") +public class Compilation { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + Boolean pinned; + + @Column(nullable = false, length = 50) + String title; + + @ManyToMany + @JoinTable(name = "compilations_event", + joinColumns = @JoinColumn(name = "compilation_id"), + inverseJoinColumns = @JoinColumn(name = "event_id")) + Set events; +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/service/CompilationService.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/service/CompilationService.java new file mode 100644 index 0000000..f211522 --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/service/CompilationService.java @@ -0,0 +1,19 @@ +package ru.practicum.ewm.compilation.service; + +import ru.practicum.ewm.compilation.dto.CompilationDto; +import ru.practicum.ewm.compilation.dto.NewCompilationDto; +import ru.practicum.ewm.compilation.dto.UpdateCompilationRequest; + +import java.util.List; + +public interface CompilationService { + CompilationDto create(NewCompilationDto newCompilationDto); + + CompilationDto update(Long compId, UpdateCompilationRequest updateCompilationRequest); + + void delete(Long id); + + List getAllCompilations(Integer from, Integer size, Boolean pinned); + + CompilationDto findCompilationById(Long compId); +} diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/service/CompilationServiceImpl.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/service/CompilationServiceImpl.java new file mode 100644 index 0000000..94e800f --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/compilation/service/CompilationServiceImpl.java @@ -0,0 +1,125 @@ +package ru.practicum.ewm.compilation.service; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.ewm.compilation.CompilationRepository; +import ru.practicum.ewm.compilation.dto.CompilationDto; +import ru.practicum.ewm.compilation.dto.NewCompilationDto; +import ru.practicum.ewm.compilation.dto.UpdateCompilationRequest; +import ru.practicum.ewm.compilation.mapper.CompilationMapper; +import ru.practicum.ewm.compilation.model.Compilation; +import ru.practicum.ewm.event.EventRepository; +import ru.practicum.ewm.event.dto.EventShortDto; +import ru.practicum.ewm.event.mapper.EventMapper; +import ru.practicum.ewm.event.model.Event; +import ru.practicum.ewm.exception.NotFoundException; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class CompilationServiceImpl implements CompilationService { + + CompilationRepository compilationRepository; + EventRepository eventRepository; + + @Override + public CompilationDto create(NewCompilationDto newCompilationDto) { + log.info("Добавление подборки: {}", newCompilationDto); + Set eventIds = Optional.ofNullable(newCompilationDto.getEvents()).orElse(Collections.emptySet()); + List events = eventRepository.findAllByIdIn(new ArrayList<>(eventIds)); + + Compilation compilation = CompilationMapper.toCompilationFromNew(newCompilationDto, new HashSet<>(events)); + compilation.setPinned(Optional.ofNullable(compilation.getPinned()).orElse(false)); + Compilation savedCompilation = compilationRepository.save(compilation); + log.info("Подборка сохранена: {}", savedCompilation); + + List eventShortDtos = events.stream() + .map(EventMapper::toEventShortDto) + .collect(Collectors.toList()); + + return CompilationMapper.toCompilationDto(savedCompilation, eventShortDtos); + } + + @Override + public CompilationDto update(Long compId, UpdateCompilationRequest updateCompilationRequest) { + Compilation compilation = compilationRepository.findById(compId) + .orElseThrow(() -> new NotFoundException("Подборка c ID " + compId + " не найдена")); + log.info("Обновление подборки c {}, на {}", updateCompilationRequest.toString(), compilation.toString()); + if (updateCompilationRequest.getEvents() != null) { + Set eventIds = updateCompilationRequest.getEvents(); + List events = eventRepository.findAllByIdIn(new ArrayList<>(eventIds)); + compilation.setEvents(new HashSet<>(events)); + log.trace("Events = {}", compilation.getEvents()); + } + compilation.setPinned(Optional.ofNullable(updateCompilationRequest.getPinned()).orElse(false)); + log.trace("Pinned = {}", compilation.getPinned()); + + compilation.setTitle(Optional.ofNullable(updateCompilationRequest.getTitle()).orElse(compilation.getTitle())); + Compilation updatedCompilation = compilationRepository.save(compilation); + log.info("Подборка обновлена: {}", compilation); + List eventShortDtos = updatedCompilation.getEvents().stream() + .map(EventMapper::toEventShortDto) + .collect(Collectors.toList()); + + return CompilationMapper.toCompilationDto(updatedCompilation, eventShortDtos); + } + + @Override + public void delete(Long id) { + compilationRepository.deleteById(id); + log.info("Подборка удалена"); + } + + @Transactional(readOnly = true) + @Override + public List getAllCompilations(Integer from, Integer size, Boolean pinned) { + log.info("Получение всех подборок с from={}, size={}, pinned={}", from, size, pinned); + PageRequest pageRequest = PageRequest.of(from, size); + List compilations; + if (pinned != null) { + log.info("Получение всех подборок с pinned: {}", pinned); + compilations = compilationRepository.findAllByPinned(pinned, pageRequest); + log.info("Получены подборки с pinned={}: {}", pinned, compilations); + } else { + log.info("Получение всех подборок без фильтрации по pinned"); + compilations = compilationRepository.findAll(pageRequest).getContent(); + log.info("Получены все подборки: {}", compilations); + + } + return compilations.stream() + .map(compilation -> { + List eventShortDtos = compilation.getEvents().stream() + .map(EventMapper::toEventShortDto) + .collect(Collectors.toList()); + + return CompilationMapper.toCompilationDto(compilation, eventShortDtos); + }) + .collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + @Override + public CompilationDto findCompilationById(Long compId) { + log.info("Получение подборки с compId={}", compId); + Compilation compilation = compilationRepository.findById(compId) + .orElseThrow(() -> new NotFoundException("Подборка c ID " + compId + " не найдена")); + + log.info("Подборка найдена: {}", compilation); + + List eventShortDtos = compilation.getEvents().stream() + .map(EventMapper::toEventShortDto) + .collect(Collectors.toList()); + + return CompilationMapper.toCompilationDto(compilation, eventShortDtos); + } +} \ No newline at end of file diff --git a/ewm-main-svc/src/main/resources/application-h2.properties b/ewm-main-svc/src/main/resources/application-h2.properties new file mode 100644 index 0000000..d2c9aee --- /dev/null +++ b/ewm-main-svc/src/main/resources/application-h2.properties @@ -0,0 +1,5 @@ +spring.config.activate.on-profile=h2 +spring.datasource.driverClassName = org.h2.Driver +spring.datasource.url = jdbc:h2:file:./db/ewmdb +spring.datasource.username = admin +spring.datasource.password = admin \ No newline at end of file diff --git a/ewm-main-svc/src/main/resources/application-postgres.properties b/ewm-main-svc/src/main/resources/application-postgres.properties new file mode 100644 index 0000000..9c6bcaf --- /dev/null +++ b/ewm-main-svc/src/main/resources/application-postgres.properties @@ -0,0 +1,5 @@ +spring.config.activate.on-profile=postgres +spring.datasource.driverClassName = org.postgresql.Driver +spring.datasource.url = jdbc:postgresql://ewmdb:5432/ewmdb +spring.datasource.username = admin +spring.datasource.password = admin \ No newline at end of file diff --git a/ewm-main-svc/src/main/resources/application.properties b/ewm-main-svc/src/main/resources/application.properties index 68918f8..a0f198c 100644 --- a/ewm-main-svc/src/main/resources/application.properties +++ b/ewm-main-svc/src/main/resources/application.properties @@ -1,7 +1,12 @@ -logging.level.org.springframework.web.client.RestTemplate=DEBUG -#logging.level.org.apache.http=DEBUG -#logging.level.httpclient.wire=DEBUG - server.port=8080 +stats-server.url=http://stats-server:9090 +app.name=ewm-main-service +spring.jpa.hibernate.ddl-auto=none +spring.jpa.properties.hibernate.format_sql=true +spring.sql.init.mode=always -stat-server.url=${STAT_SERVER_URL:http://localhost:9090} \ No newline at end of file +logging.level.org.springframework.orm.jpa=INFO +logging.level.org.springframework.transaction=INFO +logging.level.org.springframework.transaction.interceptor=TRACE +logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG +logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE \ No newline at end of file diff --git a/ewm-main-svc/src/main/resources/schema.sql b/ewm-main-svc/src/main/resources/schema.sql new file mode 100644 index 0000000..1e7f724 --- /dev/null +++ b/ewm-main-svc/src/main/resources/schema.sql @@ -0,0 +1,68 @@ +DROP TABLE IF EXISTS users CASCADE; +DROP TABLE IF EXISTS categories CASCADE; +DROP TABLE IF EXISTS locations CASCADE; +DROP TABLE IF EXISTS events CASCADE; +DROP TABLE IF EXISTS compilations CASCADE; +DROP TABLE IF EXISTS compilations_events CASCADE; +DROP TABLE IF EXISTS requests CASCADE; + +CREATE TABLE IF NOT EXISTS users( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(250) NOT NULL, + email VARCHAR(254) NOT NULL UNIQUE +); + +CREATE TABLE IF NOT EXISTS categories( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(50) NOT NULL UNIQUE +); + +CREATE TABLE IF NOT EXISTS locations ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + lat DOUBLE PRECISION NOT NULL, + lon DOUBLE PRECISION NOT NULL +); + +CREATE TABLE IF NOT EXISTS events ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + location_id BIGINT NOT NULL, + initiator_id BIGINT NOT NULL, + category_id BIGINT NOT NULL, + title VARCHAR(120) NOT NULL, + annotation VARCHAR(2000) NOT NULL, + description VARCHAR(7000) NOT NULL, + event_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + paid BOOLEAN NOT NULL DEFAULT false, + participant_limit INT NOT NULL DEFAULT 0, + request_moderation BOOLEAN NOT NULL DEFAULT false, + state VARCHAR(50) NOT NULL, + created_on TIMESTAMP WITHOUT TIME ZONE NOT NULL, + published_on TIMESTAMP WITHOUT TIME ZONE, + FOREIGN KEY (location_id) REFERENCES locations(id) ON DELETE CASCADE, + FOREIGN KEY (initiator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE +); + + +CREATE TABLE IF NOT EXISTS compilations ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY UNIQUE, + pinned BOOLEAN NOT NULL, + title VARCHAR(50) NOT NULL +); + +CREATE TABLE IF NOT EXISTS compilations_event( + compilation_id BIGINT REFERENCES compilations(id) ON DELETE CASCADE, + event_id BIGINT REFERENCES events(id) ON DELETE CASCADE, + PRIMARY KEY (compilation_id, event_id) +); + +CREATE TABLE IF NOT EXISTS requests ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + event_id BIGINT NOT NULL, + requester_id BIGINT NOT NULL, + status VARCHAR(50) NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE, + FOREIGN KEY (requester_id) REFERENCES users(id) ON DELETE CASCADE, + CONSTRAINT unique_request UNIQUE (event_id, requester_id) +); \ No newline at end of file diff --git a/pom.xml b/pom.xml index f43f89d..8acb6b7 100644 --- a/pom.xml +++ b/pom.xml @@ -11,16 +11,19 @@ Explore With Me - - ewm-main-svc - stat-svc - ru.practicum explore-with-me 0.0.1-SNAPSHOT pom + + ewm-main-svc + stat-svc + + + + 21 UTF-8 diff --git a/stat-svc/pom.xml b/stat-svc/pom.xml index 2f46f6e..94bedb7 100644 --- a/stat-svc/pom.xml +++ b/stat-svc/pom.xml @@ -10,12 +10,14 @@ stat-svc + 0.0.1-SNAPSHOT pom + stat-client stat-dto - stat-server + stats-server diff --git a/stat-svc/stat-server/Dockerfile b/stat-svc/stat-client/Dockerfile similarity index 100% rename from stat-svc/stat-server/Dockerfile rename to stat-svc/stat-client/Dockerfile diff --git a/stat-svc/stat-client/pom.xml b/stat-svc/stat-client/pom.xml index 6011f30..10f29b9 100644 --- a/stat-svc/stat-client/pom.xml +++ b/stat-svc/stat-client/pom.xml @@ -5,12 +5,12 @@ 4.0.0 ru.practicum - explore-with-me + stat-svc 0.0.1-SNAPSHOT - ../../pom.xml stat-client + 0.0.1-SNAPSHOT 22 @@ -23,14 +23,21 @@ ru.practicum stat-dto 0.0.1-SNAPSHOT - compile + + org.springframework.boot + spring-boot-starter-webflux + org.springframework.boot spring-boot-starter-web - + + org.projectlombok + lombok + true + org.springframework.boot spring-boot-starter-actuator diff --git a/stat-svc/stat-client/src/main/java/ru/practicum/stat/StatisticsClient.java b/stat-svc/stat-client/src/main/java/ru/practicum/stat/StatisticsClient.java index 212f121..78aaeaa 100644 --- a/stat-svc/stat-client/src/main/java/ru/practicum/stat/StatisticsClient.java +++ b/stat-svc/stat-client/src/main/java/ru/practicum/stat/StatisticsClient.java @@ -23,19 +23,21 @@ public class StatisticsClient extends BaseClient { private final String appName; @Autowired - public StatisticsClient(@Value("${stat-server.url}") String serverUrl, + public StatisticsClient(@Value("${stats-server.url}") String serverUrl, @Value("${app.name}") String appName, RestTemplateBuilder builder) { super( builder .uriTemplateHandler(new DefaultUriBuilderFactory(serverUrl)) .requestFactory(() -> new HttpComponentsClientHttpRequestFactory()) - .build() + .build(), + serverUrl ); this.appName = appName; } public ResponseEntity create(HttpServletRequest request) { + EndpointHitCreateDto endpointHitCreateDto = EndpointHitCreateDto.builder() .app(appName) .uri(request.getRequestURI()) @@ -46,15 +48,18 @@ public ResponseEntity create(HttpServletRequest request) { } public ResponseEntity getStats(LocalDateTime start, LocalDateTime end, List uris, Boolean unique) { + UriComponentsBuilder builder = UriComponentsBuilder.fromPath("/stats") .queryParam("start", start.format(formatter)) .queryParam("end", end.format(formatter)) .queryParam("unique", unique); if (uris != null && !uris.isEmpty()) { - builder.queryParam("uris", String.join(",", uris)); + builder.queryParam("uris", uris); } - String url = builder.toUriString(); + + String url = builder.build().toUriString(); + return get(url); } } \ No newline at end of file diff --git a/stat-svc/stat-client/src/main/java/ru/practicum/stat/base/BaseClient.java b/stat-svc/stat-client/src/main/java/ru/practicum/stat/base/BaseClient.java index 9cc744b..2eaa3ca 100644 --- a/stat-svc/stat-client/src/main/java/ru/practicum/stat/base/BaseClient.java +++ b/stat-svc/stat-client/src/main/java/ru/practicum/stat/base/BaseClient.java @@ -1,36 +1,50 @@ package ru.practicum.stat.base; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.*; import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; - +import org.springframework.beans.factory.annotation.Value; import java.util.List; +@Slf4j public class BaseClient { protected final RestTemplate rest; + private final String statsUri; - public BaseClient(RestTemplate rest) { + public BaseClient(RestTemplate rest,@Value("${stats-server.url}") String statsUri) { this.rest = rest; + this.statsUri = statsUri; } protected ResponseEntity get(String path) { - return makeAndSendRequest(path); + return makeAndSendRequest(statsUri + path); } protected ResponseEntity post(Object body) { HttpEntity requestEntity = new HttpEntity<>(body); - return rest.postForEntity("/hit", requestEntity, Object.class); + try { + log.info("Отправка POST запроса на URL: {}, тело: {}", statsUri + "/hit", body); + ResponseEntity response = rest.postForEntity(statsUri + "/hit", requestEntity, Object.class); + log.info("Получен ответ от сервиса статистики, статус: {}", response.getStatusCode()); + return response; + } catch (HttpStatusCodeException e) { + log.error("Ошибка при отправке POST запроса: {}, тело ответа: {}", e.getStatusCode(), e.getResponseBodyAsString()); + return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsString()); + } } private ResponseEntity makeAndSendRequest(String path) { HttpEntity requestEntity = new HttpEntity<>(null, defaultHeaders()); - ResponseEntity responseEntity; try { + log.info("Отправка GET запроса на URL: {}", path); responseEntity = rest.exchange(path, HttpMethod.GET, requestEntity, Object.class); + log.info("Получен ответ от сервиса статистики, статус: {}", responseEntity.getStatusCode()); } catch (HttpStatusCodeException e) { - return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsByteArray()); + log.error("Ошибка при отправке GET запроса: {}, тело ответа: {}", e.getStatusCode(), e.getResponseBodyAsString()); + return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsString()); } return prepareResponse(responseEntity); } @@ -52,4 +66,4 @@ private static ResponseEntity prepareResponse(ResponseEntity res } return responseBuilder.build(); } -} \ No newline at end of file +} diff --git a/stat-svc/stat-dto/pom.xml b/stat-svc/stat-dto/pom.xml index 4067fff..c75fd1b 100644 --- a/stat-svc/stat-dto/pom.xml +++ b/stat-svc/stat-dto/pom.xml @@ -5,12 +5,12 @@ 4.0.0 ru.practicum - explore-with-me + stat-svc 0.0.1-SNAPSHOT - ../../pom.xml stat-dto + 0.0.1-SNAPSHOT 22 diff --git a/stat-svc/stats-server/Dockerfile b/stat-svc/stats-server/Dockerfile new file mode 100644 index 0000000..0ff1817 --- /dev/null +++ b/stat-svc/stats-server/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/stat-svc/stat-server/pom.xml b/stat-svc/stats-server/pom.xml similarity index 94% rename from stat-svc/stat-server/pom.xml rename to stat-svc/stats-server/pom.xml index d5b0654..ff280d2 100644 --- a/stat-svc/stat-server/pom.xml +++ b/stat-svc/stats-server/pom.xml @@ -5,12 +5,12 @@ 4.0.0 ru.practicum - explore-with-me + stat-svc 0.0.1-SNAPSHOT - ../../pom.xml stats-server + 0.0.1-SNAPSHOT 22 @@ -26,7 +26,7 @@ compile - + org.postgresql postgresql runtime diff --git a/stat-svc/stat-server/src/main/java/ru/practicum/stat/EndpointHitRepository.java b/stat-svc/stats-server/src/main/java/ru/practicum/stat/EndpointHitRepository.java similarity index 100% rename from stat-svc/stat-server/src/main/java/ru/practicum/stat/EndpointHitRepository.java rename to stat-svc/stats-server/src/main/java/ru/practicum/stat/EndpointHitRepository.java diff --git a/stat-svc/stat-server/src/main/java/ru/practicum/stat/StatisticsServer.java b/stat-svc/stats-server/src/main/java/ru/practicum/stat/StatisticsServer.java similarity index 100% rename from stat-svc/stat-server/src/main/java/ru/practicum/stat/StatisticsServer.java rename to stat-svc/stats-server/src/main/java/ru/practicum/stat/StatisticsServer.java diff --git a/stat-svc/stat-server/src/main/java/ru/practicum/stat/controller/StatsController.java b/stat-svc/stats-server/src/main/java/ru/practicum/stat/controller/StatsController.java similarity index 100% rename from stat-svc/stat-server/src/main/java/ru/practicum/stat/controller/StatsController.java rename to stat-svc/stats-server/src/main/java/ru/practicum/stat/controller/StatsController.java diff --git a/stat-svc/stat-server/src/main/java/ru/practicum/stat/exception/ErrorHandler.java b/stat-svc/stats-server/src/main/java/ru/practicum/stat/exception/ErrorHandler.java similarity index 100% rename from stat-svc/stat-server/src/main/java/ru/practicum/stat/exception/ErrorHandler.java rename to stat-svc/stats-server/src/main/java/ru/practicum/stat/exception/ErrorHandler.java diff --git a/stat-svc/stat-server/src/main/java/ru/practicum/stat/exception/ErrorResponse.java b/stat-svc/stats-server/src/main/java/ru/practicum/stat/exception/ErrorResponse.java similarity index 100% rename from stat-svc/stat-server/src/main/java/ru/practicum/stat/exception/ErrorResponse.java rename to stat-svc/stats-server/src/main/java/ru/practicum/stat/exception/ErrorResponse.java diff --git a/stat-svc/stat-server/src/main/java/ru/practicum/stat/mapper/EndpointHitMapper.java b/stat-svc/stats-server/src/main/java/ru/practicum/stat/mapper/EndpointHitMapper.java similarity index 100% rename from stat-svc/stat-server/src/main/java/ru/practicum/stat/mapper/EndpointHitMapper.java rename to stat-svc/stats-server/src/main/java/ru/practicum/stat/mapper/EndpointHitMapper.java diff --git a/stat-svc/stat-server/src/main/java/ru/practicum/stat/mapper/ViewStatsMapper.java b/stat-svc/stats-server/src/main/java/ru/practicum/stat/mapper/ViewStatsMapper.java similarity index 100% rename from stat-svc/stat-server/src/main/java/ru/practicum/stat/mapper/ViewStatsMapper.java rename to stat-svc/stats-server/src/main/java/ru/practicum/stat/mapper/ViewStatsMapper.java diff --git a/stat-svc/stat-server/src/main/java/ru/practicum/stat/model/EndpointHit.java b/stat-svc/stats-server/src/main/java/ru/practicum/stat/model/EndpointHit.java similarity index 100% rename from stat-svc/stat-server/src/main/java/ru/practicum/stat/model/EndpointHit.java rename to stat-svc/stats-server/src/main/java/ru/practicum/stat/model/EndpointHit.java diff --git a/stat-svc/stat-server/src/main/java/ru/practicum/stat/model/ViewStats.java b/stat-svc/stats-server/src/main/java/ru/practicum/stat/model/ViewStats.java similarity index 100% rename from stat-svc/stat-server/src/main/java/ru/practicum/stat/model/ViewStats.java rename to stat-svc/stats-server/src/main/java/ru/practicum/stat/model/ViewStats.java diff --git a/stat-svc/stat-server/src/main/java/ru/practicum/stat/service/StatisticsService.java b/stat-svc/stats-server/src/main/java/ru/practicum/stat/service/StatisticsService.java similarity index 100% rename from stat-svc/stat-server/src/main/java/ru/practicum/stat/service/StatisticsService.java rename to stat-svc/stats-server/src/main/java/ru/practicum/stat/service/StatisticsService.java diff --git a/stat-svc/stat-server/src/main/java/ru/practicum/stat/service/StatisticsServiceImpl.java b/stat-svc/stats-server/src/main/java/ru/practicum/stat/service/StatisticsServiceImpl.java similarity index 100% rename from stat-svc/stat-server/src/main/java/ru/practicum/stat/service/StatisticsServiceImpl.java rename to stat-svc/stats-server/src/main/java/ru/practicum/stat/service/StatisticsServiceImpl.java diff --git a/stat-svc/stat-server/src/main/resources/application-h2.properties b/stat-svc/stats-server/src/main/resources/application-h2.properties similarity index 100% rename from stat-svc/stat-server/src/main/resources/application-h2.properties rename to stat-svc/stats-server/src/main/resources/application-h2.properties diff --git a/stat-svc/stat-server/src/main/resources/application-postgres.properties b/stat-svc/stats-server/src/main/resources/application-postgres.properties similarity index 100% rename from stat-svc/stat-server/src/main/resources/application-postgres.properties rename to stat-svc/stats-server/src/main/resources/application-postgres.properties diff --git a/stat-svc/stat-server/src/main/resources/application.properties b/stat-svc/stats-server/src/main/resources/application.properties similarity index 100% rename from stat-svc/stat-server/src/main/resources/application.properties rename to stat-svc/stats-server/src/main/resources/application.properties diff --git a/stat-svc/stat-server/src/main/resources/schema.sql b/stat-svc/stats-server/src/main/resources/schema.sql similarity index 100% rename from stat-svc/stat-server/src/main/resources/schema.sql rename to stat-svc/stats-server/src/main/resources/schema.sql From 94374038415bc88c1a9e47ca9afdff9b394915a5 Mon Sep 17 00:00:00 2001 From: pavelrk97 Date: Mon, 15 Sep 2025 15:01:59 +0300 Subject: [PATCH 5/7] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20Pagable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ewm/category/controller/PublicCategoryController.java | 8 ++++++-- .../practicum/ewm/category/service/CategoryService.java | 3 ++- .../ewm/category/service/CategoryServiceImpl.java | 8 ++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/controller/PublicCategoryController.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/controller/PublicCategoryController.java index ea4b50f..f5743dd 100644 --- a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/controller/PublicCategoryController.java +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/controller/PublicCategoryController.java @@ -4,6 +4,8 @@ import jakarta.validation.constraints.PositiveOrZero; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.*; import ru.practicum.ewm.category.dto.CategoryDto; import ru.practicum.ewm.category.service.CategoryService; @@ -27,7 +29,9 @@ public CategoryDto getCategoryById(@PathVariable Long catId) { @GetMapping public List getAllCategories(@RequestParam(defaultValue = "0") @PositiveOrZero int from, @RequestParam(defaultValue = "10") @Positive int size) { - log.info("GET запрос на получение списка всех категорий."); - return service.getAllCategories(from, size); + log.info("GET запрос на получение списка всех категорий (from={}, size={})", from, size); + int page = from / size; + Pageable pageable = PageRequest.of(page, size); + return service.getAllCategories(pageable); } } \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryService.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryService.java index 5c52700..baa0371 100644 --- a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryService.java +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryService.java @@ -3,6 +3,7 @@ import ru.practicum.ewm.category.dto.CategoryDto; import ru.practicum.ewm.category.dto.NewCategoryDto; import ru.practicum.ewm.category.dto.UpdateCategoryDto; +import org.springframework.data.domain.Pageable; import java.util.List; @@ -15,6 +16,6 @@ public interface CategoryService { CategoryDto getCategoryById(Long catId); - List getAllCategories(int from, int size); + List getAllCategories(Pageable pageable); } \ No newline at end of file diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryServiceImpl.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryServiceImpl.java index f97dc79..3b16b5d 100644 --- a/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryServiceImpl.java +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/category/service/CategoryServiceImpl.java @@ -4,7 +4,6 @@ import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -76,9 +75,10 @@ public CategoryDto getCategoryById(Long catId) { @Override @Transactional(readOnly = true) - public List getAllCategories(int from, int size) { - Pageable pageable = PageRequest.of(from / size, size); + public List getAllCategories(Pageable pageable) { return categoryRepository.findAll(pageable) - .stream().map(CategoryMapper::toCategoryDto).collect(Collectors.toList()); + .stream() + .map(CategoryMapper::toCategoryDto) + .collect(Collectors.toList()); } } \ No newline at end of file From 8034fd5d456d91bbe4c87f74d532122447afa21d Mon Sep 17 00:00:00 2001 From: pavelrk97 Date: Mon, 15 Sep 2025 15:34:53 +0300 Subject: [PATCH 6/7] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=9B=D0=BE=D0=B3=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20ErrorHandler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practicum/ewm/exception/ErrorHandler.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ErrorHandler.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ErrorHandler.java index 4935f92..a5aa8d9 100644 --- a/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ErrorHandler.java +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/ErrorHandler.java @@ -19,64 +19,70 @@ public class ErrorHandler { @ExceptionHandler(ConstraintViolationException.class) @ResponseStatus(BAD_REQUEST) public ErrorResponse handleConstraintViolationException(ConstraintViolationException ex) { + log.error("ConstraintViolationException: {}", ex.getMessage(), ex); return new ErrorResponse("BAD_REQUEST", ex.getMessage()); } @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error("MethodArgumentNotValidException: {}", e.getMessage(), e); return new ErrorResponse("Ошибка валидации: ", e.getMessage()); } @ExceptionHandler(ValidationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handlerValidationException(ValidationException e) { + log.error("ValidationException: {}", e.getMessage(), e); return new ErrorResponse("BAD_REQUEST", e.getMessage()); } @ExceptionHandler(NotFoundException.class) @ResponseStatus(NOT_FOUND) public ErrorResponse handleNotFoundException(NotFoundException ex) { + log.error("NotFoundException: {}", ex.getMessage(), ex); return new ErrorResponse("NOT_FOUND", ex.getMessage()); } @ExceptionHandler(DuplicatedDataException.class) @ResponseStatus(HttpStatus.CONFLICT) public ErrorResponse handleDuplicatedDataException(DuplicatedDataException ex) { - log.warn("DuplicatedDataException: {}", ex.getMessage()); + log.warn("DuplicatedDataException: {}", ex.getMessage(), ex); return new ErrorResponse("CONFLICT", ex.getMessage()); } @ExceptionHandler(ConflictException.class) @ResponseStatus(HttpStatus.CONFLICT) public ErrorResponse handleConflictException(ConflictException ex) { - log.warn("ConflictException: {}", ex.getMessage()); + log.warn("ConflictException: {}", ex.getMessage(), ex); return new ErrorResponse("CONFLICT", ex.getMessage()); } @ExceptionHandler(IllegalArgumentException.class) @ResponseStatus(BAD_REQUEST) public ErrorResponse handleIllegalArgumentException(IllegalArgumentException ex) { + log.error("IllegalArgumentException: {}", ex.getMessage(), ex); return new ErrorResponse("BAD_REQUEST", ex.getMessage()); } @ExceptionHandler(IllegalStateException.class) @ResponseStatus(BAD_REQUEST) public ErrorResponse handleIllegalStateException(IllegalStateException ex) { + log.error("IllegalStateException: {}", ex.getMessage(), ex); return new ErrorResponse("BAD_REQUEST", ex.getMessage()); } @ExceptionHandler(DataIntegrityViolationException.class) @ResponseStatus(HttpStatus.CONFLICT) public ErrorResponse handleDataIntegrityViolationException(DataIntegrityViolationException ex) { - log.warn("DataIntegrityViolationException: {}", ex.getMessage()); + log.warn("DataIntegrityViolationException: {}", ex.getMessage(), ex); return new ErrorResponse("CONFLICT", "Нарушение уникальности данных: " + ex.getMessage()); } - @ExceptionHandler + @ExceptionHandler(ForbiddenException.class) @ResponseStatus(HttpStatus.FORBIDDEN) public ApiError handleConflict(final ForbiddenException e) { - log.info("403 {}", e.getMessage(), e); + log.error("ForbiddenException: {}", e.getMessage(), e); return new ApiError( HttpStatus.FORBIDDEN.value(), "For the requested operation the conditions are not met.", @@ -84,10 +90,10 @@ public ApiError handleConflict(final ForbiddenException e) { ); } - @ExceptionHandler + @ExceptionHandler(IncorrectRequestException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ApiError handleIncorrectRequestException(final IncorrectRequestException e) { - log.info("400 {}", e.getMessage(), e); + log.error("IncorrectRequestException: {}", e.getMessage(), e); return new ApiError( HttpStatus.BAD_REQUEST.value(), "Incorrectly made request.", From 779868fa25661cf279e1879cbdfae1d44de573db Mon Sep 17 00:00:00 2001 From: pavelrk97 Date: Mon, 15 Sep 2025 16:28:10 +0300 Subject: [PATCH 7/7] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20RuntimeException?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ewm/event/service/EventServiceImpl.java | 13 ++++++++++--- .../ewm/exception/DatabaseAccessException.java | 7 +++++++ 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 ewm-main-svc/src/main/java/ru/practicum/ewm/exception/DatabaseAccessException.java diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/service/EventServiceImpl.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/service/EventServiceImpl.java index 05acab7..73fcb58 100644 --- a/ewm-main-svc/src/main/java/ru/practicum/ewm/event/service/EventServiceImpl.java +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/event/service/EventServiceImpl.java @@ -240,10 +240,17 @@ public Collection findAllByAdmin(EventSearchParams params, HttpSer List eventList; try { - eventList = eventRepository.findAllByAdmin(params.getUsers(), params.getStates(), params.getCategories(), params.getRangeStart(), params.getRangeEnd(), pageable); + eventList = eventRepository.findAllByAdmin( + params.getUsers(), + params.getStates(), + params.getCategories(), + params.getRangeStart(), + params.getRangeEnd(), + pageable + ); } catch (Exception e) { - log.error("Ошибка при выполнении запроса к БД: ", e); - throw new RuntimeException("Ошибка при получении данных из базы данных", e); + log.error("Ошибка при выполнении запроса к БД для поиска событий админом", e); + throw new DatabaseAccessException("Не удалось получить события из базы данных", e); } List eventIds = eventList.stream().map(Event::getId).collect(Collectors.toList()); diff --git a/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/DatabaseAccessException.java b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/DatabaseAccessException.java new file mode 100644 index 0000000..665971f --- /dev/null +++ b/ewm-main-svc/src/main/java/ru/practicum/ewm/exception/DatabaseAccessException.java @@ -0,0 +1,7 @@ +package ru.practicum.ewm.exception; + +public class DatabaseAccessException extends RuntimeException { + public DatabaseAccessException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file