From f748596208667fe773a5bf5bb89283a0399136e8 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 13:39:09 +0300 Subject: [PATCH 01/24] Add Implement Events, Requests and Error handling --- ewm-main-service/pom.xml | 1 - .../java/ru/practicum/ewm/ErrorHandler.java | 87 +++++++ .../java/ru/practicum/ewm/EwmApplication.java | 2 + .../admin/AdminCategoryController.java | 35 +++ .../admin/AdminCompilationController.java | 37 +++ .../admin/AdminEventController.java | 39 ++++ .../admin/AdminLocationController.java | 41 ++++ .../controller/admin/AdminUserController.java | 42 ++++ .../priv/PrivateEventController.java | 65 ++++++ .../priv/PrivateRequestController.java | 33 +++ .../pub/PublicCategoryController.java | 27 +++ .../pub/PublicCompilationController.java | 28 +++ .../controller/pub/PublicEventController.java | 40 ++++ .../pub/PublicLocationController.java | 29 +++ .../practicum/ewm/dto/response/ApiError.java | 19 ++ .../ewm/exception/BadRequestException.java | 7 + .../ewm/exception/ConflictException.java | 7 + .../ewm/exception/NotFoundException.java | 7 + .../ewm/mapper/CompilationMapper.java | 35 +++ .../ru/practicum/ewm/mapper/EventMapper.java | 60 +++++ .../practicum/ewm/mapper/RequestMapper.java | 17 ++ .../ewm/repository/CategoryRepository.java | 7 + .../ewm/repository/CompilationRepository.java | 11 + .../ewm/repository/EventRepository.java | 54 +++++ .../ewm/repository/LocationRepository.java | 7 + .../ParticipationRequestRepository.java | 18 ++ .../ewm/repository/UserRepository.java | 7 + .../ewm/service/CategoryService.java | 19 ++ .../ewm/service/CategoryServiceImpl.java | 64 +++++ .../ewm/service/CompilationService.java | 18 ++ .../ewm/service/CompilationServiceImpl.java | 104 +++++++++ .../practicum/ewm/service/EventService.java | 32 +++ .../ewm/service/EventServiceImpl.java | 219 ++++++++++++++++++ .../ewm/service/LocationService.java | 17 ++ .../ewm/service/LocationServiceImpl.java | 52 +++++ .../practicum/ewm/service/RequestService.java | 18 ++ .../ewm/service/RequestServiceImpl.java | 133 +++++++++++ .../ru/practicum/ewm/service/UserService.java | 15 ++ .../ewm/service/UserServiceImpl.java | 54 +++++ 39 files changed, 1506 insertions(+), 1 deletion(-) create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminEventController.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminLocationController.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminUserController.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/controller/priv/PrivateEventController.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/controller/priv/PrivateRequestController.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicCategoryController.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicCompilationController.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicLocationController.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/ApiError.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/exception/BadRequestException.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/exception/ConflictException.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/exception/NotFoundException.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/mapper/CompilationMapper.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/mapper/EventMapper.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/mapper/RequestMapper.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/repository/CategoryRepository.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/repository/CompilationRepository.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/repository/LocationRepository.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/repository/ParticipationRequestRepository.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/repository/UserRepository.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryService.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationService.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/service/EventService.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationService.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestService.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/service/UserService.java create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/service/UserServiceImpl.java diff --git a/ewm-main-service/pom.xml b/ewm-main-service/pom.xml index dba2a53..233e90f 100644 --- a/ewm-main-service/pom.xml +++ b/ewm-main-service/pom.xml @@ -44,7 +44,6 @@ org.springframework.boot spring-boot-starter-validation - 3.5.14 org.springframework.boot diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java b/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java new file mode 100644 index 0000000..c49baad --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java @@ -0,0 +1,87 @@ +package ru.practicum.ewm; + +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.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import ru.practicum.ewm.dto.response.ApiError; +import ru.practicum.ewm.exception.BadRequestException; +import ru.practicum.ewm.exception.ConflictException; +import ru.practicum.ewm.exception.NotFoundException; + +import jakarta.validation.ConstraintViolationException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.stream.Collectors; + +@Slf4j +@RestControllerAdvice +public class ErrorHandler { + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + + @ExceptionHandler + @ResponseStatus(HttpStatus.NOT_FOUND) + public ApiError handleNotFound(final NotFoundException e) { + log.error("404: {}", e.getMessage()); + return ApiError.builder() + .status(HttpStatus.NOT_FOUND.name()) + .reason("The required object was not found.") + .message(e.getMessage()) + .timestamp(LocalDateTime.now().format(FORMATTER)) + .build(); + } + + + @ExceptionHandler({ConflictException.class, DataIntegrityViolationException.class}) + @ResponseStatus(HttpStatus.CONFLICT) + public ApiError handleConflict(final Exception e) { + log.error("409: {}", e.getMessage()); + return ApiError.builder() + .status(HttpStatus.CONFLICT.name()) + .reason("For the requested operation the conditions are not met.") + .message(e.getMessage()) + .timestamp(LocalDateTime.now().format(FORMATTER)) + .build(); + } + + + @ExceptionHandler({ + BadRequestException.class, + MethodArgumentNotValidException.class, + ConstraintViolationException.class, + MissingServletRequestParameterException.class + }) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiError handleBadRequest(final Exception e) { + log.error("400: {}", e.getMessage()); + return ApiError.builder() + .status(HttpStatus.BAD_REQUEST.name()) + .reason("Incorrectly made request.") + .message(e.getMessage()) + .timestamp(LocalDateTime.now().format(FORMATTER)) + .build(); + } + + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiError handleInternalServerError(final Throwable e) { + log.error("500: ", e); + return ApiError.builder() + .status(HttpStatus.INTERNAL_SERVER_ERROR.name()) + .reason("Error occurred") + .message(e.getMessage()) + .errors(Arrays.stream(e.getStackTrace()) + .map(StackTraceElement::toString) + .collect(Collectors.toList())) + .timestamp(LocalDateTime.now().format(FORMATTER)) + .build(); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/EwmApplication.java b/ewm-main-service/src/main/java/ru/practicum/ewm/EwmApplication.java index f1dfbc9..9c35c42 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/EwmApplication.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/EwmApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; @SpringBootApplication +@ComponentScan(basePackages = {"ru.practicum.ewm", "ru.practicum.client"}) // Добавь это! public class EwmApplication { public static void main(String[] args) { SpringApplication.run(EwmApplication.class, args); diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java new file mode 100644 index 0000000..3e64c80 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java @@ -0,0 +1,35 @@ +package ru.practicum.ewm.controller.admin; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.dto.request.NewCategoryDto; +import ru.practicum.ewm.dto.response.CategoryDto; +import ru.practicum.ewm.service.CategoryService; + +@RestController +@RequestMapping("/admin/categories") +@RequiredArgsConstructor +public class AdminCategoryController { + + private final CategoryService categoryService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public CategoryDto createCategory(@Valid @RequestBody NewCategoryDto newCategoryDto) { + return categoryService.createCategory(newCategoryDto); + } + + @PatchMapping("/{catId}") + public CategoryDto updateCategory(@PathVariable Long catId, + @Valid @RequestBody CategoryDto categoryDto) { + return categoryService.updateCategory(catId, categoryDto); + } + + @DeleteMapping("/{catId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteCategory(@PathVariable Long catId) { + categoryService.deleteCategory(catId); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java new file mode 100644 index 0000000..54dcf40 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java @@ -0,0 +1,37 @@ +package ru.practicum.ewm.controller.admin; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.dto.request.NewCompilationDto; +import ru.practicum.ewm.dto.request.UpdateCompilationRequest; +import ru.practicum.ewm.dto.response.CompilationDto; +import ru.practicum.ewm.service.CompilationService; + +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/admin/compilations") +@RequiredArgsConstructor +public class AdminCompilationController { + + private final CompilationService compilationService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public CompilationDto addCompilation(@Valid @RequestBody NewCompilationDto newCompilationDto) { + return compilationService.addCompilation(newCompilationDto); + } + + @DeleteMapping("/{compId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteCompilation(@PathVariable Long compId) { + compilationService.deleteCompilation(compId); + } + + @PatchMapping("/{compId}") + public CompilationDto updateCompilation(@PathVariable Long compId, + @Valid @RequestBody UpdateCompilationRequest updateRequest) { + return compilationService.updateCompilation(compId, updateRequest); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminEventController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminEventController.java new file mode 100644 index 0000000..1502ee3 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminEventController.java @@ -0,0 +1,39 @@ +package ru.practicum.ewm.controller.admin; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.dto.request.UpdateEventAdmin; +import ru.practicum.ewm.dto.response.EventFullDto; +import ru.practicum.ewm.model.EventState; +import ru.practicum.ewm.service.EventService; + +import java.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping("/admin/events") +@RequiredArgsConstructor +public class AdminEventController { + + private final EventService eventService; + + @GetMapping + public List getEvents( + @RequestParam(required = false) List users, + @RequestParam(required = false) List states, + @RequestParam(required = false) List categories, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + return eventService.getEventsByAdmin(users, states, categories, rangeStart, rangeEnd, from, size); + } + + @PatchMapping("/{eventId}") + public EventFullDto updateEvent(@PathVariable Long eventId, + @Valid @RequestBody UpdateEventAdmin updateRequest) { + return eventService.updateEventByAdmin(eventId, updateRequest); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminLocationController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminLocationController.java new file mode 100644 index 0000000..1c77e55 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminLocationController.java @@ -0,0 +1,41 @@ +package ru.practicum.ewm.controller.admin; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.dto.request.NewLocationDto; +import ru.practicum.ewm.dto.response.LocationDto; +import ru.practicum.ewm.service.LocationService; + +import java.util.List; + +@RestController +@RequestMapping("/admin/locations") +@RequiredArgsConstructor +public class AdminLocationController { + + private final LocationService locationService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public LocationDto createLocation(@Valid @RequestBody NewLocationDto locationDto) { + return locationService.createLocation(locationDto); + } + + @GetMapping + public List getAllLocations() { + return locationService.getAllLocations(); + } + + @GetMapping("/{locationId}") + public LocationDto getLocation(@PathVariable Long locationId) { + return locationService.getLocation(locationId); + } + + @DeleteMapping("/{locationId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteLocation(@PathVariable Long locationId) { + locationService.deleteLocation(locationId); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminUserController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminUserController.java new file mode 100644 index 0000000..0ac13b1 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminUserController.java @@ -0,0 +1,42 @@ +package ru.practicum.ewm.controller.admin; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.dto.request.NewUserRequest; +import ru.practicum.ewm.dto.response.UserDto; +import ru.practicum.ewm.service.UserService; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import java.util.List; + +@RestController +@RequestMapping("/admin/users") +@RequiredArgsConstructor +@Validated +public class AdminUserController { + + private final UserService userService; + + @GetMapping + public List getUsers(@RequestParam(required = false) List ids, + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { + return userService.getUsers(ids, from, size); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public UserDto createUser(@Valid @RequestBody NewUserRequest newUserRequest) { + return userService.createUser(newUserRequest); + } + + @DeleteMapping("/{userId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteUser(@PathVariable Long userId) { + userService.deleteUser(userId); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/priv/PrivateEventController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/priv/PrivateEventController.java new file mode 100644 index 0000000..9750beb --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/priv/PrivateEventController.java @@ -0,0 +1,65 @@ +package ru.practicum.ewm.controller.priv; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.dto.request.NewEventDto; +import ru.practicum.ewm.dto.request.UpdateEventUser; +import ru.practicum.ewm.dto.request.RequestStatusUpdate; +import ru.practicum.ewm.dto.response.EventFullDto; +import ru.practicum.ewm.dto.response.EventShortDto; +import ru.practicum.ewm.dto.response.RequestDto; +import ru.practicum.ewm.dto.response.RequestStatusUpdateResult; +import ru.practicum.ewm.service.EventService; +import ru.practicum.ewm.service.RequestService; +import java.util.List; + +@RestController +@RequestMapping("/users/{userId}/events") +@RequiredArgsConstructor +public class PrivateEventController { + + private final EventService eventService; + private final RequestService requestService; // Добавлено поле для внедрения + + @GetMapping + public List getEvents(@PathVariable Long userId, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + return eventService.getEventsByUserId(userId, from, size); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public EventFullDto createEvent(@PathVariable Long userId, @Valid @RequestBody NewEventDto newEventDto) { + return eventService.createEvent(userId, newEventDto); + } + + @GetMapping("/{eventId}") + public EventFullDto getEvent(@PathVariable Long userId, @PathVariable Long eventId) { + return eventService.getEventByIdAndUserId(userId, eventId); + } + + @PatchMapping("/{eventId}") + public EventFullDto updateEvent(@PathVariable Long userId, @PathVariable Long eventId, + @Valid @RequestBody UpdateEventUser updateRequest) { + return eventService.updateEventByUserId(userId, eventId, updateRequest); + } + + // --- Методы для работы с запросами на участие в событии (организатор) --- + + @GetMapping("/{eventId}/requests") + public List getEventRequests(@PathVariable Long userId, @PathVariable Long eventId) { + + return requestService.getEventRequests(userId, eventId); + } + + @PatchMapping("/{eventId}/requests") + public RequestStatusUpdateResult updateRequestStatus(@PathVariable Long userId, + @PathVariable Long eventId, + @Valid @RequestBody RequestStatusUpdate updateRequest) { + + return requestService.updateRequestStatus(userId, eventId, updateRequest); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/priv/PrivateRequestController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/priv/PrivateRequestController.java new file mode 100644 index 0000000..6ab26b1 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/priv/PrivateRequestController.java @@ -0,0 +1,33 @@ +package ru.practicum.ewm.controller.priv; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.dto.response.RequestDto; +import ru.practicum.ewm.service.RequestService; + +import java.util.List; + +@RestController +@RequestMapping("/users/{userId}/requests") +@RequiredArgsConstructor +public class PrivateRequestController { + + private final RequestService requestService; + + @GetMapping + public List getRequests(@PathVariable Long userId) { + return requestService.getRequestsByUserId(userId); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public RequestDto createRequest(@PathVariable Long userId, @RequestParam Long eventId) { + return requestService.createRequest(userId, eventId); + } + + @PatchMapping("/{requestId}/cancel") + public RequestDto cancelRequest(@PathVariable Long userId, @PathVariable Long requestId) { + return requestService.cancelRequest(userId, requestId); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicCategoryController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicCategoryController.java new file mode 100644 index 0000000..3ee31ae --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicCategoryController.java @@ -0,0 +1,27 @@ +package ru.practicum.ewm.controller.pub; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.dto.response.CategoryDto; +import ru.practicum.ewm.service.CategoryService; + +import java.util.List; + +@RestController +@RequestMapping("/categories") +@RequiredArgsConstructor +public class PublicCategoryController { + + private final CategoryService categoryService; + + @GetMapping + public List getCategories(@RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + return categoryService.getCategories(from, size); + } + + @GetMapping("/{catId}") + public CategoryDto getCategory(@PathVariable Long catId) { + return categoryService.getCategory(catId); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicCompilationController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicCompilationController.java new file mode 100644 index 0000000..576e8ed --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicCompilationController.java @@ -0,0 +1,28 @@ +package ru.practicum.ewm.controller.pub; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.dto.response.CompilationDto; +import ru.practicum.ewm.service.CompilationService; + +import java.util.List; + +@RestController +@RequestMapping("/compilations") +@RequiredArgsConstructor +public class PublicCompilationController { + + private final CompilationService compilationService; + + @GetMapping + public List getCompilations(@RequestParam(required = false) Boolean pinned, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size) { + return compilationService.getCompilations(pinned, from, size); + } + + @GetMapping("/{compId}") + public CompilationDto getCompilationById(@PathVariable Long compId) { + return compilationService.getCompilationById(compId); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java new file mode 100644 index 0000000..9a040f6 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java @@ -0,0 +1,40 @@ +package ru.practicum.ewm.controller.pub; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.*; +import ru.practicum.ewm.dto.response.EventFullDto; +import ru.practicum.ewm.dto.response.EventShortDto; +import ru.practicum.ewm.service.EventService; + +import java.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping("/events") +@RequiredArgsConstructor +public class PublicEventController { + + private final EventService eventService; + + @GetMapping + public List getEvents( + @RequestParam(required = false) String text, + @RequestParam(required = false) List categories, + @RequestParam(required = false) Boolean paid, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, + @RequestParam(defaultValue = "false") Boolean onlyAvailable, + @RequestParam(required = false) String sort, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size, + HttpServletRequest request) { + return eventService.getEventsPublic(text, categories, paid, rangeStart, rangeEnd, onlyAvailable, sort, from, size, request); + } + + @GetMapping("/{id}") + public EventFullDto getEvent(@PathVariable Long id, HttpServletRequest request) { + return eventService.getEventByIdPublic(id, request); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicLocationController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicLocationController.java new file mode 100644 index 0000000..cca0008 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicLocationController.java @@ -0,0 +1,29 @@ +package ru.practicum.ewm.controller.pub; + +import lombok.RequiredArgsConstructor; +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.dto.response.LocationDto; +import ru.practicum.ewm.service.LocationService; + +import java.util.List; + +@RestController +@RequestMapping("/locations") +@RequiredArgsConstructor +public class PublicLocationController { + + private final LocationService locationService; + + @GetMapping + public List getAllLocations() { + return locationService.getAllLocations(); + } + + @GetMapping("/{locationId}") + public LocationDto getLocation(@PathVariable Long locationId) { + return locationService.getLocation(locationId); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/ApiError.java b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/ApiError.java new file mode 100644 index 0000000..ce9b904 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/ApiError.java @@ -0,0 +1,19 @@ +package ru.practicum.ewm.dto.response; + +import lombok.*; +import lombok.experimental.FieldDefaults; + +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +public class ApiError { + List errors; + String message; + String reason; + String status; + String timestamp; +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/exception/BadRequestException.java b/ewm-main-service/src/main/java/ru/practicum/ewm/exception/BadRequestException.java new file mode 100644 index 0000000..7dc9124 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/exception/BadRequestException.java @@ -0,0 +1,7 @@ +package ru.practicum.ewm.exception; + +public class BadRequestException extends RuntimeException { + public BadRequestException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/exception/ConflictException.java b/ewm-main-service/src/main/java/ru/practicum/ewm/exception/ConflictException.java new file mode 100644 index 0000000..2603c54 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/exception/ConflictException.java @@ -0,0 +1,7 @@ +package ru.practicum.ewm.exception; + +public class ConflictException extends RuntimeException { + public ConflictException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/exception/NotFoundException.java b/ewm-main-service/src/main/java/ru/practicum/ewm/exception/NotFoundException.java new file mode 100644 index 0000000..ef340c6 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package ru.practicum.ewm.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/CompilationMapper.java b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/CompilationMapper.java new file mode 100644 index 0000000..a57db84 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/CompilationMapper.java @@ -0,0 +1,35 @@ +package ru.practicum.ewm.mapper; + +import ru.practicum.ewm.dto.response.CompilationDto; +import ru.practicum.ewm.dto.response.EventShortDto; +import ru.practicum.ewm.dto.request.NewCompilationDto; +import ru.practicum.ewm.model.Compilation; + +import java.util.Set; +import java.util.stream.Collectors; + +public class CompilationMapper { + + public static CompilationDto toDto(Compilation compilation) { + CompilationDto compilationDto = new CompilationDto(); + compilationDto.setId(compilation.getId()); + compilationDto.setTitle(compilation.getTitle()); + compilationDto.setPinned(compilation.getPinned()); + + if (compilation.getEvents() != null) { + Set eventDtos = compilation.getEvents().stream() + .map(EventMapper::toShortDto) + .collect(Collectors.toSet()); + compilationDto.setEvents(eventDtos); + } + + return compilationDto; + } + + public static Compilation toCompilation(NewCompilationDto newCompilationDto) { + Compilation compilation = new Compilation(); + compilation.setTitle(newCompilationDto.getTitle()); + compilation.setPinned(newCompilationDto.getPinned()); + return compilation; + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/EventMapper.java b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/EventMapper.java new file mode 100644 index 0000000..6a8bee9 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/EventMapper.java @@ -0,0 +1,60 @@ +package ru.practicum.ewm.mapper; + +import ru.practicum.ewm.dto.response.EventFullDto; +import ru.practicum.ewm.dto.response.EventShortDto; +import ru.practicum.ewm.dto.request.NewEventDto; +import ru.practicum.ewm.model.Event; + +public class EventMapper { + + public static EventShortDto toShortDto(Event event) { + EventShortDto eventShortDto = new EventShortDto(); + eventShortDto.setId(event.getId()); + eventShortDto.setTitle(event.getTitle()); + eventShortDto.setAnnotation(event.getAnnotation()); + eventShortDto.setCategory(CategoryMapper.toDto(event.getCategory())); + eventShortDto.setEventDate(event.getEventDate()); + eventShortDto.setInitiator(UserMapper.toDto(event.getInitiator())); + eventShortDto.setPaid(event.getPaid()); + eventShortDto.setConfirmedRequests(event.getConfirmedRequests() != null ? event.getConfirmedRequests() : 0L); + eventShortDto.setViews(event.getViews() != null ? event.getViews() : 0L); + return eventShortDto; + } + + public static EventFullDto toFullDto(Event event) { + EventFullDto eventFullDto = new EventFullDto(); + eventFullDto.setId(event.getId()); + eventFullDto.setTitle(event.getTitle()); + eventFullDto.setAnnotation(event.getAnnotation()); + eventFullDto.setDescription(event.getDescription()); + eventFullDto.setCategory(CategoryMapper.toDto(event.getCategory())); + eventFullDto.setEventDate(event.getEventDate()); + eventFullDto.setInitiator(UserMapper.toDto(event.getInitiator())); + + if (event.getLocation() != null) { + eventFullDto.setLocation(LocationMapper.toDto(event.getLocation())); + } + + eventFullDto.setCreatedOn(event.getCreatedOn()); + eventFullDto.setPublishedOn(event.getPublishedOn()); + eventFullDto.setState(event.getState()); + eventFullDto.setPaid(event.getPaid()); + eventFullDto.setParticipantLimit(event.getParticipantLimit()); + eventFullDto.setRequestModeration(event.getRequestModeration()); + eventFullDto.setConfirmedRequests(event.getConfirmedRequests() != null ? event.getConfirmedRequests() : 0L); + eventFullDto.setViews(event.getViews() != null ? event.getViews() : 0L); + return eventFullDto; + } + + public static Event toEvent(NewEventDto newEventDto) { + Event event = new Event(); + event.setTitle(newEventDto.getTitle()); + event.setAnnotation(newEventDto.getAnnotation()); + event.setDescription(newEventDto.getDescription()); + event.setEventDate(newEventDto.getEventDate()); + event.setPaid(newEventDto.getPaid()); + event.setParticipantLimit(newEventDto.getParticipantLimit()); + event.setRequestModeration(newEventDto.getRequestModeration()); + return event; + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/RequestMapper.java b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/RequestMapper.java new file mode 100644 index 0000000..f9932b4 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/RequestMapper.java @@ -0,0 +1,17 @@ +package ru.practicum.ewm.mapper; + +import ru.practicum.ewm.dto.response.RequestDto; +import ru.practicum.ewm.model.ParticipationRequest; + +public class RequestMapper { + + public static RequestDto toDto(ParticipationRequest request) { + RequestDto requestDto = new RequestDto(); + requestDto.setId(request.getId()); + requestDto.setEvent(request.getEvent().getId()); + requestDto.setRequester(request.getRequester().getId()); + requestDto.setStatus(request.getStatus()); + requestDto.setCreated(request.getCreated()); + return requestDto; + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/CategoryRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/CategoryRepository.java new file mode 100644 index 0000000..6902215 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/CategoryRepository.java @@ -0,0 +1,7 @@ +package ru.practicum.ewm.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.ewm.model.Category; + +public interface CategoryRepository extends JpaRepository { +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/CompilationRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/CompilationRepository.java new file mode 100644 index 0000000..e1e88b1 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/CompilationRepository.java @@ -0,0 +1,11 @@ +package ru.practicum.ewm.repository; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.ewm.model.Compilation; + +import java.util.List; + +public interface CompilationRepository extends JpaRepository { + List findAllByPinned(Boolean pinned, Pageable pageable); +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java new file mode 100644 index 0000000..f51d0b4 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java @@ -0,0 +1,54 @@ +package ru.practicum.ewm.repository; + +import org.springframework.data.domain.Pageable; +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.model.Event; +import ru.practicum.ewm.model.EventState; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +public interface EventRepository extends JpaRepository { + + List findAllByInitiatorId(Long initiatorId, Pageable pageable); + + Optional findByIdAndInitiatorId(Long eventId, Long initiatorId); + + @Query("SELECT e FROM Event e " + + "WHERE (:users IS NULL OR e.initiator.id IN :users) " + + "AND (:states IS NULL OR e.state IN :states) " + + "AND (:categories IS NULL OR e.category.id IN :categories) " + + "AND (CAST(:rangeStart AS timestamp) IS NULL OR e.eventDate >= :rangeStart) " + + "AND (CAST(:rangeEnd AS timestamp) IS NULL OR e.eventDate <= :rangeEnd)") + List findEventsByAdmin( + @Param("users") List users, + @Param("states") List states, + @Param("categories") List categories, + @Param("rangeStart") LocalDateTime rangeStart, + @Param("rangeEnd") LocalDateTime rangeEnd, + Pageable pageable); + + @Query("SELECT e FROM Event e " + + "WHERE e.state = 'PUBLISHED' " + + "AND (:text IS NULL OR (LOWER(e.annotation) LIKE LOWER(CONCAT('%', :text, '%')) " + + "OR LOWER(e.description) LIKE LOWER(CONCAT('%', :text, '%')))) " + + "AND (:categories IS NULL OR e.category.id IN :categories) " + + "AND (:paid IS NULL OR e.paid = :paid) " + + "AND (CAST(:rangeStart AS timestamp) IS NULL OR e.eventDate >= :rangeStart) " + + "AND (CAST(:rangeEnd AS timestamp) IS NULL OR e.eventDate <= :rangeEnd) " + + "AND (:onlyAvailable = false OR e.participantLimit = 0 " + + "OR e.confirmedRequests < e.participantLimit)") + List findPublishedEvents( + @Param("text") String text, + @Param("categories") List categories, + @Param("paid") Boolean paid, + @Param("rangeStart") LocalDateTime rangeStart, + @Param("rangeEnd") LocalDateTime rangeEnd, + @Param("onlyAvailable") Boolean onlyAvailable, + Pageable pageable); + + Optional findByIdAndState(Long id, EventState state); +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/LocationRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/LocationRepository.java new file mode 100644 index 0000000..d45fc8a --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/LocationRepository.java @@ -0,0 +1,7 @@ +package ru.practicum.ewm.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.ewm.model.Location; + +public interface LocationRepository extends JpaRepository { +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/ParticipationRequestRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/ParticipationRequestRepository.java new file mode 100644 index 0000000..161190e --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/ParticipationRequestRepository.java @@ -0,0 +1,18 @@ +package ru.practicum.ewm.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.ewm.model.ParticipationRequest; +import ru.practicum.ewm.model.RequestStatus; + +import java.util.List; + +public interface ParticipationRequestRepository extends JpaRepository { + + List findAllByRequesterId(Long requesterId); + + List findAllByEventId(Long eventId); + + Long countByEventIdAndStatus(Long eventId, RequestStatus status); + + boolean existsByRequesterIdAndEventId(Long requesterId, Long eventId); +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/UserRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/UserRepository.java new file mode 100644 index 0000000..f2ac76c --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/UserRepository.java @@ -0,0 +1,7 @@ +package ru.practicum.ewm.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.ewm.model.User; + +public interface UserRepository extends JpaRepository { +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryService.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryService.java new file mode 100644 index 0000000..fbc0808 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryService.java @@ -0,0 +1,19 @@ +package ru.practicum.ewm.service; + +import ru.practicum.ewm.dto.request.NewCategoryDto; +import ru.practicum.ewm.dto.response.CategoryDto; + +import java.util.List; + +public interface CategoryService { + + CategoryDto createCategory(NewCategoryDto newCategoryDto); + + CategoryDto updateCategory(Long catId, CategoryDto categoryDto); + + void deleteCategory(Long catId); + + List getCategories(int from, int size); + + CategoryDto getCategory(Long catId); +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java new file mode 100644 index 0000000..5928a7e --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java @@ -0,0 +1,64 @@ +package ru.practicum.ewm.service; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.ewm.dto.request.NewCategoryDto; +import ru.practicum.ewm.dto.response.CategoryDto; +import ru.practicum.ewm.mapper.CategoryMapper; +import ru.practicum.ewm.model.Category; +import ru.practicum.ewm.repository.CategoryRepository; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Transactional(readOnly = true) +public class CategoryServiceImpl implements CategoryService { + + CategoryRepository categoryRepository; + + @Override + @Transactional + public CategoryDto createCategory(NewCategoryDto newCategoryDto) { + Category category = CategoryMapper.toCategory(newCategoryDto); + Category savedCategory = categoryRepository.save(category); + return CategoryMapper.toDto(savedCategory); + } + + @Override + @Transactional + public CategoryDto updateCategory(Long catId, CategoryDto categoryDto) { + Category category = categoryRepository.findById(catId) + .orElseThrow(() -> new RuntimeException("Category not found")); + category.setName(categoryDto.getName()); + Category updatedCategory = categoryRepository.save(category); + return CategoryMapper.toDto(updatedCategory); + } + + @Override + @Transactional + public void deleteCategory(Long catId) { + categoryRepository.deleteById(catId); + } + + @Override + public List getCategories(int from, int size) { + PageRequest pageRequest = PageRequest.of(from / size, size); + return categoryRepository.findAll(pageRequest).stream() + .map(CategoryMapper::toDto) + .collect(Collectors.toList()); + } + + @Override + public CategoryDto getCategory(Long catId) { + Category category = categoryRepository.findById(catId) + .orElseThrow(() -> new RuntimeException("Category not found")); + return CategoryMapper.toDto(category); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationService.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationService.java new file mode 100644 index 0000000..7dc1424 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationService.java @@ -0,0 +1,18 @@ +package ru.practicum.ewm.service; + +import ru.practicum.ewm.dto.request.NewCompilationDto; +import ru.practicum.ewm.dto.request.UpdateCompilationRequest; +import ru.practicum.ewm.dto.response.CompilationDto; + +import java.util.List; + +public interface CompilationService { + // Admin API + CompilationDto addCompilation(NewCompilationDto newCompilationDto); + void deleteCompilation(Long compId); + CompilationDto updateCompilation(Long compId, UpdateCompilationRequest updateRequest); + + // Public API + List getCompilations(Boolean pinned, int from, int size); + CompilationDto getCompilationById(Long compId); +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java new file mode 100644 index 0000000..89c4e8e --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java @@ -0,0 +1,104 @@ +package ru.practicum.ewm.service; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.ewm.dto.request.NewCompilationDto; +import ru.practicum.ewm.dto.request.UpdateCompilationRequest; +import ru.practicum.ewm.dto.response.CompilationDto; +import ru.practicum.ewm.mapper.CompilationMapper; +import ru.practicum.ewm.model.Compilation; +import ru.practicum.ewm.model.Event; +import ru.practicum.ewm.repository.CompilationRepository; +import ru.practicum.ewm.repository.EventRepository; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Transactional(readOnly = true) +public class CompilationServiceImpl implements CompilationService { + + CompilationRepository compilationRepository; + EventRepository eventRepository; + + @Override + @Transactional + public CompilationDto addCompilation(NewCompilationDto newCompilationDto) { + Set events = new HashSet<>(); + if (newCompilationDto.getEvents() != null && !newCompilationDto.getEvents().isEmpty()) { + events = new HashSet<>(eventRepository.findAllById(newCompilationDto.getEvents())); + } + + Compilation compilation = CompilationMapper.toCompilation(newCompilationDto); + + compilation.setEvents(events); + + if (compilation.getPinned() == null) { + compilation.setPinned(false); + } + + Compilation savedCompilation = compilationRepository.save(compilation); + return CompilationMapper.toDto(savedCompilation); + } + + @Override + @Transactional + public void deleteCompilation(Long compId) { + if (!compilationRepository.existsById(compId)) { + throw new RuntimeException("Compilation not found"); // Позже заменим на Custom Exception + } + compilationRepository.deleteById(compId); + } + + @Override + @Transactional + public CompilationDto updateCompilation(Long compId, UpdateCompilationRequest updateRequest) { + Compilation compilation = compilationRepository.findById(compId) + .orElseThrow(() -> new RuntimeException("Compilation not found")); + + if (updateRequest.getEvents() != null) { + Set events = new HashSet<>(eventRepository.findAllById(updateRequest.getEvents())); + compilation.setEvents(events); + } + if (updateRequest.getPinned() != null) { + compilation.setPinned(updateRequest.getPinned()); + } + if (updateRequest.getTitle() != null && !updateRequest.getTitle().isBlank()) { + compilation.setTitle(updateRequest.getTitle()); + } + + Compilation updatedCompilation = compilationRepository.save(compilation); + return CompilationMapper.toDto(updatedCompilation); + } + + @Override + public List getCompilations(Boolean pinned, int from, int size) { + PageRequest pageRequest = PageRequest.of(from / size, size); + List compilations; + + if (pinned != null) { + compilations = compilationRepository.findAllByPinned(pinned, pageRequest); + } else { + compilations = compilationRepository.findAll(pageRequest).getContent(); + } + + return compilations.stream() + .map(CompilationMapper::toDto) + .collect(Collectors.toList()); + } + + @Override + public CompilationDto getCompilationById(Long compId) { + Compilation compilation = compilationRepository.findById(compId) + .orElseThrow(() -> new RuntimeException("Compilation not found")); + return CompilationMapper.toDto(compilation); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventService.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventService.java new file mode 100644 index 0000000..c74227b --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventService.java @@ -0,0 +1,32 @@ +package ru.practicum.ewm.service; + +import jakarta.servlet.http.HttpServletRequest; +import ru.practicum.ewm.dto.request.NewEventDto; +import ru.practicum.ewm.dto.request.UpdateEventAdmin; +import ru.practicum.ewm.dto.request.UpdateEventUser; +import ru.practicum.ewm.dto.response.EventFullDto; +import ru.practicum.ewm.dto.response.EventShortDto; +import ru.practicum.ewm.model.EventState; + +import java.time.LocalDateTime; +import java.util.List; + +public interface EventService { + // Private + List getEventsByUserId(Long userId, int from, int size); + EventFullDto createEvent(Long userId, NewEventDto newEventDto); + EventFullDto getEventByIdAndUserId(Long userId, Long eventId); + EventFullDto updateEventByUserId(Long userId, Long eventId, UpdateEventUser updateRequest); + + // Admin + List getEventsByAdmin(List users, List states, List categories, + LocalDateTime rangeStart, LocalDateTime rangeEnd, int from, int size); + EventFullDto updateEventByAdmin(Long eventId, UpdateEventAdmin updateRequest); + + // Public + List getEventsPublic(String text, List categories, Boolean paid, + LocalDateTime rangeStart, LocalDateTime rangeEnd, + Boolean onlyAvailable, String sort, int from, int size, + HttpServletRequest request); + EventFullDto getEventByIdPublic(Long id, HttpServletRequest request); +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java new file mode 100644 index 0000000..17732ca --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -0,0 +1,219 @@ +package ru.practicum.ewm.service; + +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.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.client.StatsClient; +import ru.practicum.dto.EndpointHitDto; +import ru.practicum.ewm.dto.request.NewEventDto; +import ru.practicum.ewm.dto.request.UpdateEventAdmin; +import ru.practicum.ewm.dto.request.UpdateEventUser; +import ru.practicum.ewm.dto.response.EventFullDto; +import ru.practicum.ewm.dto.response.EventShortDto; +import ru.practicum.ewm.exception.BadRequestException; +import ru.practicum.ewm.exception.ConflictException; +import ru.practicum.ewm.exception.NotFoundException; +import ru.practicum.ewm.mapper.EventMapper; +import ru.practicum.ewm.model.*; +import ru.practicum.ewm.repository.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@Slf4j +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Transactional(readOnly = true) +public class EventServiceImpl implements EventService { + + EventRepository eventRepository; + UserRepository userRepository; + CategoryRepository categoryRepository; + LocationRepository locationRepository; + StatsClient statsClient; + + static String APP_NAME = "ewm-main-service"; + + // --- PRIVATE API --- + + @Override + @Transactional + public EventFullDto createEvent(Long userId, NewEventDto dto) { + if (dto.getEventDate().isBefore(LocalDateTime.now().plusHours(2))) { + throw new BadRequestException("Event date must be at least 2 hours in the future."); + } + + User initiator = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("User not found")); + Category category = categoryRepository.findById(dto.getCategory()) + .orElseThrow(() -> new NotFoundException("Category not found")); + Location location = locationRepository.findById(dto.getLocation()) + .orElseThrow(() -> new NotFoundException("Location not found")); + + Event event = EventMapper.toEvent(dto); + event.setInitiator(initiator); + event.setCategory(category); + event.setLocation(location); + event.setCreatedOn(LocalDateTime.now()); + event.setState(EventState.PENDING); + event.setConfirmedRequests(0L); + event.setViews(0L); + + return EventMapper.toFullDto(eventRepository.save(event)); + } + + @Override + public List getEventsByUserId(Long userId, int from, int size) { + return eventRepository.findAllByInitiatorId(userId, PageRequest.of(from / size, size)) + .stream().map(EventMapper::toShortDto).collect(Collectors.toList()); + } + + @Override + public EventFullDto getEventByIdAndUserId(Long userId, Long eventId) { + Event event = eventRepository.findByIdAndInitiatorId(eventId, userId) + .orElseThrow(() -> new NotFoundException("Event not found")); + return EventMapper.toFullDto(event); + } + + @Override + @Transactional + public EventFullDto updateEventByUserId(Long userId, Long eventId, UpdateEventUser request) { + Event event = eventRepository.findByIdAndInitiatorId(eventId, userId) + .orElseThrow(() -> new NotFoundException("Event not found")); + + if (event.getState() == EventState.PUBLISHED) { + throw new ConflictException("Only pending or canceled events can be changed"); + } + + if (request.getEventDate() != null) { + if (request.getEventDate().isBefore(LocalDateTime.now().plusHours(2))) { + throw new BadRequestException("Event date must be at least 2 hours in the future."); + } + event.setEventDate(request.getEventDate()); + } + + updateEventCommonFields(event, request.getAnnotation(), request.getCategory(), request.getDescription(), + request.getLocation(), request.getPaid(), request.getParticipantLimit(), request.getRequestModeration(), request.getTitle()); + + if (request.getStateAction() != null) { + if (request.getStateAction().equals("SEND_TO_REVIEW")) { + event.setState(EventState.PENDING); + } else if (request.getStateAction().equals("CANCEL_REVIEW")) { + event.setState(EventState.CANCELED); + } + } + + return EventMapper.toFullDto(eventRepository.save(event)); + } + + // --- ADMIN API --- + + @Override + public List getEventsByAdmin(List users, List states, List categories, + LocalDateTime start, LocalDateTime end, int from, int size) { + return eventRepository.findEventsByAdmin(users, states, categories, start, end, PageRequest.of(from / size, size)) + .stream().map(EventMapper::toFullDto).collect(Collectors.toList()); + } + + @Override + @Transactional + public EventFullDto updateEventByAdmin(Long eventId, UpdateEventAdmin request) { + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Event not found")); + + if (request.getEventDate() != null) { + if (request.getEventDate().isBefore(LocalDateTime.now().plusHours(1))) { + throw new BadRequestException("Event date must be at least 1 hour before publication"); + } + event.setEventDate(request.getEventDate()); + } + + if (request.getStateAction() != null) { + if (request.getStateAction().equals("PUBLISH_EVENT")) { + if (event.getState() != EventState.PENDING) { + throw new ConflictException("Event must be PENDING to publish"); + } + event.setState(EventState.PUBLISHED); + event.setPublishedOn(LocalDateTime.now()); + } else if (request.getStateAction().equals("REJECT_EVENT")) { + if (event.getState() == EventState.PUBLISHED) { + throw new ConflictException("Cannot reject published event"); + } + event.setState(EventState.CANCELED); + } + } + + updateEventCommonFields(event, request.getAnnotation(), request.getCategory(), request.getDescription(), + request.getLocation(), request.getPaid(), request.getParticipantLimit(), request.getRequestModeration(), request.getTitle()); + + return EventMapper.toFullDto(eventRepository.save(event)); + } + + // --- PUBLIC API --- + + @Override + public List getEventsPublic(String text, List cats, Boolean paid, LocalDateTime start, + LocalDateTime end, Boolean onlyAvail, String sort, int from, int size, + HttpServletRequest request) { + if (start != null && end != null && start.isAfter(end)) { + throw new BadRequestException("Start date cannot be after end date"); + } + + sendStats(request); + + List events = eventRepository.findPublishedEvents(text, cats, paid, start, end, onlyAvail, PageRequest.of(from / size, size)); + + + + return events.stream().map(EventMapper::toShortDto).collect(Collectors.toList()); + } + + @Override + public EventFullDto getEventByIdPublic(Long id, HttpServletRequest request) { + Event event = eventRepository.findByIdAndState(id, EventState.PUBLISHED) + .orElseThrow(() -> new NotFoundException("Published event not found")); + + sendStats(request); + event.setViews(event.getViews() + 1); + eventRepository.save(event); + + return EventMapper.toFullDto(event); + } + + // --- Вспомогательные методы --- + + private void updateEventCommonFields(Event event, String annotation, Long catId, String description, + Long locId, Boolean paid, Integer partLimit, Boolean reqMod, String title) { + if (annotation != null) event.setAnnotation(annotation); + if (catId != null) { + event.setCategory(categoryRepository.findById(catId) + .orElseThrow(() -> new NotFoundException("Category not found"))); + } + if (description != null) event.setDescription(description); + if (locId != null) { + event.setLocation(locationRepository.findById(locId) + .orElseThrow(() -> new NotFoundException("Location not found"))); + } + if (paid != null) event.setPaid(paid); + if (partLimit != null) event.setParticipantLimit(partLimit); + if (reqMod != null) event.setRequestModeration(reqMod); + if (title != null) event.setTitle(title); + } + + private void sendStats(HttpServletRequest request) { + + statsClient.hit( + APP_NAME, + request.getRequestURI(), + request.getRemoteAddr(), + LocalDateTime.now() + ); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationService.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationService.java new file mode 100644 index 0000000..05f12f7 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationService.java @@ -0,0 +1,17 @@ +package ru.practicum.ewm.service; + +import ru.practicum.ewm.dto.request.NewLocationDto; +import ru.practicum.ewm.dto.response.LocationDto; + +import java.util.List; + +public interface LocationService { + + LocationDto createLocation(NewLocationDto newLocationDto); + + LocationDto getLocation(Long locationId); + + List getAllLocations(); + + void deleteLocation(Long locationId); +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java new file mode 100644 index 0000000..b07136e --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java @@ -0,0 +1,52 @@ +package ru.practicum.ewm.service; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.ewm.dto.request.NewLocationDto; +import ru.practicum.ewm.dto.response.LocationDto; +import ru.practicum.ewm.mapper.LocationMapper; +import ru.practicum.ewm.model.Location; +import ru.practicum.ewm.repository.LocationRepository; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Transactional(readOnly = true) +public class LocationServiceImpl implements LocationService { + + LocationRepository locationRepository; + + @Override + @Transactional + public LocationDto createLocation(NewLocationDto newLocationDto) { + Location location = LocationMapper.toLocation(newLocationDto); + Location savedLocation = locationRepository.save(location); + return LocationMapper.toDto(savedLocation); + } + + @Override + public LocationDto getLocation(Long locationId) { + Location location = locationRepository.findById(locationId) + .orElseThrow(() -> new RuntimeException("Location not found")); + return LocationMapper.toDto(location); + } + + @Override + public List getAllLocations() { + return locationRepository.findAll().stream() + .map(LocationMapper::toDto) + .collect(Collectors.toList()); + } + + @Override + @Transactional + public void deleteLocation(Long locationId) { + locationRepository.deleteById(locationId); + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestService.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestService.java new file mode 100644 index 0000000..93679df --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestService.java @@ -0,0 +1,18 @@ +package ru.practicum.ewm.service; + +import ru.practicum.ewm.dto.request.RequestStatusUpdate; +import ru.practicum.ewm.dto.response.RequestDto; +import ru.practicum.ewm.dto.response.RequestStatusUpdateResult; + +import java.util.List; + +public interface RequestService { + // Для тех, кто подает заявку (Requester) + List getRequestsByUserId(Long userId); + RequestDto createRequest(Long userId, Long eventId); + RequestDto cancelRequest(Long userId, Long requestId); + + // Для организатора события (Initiator) + List getEventRequests(Long userId, Long eventId); + RequestStatusUpdateResult updateRequestStatus(Long userId, Long eventId, RequestStatusUpdate updateRequest); +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java new file mode 100644 index 0000000..f5cefab --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java @@ -0,0 +1,133 @@ +package ru.practicum.ewm.service; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.ewm.dto.request.RequestStatusUpdate; +import ru.practicum.ewm.dto.response.RequestDto; +import ru.practicum.ewm.dto.response.RequestStatusUpdateResult; +import ru.practicum.ewm.exception.ConflictException; +import ru.practicum.ewm.exception.NotFoundException; +import ru.practicum.ewm.mapper.RequestMapper; +import ru.practicum.ewm.model.*; +import ru.practicum.ewm.repository.EventRepository; +import ru.practicum.ewm.repository.ParticipationRequestRepository; +import ru.practicum.ewm.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Transactional(readOnly = true) +public class RequestServiceImpl implements RequestService { + + ParticipationRequestRepository requestRepository; + EventRepository eventRepository; + UserRepository userRepository; + + @Override + public List getRequestsByUserId(Long userId) { + if (!userRepository.existsById(userId)) throw new NotFoundException("User not found"); + return requestRepository.findAllByRequesterId(userId).stream() + .map(RequestMapper::toDto).collect(Collectors.toList()); + } + + @Override + @Transactional + public RequestDto createRequest(Long userId, Long eventId) { + User requester = userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User not found")); + Event event = eventRepository.findById(eventId).orElseThrow(() -> new NotFoundException("Event not found")); + + if (requestRepository.existsByRequesterIdAndEventId(userId, eventId)) { + throw new ConflictException("Request already exists"); + } + if (event.getInitiator().getId().equals(userId)) { + throw new ConflictException("Initiator cannot request participation in their own event"); + } + if (event.getState() != EventState.PUBLISHED) { + throw new ConflictException("Cannot participate in unpublished event"); + } + if (event.getParticipantLimit() > 0 && event.getConfirmedRequests() >= event.getParticipantLimit()) { + throw new ConflictException("Participant limit reached"); + } + + ParticipationRequest request = new ParticipationRequest(); + request.setCreated(LocalDateTime.now()); + request.setEvent(event); + request.setRequester(requester); + + // Если модерация не нужна или лимит 0 — подтверждаем сразу + if (!event.getRequestModeration() || event.getParticipantLimit() == 0) { + request.setStatus(RequestStatus.CONFIRMED); + event.setConfirmedRequests(event.getConfirmedRequests() + 1); + eventRepository.save(event); + } else { + request.setStatus(RequestStatus.PENDING); + } + + return RequestMapper.toDto(requestRepository.save(request)); + } + + @Override + @Transactional + public RequestDto cancelRequest(Long userId, Long requestId) { + ParticipationRequest request = requestRepository.findById(requestId) + .orElseThrow(() -> new NotFoundException("Request not found")); + if (!request.getRequester().getId().equals(userId)) { + throw new ConflictException("Not your request"); + } + request.setStatus(RequestStatus.CANCELED); + return RequestMapper.toDto(requestRepository.save(request)); + } + + @Override + public List getEventRequests(Long userId, Long eventId) { + Event event = eventRepository.findById(eventId).orElseThrow(() -> new NotFoundException("Event not found")); + if (!event.getInitiator().getId().equals(userId)) throw new ConflictException("Not your event"); + + return requestRepository.findAllByEventId(eventId).stream() + .map(RequestMapper::toDto).collect(Collectors.toList()); + } + + @Override + @Transactional + public RequestStatusUpdateResult updateRequestStatus(Long userId, Long eventId, RequestStatusUpdate update) { + Event event = eventRepository.findById(eventId).orElseThrow(() -> new NotFoundException("Event not found")); + if (!event.getInitiator().getId().equals(userId)) throw new ConflictException("Not your event"); + + List requests = requestRepository.findAllById(update.getRequestIds()); + + RequestStatusUpdateResult result = new RequestStatusUpdateResult(); + result.setConfirmedRequests(new ArrayList<>()); + result.setRejectedRequests(new ArrayList<>()); + + for (ParticipationRequest req : requests) { + if (req.getStatus() != RequestStatus.PENDING) { + throw new ConflictException("Request must be in status PENDING"); + } + + if (update.getStatus() == RequestStatus.REJECTED) { + req.setStatus(RequestStatus.REJECTED); + result.getRejectedRequests().add(RequestMapper.toDto(requestRepository.save(req))); + } else { + if (event.getParticipantLimit() == 0 || event.getConfirmedRequests() < event.getParticipantLimit()) { + req.setStatus(RequestStatus.CONFIRMED); + event.setConfirmedRequests(event.getConfirmedRequests() + 1); + result.getConfirmedRequests().add(RequestMapper.toDto(requestRepository.save(req))); + } else { + req.setStatus(RequestStatus.REJECTED); + result.getRejectedRequests().add(RequestMapper.toDto(requestRepository.save(req))); + throw new ConflictException("Participant limit reached"); + } + } + } + eventRepository.save(event); + return result; + } +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/UserService.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/UserService.java new file mode 100644 index 0000000..534a248 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/UserService.java @@ -0,0 +1,15 @@ +package ru.practicum.ewm.service; + +import ru.practicum.ewm.dto.request.NewUserRequest; +import ru.practicum.ewm.dto.response.UserDto; + +import java.util.List; + +public interface UserService { + + UserDto createUser(NewUserRequest newUserRequest); + + List getUsers(List ids, int from, int size); + + void deleteUser(Long userId); +} \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/UserServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/UserServiceImpl.java new file mode 100644 index 0000000..81a4b1f --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/UserServiceImpl.java @@ -0,0 +1,54 @@ +package ru.practicum.ewm.service; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.ewm.dto.request.NewUserRequest; +import ru.practicum.ewm.dto.response.UserDto; +import ru.practicum.ewm.mapper.UserMapper; +import ru.practicum.ewm.model.User; +import ru.practicum.ewm.repository.UserRepository; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@Transactional(readOnly = true) +public class UserServiceImpl implements UserService { + + UserRepository userRepository; + + @Override + @Transactional + public UserDto createUser(NewUserRequest newUserRequest) { + User user = UserMapper.toUser(newUserRequest); + User savedUser = userRepository.save(user); + return UserMapper.toDto(savedUser); + } + + @Override + public List getUsers(List ids, int from, int size) { + PageRequest pageRequest = PageRequest.of(from / size, size); + + if (ids == null || ids.isEmpty()) { + return userRepository.findAll(pageRequest).stream() + .map(UserMapper::toDto) + .collect(Collectors.toList()); + } + + return userRepository.findAllById(ids).stream() + .map(UserMapper::toDto) + .collect(Collectors.toList()); + } + + @Override + @Transactional + public void deleteUser(Long userId) { + userRepository.deleteById(userId); + } +} \ No newline at end of file From 628489aa7dfdf5395359608c31ac4c970a5b4394 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 14:07:55 +0300 Subject: [PATCH 02/24] fix: resolve postman test failures, add validation handling and default values --- .../java/ru/practicum/ewm/ErrorHandler.java | 10 ++- .../ewm/mapper/CompilationMapper.java | 36 ++++---- .../ewm/repository/EventRepository.java | 2 + .../ewm/service/CategoryServiceImpl.java | 25 ++++-- .../ewm/service/CompilationServiceImpl.java | 17 ++-- .../ewm/service/EventServiceImpl.java | 90 +++++++++++++------ .../ewm/service/RequestServiceImpl.java | 17 ++-- 7 files changed, 128 insertions(+), 69 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java b/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java index c49baad..895bcf4 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java @@ -61,15 +61,21 @@ public ApiError handleConflict(final Exception e) { @ResponseStatus(HttpStatus.BAD_REQUEST) public ApiError handleBadRequest(final Exception e) { log.error("400: {}", e.getMessage()); + String message = e.getMessage(); + + + if (e instanceof MethodArgumentNotValidException) { + message = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors().get(0).getDefaultMessage(); + } + return ApiError.builder() .status(HttpStatus.BAD_REQUEST.name()) .reason("Incorrectly made request.") - .message(e.getMessage()) + .message(message) .timestamp(LocalDateTime.now().format(FORMATTER)) .build(); } - @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ApiError handleInternalServerError(final Throwable e) { diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/CompilationMapper.java b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/CompilationMapper.java index a57db84..cdce13d 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/CompilationMapper.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/CompilationMapper.java @@ -1,35 +1,35 @@ package ru.practicum.ewm.mapper; -import ru.practicum.ewm.dto.response.CompilationDto; -import ru.practicum.ewm.dto.response.EventShortDto; import ru.practicum.ewm.dto.request.NewCompilationDto; +import ru.practicum.ewm.dto.response.CompilationDto; import ru.practicum.ewm.model.Compilation; +import ru.practicum.ewm.model.Event; import java.util.Set; import java.util.stream.Collectors; public class CompilationMapper { + public static Compilation toCompilation(NewCompilationDto dto, Set events) { + Compilation compilation = new Compilation(); + compilation.setTitle(dto.getTitle()); + compilation.setEvents(events); + compilation.setPinned(dto.getPinned() != null ? dto.getPinned() : false); + return compilation; + } + public static CompilationDto toDto(Compilation compilation) { - CompilationDto compilationDto = new CompilationDto(); - compilationDto.setId(compilation.getId()); - compilationDto.setTitle(compilation.getTitle()); - compilationDto.setPinned(compilation.getPinned()); + CompilationDto dto = new CompilationDto(); + dto.setId(compilation.getId()); + dto.setTitle(compilation.getTitle()); + dto.setPinned(compilation.getPinned()); if (compilation.getEvents() != null) { - Set eventDtos = compilation.getEvents().stream() + + dto.setEvents(compilation.getEvents().stream() .map(EventMapper::toShortDto) - .collect(Collectors.toSet()); - compilationDto.setEvents(eventDtos); + .collect(Collectors.toSet())); } - - return compilationDto; - } - - public static Compilation toCompilation(NewCompilationDto newCompilationDto) { - Compilation compilation = new Compilation(); - compilation.setTitle(newCompilationDto.getTitle()); - compilation.setPinned(newCompilationDto.getPinned()); - return compilation; + return dto; } } \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java index f51d0b4..2c6ec50 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java @@ -13,6 +13,8 @@ public interface EventRepository extends JpaRepository { + boolean existsByCategoryId(Long categoryId); + List findAllByInitiatorId(Long initiatorId, Pageable pageable); Optional findByIdAndInitiatorId(Long eventId, Long initiatorId); diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java index 5928a7e..723f2fe 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java @@ -8,9 +8,12 @@ import org.springframework.transaction.annotation.Transactional; import ru.practicum.ewm.dto.request.NewCategoryDto; import ru.practicum.ewm.dto.response.CategoryDto; +import ru.practicum.ewm.exception.ConflictException; +import ru.practicum.ewm.exception.NotFoundException; import ru.practicum.ewm.mapper.CategoryMapper; import ru.practicum.ewm.model.Category; import ru.practicum.ewm.repository.CategoryRepository; +import ru.practicum.ewm.repository.EventRepository; import java.util.List; import java.util.stream.Collectors; @@ -41,12 +44,6 @@ public CategoryDto updateCategory(Long catId, CategoryDto categoryDto) { return CategoryMapper.toDto(updatedCategory); } - @Override - @Transactional - public void deleteCategory(Long catId) { - categoryRepository.deleteById(catId); - } - @Override public List getCategories(int from, int size) { PageRequest pageRequest = PageRequest.of(from / size, size); @@ -61,4 +58,20 @@ public CategoryDto getCategory(Long catId) { .orElseThrow(() -> new RuntimeException("Category not found")); return CategoryMapper.toDto(category); } + + + private final EventRepository eventRepository; + + @Override + @Transactional + public void deleteCategory(Long catId) { + if (!categoryRepository.existsById(catId)) { + throw new NotFoundException("Category not found"); + } + + if (eventRepository.existsByCategoryId(catId)) { + throw new ConflictException("The category is not empty"); + } + categoryRepository.deleteById(catId); + } } \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java index 89c4e8e..641568d 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java @@ -9,6 +9,7 @@ import ru.practicum.ewm.dto.request.NewCompilationDto; import ru.practicum.ewm.dto.request.UpdateCompilationRequest; import ru.practicum.ewm.dto.response.CompilationDto; +import ru.practicum.ewm.exception.NotFoundException; import ru.practicum.ewm.mapper.CompilationMapper; import ru.practicum.ewm.model.Compilation; import ru.practicum.ewm.model.Event; @@ -33,22 +34,19 @@ public class CompilationServiceImpl implements CompilationService { @Transactional public CompilationDto addCompilation(NewCompilationDto newCompilationDto) { Set events = new HashSet<>(); + if (newCompilationDto.getEvents() != null && !newCompilationDto.getEvents().isEmpty()) { events = new HashSet<>(eventRepository.findAllById(newCompilationDto.getEvents())); } - Compilation compilation = CompilationMapper.toCompilation(newCompilationDto); - - compilation.setEvents(events); + Compilation compilation = CompilationMapper.toCompilation(newCompilationDto, events); if (compilation.getPinned() == null) { compilation.setPinned(false); } - Compilation savedCompilation = compilationRepository.save(compilation); - return CompilationMapper.toDto(savedCompilation); + return CompilationMapper.toDto(compilationRepository.save(compilation)); } - @Override @Transactional public void deleteCompilation(Long compId) { @@ -62,15 +60,20 @@ public void deleteCompilation(Long compId) { @Transactional public CompilationDto updateCompilation(Long compId, UpdateCompilationRequest updateRequest) { Compilation compilation = compilationRepository.findById(compId) - .orElseThrow(() -> new RuntimeException("Compilation not found")); + .orElseThrow(() -> new NotFoundException("Compilation with id=" + compId + " was not found")); + if (updateRequest.getEvents() != null) { Set events = new HashSet<>(eventRepository.findAllById(updateRequest.getEvents())); compilation.setEvents(events); } + + if (updateRequest.getPinned() != null) { compilation.setPinned(updateRequest.getPinned()); } + + if (updateRequest.getTitle() != null && !updateRequest.getTitle().isBlank()) { compilation.setTitle(updateRequest.getTitle()); } diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 17732ca..5683a31 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -45,30 +45,53 @@ public class EventServiceImpl implements EventService { @Override @Transactional - public EventFullDto createEvent(Long userId, NewEventDto dto) { - if (dto.getEventDate().isBefore(LocalDateTime.now().plusHours(2))) { - throw new BadRequestException("Event date must be at least 2 hours in the future."); + public EventFullDto createEvent(Long userId, NewEventDto newEventDto) { + + if (newEventDto.getEventDate().isBefore(LocalDateTime.now().plusHours(2))) { + throw new BadRequestException("Field: eventDate. Error: must contain a date that has not yet arrived. " + + "Value: " + newEventDto.getEventDate()); } + User initiator = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("User not found")); - Category category = categoryRepository.findById(dto.getCategory()) - .orElseThrow(() -> new NotFoundException("Category not found")); - Location location = locationRepository.findById(dto.getLocation()) - .orElseThrow(() -> new NotFoundException("Location not found")); + .orElseThrow(() -> new NotFoundException("User with id=" + userId + " was not found")); + + + Category category = categoryRepository.findById(newEventDto.getCategory()) + .orElseThrow(() -> new NotFoundException("Category with id=" + newEventDto.getCategory() + " was not found")); + + + Location location = locationRepository.findById(newEventDto.getLocation()) + .orElseThrow(() -> new NotFoundException("Location with id=" + newEventDto.getLocation() + " was not found")); + + + Event event = EventMapper.toEvent(newEventDto); + - Event event = EventMapper.toEvent(dto); event.setInitiator(initiator); event.setCategory(category); event.setLocation(location); event.setCreatedOn(LocalDateTime.now()); event.setState(EventState.PENDING); + + event.setConfirmedRequests(0L); event.setViews(0L); - return EventMapper.toFullDto(eventRepository.save(event)); - } + if (event.getPaid() == null) { + event.setPaid(false); + } + if (event.getParticipantLimit() == null) { + event.setParticipantLimit(0); + } + if (event.getRequestModeration() == null) { + event.setRequestModeration(true); + } + + Event savedEvent = eventRepository.save(event); + return EventMapper.toFullDto(savedEvent); + } @Override public List getEventsByUserId(Long userId, int from, int size) { return eventRepository.findAllByInitiatorId(userId, PageRequest.of(from / size, size)) @@ -86,7 +109,7 @@ public EventFullDto getEventByIdAndUserId(Long userId, Long eventId) { @Transactional public EventFullDto updateEventByUserId(Long userId, Long eventId, UpdateEventUser request) { Event event = eventRepository.findByIdAndInitiatorId(eventId, userId) - .orElseThrow(() -> new NotFoundException("Event not found")); + .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); if (event.getState() == EventState.PUBLISHED) { throw new ConflictException("Only pending or canceled events can be changed"); @@ -99,9 +122,6 @@ public EventFullDto updateEventByUserId(Long userId, Long eventId, UpdateEventUs event.setEventDate(request.getEventDate()); } - updateEventCommonFields(event, request.getAnnotation(), request.getCategory(), request.getDescription(), - request.getLocation(), request.getPaid(), request.getParticipantLimit(), request.getRequestModeration(), request.getTitle()); - if (request.getStateAction() != null) { if (request.getStateAction().equals("SEND_TO_REVIEW")) { event.setState(EventState.PENDING); @@ -110,6 +130,9 @@ public EventFullDto updateEventByUserId(Long userId, Long eventId, UpdateEventUs } } + updateEventCommonFields(event, request.getAnnotation(), request.getCategory(), request.getDescription(), + request.getLocation(), request.getPaid(), request.getParticipantLimit(), request.getRequestModeration(), request.getTitle()); + return EventMapper.toFullDto(eventRepository.save(event)); } @@ -125,26 +148,30 @@ public List getEventsByAdmin(List users, List st @Override @Transactional public EventFullDto updateEventByAdmin(Long eventId, UpdateEventAdmin request) { + Event event = eventRepository.findById(eventId) - .orElseThrow(() -> new NotFoundException("Event not found")); + .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); if (request.getEventDate() != null) { if (request.getEventDate().isBefore(LocalDateTime.now().plusHours(1))) { - throw new BadRequestException("Event date must be at least 1 hour before publication"); + throw new BadRequestException("Event date must be at least 1 hour before publication. " + + "Value: " + request.getEventDate()); } event.setEventDate(request.getEventDate()); } if (request.getStateAction() != null) { if (request.getStateAction().equals("PUBLISH_EVENT")) { + if (event.getState() != EventState.PENDING) { - throw new ConflictException("Event must be PENDING to publish"); + throw new ConflictException("Cannot publish the event because it's not in the PENDING state: " + event.getState()); } event.setState(EventState.PUBLISHED); event.setPublishedOn(LocalDateTime.now()); } else if (request.getStateAction().equals("REJECT_EVENT")) { + if (event.getState() == EventState.PUBLISHED) { - throw new ConflictException("Cannot reject published event"); + throw new ConflictException("Cannot reject the event because it's already published."); } event.setState(EventState.CANCELED); } @@ -159,20 +186,33 @@ public EventFullDto updateEventByAdmin(Long eventId, UpdateEventAdmin request) { // --- PUBLIC API --- @Override - public List getEventsPublic(String text, List cats, Boolean paid, LocalDateTime start, - LocalDateTime end, Boolean onlyAvail, String sort, int from, int size, + public List getEventsPublic(String text, List categories, Boolean paid, + LocalDateTime rangeStart, LocalDateTime rangeEnd, + Boolean onlyAvailable, String sort, int from, int size, HttpServletRequest request) { - if (start != null && end != null && start.isAfter(end)) { + + if (rangeStart != null && rangeEnd != null && rangeStart.isAfter(rangeEnd)) { throw new BadRequestException("Start date cannot be after end date"); } - sendStats(request); + if (rangeStart == null && rangeEnd == null) { + rangeStart = LocalDateTime.now(); + } - List events = eventRepository.findPublishedEvents(text, cats, paid, start, end, onlyAvail, PageRequest.of(from / size, size)); + sendStats(request); + List events = eventRepository.findPublishedEvents(text, categories, paid, rangeStart, rangeEnd, + onlyAvailable, PageRequest.of(from / size, size)); + if ("VIEWS".equals(sort)) { + events = events.stream() + .sorted((e1, e2) -> e2.getViews().compareTo(e1.getViews())) + .collect(Collectors.toList()); + } - return events.stream().map(EventMapper::toShortDto).collect(Collectors.toList()); + return events.stream() + .map(EventMapper::toShortDto) + .collect(Collectors.toList()); } @Override diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java index f5cefab..961f610 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java @@ -53,6 +53,7 @@ public RequestDto createRequest(Long userId, Long eventId) { if (event.getState() != EventState.PUBLISHED) { throw new ConflictException("Cannot participate in unpublished event"); } + if (event.getParticipantLimit() > 0 && event.getConfirmedRequests() >= event.getParticipantLimit()) { throw new ConflictException("Participant limit reached"); } @@ -99,32 +100,26 @@ public List getEventRequests(Long userId, Long eventId) { @Transactional public RequestStatusUpdateResult updateRequestStatus(Long userId, Long eventId, RequestStatusUpdate update) { Event event = eventRepository.findById(eventId).orElseThrow(() -> new NotFoundException("Event not found")); - if (!event.getInitiator().getId().equals(userId)) throw new ConflictException("Not your event"); List requests = requestRepository.findAllById(update.getRequestIds()); - RequestStatusUpdateResult result = new RequestStatusUpdateResult(); result.setConfirmedRequests(new ArrayList<>()); result.setRejectedRequests(new ArrayList<>()); for (ParticipationRequest req : requests) { - if (req.getStatus() != RequestStatus.PENDING) { - throw new ConflictException("Request must be in status PENDING"); - } - if (update.getStatus() == RequestStatus.REJECTED) { req.setStatus(RequestStatus.REJECTED); result.getRejectedRequests().add(RequestMapper.toDto(requestRepository.save(req))); } else { - if (event.getParticipantLimit() == 0 || event.getConfirmedRequests() < event.getParticipantLimit()) { - req.setStatus(RequestStatus.CONFIRMED); - event.setConfirmedRequests(event.getConfirmedRequests() + 1); - result.getConfirmedRequests().add(RequestMapper.toDto(requestRepository.save(req))); - } else { + + if (event.getParticipantLimit() != 0 && event.getConfirmedRequests() >= event.getParticipantLimit()) { req.setStatus(RequestStatus.REJECTED); result.getRejectedRequests().add(RequestMapper.toDto(requestRepository.save(req))); throw new ConflictException("Participant limit reached"); } + req.setStatus(RequestStatus.CONFIRMED); + event.setConfirmedRequests(event.getConfirmedRequests() + 1); + result.getConfirmedRequests().add(RequestMapper.toDto(requestRepository.save(req))); } } eventRepository.save(event); From 2668e375fa6d00b4ba746b42a72eb80a0ecd4dd7 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 14:30:57 +0300 Subject: [PATCH 03/24] fix: wrap stats hit in try-catch and improve error handling --- .../java/ru/practicum/ewm/ErrorHandler.java | 66 +++++-------------- .../admin/AdminCategoryController.java | 2 +- .../ewm/service/EventServiceImpl.java | 24 ++++--- 3 files changed, 30 insertions(+), 62 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java b/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java index 895bcf4..41f0b5a 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java @@ -4,7 +4,6 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -13,11 +12,8 @@ import ru.practicum.ewm.exception.ConflictException; import ru.practicum.ewm.exception.NotFoundException; -import jakarta.validation.ConstraintViolationException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.stream.Collectors; @Slf4j @RestControllerAdvice @@ -25,68 +21,36 @@ public class ErrorHandler { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - - @ExceptionHandler + @ExceptionHandler(NotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ApiError handleNotFound(final NotFoundException e) { - log.error("404: {}", e.getMessage()); - return ApiError.builder() - .status(HttpStatus.NOT_FOUND.name()) - .reason("The required object was not found.") - .message(e.getMessage()) - .timestamp(LocalDateTime.now().format(FORMATTER)) - .build(); + return createApiError(HttpStatus.NOT_FOUND, "The required object was not found.", e.getMessage()); } - @ExceptionHandler({ConflictException.class, DataIntegrityViolationException.class}) @ResponseStatus(HttpStatus.CONFLICT) public ApiError handleConflict(final Exception e) { - log.error("409: {}", e.getMessage()); - return ApiError.builder() - .status(HttpStatus.CONFLICT.name()) - .reason("For the requested operation the conditions are not met.") - .message(e.getMessage()) - .timestamp(LocalDateTime.now().format(FORMATTER)) - .build(); + return createApiError(HttpStatus.CONFLICT, "For the requested operation the conditions are not met.", e.getMessage()); } - - @ExceptionHandler({ - BadRequestException.class, - MethodArgumentNotValidException.class, - ConstraintViolationException.class, - MissingServletRequestParameterException.class - }) + @ExceptionHandler({BadRequestException.class, MethodArgumentNotValidException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) public ApiError handleBadRequest(final Exception e) { - log.error("400: {}", e.getMessage()); - String message = e.getMessage(); - - - if (e instanceof MethodArgumentNotValidException) { - message = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors().get(0).getDefaultMessage(); - } - - return ApiError.builder() - .status(HttpStatus.BAD_REQUEST.name()) - .reason("Incorrectly made request.") - .message(message) - .timestamp(LocalDateTime.now().format(FORMATTER)) - .build(); + return createApiError(HttpStatus.BAD_REQUEST, "Incorrectly made request.", e.getMessage()); } - @ExceptionHandler + @ExceptionHandler(Throwable.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ApiError handleInternalServerError(final Throwable e) { - log.error("500: ", e); + public ApiError handleThrowable(final Throwable e) { + log.error("Unhandled Exception: ", e); + return createApiError(HttpStatus.INTERNAL_SERVER_ERROR, "Error occurred", e.getMessage()); + } + + private ApiError createApiError(HttpStatus status, String reason, String message) { return ApiError.builder() - .status(HttpStatus.INTERNAL_SERVER_ERROR.name()) - .reason("Error occurred") - .message(e.getMessage()) - .errors(Arrays.stream(e.getStackTrace()) - .map(StackTraceElement::toString) - .collect(Collectors.toList())) + .status(status.name()) + .reason(reason) + .message(message) .timestamp(LocalDateTime.now().format(FORMATTER)) .build(); } diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java index 3e64c80..7dca8da 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java @@ -23,7 +23,7 @@ public CategoryDto createCategory(@Valid @RequestBody NewCategoryDto newCategory @PatchMapping("/{catId}") public CategoryDto updateCategory(@PathVariable Long catId, - @Valid @RequestBody CategoryDto categoryDto) { + @Valid @RequestBody CategoryDto categoryDto) { // @Valid здесь есть? return categoryService.updateCategory(catId, categoryDto); } diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 5683a31..ffd930c 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -191,8 +191,10 @@ public List getEventsPublic(String text, List categories, B Boolean onlyAvailable, String sort, int from, int size, HttpServletRequest request) { - if (rangeStart != null && rangeEnd != null && rangeStart.isAfter(rangeEnd)) { - throw new BadRequestException("Start date cannot be after end date"); + if (rangeStart != null && rangeEnd != null) { + if (rangeStart.isAfter(rangeEnd)) { + throw new BadRequestException("Start date cannot be after end date"); + } } if (rangeStart == null && rangeEnd == null) { @@ -200,7 +202,6 @@ public List getEventsPublic(String text, List categories, B } sendStats(request); - List events = eventRepository.findPublishedEvents(text, categories, paid, rangeStart, rangeEnd, onlyAvailable, PageRequest.of(from / size, size)); @@ -248,12 +249,15 @@ private void updateEventCommonFields(Event event, String annotation, Long catId, } private void sendStats(HttpServletRequest request) { - - statsClient.hit( - APP_NAME, - request.getRequestURI(), - request.getRemoteAddr(), - LocalDateTime.now() - ); + try { + statsClient.hit( + APP_NAME, + request.getRequestURI(), + request.getRemoteAddr(), + LocalDateTime.now() + ); + } catch (Exception e) { + log.error("Ошибка при отправке статистики в stats-server: {}", e.getMessage()); + } } } \ No newline at end of file From 3de13022e92007c302a2084f08f88f8f8613fbad Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 14:41:46 +0300 Subject: [PATCH 04/24] fix: update ErrorHandler --- .../ewm/repository/EventRepository.java | 3 ++- .../ewm/{ => service}/ErrorHandler.java | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) rename ewm-main-service/src/main/java/ru/practicum/ewm/{ => service}/ErrorHandler.java (69%) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java index 2c6ec50..aa0f12c 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java @@ -13,7 +13,8 @@ public interface EventRepository extends JpaRepository { - boolean existsByCategoryId(Long categoryId); + @Query("SELECT COUNT(e) > 0 FROM Event e WHERE e.category.id = :catId") + boolean existsByCategoryId(@Param("catId") Long catId); List findAllByInitiatorId(Long initiatorId, Pageable pageable); diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/ErrorHandler.java similarity index 69% rename from ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java rename to ewm-main-service/src/main/java/ru/practicum/ewm/service/ErrorHandler.java index 41f0b5a..9d5e04e 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/ErrorHandler.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/ErrorHandler.java @@ -1,17 +1,20 @@ -package ru.practicum.ewm; +package ru.practicum.ewm.service; 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.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.HandlerMethodValidationException; import ru.practicum.ewm.dto.response.ApiError; import ru.practicum.ewm.exception.BadRequestException; import ru.practicum.ewm.exception.ConflictException; import ru.practicum.ewm.exception.NotFoundException; +import jakarta.validation.ConstraintViolationException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -24,25 +27,34 @@ public class ErrorHandler { @ExceptionHandler(NotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ApiError handleNotFound(final NotFoundException e) { + log.error("404 NotFound: {}", e.getMessage()); return createApiError(HttpStatus.NOT_FOUND, "The required object was not found.", e.getMessage()); } @ExceptionHandler({ConflictException.class, DataIntegrityViolationException.class}) @ResponseStatus(HttpStatus.CONFLICT) public ApiError handleConflict(final Exception e) { + log.error("409 Conflict: {}", e.getMessage()); return createApiError(HttpStatus.CONFLICT, "For the requested operation the conditions are not met.", e.getMessage()); } - @ExceptionHandler({BadRequestException.class, MethodArgumentNotValidException.class}) + @ExceptionHandler({ + BadRequestException.class, + MethodArgumentNotValidException.class, // Ошибки в @RequestBody + ConstraintViolationException.class, // Ошибки jakarta валидации + MissingServletRequestParameterException.class, // Ошибка №001 (отсутствие eventId) + HandlerMethodValidationException.class // Новое в Spring 6 / Boot 3 для параметров контроллера + }) @ResponseStatus(HttpStatus.BAD_REQUEST) public ApiError handleBadRequest(final Exception e) { + log.error("400 BadRequest: {}", e.getMessage()); return createApiError(HttpStatus.BAD_REQUEST, "Incorrectly made request.", e.getMessage()); } @ExceptionHandler(Throwable.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ApiError handleThrowable(final Throwable e) { - log.error("Unhandled Exception: ", e); + log.error("500 InternalServerError: ", e); return createApiError(HttpStatus.INTERNAL_SERVER_ERROR, "Error occurred", e.getMessage()); } From e472d72d3ac206470075efcf1433168822fcce5a Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 15:04:42 +0300 Subject: [PATCH 05/24] fix: align event location with spec and update error handler --- .../admin/AdminCompilationController.java | 2 +- .../ewm/dto/request/NewEventDto.java | 3 +- .../ewm/dto/request/UpdateEventAdmin.java | 3 +- .../ewm/dto/request/UpdateEventUser.java | 3 +- .../ewm/dto/response/CategoryDto.java | 6 +- .../practicum/ewm/mapper/LocationMapper.java | 10 +-- .../practicum/ewm/service/ErrorHandler.java | 42 ++++++--- .../ewm/service/EventServiceImpl.java | 89 ++++++++----------- 8 files changed, 84 insertions(+), 74 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java index 54dcf40..b254016 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java @@ -24,7 +24,7 @@ public CompilationDto addCompilation(@Valid @RequestBody NewCompilationDto newCo } @DeleteMapping("/{compId}") - @ResponseStatus(HttpStatus.NO_CONTENT) + @ResponseStatus(HttpStatus.NO_CONTENT) // Только этот статус, без возврата тела public void deleteCompilation(@PathVariable Long compId) { compilationService.deleteCompilation(compId); } diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/dto/request/NewEventDto.java b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/request/NewEventDto.java index cfb19ff..9f7a7f3 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/dto/request/NewEventDto.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/request/NewEventDto.java @@ -6,6 +6,7 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.dto.response.LocationDto; import java.time.LocalDateTime; @@ -30,7 +31,7 @@ public class NewEventDto { Long category; @NotNull - Long location; + LocationDto location; @NotNull @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/dto/request/UpdateEventAdmin.java b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/request/UpdateEventAdmin.java index b707545..2673a71 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/dto/request/UpdateEventAdmin.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/request/UpdateEventAdmin.java @@ -6,6 +6,7 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.dto.response.LocationDto; import java.time.LocalDateTime; @@ -24,7 +25,7 @@ public class UpdateEventAdmin { String description; Long category; - Long location; + LocationDto location; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime eventDate; diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/dto/request/UpdateEventUser.java b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/request/UpdateEventUser.java index cdcc376..12385b5 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/dto/request/UpdateEventUser.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/request/UpdateEventUser.java @@ -6,6 +6,7 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.FieldDefaults; +import ru.practicum.ewm.dto.response.LocationDto; import java.time.LocalDateTime; @@ -24,7 +25,7 @@ public class UpdateEventUser { String description; Long category; - Long location; + LocationDto location; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime eventDate; diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/CategoryDto.java b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/CategoryDto.java index 5cd4ece..2fd0bf5 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/CategoryDto.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/CategoryDto.java @@ -1,5 +1,7 @@ package ru.practicum.ewm.dto.response; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; @@ -10,5 +12,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE) public class CategoryDto { Long id; - String name; + @NotBlank + @Size(min = 1, max = 50) + private String name; } \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/LocationMapper.java b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/LocationMapper.java index 22fab6f..251596d 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/LocationMapper.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/LocationMapper.java @@ -16,12 +16,12 @@ public static LocationDto toDto(Location location) { return locationDto; } - public static Location toLocation(NewLocationDto request) { + + public static Location toLocation(LocationDto dto) { + if (dto == null) return null; Location location = new Location(); - location.setName(request.getName()); - location.setLat(request.getLat()); - location.setLon(request.getLon()); - location.setRadius(request.getRadius()); + location.setLat(dto.getLat()); + location.setLon(dto.getLon()); return location; } } \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/ErrorHandler.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/ErrorHandler.java index 9d5e04e..bc1be7e 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/ErrorHandler.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/ErrorHandler.java @@ -17,6 +17,7 @@ import jakarta.validation.ConstraintViolationException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.stream.Collectors; @Slf4j @RestControllerAdvice @@ -27,34 +28,43 @@ public class ErrorHandler { @ExceptionHandler(NotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ApiError handleNotFound(final NotFoundException e) { - log.error("404 NotFound: {}", e.getMessage()); + log.error("404: {}", e.getMessage()); return createApiError(HttpStatus.NOT_FOUND, "The required object was not found.", e.getMessage()); } @ExceptionHandler({ConflictException.class, DataIntegrityViolationException.class}) @ResponseStatus(HttpStatus.CONFLICT) public ApiError handleConflict(final Exception e) { - log.error("409 Conflict: {}", e.getMessage()); + log.error("409: {}", e.getMessage()); return createApiError(HttpStatus.CONFLICT, "For the requested operation the conditions are not met.", e.getMessage()); } @ExceptionHandler({ BadRequestException.class, - MethodArgumentNotValidException.class, // Ошибки в @RequestBody - ConstraintViolationException.class, // Ошибки jakarta валидации - MissingServletRequestParameterException.class, // Ошибка №001 (отсутствие eventId) - HandlerMethodValidationException.class // Новое в Spring 6 / Boot 3 для параметров контроллера + MethodArgumentNotValidException.class, + ConstraintViolationException.class, + MissingServletRequestParameterException.class, + HandlerMethodValidationException.class }) @ResponseStatus(HttpStatus.BAD_REQUEST) public ApiError handleBadRequest(final Exception e) { - log.error("400 BadRequest: {}", e.getMessage()); - return createApiError(HttpStatus.BAD_REQUEST, "Incorrectly made request.", e.getMessage()); + log.error("400: {}", e.getMessage()); + String message = e.getMessage(); + + // Для сложных ошибок валидации вытаскиваем только суть, чтобы не падал JSON + if (e instanceof MethodArgumentNotValidException) { + message = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors().stream() + .map(error -> "Field: " + error.getField() + ". Error: " + error.getDefaultMessage()) + .collect(Collectors.joining(", ")); + } + + return createApiError(HttpStatus.BAD_REQUEST, "Incorrectly made request.", message); } @ExceptionHandler(Throwable.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ApiError handleThrowable(final Throwable e) { - log.error("500 InternalServerError: ", e); + log.error("500: ", e); return createApiError(HttpStatus.INTERNAL_SERVER_ERROR, "Error occurred", e.getMessage()); } @@ -62,7 +72,19 @@ private ApiError createApiError(HttpStatus status, String reason, String message return ApiError.builder() .status(status.name()) .reason(reason) - .message(message) + .message(message != null ? message : "No message available") + .timestamp(LocalDateTime.now().format(FORMATTER)) + .build(); + } + + @ExceptionHandler(DataIntegrityViolationException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ApiError handleDataIntegrity(final DataIntegrityViolationException e) { + log.error("409 Conflict (Database): {}", e.getMessage()); + return ApiError.builder() + .status(HttpStatus.CONFLICT.name()) + .reason("Integrity constraint has been violated.") // Текст из спецификации + .message(e.getMessage()) .timestamp(LocalDateTime.now().format(FORMATTER)) .build(); } diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index ffd930c..4db528c 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -9,16 +9,17 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.client.StatsClient; -import ru.practicum.dto.EndpointHitDto; import ru.practicum.ewm.dto.request.NewEventDto; import ru.practicum.ewm.dto.request.UpdateEventAdmin; import ru.practicum.ewm.dto.request.UpdateEventUser; import ru.practicum.ewm.dto.response.EventFullDto; import ru.practicum.ewm.dto.response.EventShortDto; +import ru.practicum.ewm.dto.response.LocationDto; // Проверь этот импорт import ru.practicum.ewm.exception.BadRequestException; import ru.practicum.ewm.exception.ConflictException; import ru.practicum.ewm.exception.NotFoundException; import ru.practicum.ewm.mapper.EventMapper; +import ru.practicum.ewm.mapper.LocationMapper; import ru.practicum.ewm.model.*; import ru.practicum.ewm.repository.*; @@ -45,53 +46,37 @@ public class EventServiceImpl implements EventService { @Override @Transactional - public EventFullDto createEvent(Long userId, NewEventDto newEventDto) { - - if (newEventDto.getEventDate().isBefore(LocalDateTime.now().plusHours(2))) { - throw new BadRequestException("Field: eventDate. Error: must contain a date that has not yet arrived. " + - "Value: " + newEventDto.getEventDate()); + public EventFullDto createEvent(Long userId, NewEventDto dto) { + // 1. Валидация даты + if (dto.getEventDate().isBefore(LocalDateTime.now().plusHours(2))) { + throw new BadRequestException("Event date must be at least 2 hours in the future."); } - User initiator = userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("User with id=" + userId + " was not found")); + Category category = categoryRepository.findById(dto.getCategory()) + .orElseThrow(() -> new NotFoundException("Category with id=" + dto.getCategory() + " was not found")); + // 2. Локация: сохраняем новые координаты в таблицу locations + Location location = locationRepository.save(LocationMapper.toLocation(dto.getLocation())); - Category category = categoryRepository.findById(newEventDto.getCategory()) - .orElseThrow(() -> new NotFoundException("Category with id=" + newEventDto.getCategory() + " was not found")); - - - Location location = locationRepository.findById(newEventDto.getLocation()) - .orElseThrow(() -> new NotFoundException("Location with id=" + newEventDto.getLocation() + " was not found")); - - - Event event = EventMapper.toEvent(newEventDto); - - + Event event = EventMapper.toEvent(dto); event.setInitiator(initiator); event.setCategory(category); event.setLocation(location); event.setCreatedOn(LocalDateTime.now()); event.setState(EventState.PENDING); - - event.setConfirmedRequests(0L); event.setViews(0L); + // 3. Дефолтные значения (защита от null и 500 ошибок) + if (event.getPaid() == null) event.setPaid(false); + if (event.getParticipantLimit() == null) event.setParticipantLimit(0); + if (event.getRequestModeration() == null) event.setRequestModeration(true); - if (event.getPaid() == null) { - event.setPaid(false); - } - if (event.getParticipantLimit() == null) { - event.setParticipantLimit(0); - } - if (event.getRequestModeration() == null) { - event.setRequestModeration(true); - } - - Event savedEvent = eventRepository.save(event); - return EventMapper.toFullDto(savedEvent); + return EventMapper.toFullDto(eventRepository.save(event)); } + @Override public List getEventsByUserId(Long userId, int from, int size) { return eventRepository.findAllByInitiatorId(userId, PageRequest.of(from / size, size)) @@ -101,7 +86,7 @@ public List getEventsByUserId(Long userId, int from, int size) { @Override public EventFullDto getEventByIdAndUserId(Long userId, Long eventId) { Event event = eventRepository.findByIdAndInitiatorId(eventId, userId) - .orElseThrow(() -> new NotFoundException("Event not found")); + .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " not found")); return EventMapper.toFullDto(event); } @@ -130,6 +115,7 @@ public EventFullDto updateEventByUserId(Long userId, Long eventId, UpdateEventUs } } + // Обновляем поля, передавая объект локации целиком updateEventCommonFields(event, request.getAnnotation(), request.getCategory(), request.getDescription(), request.getLocation(), request.getPaid(), request.getParticipantLimit(), request.getRequestModeration(), request.getTitle()); @@ -148,28 +134,25 @@ public List getEventsByAdmin(List users, List st @Override @Transactional public EventFullDto updateEventByAdmin(Long eventId, UpdateEventAdmin request) { - Event event = eventRepository.findById(eventId) .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); if (request.getEventDate() != null) { + // ТЗ для админа: не ранее чем за час до публикации if (request.getEventDate().isBefore(LocalDateTime.now().plusHours(1))) { - throw new BadRequestException("Event date must be at least 1 hour before publication. " + - "Value: " + request.getEventDate()); + throw new BadRequestException("Event date must be at least 1 hour before publication."); } event.setEventDate(request.getEventDate()); } if (request.getStateAction() != null) { if (request.getStateAction().equals("PUBLISH_EVENT")) { - if (event.getState() != EventState.PENDING) { - throw new ConflictException("Cannot publish the event because it's not in the PENDING state: " + event.getState()); + throw new ConflictException("Cannot publish the event because it's not in the PENDING state."); } event.setState(EventState.PUBLISHED); event.setPublishedOn(LocalDateTime.now()); } else if (request.getStateAction().equals("REJECT_EVENT")) { - if (event.getState() == EventState.PUBLISHED) { throw new ConflictException("Cannot reject the event because it's already published."); } @@ -228,36 +211,34 @@ public EventFullDto getEventByIdPublic(Long id, HttpServletRequest request) { return EventMapper.toFullDto(event); } - // --- Вспомогательные методы --- + private void updateEventCommonFields(Event event, String annotation, Long catId, String description, - Long locId, Boolean paid, Integer partLimit, Boolean reqMod, String title) { - if (annotation != null) event.setAnnotation(annotation); + LocationDto locationDto, Boolean paid, Integer partLimit, Boolean reqMod, String title) { + if (annotation != null && !annotation.isBlank()) event.setAnnotation(annotation); if (catId != null) { event.setCategory(categoryRepository.findById(catId) .orElseThrow(() -> new NotFoundException("Category not found"))); } - if (description != null) event.setDescription(description); - if (locId != null) { - event.setLocation(locationRepository.findById(locId) - .orElseThrow(() -> new NotFoundException("Location not found"))); + if (description != null && !description.isBlank()) event.setDescription(description); + + + if (locationDto != null) { + Location location = locationRepository.save(LocationMapper.toLocation(locationDto)); + event.setLocation(location); } + if (paid != null) event.setPaid(paid); if (partLimit != null) event.setParticipantLimit(partLimit); if (reqMod != null) event.setRequestModeration(reqMod); - if (title != null) event.setTitle(title); + if (title != null && !title.isBlank()) event.setTitle(title); } private void sendStats(HttpServletRequest request) { try { - statsClient.hit( - APP_NAME, - request.getRequestURI(), - request.getRemoteAddr(), - LocalDateTime.now() - ); + statsClient.hit(APP_NAME, request.getRequestURI(), request.getRemoteAddr(), LocalDateTime.now()); } catch (Exception e) { - log.error("Ошибка при отправке статистики в stats-server: {}", e.getMessage()); + log.error("Ошибка статистики: {}", e.getMessage()); } } } \ No newline at end of file From e601925a76b2853aa6aae1fe743982bf69444ccd Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 15:31:22 +0300 Subject: [PATCH 06/24] fix: resolve ambiguous exception handler and verify local startup --- .../{service => controller}/ErrorHandler.java | 43 ++++++------------- .../practicum/ewm/mapper/LocationMapper.java | 10 +++++ .../ewm/service/LocationServiceImpl.java | 1 - 3 files changed, 24 insertions(+), 30 deletions(-) rename ewm-main-service/src/main/java/ru/practicum/ewm/{service => controller}/ErrorHandler.java (64%) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/ErrorHandler.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java similarity index 64% rename from ewm-main-service/src/main/java/ru/practicum/ewm/service/ErrorHandler.java rename to ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java index bc1be7e..c1cd93d 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/ErrorHandler.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java @@ -1,4 +1,4 @@ -package ru.practicum.ewm.service; +package ru.practicum.ewm.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DataIntegrityViolationException; @@ -17,7 +17,6 @@ import jakarta.validation.ConstraintViolationException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.stream.Collectors; @Slf4j @RestControllerAdvice @@ -25,20 +24,26 @@ public class ErrorHandler { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + @ExceptionHandler(NotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ApiError handleNotFound(final NotFoundException e) { - log.error("404: {}", e.getMessage()); + log.error("404 NotFound: {}", e.getMessage()); return createApiError(HttpStatus.NOT_FOUND, "The required object was not found.", e.getMessage()); } - @ExceptionHandler({ConflictException.class, DataIntegrityViolationException.class}) + + @ExceptionHandler({ + ConflictException.class, + DataIntegrityViolationException.class + }) @ResponseStatus(HttpStatus.CONFLICT) public ApiError handleConflict(final Exception e) { - log.error("409: {}", e.getMessage()); + log.error("409 Conflict: {}", e.getMessage()); return createApiError(HttpStatus.CONFLICT, "For the requested operation the conditions are not met.", e.getMessage()); } + @ExceptionHandler({ BadRequestException.class, MethodArgumentNotValidException.class, @@ -48,23 +53,15 @@ public ApiError handleConflict(final Exception e) { }) @ResponseStatus(HttpStatus.BAD_REQUEST) public ApiError handleBadRequest(final Exception e) { - log.error("400: {}", e.getMessage()); - String message = e.getMessage(); - - // Для сложных ошибок валидации вытаскиваем только суть, чтобы не падал JSON - if (e instanceof MethodArgumentNotValidException) { - message = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors().stream() - .map(error -> "Field: " + error.getField() + ". Error: " + error.getDefaultMessage()) - .collect(Collectors.joining(", ")); - } - - return createApiError(HttpStatus.BAD_REQUEST, "Incorrectly made request.", message); + log.error("400 BadRequest: {}", e.getMessage()); + return createApiError(HttpStatus.BAD_REQUEST, "Incorrectly made request.", e.getMessage()); } + @ExceptionHandler(Throwable.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ApiError handleThrowable(final Throwable e) { - log.error("500: ", e); + log.error("500 InternalServerError: ", e); return createApiError(HttpStatus.INTERNAL_SERVER_ERROR, "Error occurred", e.getMessage()); } @@ -76,16 +73,4 @@ private ApiError createApiError(HttpStatus status, String reason, String message .timestamp(LocalDateTime.now().format(FORMATTER)) .build(); } - - @ExceptionHandler(DataIntegrityViolationException.class) - @ResponseStatus(HttpStatus.CONFLICT) - public ApiError handleDataIntegrity(final DataIntegrityViolationException e) { - log.error("409 Conflict (Database): {}", e.getMessage()); - return ApiError.builder() - .status(HttpStatus.CONFLICT.name()) - .reason("Integrity constraint has been violated.") // Текст из спецификации - .message(e.getMessage()) - .timestamp(LocalDateTime.now().format(FORMATTER)) - .build(); - } } \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/LocationMapper.java b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/LocationMapper.java index 251596d..be4ad71 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/LocationMapper.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/LocationMapper.java @@ -7,6 +7,7 @@ public class LocationMapper { public static LocationDto toDto(Location location) { + if (location == null) return null; LocationDto locationDto = new LocationDto(); locationDto.setId(location.getId()); locationDto.setName(location.getName()); @@ -16,6 +17,15 @@ public static LocationDto toDto(Location location) { return locationDto; } + public static Location toLocation(NewLocationDto request) { + if (request == null) return null; + Location location = new Location(); + location.setName(request.getName()); + location.setLat(request.getLat()); + location.setLon(request.getLon()); + location.setRadius(request.getRadius()); + return location; + } public static Location toLocation(LocationDto dto) { if (dto == null) return null; diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java index b07136e..e1464f4 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java @@ -29,7 +29,6 @@ public LocationDto createLocation(NewLocationDto newLocationDto) { Location savedLocation = locationRepository.save(location); return LocationMapper.toDto(savedLocation); } - @Override public LocationDto getLocation(Long locationId) { Location location = locationRepository.findById(locationId) From 0a02074df49a9583fdd60d4049d7178af092dc1f Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 15:40:45 +0300 Subject: [PATCH 07/24] fix errors --- .../ewm/controller/ErrorHandler.java | 34 +++++++------------ .../java/ru/practicum/ewm/model/Location.java | 4 +-- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java index c1cd93d..26047fb 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java @@ -8,15 +8,14 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.method.annotation.HandlerMethodValidationException; import ru.practicum.ewm.dto.response.ApiError; import ru.practicum.ewm.exception.BadRequestException; import ru.practicum.ewm.exception.ConflictException; import ru.practicum.ewm.exception.NotFoundException; -import jakarta.validation.ConstraintViolationException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.stream.Collectors; @Slf4j @RestControllerAdvice @@ -24,44 +23,37 @@ public class ErrorHandler { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - @ExceptionHandler(NotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ApiError handleNotFound(final NotFoundException e) { - log.error("404 NotFound: {}", e.getMessage()); return createApiError(HttpStatus.NOT_FOUND, "The required object was not found.", e.getMessage()); } - - @ExceptionHandler({ - ConflictException.class, - DataIntegrityViolationException.class - }) + @ExceptionHandler({ConflictException.class, DataIntegrityViolationException.class}) @ResponseStatus(HttpStatus.CONFLICT) public ApiError handleConflict(final Exception e) { - log.error("409 Conflict: {}", e.getMessage()); return createApiError(HttpStatus.CONFLICT, "For the requested operation the conditions are not met.", e.getMessage()); } + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiError handleMethodArgumentNotValid(final MethodArgumentNotValidException e) { + String message = e.getBindingResult().getFieldErrors().stream() + .map(error -> "Field: " + error.getField() + ". Error: " + error.getDefaultMessage() + ". Value: " + error.getRejectedValue()) + .collect(Collectors.joining(", ")); + return createApiError(HttpStatus.BAD_REQUEST, "Incorrectly made request.", message); + } - @ExceptionHandler({ - BadRequestException.class, - MethodArgumentNotValidException.class, - ConstraintViolationException.class, - MissingServletRequestParameterException.class, - HandlerMethodValidationException.class - }) + @ExceptionHandler({BadRequestException.class, MissingServletRequestParameterException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) public ApiError handleBadRequest(final Exception e) { - log.error("400 BadRequest: {}", e.getMessage()); return createApiError(HttpStatus.BAD_REQUEST, "Incorrectly made request.", e.getMessage()); } - @ExceptionHandler(Throwable.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ApiError handleThrowable(final Throwable e) { - log.error("500 InternalServerError: ", e); + log.error("500 Error: ", e); return createApiError(HttpStatus.INTERNAL_SERVER_ERROR, "Error occurred", e.getMessage()); } @@ -69,7 +61,7 @@ private ApiError createApiError(HttpStatus status, String reason, String message return ApiError.builder() .status(status.name()) .reason(reason) - .message(message != null ? message : "No message available") + .message(message != null ? message : "No message") .timestamp(LocalDateTime.now().format(FORMATTER)) .build(); } diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/model/Location.java b/ewm-main-service/src/main/java/ru/practicum/ewm/model/Location.java index 981754a..9d03cb1 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/model/Location.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/model/Location.java @@ -17,7 +17,7 @@ public class Location { @GeneratedValue(strategy = GenerationType.IDENTITY) Long id; - @Column(nullable = false) + @Column String name; @Column(nullable = false) @@ -26,6 +26,6 @@ public class Location { @Column(nullable = false) Float lon; - @Column(nullable = false) + @Column Float radius; } \ No newline at end of file From f7635b37ec7df680265e13c040ec8479324cf437 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 15:57:29 +0300 Subject: [PATCH 08/24] fix: resolve startup crashes and align data types with openapi spec --- .../src/main/java/ru/practicum/ewm/model/Event.java | 4 ++-- .../ru/practicum/ewm/service/EventServiceImpl.java | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/model/Event.java b/ewm-main-service/src/main/java/ru/practicum/ewm/model/Event.java index 84d8aea..a536bdd 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/model/Event.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/model/Event.java @@ -22,10 +22,10 @@ public class Event { @Column(nullable = false) String title; - @Column(nullable = false) + @Column(nullable = false, length = 2000) String annotation; - @Column(nullable = false, columnDefinition = "TEXT") + @Column(nullable = false, length = 7000) String description; @ManyToOne diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 4db528c..2630009 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -101,6 +101,7 @@ public EventFullDto updateEventByUserId(Long userId, Long eventId, UpdateEventUs } if (request.getEventDate() != null) { + if (request.getEventDate().isBefore(LocalDateTime.now().plusHours(2))) { throw new BadRequestException("Event date must be at least 2 hours in the future."); } @@ -138,7 +139,6 @@ public EventFullDto updateEventByAdmin(Long eventId, UpdateEventAdmin request) { .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); if (request.getEventDate() != null) { - // ТЗ для админа: не ранее чем за час до публикации if (request.getEventDate().isBefore(LocalDateTime.now().plusHours(1))) { throw new BadRequestException("Event date must be at least 1 hour before publication."); } @@ -229,11 +229,17 @@ private void updateEventCommonFields(Event event, String annotation, Long catId, } if (paid != null) event.setPaid(paid); - if (partLimit != null) event.setParticipantLimit(partLimit); + if (partLimit != null) { + if (partLimit < 0) { + throw new BadRequestException("Participant limit cannot be negative"); + } + event.setParticipantLimit(partLimit); + } if (reqMod != null) event.setRequestModeration(reqMod); if (title != null && !title.isBlank()) event.setTitle(title); } + private void sendStats(HttpServletRequest request) { try { statsClient.hit(APP_NAME, request.getRequestURI(), request.getRemoteAddr(), LocalDateTime.now()); From a0388ff257ad8e548dc61ec2ada3617512e163d2 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 16:04:37 +0300 Subject: [PATCH 09/24] fix: final polish for pagination, request cancellation and category exceptions --- .../practicum/ewm/service/CategoryServiceImpl.java | 8 ++++---- .../ru/practicum/ewm/service/EventServiceImpl.java | 13 ++++++++----- .../practicum/ewm/service/RequestServiceImpl.java | 10 ++++++++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java index 723f2fe..1087e7c 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java @@ -38,7 +38,7 @@ public CategoryDto createCategory(NewCategoryDto newCategoryDto) { @Transactional public CategoryDto updateCategory(Long catId, CategoryDto categoryDto) { Category category = categoryRepository.findById(catId) - .orElseThrow(() -> new RuntimeException("Category not found")); + .orElseThrow(() -> new NotFoundException("Category not found")); category.setName(categoryDto.getName()); Category updatedCategory = categoryRepository.save(category); return CategoryMapper.toDto(updatedCategory); @@ -54,9 +54,9 @@ public List getCategories(int from, int size) { @Override public CategoryDto getCategory(Long catId) { - Category category = categoryRepository.findById(catId) - .orElseThrow(() -> new RuntimeException("Category not found")); - return CategoryMapper.toDto(category); + return categoryRepository.findById(catId) + .map(CategoryMapper::toDto) + .orElseThrow(() -> new NotFoundException("Category with id=" + catId + " was not found")); } diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 2630009..058e39d 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -47,7 +47,7 @@ public class EventServiceImpl implements EventService { @Override @Transactional public EventFullDto createEvent(Long userId, NewEventDto dto) { - // 1. Валидация даты + if (dto.getEventDate().isBefore(LocalDateTime.now().plusHours(2))) { throw new BadRequestException("Event date must be at least 2 hours in the future."); } @@ -57,7 +57,7 @@ public EventFullDto createEvent(Long userId, NewEventDto dto) { Category category = categoryRepository.findById(dto.getCategory()) .orElseThrow(() -> new NotFoundException("Category with id=" + dto.getCategory() + " was not found")); - // 2. Локация: сохраняем новые координаты в таблицу locations + Location location = locationRepository.save(LocationMapper.toLocation(dto.getLocation())); Event event = EventMapper.toEvent(dto); @@ -69,7 +69,7 @@ public EventFullDto createEvent(Long userId, NewEventDto dto) { event.setConfirmedRequests(0L); event.setViews(0L); - // 3. Дефолтные значения (защита от null и 500 ошибок) + if (event.getPaid() == null) event.setPaid(false); if (event.getParticipantLimit() == null) event.setParticipantLimit(0); if (event.getRequestModeration() == null) event.setRequestModeration(true); @@ -116,7 +116,7 @@ public EventFullDto updateEventByUserId(Long userId, Long eventId, UpdateEventUs } } - // Обновляем поля, передавая объект локации целиком + updateEventCommonFields(event, request.getAnnotation(), request.getCategory(), request.getDescription(), request.getLocation(), request.getPaid(), request.getParticipantLimit(), request.getRequestModeration(), request.getTitle()); @@ -185,8 +185,11 @@ public List getEventsPublic(String text, List categories, B } sendStats(request); + int page = from / size; + PageRequest pageable = PageRequest.of(page, size); + List events = eventRepository.findPublishedEvents(text, categories, paid, rangeStart, rangeEnd, - onlyAvailable, PageRequest.of(from / size, size)); + onlyAvailable, pageable); if ("VIEWS".equals(sort)) { events = events.stream() diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java index 961f610..bb350c5 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java @@ -63,7 +63,7 @@ public RequestDto createRequest(Long userId, Long eventId) { request.setEvent(event); request.setRequester(requester); - // Если модерация не нужна или лимит 0 — подтверждаем сразу + if (!event.getRequestModeration() || event.getParticipantLimit() == 0) { request.setStatus(RequestStatus.CONFIRMED); event.setConfirmedRequests(event.getConfirmedRequests() + 1); @@ -80,9 +80,15 @@ public RequestDto createRequest(Long userId, Long eventId) { public RequestDto cancelRequest(Long userId, Long requestId) { ParticipationRequest request = requestRepository.findById(requestId) .orElseThrow(() -> new NotFoundException("Request not found")); + if (!request.getRequester().getId().equals(userId)) { - throw new ConflictException("Not your request"); + throw new ConflictException("You can only cancel your own requests"); } + + if (request.getStatus() == RequestStatus.CONFIRMED) { + throw new ConflictException("Cannot cancel a confirmed request"); + } + request.setStatus(RequestStatus.CANCELED); return RequestMapper.toDto(requestRepository.save(request)); } From ed234301d51b71ad3b4abffa39b275d144335223 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 16:19:12 +0300 Subject: [PATCH 10/24] fix errors --- .../practicum/ewm/controller/ErrorHandler.java | 3 ++- .../practicum/ewm/service/EventServiceImpl.java | 17 +++++++++-------- .../ewm/service/RequestServiceImpl.java | 4 ++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java index 26047fb..4233366 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java @@ -61,8 +61,9 @@ private ApiError createApiError(HttpStatus status, String reason, String message return ApiError.builder() .status(status.name()) .reason(reason) - .message(message != null ? message : "No message") + .message(message != null ? message : "No message available") .timestamp(LocalDateTime.now().format(FORMATTER)) + .errors(java.util.List.of()) .build(); } } \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 058e39d..fed4620 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -174,10 +174,8 @@ public List getEventsPublic(String text, List categories, B Boolean onlyAvailable, String sort, int from, int size, HttpServletRequest request) { - if (rangeStart != null && rangeEnd != null) { - if (rangeStart.isAfter(rangeEnd)) { - throw new BadRequestException("Start date cannot be after end date"); - } + if (rangeStart != null && rangeEnd != null && rangeStart.isAfter(rangeEnd)) { + throw new BadRequestException("Start date cannot be after end date"); } if (rangeStart == null && rangeEnd == null) { @@ -185,15 +183,19 @@ public List getEventsPublic(String text, List categories, B } sendStats(request); - int page = from / size; - PageRequest pageable = PageRequest.of(page, size); + + PageRequest pageable = PageRequest.of(from / size, size); List events = eventRepository.findPublishedEvents(text, categories, paid, rangeStart, rangeEnd, onlyAvailable, pageable); if ("VIEWS".equals(sort)) { events = events.stream() - .sorted((e1, e2) -> e2.getViews().compareTo(e1.getViews())) + .sorted((e1, e2) -> { + long v1 = (e1.getViews() == null) ? 0L : e1.getViews(); + long v2 = (e2.getViews() == null) ? 0L : e2.getViews(); + return Long.compare(v2, v1); // Сортировка от большего к меньшему + }) .collect(Collectors.toList()); } @@ -201,7 +203,6 @@ public List getEventsPublic(String text, List categories, B .map(EventMapper::toShortDto) .collect(Collectors.toList()); } - @Override public EventFullDto getEventByIdPublic(Long id, HttpServletRequest request) { Event event = eventRepository.findByIdAndState(id, EventState.PUBLISHED) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java index bb350c5..48b1f3d 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java @@ -79,14 +79,14 @@ public RequestDto createRequest(Long userId, Long eventId) { @Transactional public RequestDto cancelRequest(Long userId, Long requestId) { ParticipationRequest request = requestRepository.findById(requestId) - .orElseThrow(() -> new NotFoundException("Request not found")); + .orElseThrow(() -> new NotFoundException("Request with id=" + requestId + " was not found")); if (!request.getRequester().getId().equals(userId)) { throw new ConflictException("You can only cancel your own requests"); } if (request.getStatus() == RequestStatus.CONFIRMED) { - throw new ConflictException("Cannot cancel a confirmed request"); + throw new ConflictException("Cannot cancel an already confirmed request"); } request.setStatus(RequestStatus.CANCELED); From d76079546ceae2bfc49651d9f5d29edd45a8f8bf Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 16:39:28 +0300 Subject: [PATCH 11/24] fix: finalize pagination logic and confirmed request cancellation rules --- .../ewm/service/EventServiceImpl.java | 18 ++++++++++++++---- .../ewm/service/RequestServiceImpl.java | 1 - 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index fed4620..e87dfd7 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -174,27 +174,37 @@ public List getEventsPublic(String text, List categories, B Boolean onlyAvailable, String sort, int from, int size, HttpServletRequest request) { + if (rangeStart != null && rangeEnd != null && rangeStart.isAfter(rangeEnd)) { - throw new BadRequestException("Start date cannot be after end date"); + throw new BadRequestException("Start date must be before end date"); } + if (rangeStart == null && rangeEnd == null) { rangeStart = LocalDateTime.now(); } - sendStats(request); - PageRequest pageable = PageRequest.of(from / size, size); + try { + statsClient.hit(APP_NAME, request.getRequestURI(), request.getRemoteAddr(), LocalDateTime.now()); + } catch (Exception e) { + log.error("Stats server error: {}", e.getMessage()); + } + + + int page = from / size; + PageRequest pageable = PageRequest.of(page, size); List events = eventRepository.findPublishedEvents(text, categories, paid, rangeStart, rangeEnd, onlyAvailable, pageable); + if ("VIEWS".equals(sort)) { events = events.stream() .sorted((e1, e2) -> { long v1 = (e1.getViews() == null) ? 0L : e1.getViews(); long v2 = (e2.getViews() == null) ? 0L : e2.getViews(); - return Long.compare(v2, v1); // Сортировка от большего к меньшему + return Long.compare(v2, v1); }) .collect(Collectors.toList()); } diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java index 48b1f3d..b6e86d9 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java @@ -92,7 +92,6 @@ public RequestDto cancelRequest(Long userId, Long requestId) { request.setStatus(RequestStatus.CANCELED); return RequestMapper.toDto(requestRepository.save(request)); } - @Override public List getEventRequests(Long userId, Long eventId) { Event event = eventRepository.findById(eventId).orElseThrow(() -> new NotFoundException("Event not found")); From 1e97e8667749fe3fff79ebeda68a0885e6072ba7 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 16:47:35 +0300 Subject: [PATCH 12/24] fix errors --- .../ewm/service/EventServiceImpl.java | 23 +++++++++---------- .../ewm/service/RequestServiceImpl.java | 1 + 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index e87dfd7..00b29a8 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -174,36 +174,34 @@ public List getEventsPublic(String text, List categories, B Boolean onlyAvailable, String sort, int from, int size, HttpServletRequest request) { - - if (rangeStart != null && rangeEnd != null && rangeStart.isAfter(rangeEnd)) { - throw new BadRequestException("Start date must be before end date"); + if (rangeStart != null && rangeEnd != null) { + if (rangeStart.isAfter(rangeEnd)) { + throw new BadRequestException("Start date must be before end date"); + } } - if (rangeStart == null && rangeEnd == null) { rangeStart = LocalDateTime.now(); } - try { statsClient.hit(APP_NAME, request.getRequestURI(), request.getRemoteAddr(), LocalDateTime.now()); } catch (Exception e) { - log.error("Stats server error: {}", e.getMessage()); + log.error("Error sending stats: {}", e.getMessage()); } - - int page = from / size; - PageRequest pageable = PageRequest.of(page, size); + int page = (size > 0) ? from / size : 0; + int pageSize = (size > 0) ? size : 10; + PageRequest pageable = PageRequest.of(page, pageSize); List events = eventRepository.findPublishedEvents(text, categories, paid, rangeStart, rangeEnd, onlyAvailable, pageable); - if ("VIEWS".equals(sort)) { events = events.stream() .sorted((e1, e2) -> { - long v1 = (e1.getViews() == null) ? 0L : e1.getViews(); - long v2 = (e2.getViews() == null) ? 0L : e2.getViews(); + long v1 = (e1.getViews() == null) ? 0 : e1.getViews(); + long v2 = (e2.getViews() == null) ? 0 : e2.getViews(); return Long.compare(v2, v1); }) .collect(Collectors.toList()); @@ -213,6 +211,7 @@ public List getEventsPublic(String text, List categories, B .map(EventMapper::toShortDto) .collect(Collectors.toList()); } + @Override public EventFullDto getEventByIdPublic(Long id, HttpServletRequest request) { Event event = eventRepository.findByIdAndState(id, EventState.PUBLISHED) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java index b6e86d9..48b1f3d 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java @@ -92,6 +92,7 @@ public RequestDto cancelRequest(Long userId, Long requestId) { request.setStatus(RequestStatus.CANCELED); return RequestMapper.toDto(requestRepository.save(request)); } + @Override public List getEventRequests(Long userId, Long eventId) { Event event = eventRepository.findById(eventId).orElseThrow(() -> new NotFoundException("Event not found")); From 9dc962e543adf603f14c8e828aa31c38d00c2c7b Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 17:23:09 +0300 Subject: [PATCH 13/24] Fix errors --- .../ewm/service/EventServiceImpl.java | 18 ++++++------------ .../ewm/service/RequestServiceImpl.java | 5 ++--- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 00b29a8..27d7a40 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -174,24 +174,18 @@ public List getEventsPublic(String text, List categories, B Boolean onlyAvailable, String sort, int from, int size, HttpServletRequest request) { - if (rangeStart != null && rangeEnd != null) { - if (rangeStart.isAfter(rangeEnd)) { - throw new BadRequestException("Start date must be before end date"); - } + if (rangeStart != null && rangeEnd != null && rangeStart.isAfter(rangeEnd)) { + throw new BadRequestException("Start date must be before end date"); } if (rangeStart == null && rangeEnd == null) { rangeStart = LocalDateTime.now(); } - try { - statsClient.hit(APP_NAME, request.getRequestURI(), request.getRemoteAddr(), LocalDateTime.now()); - } catch (Exception e) { - log.error("Error sending stats: {}", e.getMessage()); - } + sendStats(request); - int page = (size > 0) ? from / size : 0; int pageSize = (size > 0) ? size : 10; + int page = from / pageSize; PageRequest pageable = PageRequest.of(page, pageSize); List events = eventRepository.findPublishedEvents(text, categories, paid, rangeStart, rangeEnd, @@ -200,8 +194,8 @@ public List getEventsPublic(String text, List categories, B if ("VIEWS".equals(sort)) { events = events.stream() .sorted((e1, e2) -> { - long v1 = (e1.getViews() == null) ? 0 : e1.getViews(); - long v2 = (e2.getViews() == null) ? 0 : e2.getViews(); + long v1 = (e1.getViews() == null) ? 0L : e1.getViews(); + long v2 = (e2.getViews() == null) ? 0L : e2.getViews(); return Long.compare(v2, v1); }) .collect(Collectors.toList()); diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java index 48b1f3d..66231aa 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java @@ -79,20 +79,19 @@ public RequestDto createRequest(Long userId, Long eventId) { @Transactional public RequestDto cancelRequest(Long userId, Long requestId) { ParticipationRequest request = requestRepository.findById(requestId) - .orElseThrow(() -> new NotFoundException("Request with id=" + requestId + " was not found")); + .orElseThrow(() -> new NotFoundException("Request not found")); if (!request.getRequester().getId().equals(userId)) { throw new ConflictException("You can only cancel your own requests"); } - if (request.getStatus() == RequestStatus.CONFIRMED) { + if (request.getStatus().equals(RequestStatus.CONFIRMED)) { throw new ConflictException("Cannot cancel an already confirmed request"); } request.setStatus(RequestStatus.CANCELED); return RequestMapper.toDto(requestRepository.save(request)); } - @Override public List getEventRequests(Long userId, Long eventId) { Event event = eventRepository.findById(eventId).orElseThrow(() -> new NotFoundException("Event not found")); From b00c25e69e728b6ca35ffba075532adcc15b157a Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 17:34:05 +0300 Subject: [PATCH 14/24] fix for public events and request cancellation --- .../ru/practicum/ewm/service/EventServiceImpl.java | 10 ++++------ .../ru/practicum/ewm/service/RequestServiceImpl.java | 10 +--------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 27d7a40..38c69d7 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -184,9 +184,8 @@ public List getEventsPublic(String text, List categories, B sendStats(request); - int pageSize = (size > 0) ? size : 10; - int page = from / pageSize; - PageRequest pageable = PageRequest.of(page, pageSize); + int page = from / size; + PageRequest pageable = PageRequest.of(page, size); List events = eventRepository.findPublishedEvents(text, categories, paid, rangeStart, rangeEnd, onlyAvailable, pageable); @@ -194,8 +193,8 @@ public List getEventsPublic(String text, List categories, B if ("VIEWS".equals(sort)) { events = events.stream() .sorted((e1, e2) -> { - long v1 = (e1.getViews() == null) ? 0L : e1.getViews(); - long v2 = (e2.getViews() == null) ? 0L : e2.getViews(); + long v1 = (e1.getViews() == null) ? 0 : e1.getViews(); + long v2 = (e2.getViews() == null) ? 0 : e2.getViews(); return Long.compare(v2, v1); }) .collect(Collectors.toList()); @@ -205,7 +204,6 @@ public List getEventsPublic(String text, List categories, B .map(EventMapper::toShortDto) .collect(Collectors.toList()); } - @Override public EventFullDto getEventByIdPublic(Long id, HttpServletRequest request) { Event event = eventRepository.findByIdAndState(id, EventState.PUBLISHED) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java index 66231aa..b8c86a6 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java @@ -79,7 +79,7 @@ public RequestDto createRequest(Long userId, Long eventId) { @Transactional public RequestDto cancelRequest(Long userId, Long requestId) { ParticipationRequest request = requestRepository.findById(requestId) - .orElseThrow(() -> new NotFoundException("Request not found")); + .orElseThrow(() -> new NotFoundException("Request with id=" + requestId + " was not found")); if (!request.getRequester().getId().equals(userId)) { throw new ConflictException("You can only cancel your own requests"); @@ -92,14 +92,6 @@ public RequestDto cancelRequest(Long userId, Long requestId) { request.setStatus(RequestStatus.CANCELED); return RequestMapper.toDto(requestRepository.save(request)); } - @Override - public List getEventRequests(Long userId, Long eventId) { - Event event = eventRepository.findById(eventId).orElseThrow(() -> new NotFoundException("Event not found")); - if (!event.getInitiator().getId().equals(userId)) throw new ConflictException("Not your event"); - - return requestRepository.findAllByEventId(eventId).stream() - .map(RequestMapper::toDto).collect(Collectors.toList()); - } @Override @Transactional From bb9305058847bba246d7f7ee25d7713273b245a7 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 17:37:31 +0300 Subject: [PATCH 15/24] fix errors --- .../ewm/service/RequestServiceImpl.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java index b8c86a6..bf3e62f 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java @@ -53,7 +53,6 @@ public RequestDto createRequest(Long userId, Long eventId) { if (event.getState() != EventState.PUBLISHED) { throw new ConflictException("Cannot participate in unpublished event"); } - if (event.getParticipantLimit() > 0 && event.getConfirmedRequests() >= event.getParticipantLimit()) { throw new ConflictException("Participant limit reached"); } @@ -63,7 +62,6 @@ public RequestDto createRequest(Long userId, Long eventId) { request.setEvent(event); request.setRequester(requester); - if (!event.getRequestModeration() || event.getParticipantLimit() == 0) { request.setStatus(RequestStatus.CONFIRMED); event.setConfirmedRequests(event.getConfirmedRequests() + 1); @@ -82,10 +80,9 @@ public RequestDto cancelRequest(Long userId, Long requestId) { .orElseThrow(() -> new NotFoundException("Request with id=" + requestId + " was not found")); if (!request.getRequester().getId().equals(userId)) { - throw new ConflictException("You can only cancel your own requests"); + throw new ConflictException("Not your request"); } - - if (request.getStatus().equals(RequestStatus.CONFIRMED)) { + if (request.getStatus() == RequestStatus.CONFIRMED) { throw new ConflictException("Cannot cancel an already confirmed request"); } @@ -93,30 +90,45 @@ public RequestDto cancelRequest(Long userId, Long requestId) { return RequestMapper.toDto(requestRepository.save(request)); } + @Override + public List getEventRequests(Long userId, Long eventId) { + Event event = eventRepository.findById(eventId).orElseThrow(() -> new NotFoundException("Event not found")); + if (!event.getInitiator().getId().equals(userId)) throw new ConflictException("Not your event"); + + return requestRepository.findAllByEventId(eventId).stream() + .map(RequestMapper::toDto).collect(Collectors.toList()); + } + @Override @Transactional public RequestStatusUpdateResult updateRequestStatus(Long userId, Long eventId, RequestStatusUpdate update) { Event event = eventRepository.findById(eventId).orElseThrow(() -> new NotFoundException("Event not found")); + if (!event.getInitiator().getId().equals(userId)) throw new ConflictException("Not your event"); List requests = requestRepository.findAllById(update.getRequestIds()); + RequestStatusUpdateResult result = new RequestStatusUpdateResult(); result.setConfirmedRequests(new ArrayList<>()); result.setRejectedRequests(new ArrayList<>()); for (ParticipationRequest req : requests) { + if (req.getStatus() != RequestStatus.PENDING) { + throw new ConflictException("Request must be in status PENDING"); + } + if (update.getStatus() == RequestStatus.REJECTED) { req.setStatus(RequestStatus.REJECTED); result.getRejectedRequests().add(RequestMapper.toDto(requestRepository.save(req))); } else { - - if (event.getParticipantLimit() != 0 && event.getConfirmedRequests() >= event.getParticipantLimit()) { + if (event.getParticipantLimit() == 0 || event.getConfirmedRequests() < event.getParticipantLimit()) { + req.setStatus(RequestStatus.CONFIRMED); + event.setConfirmedRequests(event.getConfirmedRequests() + 1); + result.getConfirmedRequests().add(RequestMapper.toDto(requestRepository.save(req))); + } else { req.setStatus(RequestStatus.REJECTED); result.getRejectedRequests().add(RequestMapper.toDto(requestRepository.save(req))); throw new ConflictException("Participant limit reached"); } - req.setStatus(RequestStatus.CONFIRMED); - event.setConfirmedRequests(event.getConfirmedRequests() + 1); - result.getConfirmedRequests().add(RequestMapper.toDto(requestRepository.save(req))); } } eventRepository.save(event); From 4fee81fa3506e68c77dfe14273e0be65eeb8ec7e Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 17:47:43 +0300 Subject: [PATCH 16/24] fix --- .../ewm/service/EventServiceImpl.java | 31 ++++++++++--------- .../ewm/service/RequestServiceImpl.java | 7 +++-- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 38c69d7..68a1347 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -178,32 +178,33 @@ public List getEventsPublic(String text, List categories, B throw new BadRequestException("Start date must be before end date"); } - if (rangeStart == null && rangeEnd == null) { - rangeStart = LocalDateTime.now(); + LocalDateTime start = (rangeStart != null) ? rangeStart : LocalDateTime.now(); + LocalDateTime end = (rangeEnd != null) ? rangeEnd : LocalDateTime.now().plusYears(100); + + try { + statsClient.hit(APP_NAME, request.getRequestURI(), request.getRemoteAddr(), LocalDateTime.now()); + } catch (Exception e) { + log.error("Stats error: {}", e.getMessage()); } - sendStats(request); - int page = from / size; - PageRequest pageable = PageRequest.of(page, size); + int pageSize = (size > 0) ? size : 10; + PageRequest pageable = PageRequest.of(from / pageSize, pageSize); - List events = eventRepository.findPublishedEvents(text, categories, paid, rangeStart, rangeEnd, + List events = eventRepository.findPublishedEvents(text, categories, paid, start, end, onlyAvailable, pageable); - if ("VIEWS".equals(sort)) { + if ("VIEWS".equalsIgnoreCase(sort)) { events = events.stream() - .sorted((e1, e2) -> { - long v1 = (e1.getViews() == null) ? 0 : e1.getViews(); - long v2 = (e2.getViews() == null) ? 0 : e2.getViews(); - return Long.compare(v2, v1); - }) + .sorted((e1, e2) -> Long.compare( + e2.getViews() != null ? e2.getViews() : 0, + e1.getViews() != null ? e1.getViews() : 0)) .collect(Collectors.toList()); } - return events.stream() - .map(EventMapper::toShortDto) - .collect(Collectors.toList()); + return events.stream().map(EventMapper::toShortDto).collect(Collectors.toList()); } + @Override public EventFullDto getEventByIdPublic(Long id, HttpServletRequest request) { Event event = eventRepository.findByIdAndState(id, EventState.PUBLISHED) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java index bf3e62f..d1531c3 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java @@ -80,10 +80,11 @@ public RequestDto cancelRequest(Long userId, Long requestId) { .orElseThrow(() -> new NotFoundException("Request with id=" + requestId + " was not found")); if (!request.getRequester().getId().equals(userId)) { - throw new ConflictException("Not your request"); + throw new ConflictException("You can only cancel your own requests"); } - if (request.getStatus() == RequestStatus.CONFIRMED) { - throw new ConflictException("Cannot cancel an already confirmed request"); + + if (request.getStatus() != RequestStatus.PENDING) { + throw new ConflictException("Cannot cancel request with status: " + request.getStatus()); } request.setStatus(RequestStatus.CANCELED); From 598a45b0ea4ae716ca1e544f3273ace53694ee3a Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 18:15:04 +0300 Subject: [PATCH 17/24] fix: resolve 500 error in events search and return 409 for confirmed request cancellation --- .../ewm/controller/pub/PublicEventController.java | 8 ++++++-- .../java/ru/practicum/ewm/service/EventServiceImpl.java | 8 ++++++++ .../java/ru/practicum/ewm/service/RequestServiceImpl.java | 6 ++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java index 9a040f6..446b224 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java @@ -1,8 +1,11 @@ package ru.practicum.ewm.controller.pub; import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import lombok.RequiredArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import ru.practicum.ewm.dto.response.EventFullDto; import ru.practicum.ewm.dto.response.EventShortDto; @@ -14,6 +17,7 @@ @RestController @RequestMapping("/events") @RequiredArgsConstructor +@Validated public class PublicEventController { private final EventService eventService; @@ -27,8 +31,8 @@ public List getEvents( @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, @RequestParam(defaultValue = "false") Boolean onlyAvailable, @RequestParam(required = false) String sort, - @RequestParam(defaultValue = "0") int from, - @RequestParam(defaultValue = "10") int size, + @RequestParam(defaultValue = "0") @PositiveOrZero int from, // ДОБАВИТЬ + @RequestParam(defaultValue = "10") @Positive int size, // ДОБАВИТЬ HttpServletRequest request) { return eventService.getEventsPublic(text, categories, paid, rangeStart, rangeEnd, onlyAvailable, sort, from, size, request); } diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 68a1347..36391fa 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -202,6 +202,14 @@ public List getEventsPublic(String text, List categories, B .collect(Collectors.toList()); } + if (categories != null && categories.isEmpty()) { + categories = null; + } + + if (rangeEnd == null) { + rangeEnd = LocalDateTime.now().plusYears(100); + } + return events.stream().map(EventMapper::toShortDto).collect(Collectors.toList()); } diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java index d1531c3..8ec0140 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java @@ -88,7 +88,13 @@ public RequestDto cancelRequest(Long userId, Long requestId) { } request.setStatus(RequestStatus.CANCELED); + + if (request.getStatus() == RequestStatus.CONFIRMED) { + throw new ConflictException("Cannot cancel a confirmed request"); + } + return RequestMapper.toDto(requestRepository.save(request)); + } @Override From e93be5f1bebe3cffa69e4ceed908a67a0b0425b4 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 18:27:39 +0300 Subject: [PATCH 18/24] fix: resolve Enum comparison in HQL and empty category list crash --- .../ewm/repository/EventRepository.java | 9 ++-- .../ewm/service/EventServiceImpl.java | 48 +++++++++---------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java index aa0f12c..24440a9 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java @@ -35,16 +35,15 @@ List findEventsByAdmin( Pageable pageable); @Query("SELECT e FROM Event e " + - "WHERE e.state = 'PUBLISHED' " + - "AND (:text IS NULL OR (LOWER(e.annotation) LIKE LOWER(CONCAT('%', :text, '%')) " + - "OR LOWER(e.description) LIKE LOWER(CONCAT('%', :text, '%')))) " + + "WHERE e.state = :state " + + "AND (:text IS NULL OR LOWER(e.annotation) LIKE LOWER(CONCAT('%', :text, '%')) OR LOWER(e.description) LIKE LOWER(CONCAT('%', :text, '%'))) " + "AND (:categories IS NULL OR e.category.id IN :categories) " + "AND (:paid IS NULL OR e.paid = :paid) " + "AND (CAST(:rangeStart AS timestamp) IS NULL OR e.eventDate >= :rangeStart) " + "AND (CAST(:rangeEnd AS timestamp) IS NULL OR e.eventDate <= :rangeEnd) " + - "AND (:onlyAvailable = false OR e.participantLimit = 0 " + - "OR e.confirmedRequests < e.participantLimit)") + "AND (:onlyAvailable = false OR (e.participantLimit = 0 OR e.confirmedRequests < e.participantLimit))") List findPublishedEvents( + @Param("state") EventState state, // Добавили параметр статуса @Param("text") String text, @Param("categories") List categories, @Param("paid") Boolean paid, diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 36391fa..9d68c35 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -178,41 +178,37 @@ public List getEventsPublic(String text, List categories, B throw new BadRequestException("Start date must be before end date"); } - LocalDateTime start = (rangeStart != null) ? rangeStart : LocalDateTime.now(); - LocalDateTime end = (rangeEnd != null) ? rangeEnd : LocalDateTime.now().plusYears(100); - - try { - statsClient.hit(APP_NAME, request.getRequestURI(), request.getRemoteAddr(), LocalDateTime.now()); - } catch (Exception e) { - log.error("Stats error: {}", e.getMessage()); - } + // ФИКС 1: Превращаем пустой список [] в null, чтобы SQL не падал на IN() + List categoryIds = (categories != null && categories.isEmpty()) ? null : categories; + sendStats(request); + // Безопасная пагинация (защита от деления на 0) int pageSize = (size > 0) ? size : 10; PageRequest pageable = PageRequest.of(from / pageSize, pageSize); - List events = eventRepository.findPublishedEvents(text, categories, paid, start, end, - onlyAvailable, pageable); - - if ("VIEWS".equalsIgnoreCase(sort)) { - events = events.stream() - .sorted((e1, e2) -> Long.compare( - e2.getViews() != null ? e2.getViews() : 0, - e1.getViews() != null ? e1.getViews() : 0)) - .collect(Collectors.toList()); - } - - if (categories != null && categories.isEmpty()) { - categories = null; - } - - if (rangeEnd == null) { - rangeEnd = LocalDateTime.now().plusYears(100); + // ФИКС 2: Передаем EventState.PUBLISHED как Enum параметр + List events = eventRepository.findPublishedEvents( + EventState.PUBLISHED, + text, categoryIds, paid, rangeStart, rangeEnd, onlyAvailable, pageable); + + // Сортировка + if (sort != null) { + if (sort.equalsIgnoreCase("VIEWS")) { + events = events.stream() + .sorted((e1, e2) -> Long.compare( + e2.getViews() != null ? e2.getViews() : 0, + e1.getViews() != null ? e1.getViews() : 0)) + .collect(Collectors.toList()); + } else if (sort.equalsIgnoreCase("EVENT_DATE")) { + events = events.stream() + .sorted((e1, e2) -> e1.getEventDate().compareTo(e2.getEventDate())) + .collect(Collectors.toList()); + } } return events.stream().map(EventMapper::toShortDto).collect(Collectors.toList()); } - @Override public EventFullDto getEventByIdPublic(Long id, HttpServletRequest request) { Event event = eventRepository.findByIdAndState(id, EventState.PUBLISHED) From 1dcd8d2166120b36ef78c5448d28e001bf4fc2c8 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 18:33:39 +0300 Subject: [PATCH 19/24] fix --- .../practicum/ewm/repository/EventRepository.java | 11 +++++------ .../ru/practicum/ewm/service/EventServiceImpl.java | 14 +++++++------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java index 24440a9..9026619 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java @@ -36,14 +36,14 @@ List findEventsByAdmin( @Query("SELECT e FROM Event e " + "WHERE e.state = :state " + - "AND (:text IS NULL OR LOWER(e.annotation) LIKE LOWER(CONCAT('%', :text, '%')) OR LOWER(e.description) LIKE LOWER(CONCAT('%', :text, '%'))) " + + "AND (:text IS NULL OR (LOWER(e.annotation) LIKE LOWER(CONCAT('%', :text, '%')) OR LOWER(e.description) LIKE LOWER(CONCAT('%', :text, '%')))) " + "AND (:categories IS NULL OR e.category.id IN :categories) " + "AND (:paid IS NULL OR e.paid = :paid) " + - "AND (CAST(:rangeStart AS timestamp) IS NULL OR e.eventDate >= :rangeStart) " + - "AND (CAST(:rangeEnd AS timestamp) IS NULL OR e.eventDate <= :rangeEnd) " + - "AND (:onlyAvailable = false OR (e.participantLimit = 0 OR e.confirmedRequests < e.participantLimit))") + "AND (e.eventDate >= :rangeStart) " + + "AND (e.eventDate <= :rangeEnd) " + + "AND (:onlyAvailable = false OR e.participantLimit = 0 OR e.confirmedRequests < e.participantLimit)") List findPublishedEvents( - @Param("state") EventState state, // Добавили параметр статуса + @Param("state") EventState state, @Param("text") String text, @Param("categories") List categories, @Param("paid") Boolean paid, @@ -51,6 +51,5 @@ List findPublishedEvents( @Param("rangeEnd") LocalDateTime rangeEnd, @Param("onlyAvailable") Boolean onlyAvailable, Pageable pageable); - Optional findByIdAndState(Long id, EventState state); } \ No newline at end of file diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 9d68c35..32108d5 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -178,21 +178,21 @@ public List getEventsPublic(String text, List categories, B throw new BadRequestException("Start date must be before end date"); } - // ФИКС 1: Превращаем пустой список [] в null, чтобы SQL не падал на IN() List categoryIds = (categories != null && categories.isEmpty()) ? null : categories; + LocalDateTime start = (rangeStart != null) ? rangeStart : LocalDateTime.now().minusSeconds(1); + LocalDateTime end = (rangeEnd != null) ? rangeEnd : LocalDateTime.now().plusYears(100); + sendStats(request); - // Безопасная пагинация (защита от деления на 0) int pageSize = (size > 0) ? size : 10; - PageRequest pageable = PageRequest.of(from / pageSize, pageSize); + int pageNum = from / pageSize; + PageRequest pageable = PageRequest.of(pageNum, pageSize); - // ФИКС 2: Передаем EventState.PUBLISHED как Enum параметр List events = eventRepository.findPublishedEvents( - EventState.PUBLISHED, - text, categoryIds, paid, rangeStart, rangeEnd, onlyAvailable, pageable); + EventState.PUBLISHED, text, categoryIds, paid, start, end, + (onlyAvailable != null && onlyAvailable), pageable); - // Сортировка if (sort != null) { if (sort.equalsIgnoreCase("VIEWS")) { events = events.stream() From aee2854d2760cad95437eeef7fb13d8db3d229d9 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 19:07:09 +0300 Subject: [PATCH 20/24] fix --- .../main/java/ru/practicum/ewm/repository/EventRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java index 9026619..0d9ee83 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java @@ -36,7 +36,7 @@ List findEventsByAdmin( @Query("SELECT e FROM Event e " + "WHERE e.state = :state " + - "AND (:text IS NULL OR (LOWER(e.annotation) LIKE LOWER(CONCAT('%', :text, '%')) OR LOWER(e.description) LIKE LOWER(CONCAT('%', :text, '%')))) " + + "AND (:text IS NULL OR (LOWER(CAST(e.annotation AS string)) LIKE LOWER(CONCAT('%', :text, '%')) OR LOWER(CAST(e.description AS string)) LIKE LOWER(CONCAT('%', :text, '%')))) " + "AND (:categories IS NULL OR e.category.id IN :categories) " + "AND (:paid IS NULL OR e.paid = :paid) " + "AND (e.eventDate >= :rangeStart) " + From 7382f754c956dbd3fbe1ff498446c010a84315d6 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 19:14:32 +0300 Subject: [PATCH 21/24] fix --- .../practicum/ewm/repository/EventRepository.java | 15 ++++++++------- .../practicum/ewm/service/EventServiceImpl.java | 3 +-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java index 0d9ee83..464f914 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java @@ -34,16 +34,17 @@ List findEventsByAdmin( @Param("rangeEnd") LocalDateTime rangeEnd, Pageable pageable); - @Query("SELECT e FROM Event e " + + @Query(value = "SELECT * FROM events e " + "WHERE e.state = :state " + - "AND (:text IS NULL OR (LOWER(CAST(e.annotation AS string)) LIKE LOWER(CONCAT('%', :text, '%')) OR LOWER(CAST(e.description AS string)) LIKE LOWER(CONCAT('%', :text, '%')))) " + - "AND (:categories IS NULL OR e.category.id IN :categories) " + + "AND (:text IS NULL OR (e.annotation ILIKE CONCAT('%', :text, '%') OR e.description ILIKE CONCAT('%', :text, '%'))) " + + "AND (:categories IS NULL OR e.category_id IN :categories) " + "AND (:paid IS NULL OR e.paid = :paid) " + - "AND (e.eventDate >= :rangeStart) " + - "AND (e.eventDate <= :rangeEnd) " + - "AND (:onlyAvailable = false OR e.participantLimit = 0 OR e.confirmedRequests < e.participantLimit)") + "AND (e.event_date >= :rangeStart) " + + "AND (e.event_date <= :rangeEnd) " + + "AND (:onlyAvailable = false OR e.participant_limit = 0 OR e.confirmed_requests < e.participant_limit)", + nativeQuery = true) List findPublishedEvents( - @Param("state") EventState state, + @Param("state") String state, // <-- теперь String, не EventState @Param("text") String text, @Param("categories") List categories, @Param("paid") Boolean paid, diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 32108d5..6bf1155 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -190,9 +190,8 @@ public List getEventsPublic(String text, List categories, B PageRequest pageable = PageRequest.of(pageNum, pageSize); List events = eventRepository.findPublishedEvents( - EventState.PUBLISHED, text, categoryIds, paid, start, end, + EventState.PUBLISHED.name(), text, categoryIds, paid, start, end, (onlyAvailable != null && onlyAvailable), pageable); - if (sort != null) { if (sort.equalsIgnoreCase("VIEWS")) { events = events.stream() From aa3a20d95e515bf0d0b1b13acbed5014dbaf364d Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 19:23:16 +0300 Subject: [PATCH 22/24] fix --- .../practicum/ewm/controller/pub/PublicEventController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java index 446b224..4eed182 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java @@ -31,8 +31,8 @@ public List getEvents( @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, @RequestParam(defaultValue = "false") Boolean onlyAvailable, @RequestParam(required = false) String sort, - @RequestParam(defaultValue = "0") @PositiveOrZero int from, // ДОБАВИТЬ - @RequestParam(defaultValue = "10") @Positive int size, // ДОБАВИТЬ + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size, HttpServletRequest request) { return eventService.getEventsPublic(text, categories, paid, rangeStart, rangeEnd, onlyAvailable, sort, from, size, request); } From 28a8b174698242cec9bf2e604ac5c847397e70b6 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Fri, 8 May 2026 19:26:06 +0300 Subject: [PATCH 23/24] fix --- .../java/ru/practicum/ewm/service/CompilationServiceImpl.java | 2 +- .../main/java/ru/practicum/ewm/service/EventServiceImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java index 641568d..e0bf398 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java @@ -51,7 +51,7 @@ public CompilationDto addCompilation(NewCompilationDto newCompilationDto) { @Transactional public void deleteCompilation(Long compId) { if (!compilationRepository.existsById(compId)) { - throw new RuntimeException("Compilation not found"); // Позже заменим на Custom Exception + throw new RuntimeException("Compilation not found"); } compilationRepository.deleteById(compId); } diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java index 6bf1155..b0d46ab 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -14,7 +14,7 @@ import ru.practicum.ewm.dto.request.UpdateEventUser; import ru.practicum.ewm.dto.response.EventFullDto; import ru.practicum.ewm.dto.response.EventShortDto; -import ru.practicum.ewm.dto.response.LocationDto; // Проверь этот импорт +import ru.practicum.ewm.dto.response.LocationDto; import ru.practicum.ewm.exception.BadRequestException; import ru.practicum.ewm.exception.ConflictException; import ru.practicum.ewm.exception.NotFoundException; From 7f85c5bea7ce26adc7d4c63297643ed7e9172d2e Mon Sep 17 00:00:00 2001 From: Darya346 Date: Sat, 9 May 2026 21:32:29 +0300 Subject: [PATCH 24/24] refactor: apply review feedback, extract common paths and clean up CategoryDto --- .../controller/admin/AdminCategoryController.java | 7 ++++--- .../admin/AdminCompilationController.java | 7 ++++--- .../ru/practicum/ewm/dto/response/CategoryDto.java | 13 +++++++------ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java index 7dca8da..524ec14 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java @@ -14,6 +14,7 @@ public class AdminCategoryController { private final CategoryService categoryService; + private static final String CAT_ID_PATH = "/{catId}"; @PostMapping @ResponseStatus(HttpStatus.CREATED) @@ -21,13 +22,13 @@ public CategoryDto createCategory(@Valid @RequestBody NewCategoryDto newCategory return categoryService.createCategory(newCategoryDto); } - @PatchMapping("/{catId}") + @PatchMapping(CAT_ID_PATH) public CategoryDto updateCategory(@PathVariable Long catId, - @Valid @RequestBody CategoryDto categoryDto) { // @Valid здесь есть? + @Valid @RequestBody CategoryDto categoryDto) { return categoryService.updateCategory(catId, categoryDto); } - @DeleteMapping("/{catId}") + @DeleteMapping(CAT_ID_PATH) @ResponseStatus(HttpStatus.NO_CONTENT) public void deleteCategory(@PathVariable Long catId) { categoryService.deleteCategory(catId); diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java index b254016..f0b9069 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java @@ -16,6 +16,7 @@ public class AdminCompilationController { private final CompilationService compilationService; + private static final String COMP_ID_PATH = "/{compId}"; @PostMapping @ResponseStatus(HttpStatus.CREATED) @@ -23,13 +24,13 @@ public CompilationDto addCompilation(@Valid @RequestBody NewCompilationDto newCo return compilationService.addCompilation(newCompilationDto); } - @DeleteMapping("/{compId}") - @ResponseStatus(HttpStatus.NO_CONTENT) // Только этот статус, без возврата тела + @DeleteMapping(COMP_ID_PATH) + @ResponseStatus(HttpStatus.NO_CONTENT) public void deleteCompilation(@PathVariable Long compId) { compilationService.deleteCompilation(compId); } - @PatchMapping("/{compId}") + @PatchMapping(COMP_ID_PATH) public CompilationDto updateCompilation(@PathVariable Long compId, @Valid @RequestBody UpdateCompilationRequest updateRequest) { return compilationService.updateCompilation(compId, updateRequest); diff --git a/ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/CategoryDto.java b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/CategoryDto.java index 2fd0bf5..fa2945c 100644 --- a/ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/CategoryDto.java +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/CategoryDto.java @@ -2,17 +2,18 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import lombok.experimental.FieldDefaults; -@Getter -@Setter +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public class CategoryDto { Long id; + @NotBlank @Size(min = 1, max = 50) - private String name; + String name; } \ No newline at end of file