From de3549b8466c4c10b1f75832ef426f7f9af0ea2d Mon Sep 17 00:00:00 2001 From: Darya346 Date: Sun, 17 May 2026 10:48:34 +0300 Subject: [PATCH 1/2] refactor: implement all location logic and fix review comments --- README.md | 4 +- ewm-main-service/pom.xml | 1 - .../java/ru/practicum/ewm/EwmApplication.java | 2 + .../ewm/controller/ErrorHandler.java | 69 +++++ .../admin/AdminCategoryController.java | 36 +++ .../admin/AdminCompilationController.java | 38 +++ .../admin/AdminEventController.java | 39 +++ .../admin/AdminLocationController.java | 48 ++++ .../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 | 44 +++ .../pub/PublicLocationController.java | 35 +++ .../ewm/dto/request/NewEventDto.java | 3 +- .../ewm/dto/request/UpdateEventAdmin.java | 3 +- .../ewm/dto/request/UpdateEventUser.java | 3 +- .../practicum/ewm/dto/response/ApiError.java | 19 ++ .../ewm/dto/response/CategoryDto.java | 17 +- .../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/LocationMapper.java | 10 + .../practicum/ewm/mapper/RequestMapper.java | 17 ++ .../java/ru/practicum/ewm/model/Event.java | 4 +- .../java/ru/practicum/ewm/model/Location.java | 5 +- .../ewm/repository/CategoryRepository.java | 7 + .../ewm/repository/CompilationRepository.java | 11 + .../ewm/repository/EventRepository.java | 64 +++++ .../ewm/repository/LocationRepository.java | 7 + .../ParticipationRequestRepository.java | 18 ++ .../ewm/repository/UserRepository.java | 7 + .../ewm/service/CategoryService.java | 19 ++ .../ewm/service/CategoryServiceImpl.java | 77 ++++++ .../ewm/service/CompilationService.java | 18 ++ .../ewm/service/CompilationServiceImpl.java | 107 ++++++++ .../practicum/ewm/service/EventService.java | 32 +++ .../ewm/service/EventServiceImpl.java | 259 ++++++++++++++++++ .../ewm/service/LocationService.java | 22 ++ .../ewm/service/LocationServiceImpl.java | 83 ++++++ .../practicum/ewm/service/RequestService.java | 18 ++ .../ewm/service/RequestServiceImpl.java | 144 ++++++++++ .../ru/practicum/ewm/service/UserService.java | 15 + .../ewm/service/UserServiceImpl.java | 54 ++++ .../src/main/resources/application.properties | 4 +- .../src/main/resources/schema.sql | 32 +++ postman/feature.json | 178 ++++++++++++ 50 files changed, 1866 insertions(+), 18 deletions(-) create mode 100644 ewm-main-service/src/main/java/ru/practicum/ewm/controller/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 create mode 100644 ewm-main-service/src/main/resources/schema.sql create mode 100644 postman/feature.json diff --git a/README.md b/README.md index 18a246e..7c48cf7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# java-explore-with-me -Template repository for ExploreWithMe project. + +https://github.com/Darya346/java-explore-with-me/pull/5 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/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/ErrorHandler.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java new file mode 100644 index 0000000..4233366 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/ErrorHandler.java @@ -0,0 +1,69 @@ +package ru.practicum.ewm.controller; + +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 java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.stream.Collectors; + +@Slf4j +@RestControllerAdvice +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) { + 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) { + 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, MissingServletRequestParameterException.class}) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiError handleBadRequest(final Exception e) { + 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 Error: ", e); + return createApiError(HttpStatus.INTERNAL_SERVER_ERROR, "Error occurred", e.getMessage()); + } + + private ApiError createApiError(HttpStatus status, String reason, String message) { + return ApiError.builder() + .status(status.name()) + .reason(reason) + .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/controller/admin/AdminCategoryController.java b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java new file mode 100644 index 0000000..524ec14 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCategoryController.java @@ -0,0 +1,36 @@ +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; + private static final String CAT_ID_PATH = "/{catId}"; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public CategoryDto createCategory(@Valid @RequestBody NewCategoryDto newCategoryDto) { + return categoryService.createCategory(newCategoryDto); + } + + @PatchMapping(CAT_ID_PATH) + public CategoryDto updateCategory(@PathVariable Long catId, + @Valid @RequestBody CategoryDto categoryDto) { + return categoryService.updateCategory(catId, categoryDto); + } + + @DeleteMapping(CAT_ID_PATH) + @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..f0b9069 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminCompilationController.java @@ -0,0 +1,38 @@ +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; + private static final String COMP_ID_PATH = "/{compId}"; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public CompilationDto addCompilation(@Valid @RequestBody NewCompilationDto newCompilationDto) { + return compilationService.addCompilation(newCompilationDto); + } + + @DeleteMapping(COMP_ID_PATH) + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteCompilation(@PathVariable Long compId) { + compilationService.deleteCompilation(compId); + } + + @PatchMapping(COMP_ID_PATH) + 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..e7c26fe --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/admin/AdminLocationController.java @@ -0,0 +1,48 @@ +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; + private static final String LOC_ID_PATH = "/{locationId}"; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public LocationDto createLocation(@Valid @RequestBody NewLocationDto locationDto) { + return locationService.createLocation(locationDto); + } + + @GetMapping + public List getAllLocations() { + return locationService.getAllLocations(); + } + + @GetMapping(LOC_ID_PATH) + public LocationDto getLocation(@PathVariable Long locationId) { + return locationService.getLocation(locationId); + } + + @PatchMapping(LOC_ID_PATH) + public LocationDto updateLocation(@PathVariable Long locationId, + @Valid @RequestBody NewLocationDto locationDto) { + return locationService.updateLocation(locationId, locationDto); + } + + @DeleteMapping(LOC_ID_PATH) + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteLocation(@PathVariable Long locationId) { + locationService.deleteLocation(locationId); + } +} 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..4eed182 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicEventController.java @@ -0,0 +1,44 @@ +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; +import ru.practicum.ewm.service.EventService; + +import java.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping("/events") +@RequiredArgsConstructor +@Validated +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") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive 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..fa806ae --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/controller/pub/PublicLocationController.java @@ -0,0 +1,35 @@ +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.EventShortDto; +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); + } + + @GetMapping("/{locationId}/events") + public List getEventsInLocation(@PathVariable Long locationId) { + return locationService.getEventsInLocation(locationId); + } +} 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/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/dto/response/CategoryDto.java b/ewm-main-service/src/main/java/ru/practicum/ewm/dto/response/CategoryDto.java index 5cd4ece..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 @@ -1,14 +1,19 @@ package ru.practicum.ewm.dto.response; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.*; import lombok.experimental.FieldDefaults; -@Getter -@Setter +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public class CategoryDto { Long id; - String name; + + @NotBlank + @Size(min = 1, max = 50) + String name; } \ 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..cdce13d --- /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.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 dto = new CompilationDto(); + dto.setId(compilation.getId()); + dto.setTitle(compilation.getTitle()); + dto.setPinned(compilation.getPinned()); + + if (compilation.getEvents() != null) { + + dto.setEvents(compilation.getEvents().stream() + .map(EventMapper::toShortDto) + .collect(Collectors.toSet())); + } + return dto; + } +} \ 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/LocationMapper.java b/ewm-main-service/src/main/java/ru/practicum/ewm/mapper/LocationMapper.java index 22fab6f..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()); @@ -17,6 +18,7 @@ public static LocationDto toDto(Location location) { } public static Location toLocation(NewLocationDto request) { + if (request == null) return null; Location location = new Location(); location.setName(request.getName()); location.setLat(request.getLat()); @@ -24,4 +26,12 @@ public static Location toLocation(NewLocationDto request) { location.setRadius(request.getRadius()); return location; } + + public static Location toLocation(LocationDto dto) { + if (dto == null) return null; + Location location = new Location(); + 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/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/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/model/Location.java b/ewm-main-service/src/main/java/ru/practicum/ewm/model/Location.java index 981754a..298479c 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,7 @@ public class Location { @Column(nullable = false) Float lon; - @Column(nullable = false) + @Column Float radius; + } \ 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..cb1925d --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/repository/EventRepository.java @@ -0,0 +1,64 @@ +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 { + + @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); + + 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(value = "SELECT * FROM events e " + + "WHERE e.state = :state " + + "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.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") String state, // <-- теперь String, не EventState + @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); + + @Query(value = "SELECT e.* FROM events e " + + "JOIN locations l ON e.location_id = l.id " + + "WHERE distance(l.lat, l.lon, :targetLat, :targetLon) <= :targetRadius " + + "AND e.state = 'PUBLISHED'", nativeQuery = true) + List findEventsNearby(@Param("targetLat") float lat, + @Param("targetLon") float lon, + @Param("targetRadius") float radius); + 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..1087e7c --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CategoryServiceImpl.java @@ -0,0 +1,77 @@ +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.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; + +@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 NotFoundException("Category not found")); + category.setName(categoryDto.getName()); + Category updatedCategory = categoryRepository.save(category); + return CategoryMapper.toDto(updatedCategory); + } + + @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) { + return categoryRepository.findById(catId) + .map(CategoryMapper::toDto) + .orElseThrow(() -> new NotFoundException("Category with id=" + catId + " was not found")); + } + + + 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/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..e0bf398 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/CompilationServiceImpl.java @@ -0,0 +1,107 @@ +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.exception.NotFoundException; +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, events); + + if (compilation.getPinned() == null) { + compilation.setPinned(false); + } + + return CompilationMapper.toDto(compilationRepository.save(compilation)); + } + @Override + @Transactional + public void deleteCompilation(Long compId) { + if (!compilationRepository.existsById(compId)) { + throw new RuntimeException("Compilation not found"); + } + compilationRepository.deleteById(compId); + } + + @Override + @Transactional + public CompilationDto updateCompilation(Long compId, UpdateCompilationRequest updateRequest) { + Compilation compilation = compilationRepository.findById(compId) + .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()); + } + + 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..b0d46ab --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/EventServiceImpl.java @@ -0,0 +1,259 @@ +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.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.*; + +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 with id=" + userId + " was not found")); + Category category = categoryRepository.findById(dto.getCategory()) + .orElseThrow(() -> new NotFoundException("Category with id=" + dto.getCategory() + " was not found")); + + + Location location = locationRepository.save(LocationMapper.toLocation(dto.getLocation())); + + 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); + + + if (event.getPaid() == null) event.setPaid(false); + if (event.getParticipantLimit() == null) event.setParticipantLimit(0); + if (event.getRequestModeration() == null) event.setRequestModeration(true); + + 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 with id=" + eventId + " 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 with id=" + eventId + " was 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()); + } + + 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); + } + } + + + updateEventCommonFields(event, request.getAnnotation(), request.getCategory(), request.getDescription(), + request.getLocation(), request.getPaid(), request.getParticipantLimit(), request.getRequestModeration(), request.getTitle()); + + 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 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."); + } + 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.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."); + } + 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 categories, Boolean paid, + LocalDateTime rangeStart, LocalDateTime rangeEnd, + 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"); + } + + 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); + + int pageSize = (size > 0) ? size : 10; + int pageNum = from / pageSize; + PageRequest pageable = PageRequest.of(pageNum, pageSize); + + List events = eventRepository.findPublishedEvents( + EventState.PUBLISHED.name(), text, categoryIds, paid, start, end, + (onlyAvailable != null && 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) + .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, + 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 && !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) { + 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()); + } catch (Exception e) { + log.error("Ошибка статистики: {}", e.getMessage()); + } + } +} \ 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..c1e0de0 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationService.java @@ -0,0 +1,22 @@ +package ru.practicum.ewm.service; + +import ru.practicum.ewm.dto.request.NewLocationDto; +import ru.practicum.ewm.dto.response.EventShortDto; +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); + + List getEventsInLocation(Long locationId); + + LocationDto updateLocation(Long locationId, NewLocationDto dto); +} \ 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..2b6229f --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/LocationServiceImpl.java @@ -0,0 +1,83 @@ +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.EventShortDto; +import ru.practicum.ewm.dto.response.LocationDto; +import ru.practicum.ewm.exception.NotFoundException; +import ru.practicum.ewm.mapper.EventMapper; +import ru.practicum.ewm.mapper.LocationMapper; +import ru.practicum.ewm.model.Location; +import ru.practicum.ewm.repository.EventRepository; +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; + private final EventRepository eventRepository; + @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 NotFoundException("Location with id=" + locationId + " was 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); + if (!locationRepository.existsById(locationId)) { + throw new NotFoundException("Location with id=" + locationId + " was not found"); + } + } + + @Override + public List getEventsInLocation(Long locationId) { + Location location = locationRepository.findById(locationId) + .orElseThrow(() -> new NotFoundException("Location with id=" + locationId + " was not found")); // Добавлен ID + + return eventRepository.findEventsNearby(location.getLat(), location.getLon(), location.getRadius()) + .stream() + .map(EventMapper::toShortDto) + .collect(Collectors.toList()); + } + + @Override + @Transactional + public LocationDto updateLocation(Long locationId, NewLocationDto dto) { + Location location = locationRepository.findById(locationId) + .orElseThrow(() -> new NotFoundException("Location with id=" + locationId + " was not found")); // Добавлен ID + + if (dto.getName() != null && !dto.getName().isBlank()) location.setName(dto.getName()); + if (dto.getLat() != null) location.setLat(dto.getLat()); + if (dto.getLon() != null) location.setLon(dto.getLon()); + if (dto.getRadius() != null) location.setRadius(dto.getRadius()); + + return LocationMapper.toDto(locationRepository.save(location)); + } +} \ 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..8ec0140 --- /dev/null +++ b/ewm-main-service/src/main/java/ru/practicum/ewm/service/RequestServiceImpl.java @@ -0,0 +1,144 @@ +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); + + 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 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.PENDING) { + throw new ConflictException("Cannot cancel request with status: " + request.getStatus()); + } + + request.setStatus(RequestStatus.CANCELED); + + if (request.getStatus() == RequestStatus.CONFIRMED) { + throw new ConflictException("Cannot cancel a confirmed request"); + } + + 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 diff --git a/ewm-main-service/src/main/resources/application.properties b/ewm-main-service/src/main/resources/application.properties index daedddc..bc3b1de 100644 --- a/ewm-main-service/src/main/resources/application.properties +++ b/ewm-main-service/src/main/resources/application.properties @@ -3,7 +3,7 @@ server.port=8080 spring.datasource.url=jdbc:postgresql://localhost:5432/ewm spring.datasource.username=postgres spring.datasource.password=postgres -spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect - +spring.sql.init.mode=always +spring.jpa.hibernate.ddl-auto=update stats.server.url=http://localhost:9090 \ No newline at end of file diff --git a/ewm-main-service/src/main/resources/schema.sql b/ewm-main-service/src/main/resources/schema.sql new file mode 100644 index 0000000..f137553 --- /dev/null +++ b/ewm-main-service/src/main/resources/schema.sql @@ -0,0 +1,32 @@ +CREATE OR REPLACE FUNCTION distance(lat1 float, lon1 float, lat2 float, lon2 float) + RETURNS float +AS +' +declare + dist float = 0; + rad_lat1 float; + rad_lat2 float; + theta float; + rad_theta float; +BEGIN + IF lat1 = lat2 AND lon1 = lon2 + THEN + RETURN dist; + ELSE + rad_lat1 = pi() * lat1 / 180; + rad_lat2 = pi() * lat2 / 180; + theta = lon1 - lon2; + rad_theta = pi() * theta / 180; + dist = sin(rad_lat1) * sin(rad_lat2) + cos(rad_lat1) * cos(rad_lat2) * cos(rad_theta); + + IF dist > 1 THEN dist = 1; END IF; + + dist = acos(dist); + dist = dist * 180 / pi(); + dist = dist * 60 * 1.8524; + + RETURN dist; + END IF; +END; +' +LANGUAGE PLPGSQL; diff --git a/postman/feature.json b/postman/feature.json new file mode 100644 index 0000000..544ead0 --- /dev/null +++ b/postman/feature.json @@ -0,0 +1,178 @@ +{ + "info": { + "_postman_id": "a70aa31c-9893-4c9e-af88-b5f4f554393b", + "name": "Location-Final", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "51668874" + }, + "item": [ + { + "name": "New Request", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{ \"name\": \"Dasha\", \"email\": \"dasha@test.com\" }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/admin/users", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{ \"name\": \"Концерты\" }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/admin/categories", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "admin", + "categories" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"Центр\",\r\n \"lat\": 55.75,\r\n \"lon\": 37.61,\r\n \"radius\": 5.0\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/admin/locations", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "admin", + "locations" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"annotation\": \"Очень крутой концерт в самом центре города\",\r\n \"category\": 1,\r\n \"description\": \"Длинное описание события более двадцати символов\",\r\n \"eventDate\": \"2026-12-31 20:00:00\",\r\n \"location\": { \"lat\": 55.751, \"lon\": 37.611 },\r\n \"paid\": false,\r\n \"participantLimit\": 0,\r\n \"requestModeration\": false,\r\n \"title\": \"Супер Концерт\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/users/1/events", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "users", + "1", + "events" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{ \"stateAction\": \"PUBLISH_EVENT\" }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/admin/events/1", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "admin", + "events", + "1" + ] + } + }, + "response": [] + }, + { + "name": "New Request", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/locations/1/events", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "locations", + "1", + "events" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file From d0922c9e39e7ccfdf86479aa1fd9f51797f545e3 Mon Sep 17 00:00:00 2001 From: Darya346 Date: Sun, 17 May 2026 10:56:38 +0300 Subject: [PATCH 2/2] fix: resolve compilation error and improve exception messages --- .../practicum/ewm/service/LocationServiceImpl.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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 2b6229f..3455719 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 @@ -25,14 +25,15 @@ public class LocationServiceImpl implements LocationService { LocationRepository locationRepository; - private final EventRepository eventRepository; + EventRepository eventRepository; + @Override @Transactional public LocationDto createLocation(NewLocationDto newLocationDto) { Location location = LocationMapper.toLocation(newLocationDto); - Location savedLocation = locationRepository.save(location); - return LocationMapper.toDto(savedLocation); + return LocationMapper.toDto(locationRepository.save(location)); } + @Override public LocationDto getLocation(Long locationId) { Location location = locationRepository.findById(locationId) @@ -50,16 +51,16 @@ public List getAllLocations() { @Override @Transactional public void deleteLocation(Long locationId) { - locationRepository.deleteById(locationId); if (!locationRepository.existsById(locationId)) { throw new NotFoundException("Location with id=" + locationId + " was not found"); } + locationRepository.deleteById(locationId); } @Override public List getEventsInLocation(Long locationId) { Location location = locationRepository.findById(locationId) - .orElseThrow(() -> new NotFoundException("Location with id=" + locationId + " was not found")); // Добавлен ID + .orElseThrow(() -> new NotFoundException("Location with id=" + locationId + " was not found")); return eventRepository.findEventsNearby(location.getLat(), location.getLon(), location.getRadius()) .stream() @@ -71,7 +72,7 @@ public List getEventsInLocation(Long locationId) { @Transactional public LocationDto updateLocation(Long locationId, NewLocationDto dto) { Location location = locationRepository.findById(locationId) - .orElseThrow(() -> new NotFoundException("Location with id=" + locationId + " was not found")); // Добавлен ID + .orElseThrow(() -> new NotFoundException("Location with id=" + locationId + " was not found")); if (dto.getName() != null && !dto.getName().isBlank()) location.setName(dto.getName()); if (dto.getLat() != null) location.setLat(dto.getLat());