From 5acc4461e7657edb65e1b39a6e243f8fe02f5fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BB=D0=B0=D0=B2=D0=B0?= Date: Sun, 16 Nov 2025 13:49:33 +0400 Subject: [PATCH 1/6] microservices --- core/comment-service/Dockerfile | 4 + core/comment-service/pom.xml | 135 ++++++ .../practicum/CommentServiceApplication.java | 15 + .../java/ru/practicum/client/EventClient.java | 8 + .../practicum/client/EventClientHelper.java | 13 + .../java/ru/practicum/client/UserClient.java | 8 + .../ru/practicum/client/UserClientHelper.java | 13 + .../controller/CommentAdminController.java | 44 ++ .../controller/CommentPrivateController.java | 33 ++ .../controller/CommentPublicController.java | 35 ++ .../ru/practicum/comment/dal}/Comment.java | 30 +- .../comment/dal/CommentRepository.java | 20 + .../comment/service/CommentAdminService.java | 5 +- .../service/CommentAdminServiceImpl.java | 118 +++++ .../comment/service/CommentMapper.java | 31 ++ .../service/CommentPrivateService.java | 7 +- .../service/CommentPrivateServiceImpl.java | 86 ++++ .../comment/service/CommentPublicService.java | 5 +- .../service/CommentPublicServiceImpl.java | 91 ++++ .../src/main/resources/application.yaml | 25 + .../src/main/resources/schema.sql | 1 + core/core-common/pom.xml | 134 ++++++ .../api/category/CategoryAdminApi.java | 31 ++ .../api/category/CategoryPublicApi.java | 29 ++ .../api/comment/CommentAdminApi.java | 47 ++ .../api/comment/CommentPrivateApi.java | 35 ++ .../api/comment/CommentPublicApi.java | 37 ++ .../api/compilation/CompilationAdminApi.java | 31 ++ .../api/compilation/CompilationPublicApi.java | 30 ++ .../ru/practicum/api/event/EventAdminApi.java | 40 ++ .../ru/practicum/api/event/EventAllApi.java | 4 + .../practicum/api/event/EventPrivateApi.java | 50 ++ .../practicum/api/event/EventPublicApi.java | 61 +++ .../ru/practicum/api/request/RequestApi.java} | 61 ++- .../java/ru/practicum/api/user/UserApi.java | 66 +++ .../client/EventClientAbstractHelper.java | 89 ++++ .../client/RequestClientAbstractHelper.java | 27 ++ .../client/UserClientAbstractHelper.java | 100 ++++ .../practicum/dto/category}/CategoryDto.java | 2 +- .../dto/comment}/CommentCountDto.java | 2 +- .../dto/comment}/CommentCreateDto.java | 2 +- .../ru/practicum/dto/comment}/CommentDto.java | 6 +- .../dto/comment}/CommentShortDto.java | 4 +- .../dto/compilation}/CompilationDto.java | 4 +- .../dto/compilation}/NewCompilationDto.java | 2 +- .../compilation}/UpdateCompilationDto.java | 2 +- .../dto/event}/EventAdminParams.java | 8 +- .../practicum/dto/event}/EventCommentDto.java | 13 +- .../ru/practicum/dto/event}/EventFullDto.java | 10 +- .../dto/event/EventInteractionDto.java | 54 +++ .../ru/practicum/dto/event}/EventParams.java | 8 +- .../EventRequestStatusUpdateRequest.java | 4 +- .../EventRequestStatusUpdateResult.java | 6 +- .../practicum/dto/event}/EventShortDto.java | 8 +- .../ru/practicum/dto/event}/EventSort.java | 4 +- .../ru/practicum/dto/event}/LocationDto.java | 4 +- .../ru/practicum/dto/event}/NewEventDto.java | 4 +- .../java/ru/practicum/dto/event}/State.java | 4 +- .../ru/practicum/dto/event}/StateAction.java | 4 +- .../practicum/dto/event}/UpdateEventDto.java | 4 +- .../EventRequestStatusUpdateRequestDto.java | 7 +- .../EventRequestStatusUpdateResultDto.java | 4 +- .../dto/request}/ParticipationRequestDto.java | 4 +- .../request}/ParticipationRequestStatus.java | 4 +- .../dto/user}/NewUserRequestDto.java | 2 +- .../java/ru/practicum/dto/user}/UserDto.java | 8 +- .../ru/practicum/dto/user}/UserShortDto.java | 10 +- .../java/ru/practicum/exception/ApiError.java | 2 +- .../exception/BadRequestException.java | 7 +- .../exception/ConflictException.java | 7 +- .../exception/ForbiddenException.java | 7 +- .../exception/GlobalExceptionHandler.java | 17 +- .../exception/NotFoundException.java | 7 +- .../ServiceInteractionException.java | 20 + .../serialize/LocalDateTimeDeserializer.java | 2 +- .../serialize/LocalDateTimeSerializer.java | 2 +- .../validation/AtLeastOneNotNull.java | 0 .../AtLeastOneNotNullValidator.java | 0 .../validation/CategoryCreateValidator.java | 2 +- .../validation/CategoryUpdateValidator.java | 4 +- .../validation/CreateOrUpdateValidator.java | 0 .../NewCompilationCreateValidator.java | 2 +- .../NewCompilationUpdateValidator.java | 4 +- .../validation/NotBlankButNullAllowed.java | 2 +- .../NotBlankButNullAllowedValidator.java | 0 .../Dockerfile | 0 core/{main-service => event-service}/pom.xml | 20 +- .../practicum/EventServiceApplication.java} | 9 +- .../controller/CategoryAdminController.java | 32 ++ .../controller/CategoryPublicController.java | 29 ++ .../ru/practicum/category/dal}/Category.java | 20 +- .../category/dal}/CategoryRepository.java | 3 +- .../service/CategoryAdminService.java | 6 +- .../service/CategoryAdminServiceImpl.java | 18 +- .../category/service}/CategoryMapper.java | 8 +- .../service/CategoryPublicService.java | 4 +- .../service/CategoryPublicServiceImpl.java | 19 +- .../ru/practicum/client/RequestClient.java | 8 + .../practicum/client/RequestClientHelper.java | 13 + .../java/ru/practicum/client/UserClient.java | 8 + .../ru/practicum/client/UserClientHelper.java | 13 + .../CompilationAdminController.java | 34 ++ .../CompilationPublicController.java | 29 ++ .../compilation/dal/Compilation.java | 45 ++ .../dal/CompilationRepository.java | 11 + .../service/CompilationAdminService.java | 10 +- .../service/CompilationAdminServiceImpl.java | 102 +++++ .../service/CompilationMapper.java | 27 ++ .../service/CompilationPublicService.java | 4 +- .../service/CompilationPublicServiceImpl.java | 73 +++ .../controller/EventAdminController.java | 46 ++ .../controller/EventPrivateController.java | 46 ++ .../controller/EventPublicController.java | 71 +++ .../java/ru/practicum/event/dal}/Event.java | 36 +- .../practicum/event/dal}/EventRepository.java | 7 +- .../event/dal}/JpaSpecifications.java | 25 +- .../ru/practicum/event/dal}/Location.java | 10 +- .../java/ru/practicum/event/dal/View.java | 33 ++ .../practicum/event/dal}/ViewRepository.java | 5 +- .../event/service/EventAdminService.java | 8 +- .../event/service/EventAdminServiceImpl.java | 133 ++++++ .../practicum/event/service}/EventMapper.java | 50 +- .../event/service/EventPrivateService.java | 12 +- .../service/EventPrivateServiceImpl.java | 160 +++++++ .../event/service/EventPublicService.java | 13 +- .../event/service/EventPublicServiceImpl.java | 166 +++++++ .../event/service}/LocationMapper.java | 8 +- .../src/main/resources/application.yaml | 25 + .../src/main/resources/schema.sql | 1 + .../controller/CategoryAdminController.java | 53 --- .../controller/CategoryPublicController.java | 40 -- .../controller/CommentAdminController.java | 63 --- .../controller/CommentPrivateController.java | 49 -- .../controller/CommentPublicController.java | 45 -- .../comment/mapper/CommentMapper.java | 49 -- .../comment/repository/CommentRepository.java | 31 -- .../service/CommentAdminServiceImpl.java | 85 ---- .../service/CommentPrivateServiceImpl.java | 110 ----- .../service/CommentPublicServiceImpl.java | 102 ----- .../CompilationAdminController.java | 55 --- .../CompilationPublicController.java | 44 -- .../compilation/mapper/CompilationMapper.java | 33 -- .../compilation/model/Compilation.java | 49 -- .../repository/CompilationRepository.java | 19 - .../service/CompilationAdminServiceImpl.java | 98 ---- .../service/CompilationPublicServiceImpl.java | 56 --- .../controller/EventAdminController.java | 66 --- .../controller/EventPrivateController.java | 74 --- .../controller/EventPublicController.java | 65 --- .../java/ru/practicum/event/model/View.java | 27 -- .../event/service/EventAdminServiceImpl.java | 115 ----- .../service/EventPrivateServiceImpl.java | 158 ------- .../event/service/EventPublicServiceImpl.java | 117 ----- .../request/mapper/RequestMapper.java | 30 -- .../ru/practicum/request/model/Request.java | 40 -- .../request/service/RequestService.java | 197 -------- .../user/controller/UserController.java | 53 --- .../src/main/java/ru/practicum/util/Util.java | 23 - .../src/main/resources/schema.sql | 76 ---- .../ru/practicum/client/StatClientTest.java | 48 -- .../controller/UserControllerTest.java | 426 ------------------ .../service/UserServiceIntegrationTest.java | 365 --------------- core/pom.xml | 7 +- core/request-service/Dockerfile | 4 + core/request-service/pom.xml | 136 ++++++ .../practicum/RequestServiceApplication.java | 15 + .../java/ru/practicum/client/EventClient.java | 8 + .../practicum/client/EventClientHelper.java | 13 + .../java/ru/practicum/client/UserClient.java | 8 + .../ru/practicum/client/UserClientHelper.java | 13 + .../request/controller/RequestController.java | 62 +++ .../ru/practicum/request/dal/Request.java | 43 ++ .../request/dal}/RequestRepository.java | 19 +- .../request/service/RequestMapper.java | 18 + .../request/service/RequestService.java | 194 ++++++++ .../src/main/resources/application.yaml | 25 + .../src/main/resources/schema.sql | 1 + core/user-service/Dockerfile | 4 + core/user-service/pom.xml | 124 +++++ .../ru/practicum/UserServiceApplication.java | 11 + .../user/controller/UserController.java | 63 +++ .../java/ru/practicum/user/dal}/User.java | 11 +- .../practicum/user/dal}/UserRepository.java | 5 +- .../practicum/user/service}/UserMapper.java | 12 +- .../practicum/user/service/UserService.java | 44 +- .../src/main/resources/application.yaml | 4 +- .../src/main/resources/schema.sql | 1 + docker-compose.yml | 119 ++++- infra/config-server/Dockerfile | 4 + .../src/main/resources/application.yaml | 2 +- .../config/comment-service/application.yaml | 58 +++ .../config/event-service/application.yaml | 58 +++ .../config/gateway-server/application.yaml | 46 +- .../config/request-service/application.yaml | 58 +++ .../application.yaml | 4 +- infra/discovery-server/Dockerfile | 4 + .../src/main/resources/application.yaml | 2 +- infra/gateway-server/Dockerfile | 4 + pom.xml | 19 +- 199 files changed, 4162 insertions(+), 3204 deletions(-) create mode 100644 core/comment-service/Dockerfile create mode 100644 core/comment-service/pom.xml create mode 100644 core/comment-service/src/main/java/ru/practicum/CommentServiceApplication.java create mode 100644 core/comment-service/src/main/java/ru/practicum/client/EventClient.java create mode 100644 core/comment-service/src/main/java/ru/practicum/client/EventClientHelper.java create mode 100644 core/comment-service/src/main/java/ru/practicum/client/UserClient.java create mode 100644 core/comment-service/src/main/java/ru/practicum/client/UserClientHelper.java create mode 100644 core/comment-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java create mode 100644 core/comment-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java create mode 100644 core/comment-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java rename core/{main-service/src/main/java/ru/practicum/comment/model => comment-service/src/main/java/ru/practicum/comment/dal}/Comment.java (55%) create mode 100644 core/comment-service/src/main/java/ru/practicum/comment/dal/CommentRepository.java rename core/{main-service => comment-service}/src/main/java/ru/practicum/comment/service/CommentAdminService.java (81%) create mode 100644 core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java create mode 100644 core/comment-service/src/main/java/ru/practicum/comment/service/CommentMapper.java rename core/{main-service => comment-service}/src/main/java/ru/practicum/comment/service/CommentPrivateService.java (64%) create mode 100644 core/comment-service/src/main/java/ru/practicum/comment/service/CommentPrivateServiceImpl.java rename core/{main-service => comment-service}/src/main/java/ru/practicum/comment/service/CommentPublicService.java (76%) create mode 100644 core/comment-service/src/main/java/ru/practicum/comment/service/CommentPublicServiceImpl.java create mode 100644 core/comment-service/src/main/resources/application.yaml create mode 100644 core/comment-service/src/main/resources/schema.sql create mode 100644 core/core-common/pom.xml create mode 100644 core/core-common/src/main/java/ru/practicum/api/category/CategoryAdminApi.java create mode 100644 core/core-common/src/main/java/ru/practicum/api/category/CategoryPublicApi.java create mode 100644 core/core-common/src/main/java/ru/practicum/api/comment/CommentAdminApi.java create mode 100644 core/core-common/src/main/java/ru/practicum/api/comment/CommentPrivateApi.java create mode 100644 core/core-common/src/main/java/ru/practicum/api/comment/CommentPublicApi.java create mode 100644 core/core-common/src/main/java/ru/practicum/api/compilation/CompilationAdminApi.java create mode 100644 core/core-common/src/main/java/ru/practicum/api/compilation/CompilationPublicApi.java create mode 100644 core/core-common/src/main/java/ru/practicum/api/event/EventAdminApi.java create mode 100644 core/core-common/src/main/java/ru/practicum/api/event/EventAllApi.java create mode 100644 core/core-common/src/main/java/ru/practicum/api/event/EventPrivateApi.java create mode 100644 core/core-common/src/main/java/ru/practicum/api/event/EventPublicApi.java rename core/{main-service/src/main/java/ru/practicum/request/controller/RequestController.java => core-common/src/main/java/ru/practicum/api/request/RequestApi.java} (64%) create mode 100644 core/core-common/src/main/java/ru/practicum/api/user/UserApi.java create mode 100644 core/core-common/src/main/java/ru/practicum/client/EventClientAbstractHelper.java create mode 100644 core/core-common/src/main/java/ru/practicum/client/RequestClientAbstractHelper.java create mode 100644 core/core-common/src/main/java/ru/practicum/client/UserClientAbstractHelper.java rename core/{main-service/src/main/java/ru/practicum/category/dto => core-common/src/main/java/ru/practicum/dto/category}/CategoryDto.java (94%) rename core/{main-service/src/main/java/ru/practicum/comment/dto => core-common/src/main/java/ru/practicum/dto/comment}/CommentCountDto.java (88%) rename core/{main-service/src/main/java/ru/practicum/comment/dto => core-common/src/main/java/ru/practicum/dto/comment}/CommentCreateDto.java (91%) rename core/{main-service/src/main/java/ru/practicum/comment/dto => core-common/src/main/java/ru/practicum/dto/comment}/CommentDto.java (83%) rename core/{main-service/src/main/java/ru/practicum/comment/dto => core-common/src/main/java/ru/practicum/dto/comment}/CommentShortDto.java (85%) rename core/{main-service/src/main/java/ru/practicum/compilation/dto => core-common/src/main/java/ru/practicum/dto/compilation}/CompilationDto.java (80%) rename core/{main-service/src/main/java/ru/practicum/compilation/dto => core-common/src/main/java/ru/practicum/dto/compilation}/NewCompilationDto.java (93%) rename core/{main-service/src/main/java/ru/practicum/compilation/dto => core-common/src/main/java/ru/practicum/dto/compilation}/UpdateCompilationDto.java (94%) rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/EventAdminParams.java (83%) rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/EventCommentDto.java (52%) rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/EventFullDto.java (85%) create mode 100644 core/core-common/src/main/java/ru/practicum/dto/event/EventInteractionDto.java rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/EventParams.java (84%) rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/EventRequestStatusUpdateRequest.java (82%) rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/EventRequestStatusUpdateResult.java (70%) rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/EventShortDto.java (83%) rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/EventSort.java (58%) rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/LocationDto.java (87%) rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/NewEventDto.java (95%) rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/State.java (61%) rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/StateAction.java (75%) rename core/{main-service/src/main/java/ru/practicum/event/dto => core-common/src/main/java/ru/practicum/dto/event}/UpdateEventDto.java (97%) rename core/{main-service/src/main/java/ru/practicum/request/dto => core-common/src/main/java/ru/practicum/dto/request}/EventRequestStatusUpdateRequestDto.java (79%) rename core/{main-service/src/main/java/ru/practicum/request/dto => core-common/src/main/java/ru/practicum/dto/request}/EventRequestStatusUpdateResultDto.java (92%) rename core/{main-service/src/main/java/ru/practicum/request/dto => core-common/src/main/java/ru/practicum/dto/request}/ParticipationRequestDto.java (93%) rename core/{main-service/src/main/java/ru/practicum/request/dto => core-common/src/main/java/ru/practicum/dto/request}/ParticipationRequestStatus.java (70%) rename core/{main-service/src/main/java/ru/practicum/user/dto => core-common/src/main/java/ru/practicum/dto/user}/NewUserRequestDto.java (96%) rename core/{main-service/src/main/java/ru/practicum/user/dto => core-common/src/main/java/ru/practicum/dto/user}/UserDto.java (61%) rename core/{main-service/src/main/java/ru/practicum/user/dto => core-common/src/main/java/ru/practicum/dto/user}/UserShortDto.java (57%) rename core/{main-service => core-common}/src/main/java/ru/practicum/exception/ApiError.java (99%) rename core/{main-service => core-common}/src/main/java/ru/practicum/exception/BadRequestException.java (86%) rename core/{main-service => core-common}/src/main/java/ru/practicum/exception/ConflictException.java (86%) rename core/{main-service => core-common}/src/main/java/ru/practicum/exception/ForbiddenException.java (87%) rename core/{main-service => core-common}/src/main/java/ru/practicum/exception/GlobalExceptionHandler.java (92%) rename core/{main-service => core-common}/src/main/java/ru/practicum/exception/NotFoundException.java (86%) create mode 100644 core/core-common/src/main/java/ru/practicum/exception/ServiceInteractionException.java rename core/{main-service => core-common}/src/main/java/ru/practicum/serialize/LocalDateTimeDeserializer.java (99%) rename core/{main-service => core-common}/src/main/java/ru/practicum/serialize/LocalDateTimeSerializer.java (99%) rename core/{main-service => core-common}/src/main/java/ru/practicum/validation/AtLeastOneNotNull.java (100%) rename core/{main-service => core-common}/src/main/java/ru/practicum/validation/AtLeastOneNotNullValidator.java (100%) rename core/{main-service => core-common}/src/main/java/ru/practicum/validation/CategoryCreateValidator.java (94%) rename core/{main-service => core-common}/src/main/java/ru/practicum/validation/CategoryUpdateValidator.java (94%) rename core/{main-service => core-common}/src/main/java/ru/practicum/validation/CreateOrUpdateValidator.java (100%) rename core/{main-service => core-common}/src/main/java/ru/practicum/validation/NewCompilationCreateValidator.java (94%) rename core/{main-service => core-common}/src/main/java/ru/practicum/validation/NewCompilationUpdateValidator.java (93%) rename core/{main-service => core-common}/src/main/java/ru/practicum/validation/NotBlankButNullAllowed.java (99%) rename core/{main-service => core-common}/src/main/java/ru/practicum/validation/NotBlankButNullAllowedValidator.java (100%) rename core/{main-service => event-service}/Dockerfile (100%) rename core/{main-service => event-service}/pom.xml (86%) rename core/{main-service/src/main/java/ru/practicum/Main.java => event-service/src/main/java/ru/practicum/EventServiceApplication.java} (61%) create mode 100644 core/event-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java create mode 100644 core/event-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java rename core/{main-service/src/main/java/ru/practicum/category/model => event-service/src/main/java/ru/practicum/category/dal}/Category.java (57%) rename core/{main-service/src/main/java/ru/practicum/category/repository => event-service/src/main/java/ru/practicum/category/dal}/CategoryRepository.java (67%) rename core/{main-service => event-service}/src/main/java/ru/practicum/category/service/CategoryAdminService.java (71%) rename core/{main-service => event-service}/src/main/java/ru/practicum/category/service/CategoryAdminServiceImpl.java (88%) rename core/{main-service/src/main/java/ru/practicum/category/mapper => event-service/src/main/java/ru/practicum/category/service}/CategoryMapper.java (83%) rename core/{main-service => event-service}/src/main/java/ru/practicum/category/service/CategoryPublicService.java (82%) rename core/{main-service => event-service}/src/main/java/ru/practicum/category/service/CategoryPublicServiceImpl.java (67%) create mode 100644 core/event-service/src/main/java/ru/practicum/client/RequestClient.java create mode 100644 core/event-service/src/main/java/ru/practicum/client/RequestClientHelper.java create mode 100644 core/event-service/src/main/java/ru/practicum/client/UserClient.java create mode 100644 core/event-service/src/main/java/ru/practicum/client/UserClientHelper.java create mode 100644 core/event-service/src/main/java/ru/practicum/compilation/controller/CompilationAdminController.java create mode 100644 core/event-service/src/main/java/ru/practicum/compilation/controller/CompilationPublicController.java create mode 100644 core/event-service/src/main/java/ru/practicum/compilation/dal/Compilation.java create mode 100644 core/event-service/src/main/java/ru/practicum/compilation/dal/CompilationRepository.java rename core/{main-service => event-service}/src/main/java/ru/practicum/compilation/service/CompilationAdminService.java (54%) create mode 100644 core/event-service/src/main/java/ru/practicum/compilation/service/CompilationAdminServiceImpl.java create mode 100644 core/event-service/src/main/java/ru/practicum/compilation/service/CompilationMapper.java rename core/{main-service => event-service}/src/main/java/ru/practicum/compilation/service/CompilationPublicService.java (82%) create mode 100644 core/event-service/src/main/java/ru/practicum/compilation/service/CompilationPublicServiceImpl.java create mode 100644 core/event-service/src/main/java/ru/practicum/event/controller/EventAdminController.java create mode 100644 core/event-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java create mode 100644 core/event-service/src/main/java/ru/practicum/event/controller/EventPublicController.java rename core/{main-service/src/main/java/ru/practicum/event/model => event-service/src/main/java/ru/practicum/event/dal}/Event.java (68%) rename core/{main-service/src/main/java/ru/practicum/event/repository => event-service/src/main/java/ru/practicum/event/dal}/EventRepository.java (73%) rename core/{main-service/src/main/java/ru/practicum/event/repository => event-service/src/main/java/ru/practicum/event/dal}/JpaSpecifications.java (70%) rename core/{main-service/src/main/java/ru/practicum/event/model => event-service/src/main/java/ru/practicum/event/dal}/Location.java (76%) create mode 100644 core/event-service/src/main/java/ru/practicum/event/dal/View.java rename core/{main-service/src/main/java/ru/practicum/event/repository => event-service/src/main/java/ru/practicum/event/dal}/ViewRepository.java (89%) rename core/{main-service => event-service}/src/main/java/ru/practicum/event/service/EventAdminService.java (65%) create mode 100644 core/event-service/src/main/java/ru/practicum/event/service/EventAdminServiceImpl.java rename core/{main-service/src/main/java/ru/practicum/event/mapper => event-service/src/main/java/ru/practicum/event/service}/EventMapper.java (65%) rename core/{main-service => event-service}/src/main/java/ru/practicum/event/service/EventPrivateService.java (56%) create mode 100644 core/event-service/src/main/java/ru/practicum/event/service/EventPrivateServiceImpl.java rename core/{main-service => event-service}/src/main/java/ru/practicum/event/service/EventPublicService.java (55%) create mode 100644 core/event-service/src/main/java/ru/practicum/event/service/EventPublicServiceImpl.java rename core/{main-service/src/main/java/ru/practicum/event/mapper => event-service/src/main/java/ru/practicum/event/service}/LocationMapper.java (80%) create mode 100644 core/event-service/src/main/resources/application.yaml create mode 100644 core/event-service/src/main/resources/schema.sql delete mode 100644 core/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java delete mode 100644 core/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java delete mode 100644 core/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java delete mode 100644 core/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java delete mode 100644 core/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java delete mode 100644 core/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java delete mode 100644 core/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java delete mode 100644 core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java delete mode 100644 core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateServiceImpl.java delete mode 100644 core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicServiceImpl.java delete mode 100644 core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationAdminController.java delete mode 100644 core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationPublicController.java delete mode 100644 core/main-service/src/main/java/ru/practicum/compilation/mapper/CompilationMapper.java delete mode 100644 core/main-service/src/main/java/ru/practicum/compilation/model/Compilation.java delete mode 100644 core/main-service/src/main/java/ru/practicum/compilation/repository/CompilationRepository.java delete mode 100644 core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminServiceImpl.java delete mode 100644 core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicServiceImpl.java delete mode 100644 core/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java delete mode 100644 core/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java delete mode 100644 core/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java delete mode 100644 core/main-service/src/main/java/ru/practicum/event/model/View.java delete mode 100644 core/main-service/src/main/java/ru/practicum/event/service/EventAdminServiceImpl.java delete mode 100644 core/main-service/src/main/java/ru/practicum/event/service/EventPrivateServiceImpl.java delete mode 100644 core/main-service/src/main/java/ru/practicum/event/service/EventPublicServiceImpl.java delete mode 100644 core/main-service/src/main/java/ru/practicum/request/mapper/RequestMapper.java delete mode 100644 core/main-service/src/main/java/ru/practicum/request/model/Request.java delete mode 100644 core/main-service/src/main/java/ru/practicum/request/service/RequestService.java delete mode 100644 core/main-service/src/main/java/ru/practicum/user/controller/UserController.java delete mode 100644 core/main-service/src/main/java/ru/practicum/util/Util.java delete mode 100644 core/main-service/src/main/resources/schema.sql delete mode 100644 core/main-service/src/test/java/ru/practicum/client/StatClientTest.java delete mode 100644 core/main-service/src/test/java/ru/practicum/controller/UserControllerTest.java delete mode 100644 core/main-service/src/test/java/ru/practicum/service/UserServiceIntegrationTest.java create mode 100644 core/request-service/Dockerfile create mode 100644 core/request-service/pom.xml create mode 100644 core/request-service/src/main/java/ru/practicum/RequestServiceApplication.java create mode 100644 core/request-service/src/main/java/ru/practicum/client/EventClient.java create mode 100644 core/request-service/src/main/java/ru/practicum/client/EventClientHelper.java create mode 100644 core/request-service/src/main/java/ru/practicum/client/UserClient.java create mode 100644 core/request-service/src/main/java/ru/practicum/client/UserClientHelper.java create mode 100644 core/request-service/src/main/java/ru/practicum/request/controller/RequestController.java create mode 100644 core/request-service/src/main/java/ru/practicum/request/dal/Request.java rename core/{main-service/src/main/java/ru/practicum/request/repository => request-service/src/main/java/ru/practicum/request/dal}/RequestRepository.java (76%) create mode 100644 core/request-service/src/main/java/ru/practicum/request/service/RequestMapper.java create mode 100644 core/request-service/src/main/java/ru/practicum/request/service/RequestService.java create mode 100644 core/request-service/src/main/resources/application.yaml create mode 100644 core/request-service/src/main/resources/schema.sql create mode 100644 core/user-service/Dockerfile create mode 100644 core/user-service/pom.xml create mode 100644 core/user-service/src/main/java/ru/practicum/UserServiceApplication.java create mode 100644 core/user-service/src/main/java/ru/practicum/user/controller/UserController.java rename core/{main-service/src/main/java/ru/practicum/user/model => user-service/src/main/java/ru/practicum/user/dal}/User.java (67%) rename core/{main-service/src/main/java/ru/practicum/user/repository => user-service/src/main/java/ru/practicum/user/dal}/UserRepository.java (69%) rename core/{main-service/src/main/java/ru/practicum/user/mapper => user-service/src/main/java/ru/practicum/user/service}/UserMapper.java (69%) rename core/{main-service => user-service}/src/main/java/ru/practicum/user/service/UserService.java (57%) rename core/{main-service => user-service}/src/main/resources/application.yaml (91%) create mode 100644 core/user-service/src/main/resources/schema.sql create mode 100644 infra/config-server/Dockerfile create mode 100644 infra/config-server/src/main/resources/config/comment-service/application.yaml create mode 100644 infra/config-server/src/main/resources/config/event-service/application.yaml create mode 100644 infra/config-server/src/main/resources/config/request-service/application.yaml rename infra/config-server/src/main/resources/config/{main-service => user-service}/application.yaml (89%) create mode 100644 infra/discovery-server/Dockerfile create mode 100644 infra/gateway-server/Dockerfile diff --git a/core/comment-service/Dockerfile b/core/comment-service/Dockerfile new file mode 100644 index 0000000..a52e1ca --- /dev/null +++ b/core/comment-service/Dockerfile @@ -0,0 +1,4 @@ +FROM amazoncorretto:21-alpine +LABEL authors="Слава" +COPY target/*.jar app.jar +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/core/comment-service/pom.xml b/core/comment-service/pom.xml new file mode 100644 index 0000000..fd2f012 --- /dev/null +++ b/core/comment-service/pom.xml @@ -0,0 +1,135 @@ + + + 4.0.0 + + + ru.practicum + core + 0.0.1-SNAPSHOT + + ru.practicum + comment-service + 0.0.1-SNAPSHOT + + + + + + + + + + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + + + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + javax.validation + validation-api + 2.0.1.Final + + + + + + + + + + + + + + + + + + + + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-resilience4j + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + + + + \ No newline at end of file diff --git a/core/comment-service/src/main/java/ru/practicum/CommentServiceApplication.java b/core/comment-service/src/main/java/ru/practicum/CommentServiceApplication.java new file mode 100644 index 0000000..5b05018 --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/CommentServiceApplication.java @@ -0,0 +1,15 @@ +package ru.practicum; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +@EnableFeignClients +@SpringBootApplication +public class CommentServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(CommentServiceApplication.class, args); + } + +} diff --git a/core/comment-service/src/main/java/ru/practicum/client/EventClient.java b/core/comment-service/src/main/java/ru/practicum/client/EventClient.java new file mode 100644 index 0000000..a808401 --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/client/EventClient.java @@ -0,0 +1,8 @@ +package ru.practicum.client; + +import org.springframework.cloud.openfeign.FeignClient; +import ru.practicum.api.event.EventAllApi; + +@FeignClient(name = "event-service") +public interface EventClient extends EventAllApi { +} diff --git a/core/comment-service/src/main/java/ru/practicum/client/EventClientHelper.java b/core/comment-service/src/main/java/ru/practicum/client/EventClientHelper.java new file mode 100644 index 0000000..633a138 --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/client/EventClientHelper.java @@ -0,0 +1,13 @@ +package ru.practicum.client; + +import org.springframework.stereotype.Component; +import ru.practicum.api.event.EventAllApi; + +@Component +public class EventClientHelper extends EventClientAbstractHelper { + + public EventClientHelper(EventAllApi eventApiClient) { + super(eventApiClient); + } + +} diff --git a/core/comment-service/src/main/java/ru/practicum/client/UserClient.java b/core/comment-service/src/main/java/ru/practicum/client/UserClient.java new file mode 100644 index 0000000..50ca2b2 --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/client/UserClient.java @@ -0,0 +1,8 @@ +package ru.practicum.client; + +import org.springframework.cloud.openfeign.FeignClient; +import ru.practicum.api.user.UserApi; + +@FeignClient(name = "user-service") +public interface UserClient extends UserApi { +} diff --git a/core/comment-service/src/main/java/ru/practicum/client/UserClientHelper.java b/core/comment-service/src/main/java/ru/practicum/client/UserClientHelper.java new file mode 100644 index 0000000..41cb654 --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/client/UserClientHelper.java @@ -0,0 +1,13 @@ +package ru.practicum.client; + +import org.springframework.stereotype.Component; +import ru.practicum.api.user.UserApi; + +@Component +public class UserClientHelper extends UserClientAbstractHelper { + + public UserClientHelper(UserApi userApiClient) { + super(userApiClient); + } + +} \ No newline at end of file diff --git a/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java b/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java new file mode 100644 index 0000000..c6f0aa7 --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java @@ -0,0 +1,44 @@ +package ru.practicum.comment.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.api.comment.CommentAdminApi; +import ru.practicum.comment.service.CommentAdminService; +import ru.practicum.dto.comment.CommentDto; + +import java.util.Collection; + +@RestController +@RequiredArgsConstructor +@Validated +public class CommentAdminController implements CommentAdminApi { + + private final CommentAdminService commentAdminService; + + @Override + public Collection search(String text, int from, int size) { + return commentAdminService.search(text, from, size); + } + + @Override + public Collection get(Long userId, int from, int size) { + return commentAdminService.findAllByUserId(userId, from, size); + } + + @Override + public String delete(Long comId) { + return commentAdminService.delete(comId); + } + + @Override + public CommentDto approveComment(Long comId) { + return commentAdminService.approveComment(comId); + } + + @Override + public CommentDto rejectComment(Long comId) { + return commentAdminService.rejectComment(comId); + } + +} \ No newline at end of file diff --git a/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java b/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java new file mode 100644 index 0000000..ad7869e --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java @@ -0,0 +1,33 @@ +package ru.practicum.comment.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.api.comment.CommentPrivateApi; +import ru.practicum.comment.service.CommentPrivateService; +import ru.practicum.dto.comment.CommentCreateDto; +import ru.practicum.dto.comment.CommentDto; + +@RestController +@Validated +@RequiredArgsConstructor +public class CommentPrivateController implements CommentPrivateApi { + + private final CommentPrivateService service; + + @Override + public CommentDto create(Long userId, Long eventId, CommentCreateDto commentCreateDto) { + return service.createComment(userId, eventId, commentCreateDto); + } + + @Override + public String delete(Long userId, Long comId) { + return service.deleteComment(userId, comId); + } + + @Override + public CommentDto patch(Long userId, Long comId, CommentCreateDto commentCreateDto) { + return service.patchComment(userId, comId, commentCreateDto); + } + +} \ No newline at end of file diff --git a/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java b/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java new file mode 100644 index 0000000..ddd7475 --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java @@ -0,0 +1,35 @@ +package ru.practicum.comment.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.api.comment.CommentPublicApi; +import ru.practicum.comment.service.CommentPublicService; +import ru.practicum.dto.comment.CommentDto; +import ru.practicum.dto.comment.CommentShortDto; + +import java.util.Collection; + +@RestController +@RequiredArgsConstructor +@Validated +public class CommentPublicController implements CommentPublicApi { + + private final CommentPublicService commentPublicService; + + @Override + public CommentDto getById(Long comId) { + return commentPublicService.getComment(comId); + } + + @Override + public Collection getByEventId(Long eventId, int from, int size) { + return commentPublicService.getCommentsByEvent(eventId, from, size); + } + + @Override + public CommentDto getByEventAndCommentId(Long eventId, Long commentId) { + return commentPublicService.getCommentByEventAndCommentId(eventId, commentId); + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/comment/model/Comment.java b/core/comment-service/src/main/java/ru/practicum/comment/dal/Comment.java similarity index 55% rename from core/main-service/src/main/java/ru/practicum/comment/model/Comment.java rename to core/comment-service/src/main/java/ru/practicum/comment/dal/Comment.java index 76cdb2f..d41b053 100644 --- a/core/main-service/src/main/java/ru/practicum/comment/model/Comment.java +++ b/core/comment-service/src/main/java/ru/practicum/comment/dal/Comment.java @@ -1,35 +1,37 @@ -package ru.practicum.comment.model; +package ru.practicum.comment.dal; import jakarta.persistence.*; import lombok.*; -import ru.practicum.event.model.Event; -import ru.practicum.user.model.User; import java.time.LocalDateTime; @Getter @Setter -@Builder +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString @Entity -@AllArgsConstructor +@Builder @NoArgsConstructor -@Table(name = "comments") +@AllArgsConstructor +@Table(name = "comments", indexes = { + @Index(name = "idx_comments_event_id", columnList = "event_id"), + @Index(name = "idx_comments_textual_content", columnList = "textual_content") +}) public class Comment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @EqualsAndHashCode.Include private Long id; @Column(name = "textual_content", length = 1000, nullable = false) private String text; - @ManyToOne - @JoinColumn(name = "author_id", nullable = false) - private User author; + @Column(name = "author_id", nullable = false) + private Long authorId; - @ManyToOne - @JoinColumn(name = "event_id", nullable = false) - private Event event; + @Column(name = "event_id", nullable = false) + private Long eventId; @Column(name = "create_time", nullable = false) private LocalDateTime createTime; @@ -40,8 +42,4 @@ public class Comment { @Column(name = "approved", nullable = false) private Boolean approved; - public boolean isApproved() { - return approved; - } - } \ No newline at end of file diff --git a/core/comment-service/src/main/java/ru/practicum/comment/dal/CommentRepository.java b/core/comment-service/src/main/java/ru/practicum/comment/dal/CommentRepository.java new file mode 100644 index 0000000..06ffeaa --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/comment/dal/CommentRepository.java @@ -0,0 +1,20 @@ +package ru.practicum.comment.dal; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface CommentRepository extends JpaRepository { + + Page findAllByEventIdAndApproved(Long eventId, Boolean approved, Pageable pageable); + + Page findAllByAuthorId(Long userId, Pageable pageable); + + @Query(""" + SELECT c FROM Comment as c + WHERE c.text ILIKE CONCAT('%', ?1, '%') + """) + Page findByText(String text, Pageable pageable); + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java similarity index 81% rename from core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java rename to core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java index 707f1d1..bfa01e5 100644 --- a/core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java +++ b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java @@ -1,12 +1,12 @@ package ru.practicum.comment.service; -import ru.practicum.comment.dto.CommentDto; +import ru.practicum.dto.comment.CommentDto; import java.util.List; public interface CommentAdminService { - void delete(Long comId); + String delete(Long comId); List search(String text, int from, int size); @@ -15,4 +15,5 @@ public interface CommentAdminService { CommentDto approveComment(Long comId); CommentDto rejectComment(Long comId); + } \ No newline at end of file diff --git a/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java new file mode 100644 index 0000000..0b7e42f --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java @@ -0,0 +1,118 @@ +package ru.practicum.comment.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; +import ru.practicum.client.EventClientHelper; +import ru.practicum.client.UserClientHelper; +import ru.practicum.comment.dal.Comment; +import ru.practicum.comment.dal.CommentRepository; +import ru.practicum.dto.comment.CommentDto; +import ru.practicum.dto.event.EventCommentDto; +import ru.practicum.dto.user.UserDto; +import ru.practicum.exception.NotFoundException; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CommentAdminServiceImpl implements CommentAdminService { + + private final TransactionTemplate transactionTemplate; + private final CommentRepository commentRepository; + + private final UserClientHelper userClientHelper; + private final EventClientHelper eventClientHelper; + + @Override + @Transactional + public String delete(Long comId) { + if (!commentRepository.existsById(comId)) throw new NotFoundException("Not found Comment " + comId); + commentRepository.deleteById(comId); + return "deleted comment " + comId; + } + + @Override + public List search(String text, int from, int size) { + List comments = transactionTemplate.execute(status -> { + Pageable pageable = PageRequest.of(from / size, size); + return commentRepository.findByText(text, pageable).getContent(); + }); + if (comments == null || comments.isEmpty()) return List.of(); + + Set userIds = comments.stream().map(Comment::getAuthorId).collect(Collectors.toSet()); + Map userMap = userClientHelper.retrieveUserDtoMapByUserIdList(userIds); + + Set eventIds = comments.stream().map(Comment::getEventId).collect(Collectors.toSet()); + Map eventMap = eventClientHelper.retrieveEventCommentDtoMapByUserIdList(eventIds); + + return comments.stream() + .map(c -> CommentMapper.toCommentDto( + c, + userMap.get(c.getAuthorId()), + eventMap.get(c.getEventId()) + )) + .toList(); + } + + @Override + public List findAllByUserId(Long userId, int from, int size) { + UserDto userDto = userClientHelper.retrieveUserDtoByUserId(userId); + + List comments = transactionTemplate.execute(status -> { + Pageable pageable = PageRequest.of(from / size, size); + return commentRepository.findAllByAuthorId(userId, pageable).getContent(); + }); + if (comments == null || comments.isEmpty()) return List.of(); + + Set eventIds = comments.stream().map(Comment::getEventId).collect(Collectors.toSet()); + Map eventMap = eventClientHelper.retrieveEventCommentDtoMapByUserIdList(eventIds); + + return comments.stream() + .map(c -> CommentMapper.toCommentDto( + c, + userDto, + eventMap.get(c.getEventId()) + )) + .toList(); + } + + @Override + public CommentDto approveComment(Long comId) { + Comment comment = transactionTemplate.execute(status -> { + Comment commentEntity = commentRepository.findById(comId) + .orElseThrow(() -> new NotFoundException("Not found Comment " + comId)); + commentEntity.setApproved(true); + return commentRepository.save(commentEntity); + }); + + UserDto userDto = userClientHelper.retrieveUserDtoByUserId(comment.getAuthorId()); + EventCommentDto eventCommentDto = eventClientHelper.retrieveEventCommentDtoByEventId(comment.getEventId()); + + return CommentMapper.toCommentDto(comment, userDto, eventCommentDto); + } + + @Override + public CommentDto rejectComment(Long comId) { + Comment comment = transactionTemplate.execute(status -> { + Comment commentEntity = commentRepository.findById(comId) + .orElseThrow(() -> new NotFoundException("Not found Comment " + comId)); + commentEntity.setApproved(false); + return commentRepository.save(commentEntity); + }); + + UserDto userDto = userClientHelper.retrieveUserDtoByUserId(comment.getAuthorId()); + EventCommentDto eventCommentDto = eventClientHelper.retrieveEventCommentDtoByEventId(comment.getEventId()); + + return CommentMapper.toCommentDto(comment, userDto, eventCommentDto); + } + +} \ No newline at end of file diff --git a/core/comment-service/src/main/java/ru/practicum/comment/service/CommentMapper.java b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentMapper.java new file mode 100644 index 0000000..6955124 --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentMapper.java @@ -0,0 +1,31 @@ +package ru.practicum.comment.service; + +import ru.practicum.comment.dal.Comment; +import ru.practicum.dto.comment.CommentDto; +import ru.practicum.dto.comment.CommentShortDto; +import ru.practicum.dto.event.EventCommentDto; +import ru.practicum.dto.user.UserDto; + +public class CommentMapper { + + public static CommentDto toCommentDto(Comment comment, UserDto author, EventCommentDto eventCommentDto) { + return CommentDto.builder() + .id(comment.getId()) + .author(author) + .event(eventCommentDto) + .createTime(comment.getCreateTime()) + .text(comment.getText()) + .approved(comment.getApproved()) + .build(); + } + + public static CommentShortDto toCommentShortDto(Comment comment, UserDto author) { + return CommentShortDto.builder() + .author(author) + .createTime(comment.getText()) + .id(comment.getId()) + .text(comment.getText()) + .build(); + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateService.java b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentPrivateService.java similarity index 64% rename from core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateService.java rename to core/comment-service/src/main/java/ru/practicum/comment/service/CommentPrivateService.java index 1bf8dbd..eea48ed 100644 --- a/core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateService.java +++ b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentPrivateService.java @@ -1,13 +1,14 @@ package ru.practicum.comment.service; -import ru.practicum.comment.dto.CommentCreateDto; -import ru.practicum.comment.dto.CommentDto; +import ru.practicum.dto.comment.CommentCreateDto; +import ru.practicum.dto.comment.CommentDto; public interface CommentPrivateService { CommentDto createComment(Long userId, Long eventId, CommentCreateDto commentDto); - void deleteComment(Long userId, Long comId); + String deleteComment(Long userId, Long comId); CommentDto patchComment(Long userId, Long comId, CommentCreateDto commentCreateDto); + } \ No newline at end of file diff --git a/core/comment-service/src/main/java/ru/practicum/comment/service/CommentPrivateServiceImpl.java b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentPrivateServiceImpl.java new file mode 100644 index 0000000..68b575a --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentPrivateServiceImpl.java @@ -0,0 +1,86 @@ +package ru.practicum.comment.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; +import ru.practicum.client.EventClientHelper; +import ru.practicum.client.UserClientHelper; +import ru.practicum.comment.dal.Comment; +import ru.practicum.comment.dal.CommentRepository; +import ru.practicum.dto.comment.CommentCreateDto; +import ru.practicum.dto.comment.CommentDto; +import ru.practicum.dto.event.EventCommentDto; +import ru.practicum.dto.event.State; +import ru.practicum.dto.user.UserDto; +import ru.practicum.exception.ConflictException; +import ru.practicum.exception.NotFoundException; + +import java.time.LocalDateTime; +import java.util.Objects; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CommentPrivateServiceImpl implements CommentPrivateService { + + private final TransactionTemplate transactionTemplate; + private final CommentRepository commentRepository; + + private final UserClientHelper userClientHelper; + private final EventClientHelper eventClientHelper; + + @Override + public CommentDto createComment(Long userId, Long eventId, CommentCreateDto commentCreateDto) { + UserDto userDto = userClientHelper.retrieveUserDtoByUserIdOrFall(userId); + EventCommentDto eventCommentDto = eventClientHelper.retrieveEventCommentDtoByEventIdOrFall(eventId); + + if (!Objects.equals(eventCommentDto.getState(), State.PUBLISHED)) + throw new ConflictException("Unable to comment unpublished Event " + eventId); + + return transactionTemplate.execute(status -> { + Comment comment = Comment.builder() + .text(commentCreateDto.getText()) + .authorId(userId) + .eventId(eventId) + .approved(true) // по умолчанию комменты видны + .createTime(LocalDateTime.now()) + .build(); + commentRepository.save(comment); + return CommentMapper.toCommentDto(comment, userDto, eventCommentDto); + }); + } + + @Override + @Transactional + public String deleteComment(Long userId, Long comId) { + Comment comment = commentRepository.findById(comId) + .orElseThrow(() -> new NotFoundException("Not found Comment " + comId)); + if (!Objects.equals(comment.getAuthorId(), userId)) + throw new ConflictException("Unauthorized access by user " + userId + " to comment " + comId); + commentRepository.deleteById(comId); + return "Deleted Comment " + comId; + } + + @Override + public CommentDto patchComment(Long userId, Long comId, CommentCreateDto commentCreateDto) { + Comment comment = transactionTemplate.execute(status -> { + Comment commentEntity = commentRepository.findById(comId) + .orElseThrow(() -> new NotFoundException("Not found Comment " + comId)); + + if (!Objects.equals(commentEntity.getAuthorId(), userId)) + throw new ConflictException("Unauthorized access by user " + userId + " to comment " + comId); + + commentEntity.setText(commentCreateDto.getText()); + commentEntity.setPatchTime(LocalDateTime.now()); + return commentRepository.save(commentEntity); + }); + + UserDto userDto = userClientHelper.retrieveUserDtoByUserIdOrFall(userId); + EventCommentDto eventCommentDto = eventClientHelper.retrieveEventCommentDtoByEventIdOrFall(comment.getEventId()); + + return CommentMapper.toCommentDto(comment, userDto, eventCommentDto); + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicService.java b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentPublicService.java similarity index 76% rename from core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicService.java rename to core/comment-service/src/main/java/ru/practicum/comment/service/CommentPublicService.java index 2762467..9d81b04 100644 --- a/core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicService.java +++ b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentPublicService.java @@ -1,7 +1,7 @@ package ru.practicum.comment.service; -import ru.practicum.comment.dto.CommentDto; -import ru.practicum.comment.dto.CommentShortDto; +import ru.practicum.dto.comment.CommentDto; +import ru.practicum.dto.comment.CommentShortDto; import java.util.List; @@ -12,4 +12,5 @@ public interface CommentPublicService { List getCommentsByEvent(Long eventId, int from, int size); CommentDto getCommentByEventAndCommentId(Long eventId, Long commentId); + } \ No newline at end of file diff --git a/core/comment-service/src/main/java/ru/practicum/comment/service/CommentPublicServiceImpl.java b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentPublicServiceImpl.java new file mode 100644 index 0000000..0c5a1f2 --- /dev/null +++ b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentPublicServiceImpl.java @@ -0,0 +1,91 @@ +package ru.practicum.comment.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; +import ru.practicum.client.EventClientHelper; +import ru.practicum.client.UserClientHelper; +import ru.practicum.comment.dal.Comment; +import ru.practicum.comment.dal.CommentRepository; +import ru.practicum.dto.comment.CommentDto; +import ru.practicum.dto.comment.CommentShortDto; +import ru.practicum.dto.event.EventCommentDto; +import ru.practicum.dto.user.UserDto; +import ru.practicum.exception.ForbiddenException; +import ru.practicum.exception.NotFoundException; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CommentPublicServiceImpl implements CommentPublicService { + + private final TransactionTemplate transactionTemplate; + private final CommentRepository commentRepository; + + private final UserClientHelper userClientHelper; + private final EventClientHelper eventClientHelper; + + @Override + public CommentDto getComment(Long comId) { + Comment comment = transactionTemplate.execute(status -> { + return commentRepository.findById(comId) + .orElseThrow(() -> new NotFoundException("Not found Comment " + comId)); + }); + + if (!Objects.equals(comment.getApproved(), true)) + throw new ForbiddenException("Comment " + comId + "is not approved"); + + UserDto userDto = userClientHelper.retrieveUserDtoByUserId(comment.getAuthorId()); + EventCommentDto eventCommentDto = eventClientHelper.retrieveEventCommentDtoByEventId(comment.getEventId()); + + return CommentMapper.toCommentDto(comment, userDto, eventCommentDto); + } + + @Override + public List getCommentsByEvent(Long eventId, int from, int size) { + EventCommentDto eventCommentDto = eventClientHelper.retrieveEventCommentDtoByEventId(eventId); + + List comments = transactionTemplate.execute(status -> { + Pageable pageable = PageRequest.of(from / size, size, Sort.by("createTime").ascending()); + return commentRepository.findAllByEventIdAndApproved(eventId, true, pageable).getContent(); + }); + if (comments == null || comments.isEmpty()) return List.of(); + + Set userIds = comments.stream().map(Comment::getAuthorId).collect(Collectors.toSet()); + Map userMap = userClientHelper.retrieveUserDtoMapByUserIdList(userIds); + + return comments.stream() + .map(c -> CommentMapper.toCommentShortDto(c, userMap.get(c.getAuthorId()))) + .toList(); + } + + @Override + public CommentDto getCommentByEventAndCommentId(Long eventId, Long comId) { + Comment comment = transactionTemplate.execute(status -> { + return commentRepository.findById(comId) + .orElseThrow(() -> new NotFoundException("Not found Comment " + comId)); + }); + + if (!Objects.equals(comment.getEventId(), eventId)) + throw new NotFoundException("Comment " + comId + " does not belong to Event " + eventId); + + if (!Objects.equals(comment.getApproved(), true)) + throw new ForbiddenException("Comment " + comId + "is not approved"); + + UserDto userDto = userClientHelper.retrieveUserDtoByUserId(comment.getAuthorId()); + EventCommentDto eventCommentDto = eventClientHelper.retrieveEventCommentDtoByEventId(comment.getEventId()); + + return CommentMapper.toCommentDto(comment, userDto, eventCommentDto); + } + +} \ No newline at end of file diff --git a/core/comment-service/src/main/resources/application.yaml b/core/comment-service/src/main/resources/application.yaml new file mode 100644 index 0000000..931c1b1 --- /dev/null +++ b/core/comment-service/src/main/resources/application.yaml @@ -0,0 +1,25 @@ +spring: + application: + name: comment-service + config: + import: "configserver:" + cloud: + config: + discovery: + enabled: true + serviceId: config-server + fail-fast: true + retry: + useRandomPolicy: true + max-interval: 10000 + max-attempts: 100 + +eureka: + client: + registerWithEureka: true + serviceUrl: + defaultZone: http://localhost:8761/eureka/ + instance: + instance-id: ${spring.application.name}${random.int} + preferIpAddress: false + hostname: localhost diff --git a/core/comment-service/src/main/resources/schema.sql b/core/comment-service/src/main/resources/schema.sql new file mode 100644 index 0000000..0e28872 --- /dev/null +++ b/core/comment-service/src/main/resources/schema.sql @@ -0,0 +1 @@ +CREATE SCHEMA IF NOT EXISTS comment_service; diff --git a/core/core-common/pom.xml b/core/core-common/pom.xml new file mode 100644 index 0000000..09963fa --- /dev/null +++ b/core/core-common/pom.xml @@ -0,0 +1,134 @@ + + + 4.0.0 + + + ru.practicum + core + 0.0.1-SNAPSHOT + + + core-common + + + + + + + + + + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + + + + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + javax.validation + validation-api + 2.0.1.Final + + + + + + + + + + + + + + + + + + + + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-resilience4j + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + + + + \ No newline at end of file diff --git a/core/core-common/src/main/java/ru/practicum/api/category/CategoryAdminApi.java b/core/core-common/src/main/java/ru/practicum/api/category/CategoryAdminApi.java new file mode 100644 index 0000000..59fb0bd --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/api/category/CategoryAdminApi.java @@ -0,0 +1,31 @@ +package ru.practicum.api.category; + +import jakarta.validation.constraints.Positive; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.practicum.dto.category.CategoryDto; +import ru.practicum.validation.CreateOrUpdateValidator; + +public interface CategoryAdminApi { + + @PostMapping("/admin/categories") + @ResponseStatus(HttpStatus.CREATED) + CategoryDto addCategory( + @RequestBody @Validated(CreateOrUpdateValidator.Create.class) CategoryDto requestCategory + ); + + @DeleteMapping("/admin/categories/{catId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + String deleteCategories( + @PathVariable @Positive Long catId + ); + + @PatchMapping("/admin/categories/{catId}") + @ResponseStatus(HttpStatus.OK) + CategoryDto updateCategory( + @PathVariable Long catId, + @RequestBody @Validated(CreateOrUpdateValidator.Update.class) CategoryDto categoryDto + ); + +} \ No newline at end of file diff --git a/core/core-common/src/main/java/ru/practicum/api/category/CategoryPublicApi.java b/core/core-common/src/main/java/ru/practicum/api/category/CategoryPublicApi.java new file mode 100644 index 0000000..f3bb20b --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/api/category/CategoryPublicApi.java @@ -0,0 +1,29 @@ +package ru.practicum.api.category; + +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import ru.practicum.dto.category.CategoryDto; + +import java.util.Collection; + +public interface CategoryPublicApi { + + @GetMapping("/categories") + @ResponseStatus(HttpStatus.OK) + Collection readAllCategories( + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size + ); + + @GetMapping("/categories/{catId}") + @ResponseStatus(HttpStatus.OK) + CategoryDto readCategoryById( + @PathVariable Long catId + ); + +} \ No newline at end of file diff --git a/core/core-common/src/main/java/ru/practicum/api/comment/CommentAdminApi.java b/core/core-common/src/main/java/ru/practicum/api/comment/CommentAdminApi.java new file mode 100644 index 0000000..35eb626 --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/api/comment/CommentAdminApi.java @@ -0,0 +1,47 @@ +package ru.practicum.api.comment; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Positive; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.dto.comment.CommentDto; + +import java.util.Collection; + +public interface CommentAdminApi { + + @GetMapping("/admin/comments/search") + @ResponseStatus(HttpStatus.OK) + Collection search( + @RequestParam @NotBlank String text, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size + ); + + @GetMapping("/admin/users/{userId}/comments") + @ResponseStatus(HttpStatus.OK) + Collection get( + @PathVariable @Positive Long userId, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size + ); + + @DeleteMapping("/admin/comments/{comId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + String delete( + @PathVariable @Positive Long comId + ); + + @PatchMapping("/admin/comments/{comId}/approve") + @ResponseStatus(HttpStatus.OK) + CommentDto approveComment( + @PathVariable @Positive Long comId + ); + + @PatchMapping("/admin/comments/{comId}/reject") + @ResponseStatus(HttpStatus.OK) + CommentDto rejectComment( + @PathVariable @Positive Long comId + ); + +} \ No newline at end of file diff --git a/core/core-common/src/main/java/ru/practicum/api/comment/CommentPrivateApi.java b/core/core-common/src/main/java/ru/practicum/api/comment/CommentPrivateApi.java new file mode 100644 index 0000000..d81a03b --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/api/comment/CommentPrivateApi.java @@ -0,0 +1,35 @@ +package ru.practicum.api.comment; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.dto.comment.CommentCreateDto; +import ru.practicum.dto.comment.CommentDto; + +public interface CommentPrivateApi { + + @PostMapping("/users/{userId}/events/{eventId}/comments") + @ResponseStatus(HttpStatus.CREATED) + CommentDto create( + @PathVariable @Positive Long userId, + @PathVariable @Positive Long eventId, + @RequestBody @Valid CommentCreateDto commentCreateDto + ); + + @DeleteMapping("/users/{userId}/comments/{comId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + String delete( + @PathVariable @Positive Long userId, + @PathVariable @Positive Long comId + ); + + @PatchMapping("/users/{userId}/comments/{comId}") + @ResponseStatus(HttpStatus.OK) + CommentDto patch( + @PathVariable @Positive Long userId, + @PathVariable @Positive Long comId, + @RequestBody @Valid CommentCreateDto commentCreateDto + ); + +} \ No newline at end of file diff --git a/core/core-common/src/main/java/ru/practicum/api/comment/CommentPublicApi.java b/core/core-common/src/main/java/ru/practicum/api/comment/CommentPublicApi.java new file mode 100644 index 0000000..04a062f --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/api/comment/CommentPublicApi.java @@ -0,0 +1,37 @@ +package ru.practicum.api.comment; + +import jakarta.validation.constraints.Positive; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import ru.practicum.dto.comment.CommentDto; +import ru.practicum.dto.comment.CommentShortDto; + +import java.util.Collection; + +public interface CommentPublicApi { + + @GetMapping("/comments/{comId}") + @ResponseStatus(HttpStatus.OK) + CommentDto getById( + @PathVariable @Positive Long comId + ); + + @GetMapping("/events/{eventId}/comments") + @ResponseStatus(HttpStatus.OK) + Collection getByEventId( + @PathVariable @Positive Long eventId, + @RequestParam(defaultValue = "0") int from, + @RequestParam(defaultValue = "10") int size + ); + + @GetMapping("/events/{eventId}/comments/{commentId}") + @ResponseStatus(HttpStatus.OK) + CommentDto getByEventAndCommentId( + @PathVariable @Positive Long eventId, + @PathVariable @Positive Long commentId + ); + +} \ No newline at end of file diff --git a/core/core-common/src/main/java/ru/practicum/api/compilation/CompilationAdminApi.java b/core/core-common/src/main/java/ru/practicum/api/compilation/CompilationAdminApi.java new file mode 100644 index 0000000..6a5bc6b --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/api/compilation/CompilationAdminApi.java @@ -0,0 +1,31 @@ +package ru.practicum.api.compilation; + +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.dto.compilation.CompilationDto; +import ru.practicum.dto.compilation.NewCompilationDto; +import ru.practicum.dto.compilation.UpdateCompilationDto; + +public interface CompilationAdminApi { + + @PostMapping("/admin/compilations") + @ResponseStatus(HttpStatus.CREATED) + CompilationDto postCompilations( + @RequestBody @Valid NewCompilationDto newCompilationDto + ); + + @DeleteMapping("/admin/compilations/{compId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + String deleteCompilation( + @PathVariable Long compId + ); + + @PatchMapping("/admin/compilations/{compId}") + @ResponseStatus(HttpStatus.OK) + CompilationDto patchCompilation( + @PathVariable Long compId, + @RequestBody @Valid UpdateCompilationDto updateCompilationDto + ); + +} \ No newline at end of file diff --git a/core/core-common/src/main/java/ru/practicum/api/compilation/CompilationPublicApi.java b/core/core-common/src/main/java/ru/practicum/api/compilation/CompilationPublicApi.java new file mode 100644 index 0000000..071559d --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/api/compilation/CompilationPublicApi.java @@ -0,0 +1,30 @@ +package ru.practicum.api.compilation; + +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import ru.practicum.dto.compilation.CompilationDto; + +import java.util.Collection; + +public interface CompilationPublicApi { + + @GetMapping("/compilations") + @ResponseStatus(HttpStatus.OK) + Collection getCompilation( + @RequestParam(required = false) Boolean pinned, + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size + ); + + @GetMapping("/compilations/{compId}") + @ResponseStatus(HttpStatus.OK) + CompilationDto getCompilationById( + @PathVariable Long compId + ); + +} diff --git a/core/core-common/src/main/java/ru/practicum/api/event/EventAdminApi.java b/core/core-common/src/main/java/ru/practicum/api/event/EventAdminApi.java new file mode 100644 index 0000000..24022a1 --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/api/event/EventAdminApi.java @@ -0,0 +1,40 @@ +package ru.practicum.api.event; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.dto.event.EventFullDto; +import ru.practicum.dto.event.State; +import ru.practicum.dto.event.UpdateEventDto; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +public interface EventAdminApi { + + // Поиск событий + @GetMapping("/admin/events") + @ResponseStatus(HttpStatus.OK) + Collection getAllEventsByParams( + @RequestParam(required = false) List users, + @RequestParam(required = false) List states, + @RequestParam(required = false) List categories, + @RequestParam(required = false) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, + @RequestParam(required = false) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, + @RequestParam(defaultValue = "0") @PositiveOrZero Integer from, + @RequestParam(defaultValue = "10") @Positive Integer size + ); + + // Редактирование данных события и его статуса (отклонение/публикация). + @PatchMapping("/admin/events/{eventId}") + @ResponseStatus(HttpStatus.OK) + EventFullDto updateEventByAdmin( + @PathVariable Long eventId, + @RequestBody @Valid UpdateEventDto updateEventDto + ); + +} diff --git a/core/core-common/src/main/java/ru/practicum/api/event/EventAllApi.java b/core/core-common/src/main/java/ru/practicum/api/event/EventAllApi.java new file mode 100644 index 0000000..dc37691 --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/api/event/EventAllApi.java @@ -0,0 +1,4 @@ +package ru.practicum.api.event; + +public interface EventAllApi extends EventPublicApi, EventPrivateApi, EventAdminApi { +} diff --git a/core/core-common/src/main/java/ru/practicum/api/event/EventPrivateApi.java b/core/core-common/src/main/java/ru/practicum/api/event/EventPrivateApi.java new file mode 100644 index 0000000..8302fef --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/api/event/EventPrivateApi.java @@ -0,0 +1,50 @@ +package ru.practicum.api.event; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.dto.event.EventFullDto; +import ru.practicum.dto.event.EventShortDto; +import ru.practicum.dto.event.NewEventDto; +import ru.practicum.dto.event.UpdateEventDto; + +import java.util.Collection; + +public interface EventPrivateApi { + + // Добавление нового события + @PostMapping("/users/{userId}/events") + @ResponseStatus(HttpStatus.CREATED) + EventFullDto addNewEventByUser( + @PathVariable @Positive Long userId, + @Valid @RequestBody NewEventDto newEventDto + ); + + // Получение событий, добавленных текущим пользователем + @GetMapping("/users/{userId}/events") + @ResponseStatus(HttpStatus.OK) + Collection getAllEventsByUserId( + @PathVariable @Positive Long userId, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size + ); + + // Получение полной информации о событии добавленном текущим пользователем + @GetMapping("/users/{userId}/events/{eventId}") + @ResponseStatus(HttpStatus.OK) + EventFullDto getEventByUserIdAndEventId( + @PathVariable @Positive Long userId, + @PathVariable @Positive Long eventId + ); + + // Изменение события добавленного текущим пользователем + @PatchMapping("/users/{userId}/events/{eventId}") + @ResponseStatus(HttpStatus.OK) + EventFullDto updateEventByUserIdAndEventId( + @PathVariable @Positive Long userId, + @PathVariable @Positive Long eventId, + @Valid @RequestBody UpdateEventDto updateEventDto + ); + +} diff --git a/core/core-common/src/main/java/ru/practicum/api/event/EventPublicApi.java b/core/core-common/src/main/java/ru/practicum/api/event/EventPublicApi.java new file mode 100644 index 0000000..7119219 --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/api/event/EventPublicApi.java @@ -0,0 +1,61 @@ +package ru.practicum.api.event; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.Positive; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.dto.event.*; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +public interface EventPublicApi { + + // Получение событий с возможностью фильтрации + @GetMapping("/events") + @ResponseStatus(HttpStatus.OK) + List getAllEventsByParams( + @RequestParam(required = false) String text, + @RequestParam(required = false) List categories, + @RequestParam(required = false) Boolean paid, + @RequestParam(required = false) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, + @RequestParam(required = false) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, + @RequestParam(defaultValue = "false") Boolean onlyAvailable, + @RequestParam(defaultValue = "EVENT_DATE") EventSort eventSort, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size, + HttpServletRequest request + ); + + // Получение подробной информации об опубликованном событии по его идентификатору + @GetMapping("/events/{id}") + @ResponseStatus(HttpStatus.OK) + EventFullDto getInformationAboutEventByEventId( + @PathVariable @Positive Long id, + HttpServletRequest request + ); + + // Получение информации о событии для сервиса комментариев + @GetMapping("/events/{id}/dto/comment") + @ResponseStatus(HttpStatus.OK) + EventCommentDto getEventCommentDto( + @PathVariable @Positive Long id + ); + + // Получение информации о списке событий для сервиса комментариев + @PostMapping("/events/dto/list/comment") + @ResponseStatus(HttpStatus.OK) + Collection getEventCommentDtoList( + @RequestBody Collection ids + ); + + // Получение информации о событии для сервиса заявок + @GetMapping("/events/{id}/dto/interaction") + @ResponseStatus(HttpStatus.OK) + EventInteractionDto getEventInteractionDto( + @PathVariable @Positive Long id + ); + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/request/controller/RequestController.java b/core/core-common/src/main/java/ru/practicum/api/request/RequestApi.java similarity index 64% rename from core/main-service/src/main/java/ru/practicum/request/controller/RequestController.java rename to core/core-common/src/main/java/ru/practicum/api/request/RequestApi.java index ac51204..163c60a 100644 --- a/core/main-service/src/main/java/ru/practicum/request/controller/RequestController.java +++ b/core/core-common/src/main/java/ru/practicum/api/request/RequestApi.java @@ -1,74 +1,69 @@ -package ru.practicum.request.controller; +package ru.practicum.api.request; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.practicum.request.dto.EventRequestStatusUpdateRequestDto; -import ru.practicum.request.dto.EventRequestStatusUpdateResultDto; -import ru.practicum.request.dto.ParticipationRequestDto; -import ru.practicum.request.service.RequestService; +import ru.practicum.dto.request.EventRequestStatusUpdateRequestDto; +import ru.practicum.dto.request.EventRequestStatusUpdateResultDto; +import ru.practicum.dto.request.ParticipationRequestDto; import java.util.Collection; +import java.util.Map; -@RestController -@RequiredArgsConstructor -@Validated -public class RequestController { - - private final RequestService requestService; +public interface RequestApi { // ЗАЯВКИ ТЕКУЩЕГО ПОЛЬЗОВАТЕЛЯ // Добавление запроса от текущего пользователя на участие в событии @PostMapping("/users/{userId}/requests") @ResponseStatus(HttpStatus.CREATED) - public ParticipationRequestDto addRequest( + ParticipationRequestDto addRequest( @PathVariable @Positive(message = "User Id not valid") Long userId, @RequestParam @Positive(message = "Event Id not valid") Long eventId - ) { - return requestService.addRequest(userId, eventId); - } + ); // Отмена своего запроса на участие в событии @PatchMapping("/users/{userId}/requests/{requestId}/cancel") - public ParticipationRequestDto cancelRequest( + @ResponseStatus(HttpStatus.OK) + ParticipationRequestDto cancelRequest( @PathVariable @Positive(message = "User Id not valid") Long userId, @PathVariable @Positive(message = "Request Id not valid") Long requestId - ) { - return requestService.cancelRequest(userId, requestId); - } + ); // Получение информации о заявках текущего пользователя на участие в чужих событиях @GetMapping("/users/{userId}/requests") - public Collection getRequesterRequests( + @ResponseStatus(HttpStatus.OK) + Collection getRequesterRequests( @PathVariable @Positive(message = "User Id not valid") Long userId - ) { - return requestService.findRequesterRequests(userId); - } + ); // ЗАЯВКИ НА КОНКРЕТНОЕ СОБЫТИЕ // Изменение статуса (подтверждена, отменена) заявок на участие в событии текущего пользователя @PatchMapping("/users/{userId}/events/{eventId}/requests") - public EventRequestStatusUpdateResultDto moderateRequest( + @ResponseStatus(HttpStatus.OK) + EventRequestStatusUpdateResultDto moderateRequest( @PathVariable @Positive(message = "User Id not valid") Long userId, @PathVariable @Positive(message = "Event Id not valid") Long eventId, @RequestBody @Valid EventRequestStatusUpdateRequestDto updateRequestDto - ) { - return requestService.moderateRequest(userId, eventId, updateRequestDto); - } + ); // Получение информации о запросах на участие в событии текущего пользователя @GetMapping("/users/{userId}/events/{eventId}/requests") - public Collection getEventRequests( + @ResponseStatus(HttpStatus.OK) + Collection getEventRequests( @PathVariable @Positive(message = "User Id not valid") Long userId, @PathVariable @Positive(message = "Event Id not valid") Long eventId - ) { - return requestService.findEventRequests(userId, eventId); - } + ); + + // INTERACTION API + // Запрос количества подтвержденных заявок по списку eventId + @PostMapping("/requests/confirmed") + @ResponseStatus(HttpStatus.OK) + Map getConfirmedRequestsByEventIds( + @RequestBody Collection eventIds + ); } \ No newline at end of file diff --git a/core/core-common/src/main/java/ru/practicum/api/user/UserApi.java b/core/core-common/src/main/java/ru/practicum/api/user/UserApi.java new file mode 100644 index 0000000..4bdbb74 --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/api/user/UserApi.java @@ -0,0 +1,66 @@ +package ru.practicum.api.user; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.dto.user.NewUserRequestDto; +import ru.practicum.dto.user.UserDto; +import ru.practicum.dto.user.UserShortDto; + +import java.util.Collection; +import java.util.List; + +public interface UserApi { + + // MODIFY OPS + + @PostMapping("/admin/users") + @ResponseStatus(HttpStatus.CREATED) + UserDto createUser( + @RequestBody @Valid NewUserRequestDto newUserRequestDto + ); + + @DeleteMapping("/admin/users/{userId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + void deleteUser( + @PathVariable @Positive(message = "User Id not valid") Long userId + ); + + // GET + HEAD + + @GetMapping("/admin/users/{userId}") + @ResponseStatus(HttpStatus.OK) + UserDto getUser( + @PathVariable @Positive(message = "User Id not valid") Long userId + ); + + @GetMapping("/admin/users/{userId}/short") + @ResponseStatus(HttpStatus.OK) + UserShortDto getUserShort( + @PathVariable @Positive(message = "User Id not valid") Long userId + ); + + // GET COLLECTION + + @GetMapping("/admin/users") + @ResponseStatus(HttpStatus.OK) + Collection getUsers( + @RequestParam(required = false) List ids, + @RequestParam(defaultValue = "0") Integer from, + @RequestParam(defaultValue = "10") Integer size + ); + + @GetMapping("/admin/users/all/short") + @ResponseStatus(HttpStatus.OK) + Collection getUserShortDtoListByIds( + @RequestParam(required = false) Collection ids + ); + + @GetMapping("/admin/users/all/full") + @ResponseStatus(HttpStatus.OK) + Collection getUserDtoListByIds( + @RequestParam(required = false) Collection ids + ); + +} \ No newline at end of file diff --git a/core/core-common/src/main/java/ru/practicum/client/EventClientAbstractHelper.java b/core/core-common/src/main/java/ru/practicum/client/EventClientAbstractHelper.java new file mode 100644 index 0000000..ba6620d --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/client/EventClientAbstractHelper.java @@ -0,0 +1,89 @@ +package ru.practicum.client; + +import feign.FeignException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import ru.practicum.api.event.EventAllApi; +import ru.practicum.dto.event.EventCommentDto; +import ru.practicum.dto.event.EventInteractionDto; +import ru.practicum.exception.NotFoundException; +import ru.practicum.exception.ServiceInteractionException; + +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@RequiredArgsConstructor +public abstract class EventClientAbstractHelper { + + protected final EventAllApi eventApiClient; + + // EventInteractionDto + + public EventInteractionDto retrieveEventInteractionDtoByEventIdOrFall(Long eventId) { + try { + return eventApiClient.getEventInteractionDto(eventId); + } catch (RuntimeException e) { + if (isNotFoundCode(e)) throw new NotFoundException("Not found Event " + eventId); + + log.warn("Service Interaction Error: caught " + e.getClass().getSimpleName() + " - " + e.getMessage()); + throw new ServiceInteractionException("Unable to check Event " + eventId, "event-service is unavailable"); + } + } + + public EventInteractionDto retrieveEventInteractionDtoByEventId(Long eventId) { + try { + return eventApiClient.getEventInteractionDto(eventId); + } catch (RuntimeException e) { + if (isNotFoundCode(e)) throw new NotFoundException("Not found Event " + eventId); + + log.warn("Service Interaction Error: caught " + e.getClass().getSimpleName() + " - " + e.getMessage()); + return EventInteractionDto.makeDummy(eventId); + } + } + + // EventCommentDto + + public EventCommentDto retrieveEventCommentDtoByEventIdOrFall(Long eventId) { + try { + return eventApiClient.getEventCommentDto(eventId); + } catch (RuntimeException e) { + if (isNotFoundCode(e)) throw new NotFoundException("Not found Event " + eventId); + + log.warn("Service Interaction Error: caught " + e.getClass().getSimpleName() + " - " + e.getMessage()); + throw new ServiceInteractionException("Unable to check Event " + eventId, "event-service is unavailable"); + } + } + + public EventCommentDto retrieveEventCommentDtoByEventId(Long eventId) { + try { + return eventApiClient.getEventCommentDto(eventId); + } catch (RuntimeException e) { + if (isNotFoundCode(e)) throw new NotFoundException("Not found Event " + eventId); + + log.warn("Service Interaction Error: caught " + e.getClass().getSimpleName() + " - " + e.getMessage()); + return EventCommentDto.makeDummy(eventId); + } + } + + public Map retrieveEventCommentDtoMapByUserIdList(Collection eventIdList) { + try { + return eventApiClient.getEventCommentDtoList(eventIdList).stream() + .collect(Collectors.toMap(EventCommentDto::getId, e -> e)); + } catch (RuntimeException e) { + log.warn("Service Interaction Error: caught " + e.getClass().getSimpleName() + " - " + e.getMessage()); + return eventIdList.stream() + .collect(Collectors.toMap(id -> id, EventCommentDto::makeDummy)); + } + } + + // PRIVATE METHODS + + private boolean isNotFoundCode(RuntimeException e) { + if (e instanceof FeignException.NotFound) return true; + if (e.getCause() != null && e.getCause() instanceof FeignException.NotFound) return true; + return false; + } + +} diff --git a/core/core-common/src/main/java/ru/practicum/client/RequestClientAbstractHelper.java b/core/core-common/src/main/java/ru/practicum/client/RequestClientAbstractHelper.java new file mode 100644 index 0000000..d00145e --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/client/RequestClientAbstractHelper.java @@ -0,0 +1,27 @@ +package ru.practicum.client; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import ru.practicum.api.request.RequestApi; + +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@RequiredArgsConstructor +public abstract class RequestClientAbstractHelper { + + protected final RequestApi requestApiClient; + + // Confirmed Requests Map - by EventId List + public Map retrieveConfirmedRequestsMapByEventIdList(Collection eventIdList) { + try { + return requestApiClient.getConfirmedRequestsByEventIds(eventIdList); + } catch (RuntimeException e) { + log.warn("Service Interaction Error: caught " + e.getClass().getSimpleName() + " - " + e.getMessage()); + return eventIdList.stream().collect(Collectors.toMap(id -> id, id -> -1L)); + } + } + +} \ No newline at end of file diff --git a/core/core-common/src/main/java/ru/practicum/client/UserClientAbstractHelper.java b/core/core-common/src/main/java/ru/practicum/client/UserClientAbstractHelper.java new file mode 100644 index 0000000..91bfa04 --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/client/UserClientAbstractHelper.java @@ -0,0 +1,100 @@ +package ru.practicum.client; + +import feign.FeignException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import ru.practicum.api.user.UserApi; +import ru.practicum.dto.user.UserDto; +import ru.practicum.dto.user.UserShortDto; +import ru.practicum.exception.NotFoundException; +import ru.practicum.exception.ServiceInteractionException; + +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@RequiredArgsConstructor +public abstract class UserClientAbstractHelper { + + protected final UserApi userApiClient; + + // UserShortDto + + public UserShortDto retrieveUserShortDtoByUserIdOrFall(Long userId) { + try { + return userApiClient.getUserShort(userId); + } catch (RuntimeException e) { + if (isNotFoundCode(e)) throw new NotFoundException("Not found User " + userId); + + log.warn("Service Interaction Error: caught " + e.getClass().getSimpleName() + " - " + e.getMessage()); + throw new ServiceInteractionException("Unable to check User " + userId, "user-service is unavailable"); + } + } + + public UserShortDto retrieveUserShortDtoByUserId(Long userId) { + try { + return userApiClient.getUserShort(userId); + } catch (RuntimeException e) { + if (isNotFoundCode(e)) throw new NotFoundException("Not found User " + userId); + + log.warn("Service Interaction Error: caught " + e.getClass().getSimpleName() + " - " + e.getMessage()); + return UserShortDto.makeDummy(userId); + } + } + + public Map retrieveUserShortDtoMapByUserIdList(Collection userIdList) { + try { + return userApiClient.getUserShortDtoListByIds(userIdList).stream() + .collect(Collectors.toMap(UserShortDto::getId, u -> u)); + } catch (RuntimeException e) { + log.warn("Service Interaction Error: caught " + e.getClass().getSimpleName() + " - " + e.getMessage()); + return userIdList.stream() + .collect(Collectors.toMap(id -> id, UserShortDto::makeDummy)); + } + } + + // UserDto + + public UserDto retrieveUserDtoByUserIdOrFall(Long userId) { + try { + return userApiClient.getUser(userId); + } catch (RuntimeException e) { + if (isNotFoundCode(e)) throw new NotFoundException("Not found User " + userId); + + log.warn("Service Interaction Error: caught " + e.getClass().getSimpleName() + " - " + e.getMessage()); + throw new ServiceInteractionException("Unable to check User " + userId, "user-service is unavailable"); + } + } + + public UserDto retrieveUserDtoByUserId(Long userId) { + try { + return userApiClient.getUser(userId); + } catch (RuntimeException e) { + if (isNotFoundCode(e)) throw new NotFoundException("Not found User " + userId); + + log.warn("Service Interaction Error: caught " + e.getClass().getSimpleName() + " - " + e.getMessage()); + return UserDto.makeDummy(userId); + } + } + + public Map retrieveUserDtoMapByUserIdList(Collection userIdList) { + try { + return userApiClient.getUserDtoListByIds(userIdList).stream() + .collect(Collectors.toMap(UserDto::getId, u -> u)); + } catch (RuntimeException e) { + log.warn("Service Interaction Error: caught " + e.getClass().getSimpleName() + " - " + e.getMessage()); + return userIdList.stream() + .collect(Collectors.toMap(id -> id, UserDto::makeDummy)); + } + } + + // PRIVATE METHODS + + private boolean isNotFoundCode(RuntimeException e) { + if (e instanceof FeignException.NotFound) return true; + if (e.getCause() != null && e.getCause() instanceof FeignException.NotFound) return true; + return false; + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/category/dto/CategoryDto.java b/core/core-common/src/main/java/ru/practicum/dto/category/CategoryDto.java similarity index 94% rename from core/main-service/src/main/java/ru/practicum/category/dto/CategoryDto.java rename to core/core-common/src/main/java/ru/practicum/dto/category/CategoryDto.java index 7fffa9e..2ae716b 100644 --- a/core/main-service/src/main/java/ru/practicum/category/dto/CategoryDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/category/CategoryDto.java @@ -1,4 +1,4 @@ -package ru.practicum.category.dto; +package ru.practicum.dto.category; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; diff --git a/core/main-service/src/main/java/ru/practicum/comment/dto/CommentCountDto.java b/core/core-common/src/main/java/ru/practicum/dto/comment/CommentCountDto.java similarity index 88% rename from core/main-service/src/main/java/ru/practicum/comment/dto/CommentCountDto.java rename to core/core-common/src/main/java/ru/practicum/dto/comment/CommentCountDto.java index fe16256..3e6f0e9 100644 --- a/core/main-service/src/main/java/ru/practicum/comment/dto/CommentCountDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/comment/CommentCountDto.java @@ -1,4 +1,4 @@ -package ru.practicum.comment.dto; +package ru.practicum.dto.comment; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/core/main-service/src/main/java/ru/practicum/comment/dto/CommentCreateDto.java b/core/core-common/src/main/java/ru/practicum/dto/comment/CommentCreateDto.java similarity index 91% rename from core/main-service/src/main/java/ru/practicum/comment/dto/CommentCreateDto.java rename to core/core-common/src/main/java/ru/practicum/dto/comment/CommentCreateDto.java index 4182509..c571e20 100644 --- a/core/main-service/src/main/java/ru/practicum/comment/dto/CommentCreateDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/comment/CommentCreateDto.java @@ -1,4 +1,4 @@ -package ru.practicum.comment.dto; +package ru.practicum.dto.comment; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; diff --git a/core/main-service/src/main/java/ru/practicum/comment/dto/CommentDto.java b/core/core-common/src/main/java/ru/practicum/dto/comment/CommentDto.java similarity index 83% rename from core/main-service/src/main/java/ru/practicum/comment/dto/CommentDto.java rename to core/core-common/src/main/java/ru/practicum/dto/comment/CommentDto.java index 1de7f27..db161a4 100644 --- a/core/main-service/src/main/java/ru/practicum/comment/dto/CommentDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/comment/CommentDto.java @@ -1,12 +1,12 @@ -package ru.practicum.comment.dto; +package ru.practicum.dto.comment; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import ru.practicum.event.dto.EventCommentDto; -import ru.practicum.user.dto.UserDto; +import ru.practicum.dto.event.EventCommentDto; +import ru.practicum.dto.user.UserDto; import java.time.LocalDateTime; diff --git a/core/main-service/src/main/java/ru/practicum/comment/dto/CommentShortDto.java b/core/core-common/src/main/java/ru/practicum/dto/comment/CommentShortDto.java similarity index 85% rename from core/main-service/src/main/java/ru/practicum/comment/dto/CommentShortDto.java rename to core/core-common/src/main/java/ru/practicum/dto/comment/CommentShortDto.java index a32bd4c..75ccb9d 100644 --- a/core/main-service/src/main/java/ru/practicum/comment/dto/CommentShortDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/comment/CommentShortDto.java @@ -1,11 +1,11 @@ -package ru.practicum.comment.dto; +package ru.practicum.dto.comment; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import ru.practicum.user.dto.UserDto; +import ru.practicum.dto.user.UserDto; @Data @Builder diff --git a/core/main-service/src/main/java/ru/practicum/compilation/dto/CompilationDto.java b/core/core-common/src/main/java/ru/practicum/dto/compilation/CompilationDto.java similarity index 80% rename from core/main-service/src/main/java/ru/practicum/compilation/dto/CompilationDto.java rename to core/core-common/src/main/java/ru/practicum/dto/compilation/CompilationDto.java index 8078f28..79bbe48 100644 --- a/core/main-service/src/main/java/ru/practicum/compilation/dto/CompilationDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/compilation/CompilationDto.java @@ -1,10 +1,10 @@ -package ru.practicum.compilation.dto; +package ru.practicum.dto.compilation; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import ru.practicum.event.dto.EventShortDto; +import ru.practicum.dto.event.EventShortDto; import java.util.List; diff --git a/core/main-service/src/main/java/ru/practicum/compilation/dto/NewCompilationDto.java b/core/core-common/src/main/java/ru/practicum/dto/compilation/NewCompilationDto.java similarity index 93% rename from core/main-service/src/main/java/ru/practicum/compilation/dto/NewCompilationDto.java rename to core/core-common/src/main/java/ru/practicum/dto/compilation/NewCompilationDto.java index 3dfbc8b..428903d 100644 --- a/core/main-service/src/main/java/ru/practicum/compilation/dto/NewCompilationDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/compilation/NewCompilationDto.java @@ -1,4 +1,4 @@ -package ru.practicum.compilation.dto; +package ru.practicum.dto.compilation; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; diff --git a/core/main-service/src/main/java/ru/practicum/compilation/dto/UpdateCompilationDto.java b/core/core-common/src/main/java/ru/practicum/dto/compilation/UpdateCompilationDto.java similarity index 94% rename from core/main-service/src/main/java/ru/practicum/compilation/dto/UpdateCompilationDto.java rename to core/core-common/src/main/java/ru/practicum/dto/compilation/UpdateCompilationDto.java index b655cf2..e698cdb 100644 --- a/core/main-service/src/main/java/ru/practicum/compilation/dto/UpdateCompilationDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/compilation/UpdateCompilationDto.java @@ -1,4 +1,4 @@ -package ru.practicum.compilation.dto; +package ru.practicum.dto.compilation; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventAdminParams.java b/core/core-common/src/main/java/ru/practicum/dto/event/EventAdminParams.java similarity index 83% rename from core/main-service/src/main/java/ru/practicum/event/dto/EventAdminParams.java rename to core/core-common/src/main/java/ru/practicum/dto/event/EventAdminParams.java index d09fe38..35dab99 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/EventAdminParams.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/EventAdminParams.java @@ -1,4 +1,4 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; import lombok.AllArgsConstructor; import lombok.Builder; @@ -24,8 +24,8 @@ public class EventAdminParams { private LocalDateTime rangeEnd; - private Long from; + private Integer from; - private Long size; + private Integer size; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventCommentDto.java b/core/core-common/src/main/java/ru/practicum/dto/event/EventCommentDto.java similarity index 52% rename from core/main-service/src/main/java/ru/practicum/event/dto/EventCommentDto.java rename to core/core-common/src/main/java/ru/practicum/dto/event/EventCommentDto.java index d8aeb73..8a4ada5 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/EventCommentDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/EventCommentDto.java @@ -1,4 +1,4 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; import lombok.AllArgsConstructor; import lombok.Builder; @@ -14,4 +14,13 @@ public class EventCommentDto { private Long id; private String title; -} \ No newline at end of file + + private State state; + + public static EventCommentDto makeDummy(Long id) { + EventCommentDto dto = new EventCommentDto(); + dto.setId(id); + return dto; + } + +} diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java b/core/core-common/src/main/java/ru/practicum/dto/event/EventFullDto.java similarity index 85% rename from core/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java rename to core/core-common/src/main/java/ru/practicum/dto/event/EventFullDto.java index e892531..ffa1a1c 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/EventFullDto.java @@ -1,13 +1,13 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import ru.practicum.category.dto.CategoryDto; -import ru.practicum.comment.dto.CommentShortDto; -import ru.practicum.user.dto.UserShortDto; +import ru.practicum.dto.category.CategoryDto; +import ru.practicum.dto.comment.CommentShortDto; +import ru.practicum.dto.user.UserShortDto; import java.time.LocalDateTime; import java.util.List; @@ -47,4 +47,4 @@ public class EventFullDto { private List comments; -} \ No newline at end of file +} diff --git a/core/core-common/src/main/java/ru/practicum/dto/event/EventInteractionDto.java b/core/core-common/src/main/java/ru/practicum/dto/event/EventInteractionDto.java new file mode 100644 index 0000000..706ba0d --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/dto/event/EventInteractionDto.java @@ -0,0 +1,54 @@ +package ru.practicum.dto.event; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EventInteractionDto { + + private Long id; + + private Long initiatorId; + + private Long categoryId; + + private String title; + + private String annotation; + + private String description; + + private State state; + + private LocationDto location; + + private Long participantLimit; + + private Boolean requestModeration; + + private Boolean paid; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime eventDate; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime publishedOn; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createdOn; + + public static EventInteractionDto makeDummy(Long id) { + EventInteractionDto dto = new EventInteractionDto(); + dto.setId(id); + return dto; + } + +} diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventParams.java b/core/core-common/src/main/java/ru/practicum/dto/event/EventParams.java similarity index 84% rename from core/main-service/src/main/java/ru/practicum/event/dto/EventParams.java rename to core/core-common/src/main/java/ru/practicum/dto/event/EventParams.java index 411ce9c..73e141f 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/EventParams.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/EventParams.java @@ -1,4 +1,4 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; import lombok.AllArgsConstructor; import lombok.Builder; @@ -28,8 +28,8 @@ public class EventParams { private EventSort eventSort; - private Long from; + private Integer from; - private Long size; + private Integer size; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventRequestStatusUpdateRequest.java b/core/core-common/src/main/java/ru/practicum/dto/event/EventRequestStatusUpdateRequest.java similarity index 82% rename from core/main-service/src/main/java/ru/practicum/event/dto/EventRequestStatusUpdateRequest.java rename to core/core-common/src/main/java/ru/practicum/dto/event/EventRequestStatusUpdateRequest.java index 3fbc1a3..859a768 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/EventRequestStatusUpdateRequest.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/EventRequestStatusUpdateRequest.java @@ -1,4 +1,4 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; import lombok.Data; @@ -11,4 +11,4 @@ public class EventRequestStatusUpdateRequest { private State state; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventRequestStatusUpdateResult.java b/core/core-common/src/main/java/ru/practicum/dto/event/EventRequestStatusUpdateResult.java similarity index 70% rename from core/main-service/src/main/java/ru/practicum/event/dto/EventRequestStatusUpdateResult.java rename to core/core-common/src/main/java/ru/practicum/dto/event/EventRequestStatusUpdateResult.java index 4f5c254..3be6bc9 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/EventRequestStatusUpdateResult.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/EventRequestStatusUpdateResult.java @@ -1,7 +1,7 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; import lombok.Data; -import ru.practicum.request.dto.ParticipationRequestDto; +import ru.practicum.dto.request.ParticipationRequestDto; import java.util.List; @@ -12,4 +12,4 @@ public class EventRequestStatusUpdateResult { private List rejectedRequests; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java b/core/core-common/src/main/java/ru/practicum/dto/event/EventShortDto.java similarity index 83% rename from core/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java rename to core/core-common/src/main/java/ru/practicum/dto/event/EventShortDto.java index 8b3a31a..7f3843c 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/EventShortDto.java @@ -1,12 +1,12 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import ru.practicum.category.dto.CategoryDto; -import ru.practicum.user.dto.UserShortDto; +import ru.practicum.dto.category.CategoryDto; +import ru.practicum.dto.user.UserShortDto; import java.time.LocalDateTime; @@ -32,4 +32,4 @@ public class EventShortDto { private Long confirmedRequests; private Long views; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/EventSort.java b/core/core-common/src/main/java/ru/practicum/dto/event/EventSort.java similarity index 58% rename from core/main-service/src/main/java/ru/practicum/event/dto/EventSort.java rename to core/core-common/src/main/java/ru/practicum/dto/event/EventSort.java index 56e6dc6..ea36265 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/EventSort.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/EventSort.java @@ -1,5 +1,5 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; public enum EventSort { EVENT_DATE, VIEWS -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/LocationDto.java b/core/core-common/src/main/java/ru/practicum/dto/event/LocationDto.java similarity index 87% rename from core/main-service/src/main/java/ru/practicum/event/dto/LocationDto.java rename to core/core-common/src/main/java/ru/practicum/dto/event/LocationDto.java index 687d4ee..3b6c1b9 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/LocationDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/LocationDto.java @@ -1,4 +1,4 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; import lombok.AllArgsConstructor; import lombok.Builder; @@ -14,4 +14,4 @@ public class LocationDto { private Float lat; private Float lon; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java b/core/core-common/src/main/java/ru/practicum/dto/event/NewEventDto.java similarity index 95% rename from core/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java rename to core/core-common/src/main/java/ru/practicum/dto/event/NewEventDto.java index 09e385d..7e43c7e 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/NewEventDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/NewEventDto.java @@ -1,4 +1,4 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.*; @@ -39,4 +39,4 @@ public class NewEventDto { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime eventDate; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/State.java b/core/core-common/src/main/java/ru/practicum/dto/event/State.java similarity index 61% rename from core/main-service/src/main/java/ru/practicum/event/dto/State.java rename to core/core-common/src/main/java/ru/practicum/dto/event/State.java index 8d04843..976062e 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/State.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/State.java @@ -1,5 +1,5 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; public enum State { PENDING, PUBLISHED, CANCELED -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/StateAction.java b/core/core-common/src/main/java/ru/practicum/dto/event/StateAction.java similarity index 75% rename from core/main-service/src/main/java/ru/practicum/event/dto/StateAction.java rename to core/core-common/src/main/java/ru/practicum/dto/event/StateAction.java index b6ee34d..c35bda8 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/StateAction.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/StateAction.java @@ -1,8 +1,8 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; public enum StateAction { SEND_TO_REVIEW, CANCEL_REVIEW, PUBLISH_EVENT, REJECT_EVENT -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/dto/UpdateEventDto.java b/core/core-common/src/main/java/ru/practicum/dto/event/UpdateEventDto.java similarity index 97% rename from core/main-service/src/main/java/ru/practicum/event/dto/UpdateEventDto.java rename to core/core-common/src/main/java/ru/practicum/dto/event/UpdateEventDto.java index f73ee20..1725e05 100644 --- a/core/main-service/src/main/java/ru/practicum/event/dto/UpdateEventDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/event/UpdateEventDto.java @@ -1,4 +1,4 @@ -package ru.practicum.event.dto; +package ru.practicum.dto.event; import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.Future; @@ -48,4 +48,4 @@ public class UpdateEventDto { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime eventDate; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateRequestDto.java b/core/core-common/src/main/java/ru/practicum/dto/request/EventRequestStatusUpdateRequestDto.java similarity index 79% rename from core/main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateRequestDto.java rename to core/core-common/src/main/java/ru/practicum/dto/request/EventRequestStatusUpdateRequestDto.java index 3b50544..4af1b9b 100644 --- a/core/main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateRequestDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/request/EventRequestStatusUpdateRequestDto.java @@ -1,7 +1,5 @@ -package ru.practicum.request.dto; +package ru.practicum.dto.request; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; @@ -20,8 +18,7 @@ public class EventRequestStatusUpdateRequestDto { @NotEmpty(message = "Field 'requestIds' shouldn't be empty") private List requestIds; - @Enumerated(EnumType.STRING) @NotNull(message = "Field 'status' shouldn't be null") private ParticipationRequestStatus status; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateResultDto.java b/core/core-common/src/main/java/ru/practicum/dto/request/EventRequestStatusUpdateResultDto.java similarity index 92% rename from core/main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateResultDto.java rename to core/core-common/src/main/java/ru/practicum/dto/request/EventRequestStatusUpdateResultDto.java index 4db9fc2..b678c65 100644 --- a/core/main-service/src/main/java/ru/practicum/request/dto/EventRequestStatusUpdateResultDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/request/EventRequestStatusUpdateResultDto.java @@ -1,4 +1,4 @@ -package ru.practicum.request.dto; +package ru.practicum.dto.request; import lombok.AllArgsConstructor; import lombok.Builder; @@ -20,4 +20,4 @@ public class EventRequestStatusUpdateResultDto { @Builder.Default private List rejectedRequests = new ArrayList<>(); -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/request/dto/ParticipationRequestDto.java b/core/core-common/src/main/java/ru/practicum/dto/request/ParticipationRequestDto.java similarity index 93% rename from core/main-service/src/main/java/ru/practicum/request/dto/ParticipationRequestDto.java rename to core/core-common/src/main/java/ru/practicum/dto/request/ParticipationRequestDto.java index 6285a26..25b8afb 100644 --- a/core/main-service/src/main/java/ru/practicum/request/dto/ParticipationRequestDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/request/ParticipationRequestDto.java @@ -1,4 +1,4 @@ -package ru.practicum.request.dto; +package ru.practicum.dto.request; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; @@ -25,4 +25,4 @@ public class ParticipationRequestDto { @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS") private LocalDateTime created; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/request/dto/ParticipationRequestStatus.java b/core/core-common/src/main/java/ru/practicum/dto/request/ParticipationRequestStatus.java similarity index 70% rename from core/main-service/src/main/java/ru/practicum/request/dto/ParticipationRequestStatus.java rename to core/core-common/src/main/java/ru/practicum/dto/request/ParticipationRequestStatus.java index a1fcc71..e2f335e 100644 --- a/core/main-service/src/main/java/ru/practicum/request/dto/ParticipationRequestStatus.java +++ b/core/core-common/src/main/java/ru/practicum/dto/request/ParticipationRequestStatus.java @@ -1,7 +1,7 @@ -package ru.practicum.request.dto; +package ru.practicum.dto.request; public enum ParticipationRequestStatus { PENDING, CONFIRMED, CANCELED, REJECTED -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/user/dto/NewUserRequestDto.java b/core/core-common/src/main/java/ru/practicum/dto/user/NewUserRequestDto.java similarity index 96% rename from core/main-service/src/main/java/ru/practicum/user/dto/NewUserRequestDto.java rename to core/core-common/src/main/java/ru/practicum/dto/user/NewUserRequestDto.java index fb6bf52..c2fecdf 100644 --- a/core/main-service/src/main/java/ru/practicum/user/dto/NewUserRequestDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/user/NewUserRequestDto.java @@ -1,4 +1,4 @@ -package ru.practicum.user.dto; +package ru.practicum.dto.user; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; diff --git a/core/main-service/src/main/java/ru/practicum/user/dto/UserDto.java b/core/core-common/src/main/java/ru/practicum/dto/user/UserDto.java similarity index 61% rename from core/main-service/src/main/java/ru/practicum/user/dto/UserDto.java rename to core/core-common/src/main/java/ru/practicum/dto/user/UserDto.java index ffd285f..88b1dd5 100644 --- a/core/main-service/src/main/java/ru/practicum/user/dto/UserDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/user/UserDto.java @@ -1,4 +1,4 @@ -package ru.practicum.user.dto; +package ru.practicum.dto.user; import lombok.AllArgsConstructor; import lombok.Builder; @@ -17,4 +17,10 @@ public class UserDto { private String name; + public static UserDto makeDummy(Long id) { + UserDto dto = new UserDto(); + dto.setId(id); + return dto; + } + } diff --git a/core/main-service/src/main/java/ru/practicum/user/dto/UserShortDto.java b/core/core-common/src/main/java/ru/practicum/dto/user/UserShortDto.java similarity index 57% rename from core/main-service/src/main/java/ru/practicum/user/dto/UserShortDto.java rename to core/core-common/src/main/java/ru/practicum/dto/user/UserShortDto.java index fbb974a..33418cd 100644 --- a/core/main-service/src/main/java/ru/practicum/user/dto/UserShortDto.java +++ b/core/core-common/src/main/java/ru/practicum/dto/user/UserShortDto.java @@ -1,4 +1,4 @@ -package ru.practicum.user.dto; +package ru.practicum.dto.user; import lombok.AllArgsConstructor; import lombok.Builder; @@ -15,4 +15,10 @@ public class UserShortDto { private String name; -} + public static UserShortDto makeDummy(Long id) { + UserShortDto dto = new UserShortDto(); + dto.setId(id); + return dto; + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/exception/ApiError.java b/core/core-common/src/main/java/ru/practicum/exception/ApiError.java similarity index 99% rename from core/main-service/src/main/java/ru/practicum/exception/ApiError.java rename to core/core-common/src/main/java/ru/practicum/exception/ApiError.java index b765a1d..3d2937a 100644 --- a/core/main-service/src/main/java/ru/practicum/exception/ApiError.java +++ b/core/core-common/src/main/java/ru/practicum/exception/ApiError.java @@ -33,4 +33,4 @@ public class ApiError { @JsonDeserialize(using = LocalDateTimeDeserializer.class) private LocalDateTime timestamp; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/exception/BadRequestException.java b/core/core-common/src/main/java/ru/practicum/exception/BadRequestException.java similarity index 86% rename from core/main-service/src/main/java/ru/practicum/exception/BadRequestException.java rename to core/core-common/src/main/java/ru/practicum/exception/BadRequestException.java index ed9e2b1..ad10664 100644 --- a/core/main-service/src/main/java/ru/practicum/exception/BadRequestException.java +++ b/core/core-common/src/main/java/ru/practicum/exception/BadRequestException.java @@ -1,5 +1,8 @@ package ru.practicum.exception; +import lombok.Getter; + +@Getter public class BadRequestException extends RuntimeException { private final String reason; @@ -14,8 +17,4 @@ public BadRequestException(String message, String reason) { this.reason = reason; } - public String getReason() { - return reason; - } - } \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/exception/ConflictException.java b/core/core-common/src/main/java/ru/practicum/exception/ConflictException.java similarity index 86% rename from core/main-service/src/main/java/ru/practicum/exception/ConflictException.java rename to core/core-common/src/main/java/ru/practicum/exception/ConflictException.java index beb9dc0..2565e60 100644 --- a/core/main-service/src/main/java/ru/practicum/exception/ConflictException.java +++ b/core/core-common/src/main/java/ru/practicum/exception/ConflictException.java @@ -1,5 +1,8 @@ package ru.practicum.exception; +import lombok.Getter; + +@Getter public class ConflictException extends RuntimeException { private final String reason; @@ -14,8 +17,4 @@ public ConflictException(String message, String reason) { this.reason = reason; } - public String getReason() { - return reason; - } - } \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/exception/ForbiddenException.java b/core/core-common/src/main/java/ru/practicum/exception/ForbiddenException.java similarity index 87% rename from core/main-service/src/main/java/ru/practicum/exception/ForbiddenException.java rename to core/core-common/src/main/java/ru/practicum/exception/ForbiddenException.java index f7b6e0f..8d9723e 100644 --- a/core/main-service/src/main/java/ru/practicum/exception/ForbiddenException.java +++ b/core/core-common/src/main/java/ru/practicum/exception/ForbiddenException.java @@ -1,5 +1,8 @@ package ru.practicum.exception; +import lombok.Getter; + +@Getter public class ForbiddenException extends RuntimeException { private final String reason; @@ -14,8 +17,4 @@ public ForbiddenException(String message, String reason) { this.reason = reason; } - public String getReason() { - return reason; - } - } \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/exception/GlobalExceptionHandler.java b/core/core-common/src/main/java/ru/practicum/exception/GlobalExceptionHandler.java similarity index 92% rename from core/main-service/src/main/java/ru/practicum/exception/GlobalExceptionHandler.java rename to core/core-common/src/main/java/ru/practicum/exception/GlobalExceptionHandler.java index c7ab630..0422855 100644 --- a/core/main-service/src/main/java/ru/practicum/exception/GlobalExceptionHandler.java +++ b/core/core-common/src/main/java/ru/practicum/exception/GlobalExceptionHandler.java @@ -147,21 +147,18 @@ public ResponseEntity handleNotFoundException(NotFoundException e, Htt return new ResponseEntity<>(apiError, HttpStatus.NOT_FOUND); } - // OTHER UNKNOWN EXCEPTIONS --------------------------------------------------- - @ExceptionHandler( - RuntimeException.class // Internal Server Error + ServiceInteractionException.class // custom exception ) - public ResponseEntity handleRuntimeException(RuntimeException e, HttpServletRequest request) { - log.debug("INTERNAL SERVER ERROR: {}", e.getMessage()); + public ResponseEntity handleServiceInteractionException(ServiceInteractionException e, HttpServletRequest request) { + log.debug("SERVICE INTERACTION ERROR: {}", e.getMessage()); ApiError apiError = ApiError.builder() - .status(HttpStatus.INTERNAL_SERVER_ERROR) - .reason("Internal Server Error") + .status(HttpStatus.SERVICE_UNAVAILABLE) + .reason(e.getReason()) .message(e.getMessage()) .timestamp(LocalDateTime.now()) .build(); - return new ResponseEntity<>(apiError, HttpStatus.INTERNAL_SERVER_ERROR); + return new ResponseEntity<>(apiError, HttpStatus.SERVICE_UNAVAILABLE); } - -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/exception/NotFoundException.java b/core/core-common/src/main/java/ru/practicum/exception/NotFoundException.java similarity index 86% rename from core/main-service/src/main/java/ru/practicum/exception/NotFoundException.java rename to core/core-common/src/main/java/ru/practicum/exception/NotFoundException.java index 57fdec0..f8f2227 100644 --- a/core/main-service/src/main/java/ru/practicum/exception/NotFoundException.java +++ b/core/core-common/src/main/java/ru/practicum/exception/NotFoundException.java @@ -1,5 +1,8 @@ package ru.practicum.exception; +import lombok.Getter; + +@Getter public class NotFoundException extends RuntimeException { private final String reason; @@ -14,8 +17,4 @@ public NotFoundException(String message, String reason) { this.reason = reason; } - public String getReason() { - return reason; - } - } \ No newline at end of file diff --git a/core/core-common/src/main/java/ru/practicum/exception/ServiceInteractionException.java b/core/core-common/src/main/java/ru/practicum/exception/ServiceInteractionException.java new file mode 100644 index 0000000..cb921a1 --- /dev/null +++ b/core/core-common/src/main/java/ru/practicum/exception/ServiceInteractionException.java @@ -0,0 +1,20 @@ +package ru.practicum.exception; + +import lombok.Getter; + +@Getter +public class ServiceInteractionException extends RuntimeException { + + private final String reason; + + public ServiceInteractionException(String message) { + super(message); + this.reason = "Unable to get info from microservice"; + } + + public ServiceInteractionException(String message, String reason) { + super(message); + this.reason = reason; + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/serialize/LocalDateTimeDeserializer.java b/core/core-common/src/main/java/ru/practicum/serialize/LocalDateTimeDeserializer.java similarity index 99% rename from core/main-service/src/main/java/ru/practicum/serialize/LocalDateTimeDeserializer.java rename to core/core-common/src/main/java/ru/practicum/serialize/LocalDateTimeDeserializer.java index 099319d..eb9c77f 100644 --- a/core/main-service/src/main/java/ru/practicum/serialize/LocalDateTimeDeserializer.java +++ b/core/core-common/src/main/java/ru/practicum/serialize/LocalDateTimeDeserializer.java @@ -31,4 +31,4 @@ public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext d return LocalDateTime.parse(date, formatter); } -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/serialize/LocalDateTimeSerializer.java b/core/core-common/src/main/java/ru/practicum/serialize/LocalDateTimeSerializer.java similarity index 99% rename from core/main-service/src/main/java/ru/practicum/serialize/LocalDateTimeSerializer.java rename to core/core-common/src/main/java/ru/practicum/serialize/LocalDateTimeSerializer.java index fd7b307..6275664 100644 --- a/core/main-service/src/main/java/ru/practicum/serialize/LocalDateTimeSerializer.java +++ b/core/core-common/src/main/java/ru/practicum/serialize/LocalDateTimeSerializer.java @@ -29,4 +29,4 @@ public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, jsonGenerator.writeString(localDateTime.format(formatter)); } -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/validation/AtLeastOneNotNull.java b/core/core-common/src/main/java/ru/practicum/validation/AtLeastOneNotNull.java similarity index 100% rename from core/main-service/src/main/java/ru/practicum/validation/AtLeastOneNotNull.java rename to core/core-common/src/main/java/ru/practicum/validation/AtLeastOneNotNull.java diff --git a/core/main-service/src/main/java/ru/practicum/validation/AtLeastOneNotNullValidator.java b/core/core-common/src/main/java/ru/practicum/validation/AtLeastOneNotNullValidator.java similarity index 100% rename from core/main-service/src/main/java/ru/practicum/validation/AtLeastOneNotNullValidator.java rename to core/core-common/src/main/java/ru/practicum/validation/AtLeastOneNotNullValidator.java diff --git a/core/main-service/src/main/java/ru/practicum/validation/CategoryCreateValidator.java b/core/core-common/src/main/java/ru/practicum/validation/CategoryCreateValidator.java similarity index 94% rename from core/main-service/src/main/java/ru/practicum/validation/CategoryCreateValidator.java rename to core/core-common/src/main/java/ru/practicum/validation/CategoryCreateValidator.java index a68e80a..80c87b3 100644 --- a/core/main-service/src/main/java/ru/practicum/validation/CategoryCreateValidator.java +++ b/core/core-common/src/main/java/ru/practicum/validation/CategoryCreateValidator.java @@ -1,6 +1,6 @@ package ru.practicum.validation; -import ru.practicum.category.dto.CategoryDto; +import ru.practicum.dto.category.CategoryDto; import javax.validation.Validator; import java.util.Set; diff --git a/core/main-service/src/main/java/ru/practicum/validation/CategoryUpdateValidator.java b/core/core-common/src/main/java/ru/practicum/validation/CategoryUpdateValidator.java similarity index 94% rename from core/main-service/src/main/java/ru/practicum/validation/CategoryUpdateValidator.java rename to core/core-common/src/main/java/ru/practicum/validation/CategoryUpdateValidator.java index aa6fdc5..654879a 100644 --- a/core/main-service/src/main/java/ru/practicum/validation/CategoryUpdateValidator.java +++ b/core/core-common/src/main/java/ru/practicum/validation/CategoryUpdateValidator.java @@ -1,6 +1,6 @@ package ru.practicum.validation; -import ru.practicum.category.dto.CategoryDto; +import ru.practicum.dto.category.CategoryDto; import javax.validation.Validator; import java.util.Set; @@ -23,4 +23,4 @@ public void validate(CategoryDto categoryDto) { throw new IllegalArgumentException("Validation errors: " + errors); } } -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/validation/CreateOrUpdateValidator.java b/core/core-common/src/main/java/ru/practicum/validation/CreateOrUpdateValidator.java similarity index 100% rename from core/main-service/src/main/java/ru/practicum/validation/CreateOrUpdateValidator.java rename to core/core-common/src/main/java/ru/practicum/validation/CreateOrUpdateValidator.java diff --git a/core/main-service/src/main/java/ru/practicum/validation/NewCompilationCreateValidator.java b/core/core-common/src/main/java/ru/practicum/validation/NewCompilationCreateValidator.java similarity index 94% rename from core/main-service/src/main/java/ru/practicum/validation/NewCompilationCreateValidator.java rename to core/core-common/src/main/java/ru/practicum/validation/NewCompilationCreateValidator.java index 6bf46df..69996b7 100644 --- a/core/main-service/src/main/java/ru/practicum/validation/NewCompilationCreateValidator.java +++ b/core/core-common/src/main/java/ru/practicum/validation/NewCompilationCreateValidator.java @@ -1,6 +1,6 @@ package ru.practicum.validation; -import ru.practicum.compilation.dto.NewCompilationDto; +import ru.practicum.dto.compilation.NewCompilationDto; import javax.validation.Validator; import java.util.Set; diff --git a/core/main-service/src/main/java/ru/practicum/validation/NewCompilationUpdateValidator.java b/core/core-common/src/main/java/ru/practicum/validation/NewCompilationUpdateValidator.java similarity index 93% rename from core/main-service/src/main/java/ru/practicum/validation/NewCompilationUpdateValidator.java rename to core/core-common/src/main/java/ru/practicum/validation/NewCompilationUpdateValidator.java index 995a4b8..27494a5 100644 --- a/core/main-service/src/main/java/ru/practicum/validation/NewCompilationUpdateValidator.java +++ b/core/core-common/src/main/java/ru/practicum/validation/NewCompilationUpdateValidator.java @@ -1,6 +1,6 @@ package ru.practicum.validation; -import ru.practicum.compilation.dto.NewCompilationDto; +import ru.practicum.dto.compilation.NewCompilationDto; import javax.validation.Validator; import java.util.Set; @@ -23,4 +23,4 @@ public void validate(NewCompilationDto newCompilationDto) { throw new IllegalArgumentException("Validation errors: " + errors); } } -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/validation/NotBlankButNullAllowed.java b/core/core-common/src/main/java/ru/practicum/validation/NotBlankButNullAllowed.java similarity index 99% rename from core/main-service/src/main/java/ru/practicum/validation/NotBlankButNullAllowed.java rename to core/core-common/src/main/java/ru/practicum/validation/NotBlankButNullAllowed.java index 7735d40..82168f8 100644 --- a/core/main-service/src/main/java/ru/practicum/validation/NotBlankButNullAllowed.java +++ b/core/core-common/src/main/java/ru/practicum/validation/NotBlankButNullAllowed.java @@ -19,4 +19,4 @@ Class[] payload() default {}; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/validation/NotBlankButNullAllowedValidator.java b/core/core-common/src/main/java/ru/practicum/validation/NotBlankButNullAllowedValidator.java similarity index 100% rename from core/main-service/src/main/java/ru/practicum/validation/NotBlankButNullAllowedValidator.java rename to core/core-common/src/main/java/ru/practicum/validation/NotBlankButNullAllowedValidator.java diff --git a/core/main-service/Dockerfile b/core/event-service/Dockerfile similarity index 100% rename from core/main-service/Dockerfile rename to core/event-service/Dockerfile diff --git a/core/main-service/pom.xml b/core/event-service/pom.xml similarity index 86% rename from core/main-service/pom.xml rename to core/event-service/pom.xml index dbb723f..90a8780 100644 --- a/core/main-service/pom.xml +++ b/core/event-service/pom.xml @@ -10,7 +10,7 @@ 0.0.1-SNAPSHOT - main-service + event-service @@ -21,6 +21,12 @@ stats-client + + ru.practicum + core-common + 0.0.1-SNAPSHOT + + @@ -92,6 +98,18 @@ spring-retry + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-resilience4j + + diff --git a/core/main-service/src/main/java/ru/practicum/Main.java b/core/event-service/src/main/java/ru/practicum/EventServiceApplication.java similarity index 61% rename from core/main-service/src/main/java/ru/practicum/Main.java rename to core/event-service/src/main/java/ru/practicum/EventServiceApplication.java index 52735de..901eb02 100644 --- a/core/main-service/src/main/java/ru/practicum/Main.java +++ b/core/event-service/src/main/java/ru/practicum/EventServiceApplication.java @@ -1,14 +1,17 @@ package ru.practicum; - import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.scheduling.annotation.EnableScheduling; @EnableScheduling +@EnableFeignClients @SpringBootApplication -public class Main { +public class EventServiceApplication { + public static void main(String[] args) { - SpringApplication.run(Main.class, args); + SpringApplication.run(EventServiceApplication.class, args); } + } \ No newline at end of file diff --git a/core/event-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java b/core/event-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java new file mode 100644 index 0000000..9508b24 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java @@ -0,0 +1,32 @@ +package ru.practicum.category.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.api.category.CategoryAdminApi; +import ru.practicum.category.service.CategoryAdminService; +import ru.practicum.dto.category.CategoryDto; + +@RestController +@RequiredArgsConstructor +@Validated +public class CategoryAdminController implements CategoryAdminApi { + + private final CategoryAdminService categoryAdminService; + + @Override + public CategoryDto addCategory(CategoryDto requestCategory) { + return categoryAdminService.createCategory(requestCategory); + } + + @Override + public String deleteCategories(Long catId) { + return categoryAdminService.deleteCategory(catId); + } + + @Override + public CategoryDto updateCategory(Long catId, CategoryDto categoryDto) { + return categoryAdminService.updateCategory(catId, categoryDto); + } + +} diff --git a/core/event-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java b/core/event-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java new file mode 100644 index 0000000..260b707 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java @@ -0,0 +1,29 @@ +package ru.practicum.category.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.api.category.CategoryPublicApi; +import ru.practicum.category.service.CategoryPublicService; +import ru.practicum.dto.category.CategoryDto; + +import java.util.Collection; + +@RestController +@RequiredArgsConstructor +@Validated +public class CategoryPublicController implements CategoryPublicApi { + + private final CategoryPublicService categoryPublicService; + + @Override + public Collection readAllCategories(int from, int size) { + return categoryPublicService.readAllCategories(from, size); + } + + @Override + public CategoryDto readCategoryById(Long catId) { + return categoryPublicService.readCategoryById(catId); + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/category/model/Category.java b/core/event-service/src/main/java/ru/practicum/category/dal/Category.java similarity index 57% rename from core/main-service/src/main/java/ru/practicum/category/model/Category.java rename to core/event-service/src/main/java/ru/practicum/category/dal/Category.java index 6e4439c..b10e5f5 100644 --- a/core/main-service/src/main/java/ru/practicum/category/model/Category.java +++ b/core/event-service/src/main/java/ru/practicum/category/dal/Category.java @@ -1,4 +1,4 @@ -package ru.practicum.category.model; +package ru.practicum.category.dal; import jakarta.persistence.*; import jakarta.validation.constraints.NotEmpty; @@ -7,28 +7,24 @@ @Getter @Setter -@Builder +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString @Entity -@AllArgsConstructor +@Builder @NoArgsConstructor -@Table(name = "categories") +@AllArgsConstructor +@Table(name = "categories", indexes = {@Index(name = "idx_categories_cat_name", columnList = "cat_name")}) public class Category { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @EqualsAndHashCode.Include @Column(name = "id") private Long id; - @Column(name = "cat_name") @Size(min = 1, max = 50) @NotEmpty + @Column(name = "cat_name", length = 50, nullable = false, unique = true) private String name; - @Override - public String toString() { - return "Categories{" + - "id=" + id + - ", name='" + name + '\'' + - '}'; - } } \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/category/repository/CategoryRepository.java b/core/event-service/src/main/java/ru/practicum/category/dal/CategoryRepository.java similarity index 67% rename from core/main-service/src/main/java/ru/practicum/category/repository/CategoryRepository.java rename to core/event-service/src/main/java/ru/practicum/category/dal/CategoryRepository.java index 539b9c8..f2376dc 100644 --- a/core/main-service/src/main/java/ru/practicum/category/repository/CategoryRepository.java +++ b/core/event-service/src/main/java/ru/practicum/category/dal/CategoryRepository.java @@ -1,7 +1,6 @@ -package ru.practicum.category.repository; +package ru.practicum.category.dal; import org.springframework.data.jpa.repository.JpaRepository; -import ru.practicum.category.model.Category; public interface CategoryRepository extends JpaRepository { diff --git a/core/main-service/src/main/java/ru/practicum/category/service/CategoryAdminService.java b/core/event-service/src/main/java/ru/practicum/category/service/CategoryAdminService.java similarity index 71% rename from core/main-service/src/main/java/ru/practicum/category/service/CategoryAdminService.java rename to core/event-service/src/main/java/ru/practicum/category/service/CategoryAdminService.java index e5b1c18..c8979b2 100644 --- a/core/main-service/src/main/java/ru/practicum/category/service/CategoryAdminService.java +++ b/core/event-service/src/main/java/ru/practicum/category/service/CategoryAdminService.java @@ -1,13 +1,13 @@ package ru.practicum.category.service; -import ru.practicum.category.dto.CategoryDto; +import ru.practicum.dto.category.CategoryDto; public interface CategoryAdminService { CategoryDto createCategory(CategoryDto requestCategory); - void deleteCategory(Long catId); + String deleteCategory(Long catId); CategoryDto updateCategory(Long catId, CategoryDto categoryDto); -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/category/service/CategoryAdminServiceImpl.java b/core/event-service/src/main/java/ru/practicum/category/service/CategoryAdminServiceImpl.java similarity index 88% rename from core/main-service/src/main/java/ru/practicum/category/service/CategoryAdminServiceImpl.java rename to core/event-service/src/main/java/ru/practicum/category/service/CategoryAdminServiceImpl.java index 4a9b694..b17c2f0 100644 --- a/core/main-service/src/main/java/ru/practicum/category/service/CategoryAdminServiceImpl.java +++ b/core/event-service/src/main/java/ru/practicum/category/service/CategoryAdminServiceImpl.java @@ -4,25 +4,23 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.practicum.category.dto.CategoryDto; -import ru.practicum.category.mapper.CategoryMapper; -import ru.practicum.category.model.Category; -import ru.practicum.category.repository.CategoryRepository; -import ru.practicum.event.repository.EventRepository; +import ru.practicum.category.dal.Category; +import ru.practicum.category.dal.CategoryRepository; +import ru.practicum.dto.category.CategoryDto; +import ru.practicum.event.dal.EventRepository; import ru.practicum.exception.ConflictException; import ru.practicum.exception.NotFoundException; @Service @RequiredArgsConstructor @Slf4j -@Transactional(readOnly = false) public class CategoryAdminServiceImpl implements CategoryAdminService { private final CategoryRepository categoryRepository; - private final EventRepository eventRepository; @Override + @Transactional public CategoryDto createCategory(CategoryDto requestCategory) { log.info("createCategories - invoked"); if (categoryRepository.existsByName(requestCategory.getName())) { @@ -35,7 +33,8 @@ public CategoryDto createCategory(CategoryDto requestCategory) { } @Override - public void deleteCategory(Long catId) { + @Transactional + public String deleteCategory(Long catId) { log.info("deleteCategories - invoked"); if (!categoryRepository.existsById(catId)) { log.error("Category with this id does not exist {}", catId); @@ -46,9 +45,11 @@ public void deleteCategory(Long catId) { } log.info("Result: category with id - {} - deleted", catId); categoryRepository.deleteById(catId); + return "Category deleted: " + catId; } @Override + @Transactional public CategoryDto updateCategory(Long catId, CategoryDto categoryDto) { log.info("updateCategories - invoked"); Category category = categoryRepository.findById(catId).orElseThrow(() @@ -62,4 +63,5 @@ public CategoryDto updateCategory(Long catId, CategoryDto categoryDto) { log.info("Result: category - {} updated", category.getName()); return CategoryMapper.toCategoryDto(category); } + } \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/category/mapper/CategoryMapper.java b/core/event-service/src/main/java/ru/practicum/category/service/CategoryMapper.java similarity index 83% rename from core/main-service/src/main/java/ru/practicum/category/mapper/CategoryMapper.java rename to core/event-service/src/main/java/ru/practicum/category/service/CategoryMapper.java index e675a57..162c84f 100644 --- a/core/main-service/src/main/java/ru/practicum/category/mapper/CategoryMapper.java +++ b/core/event-service/src/main/java/ru/practicum/category/service/CategoryMapper.java @@ -1,7 +1,7 @@ -package ru.practicum.category.mapper; +package ru.practicum.category.service; -import ru.practicum.category.dto.CategoryDto; -import ru.practicum.category.model.Category; +import ru.practicum.category.dal.Category; +import ru.practicum.dto.category.CategoryDto; import java.util.List; import java.util.stream.Collectors; @@ -25,4 +25,4 @@ public static List toListCategoriesDto(List list) { return list.stream().map(CategoryMapper::toCategoryDto).collect(Collectors.toList()); } -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/category/service/CategoryPublicService.java b/core/event-service/src/main/java/ru/practicum/category/service/CategoryPublicService.java similarity index 82% rename from core/main-service/src/main/java/ru/practicum/category/service/CategoryPublicService.java rename to core/event-service/src/main/java/ru/practicum/category/service/CategoryPublicService.java index de4cdd2..798d12b 100644 --- a/core/main-service/src/main/java/ru/practicum/category/service/CategoryPublicService.java +++ b/core/event-service/src/main/java/ru/practicum/category/service/CategoryPublicService.java @@ -1,6 +1,6 @@ package ru.practicum.category.service; -import ru.practicum.category.dto.CategoryDto; +import ru.practicum.dto.category.CategoryDto; import java.util.List; @@ -10,4 +10,4 @@ public interface CategoryPublicService { CategoryDto readCategoryById(Long catId); -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/category/service/CategoryPublicServiceImpl.java b/core/event-service/src/main/java/ru/practicum/category/service/CategoryPublicServiceImpl.java similarity index 67% rename from core/main-service/src/main/java/ru/practicum/category/service/CategoryPublicServiceImpl.java rename to core/event-service/src/main/java/ru/practicum/category/service/CategoryPublicServiceImpl.java index b5d6fe6..195ca51 100644 --- a/core/main-service/src/main/java/ru/practicum/category/service/CategoryPublicServiceImpl.java +++ b/core/event-service/src/main/java/ru/practicum/category/service/CategoryPublicServiceImpl.java @@ -3,38 +3,39 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.practicum.category.dto.CategoryDto; -import ru.practicum.category.mapper.CategoryMapper; -import ru.practicum.category.model.Category; -import ru.practicum.category.repository.CategoryRepository; +import ru.practicum.category.dal.Category; +import ru.practicum.category.dal.CategoryRepository; +import ru.practicum.dto.category.CategoryDto; import ru.practicum.exception.NotFoundException; import java.util.List; -import static ru.practicum.util.Util.createPageRequestAsc; - @Service @RequiredArgsConstructor @Slf4j public class CategoryPublicServiceImpl implements CategoryPublicService { - private final CategoryRepository repository; + private final CategoryRepository categoryRepository; @Override + @Transactional(readOnly = true) public List readAllCategories(Integer from, Integer size) { log.info("readAllCategories - invoked"); - Page page = repository.findAll(createPageRequestAsc(from, size)); + Page page = categoryRepository.findAll(PageRequest.of(from, size, Sort.Direction.ASC, "id")); List cat = page.getContent(); log.info("Result: categories size = {}", cat.size()); return CategoryMapper.toListCategoriesDto(cat); } @Override + @Transactional(readOnly = true) public CategoryDto readCategoryById(Long catId) { log.info("readCategoryById - invoked"); - Category category = repository.findById(catId).orElseThrow(() -> { + Category category = categoryRepository.findById(catId).orElseThrow(() -> { log.error("Category with id = {} not exist", catId); return new NotFoundException("Category not found"); }); diff --git a/core/event-service/src/main/java/ru/practicum/client/RequestClient.java b/core/event-service/src/main/java/ru/practicum/client/RequestClient.java new file mode 100644 index 0000000..daeffd2 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/client/RequestClient.java @@ -0,0 +1,8 @@ +package ru.practicum.client; + +import org.springframework.cloud.openfeign.FeignClient; +import ru.practicum.api.request.RequestApi; + +@FeignClient(name = "request-service") +public interface RequestClient extends RequestApi { +} diff --git a/core/event-service/src/main/java/ru/practicum/client/RequestClientHelper.java b/core/event-service/src/main/java/ru/practicum/client/RequestClientHelper.java new file mode 100644 index 0000000..b001849 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/client/RequestClientHelper.java @@ -0,0 +1,13 @@ +package ru.practicum.client; + +import org.springframework.stereotype.Component; +import ru.practicum.api.request.RequestApi; + +@Component +public class RequestClientHelper extends RequestClientAbstractHelper { + + public RequestClientHelper(RequestApi requestApiClient) { + super(requestApiClient); + } + +} \ No newline at end of file diff --git a/core/event-service/src/main/java/ru/practicum/client/UserClient.java b/core/event-service/src/main/java/ru/practicum/client/UserClient.java new file mode 100644 index 0000000..50ca2b2 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/client/UserClient.java @@ -0,0 +1,8 @@ +package ru.practicum.client; + +import org.springframework.cloud.openfeign.FeignClient; +import ru.practicum.api.user.UserApi; + +@FeignClient(name = "user-service") +public interface UserClient extends UserApi { +} diff --git a/core/event-service/src/main/java/ru/practicum/client/UserClientHelper.java b/core/event-service/src/main/java/ru/practicum/client/UserClientHelper.java new file mode 100644 index 0000000..41cb654 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/client/UserClientHelper.java @@ -0,0 +1,13 @@ +package ru.practicum.client; + +import org.springframework.stereotype.Component; +import ru.practicum.api.user.UserApi; + +@Component +public class UserClientHelper extends UserClientAbstractHelper { + + public UserClientHelper(UserApi userApiClient) { + super(userApiClient); + } + +} \ No newline at end of file diff --git a/core/event-service/src/main/java/ru/practicum/compilation/controller/CompilationAdminController.java b/core/event-service/src/main/java/ru/practicum/compilation/controller/CompilationAdminController.java new file mode 100644 index 0000000..9360cfb --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/compilation/controller/CompilationAdminController.java @@ -0,0 +1,34 @@ +package ru.practicum.compilation.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.api.compilation.CompilationAdminApi; +import ru.practicum.compilation.service.CompilationAdminService; +import ru.practicum.dto.compilation.CompilationDto; +import ru.practicum.dto.compilation.NewCompilationDto; +import ru.practicum.dto.compilation.UpdateCompilationDto; + +@RestController +@Validated +@RequiredArgsConstructor +public class CompilationAdminController implements CompilationAdminApi { + + private final CompilationAdminService compilationAdminService; + + @Override + public CompilationDto postCompilations(NewCompilationDto newCompilationDto) { + return compilationAdminService.createCompilation(newCompilationDto); + } + + @Override + public String deleteCompilation(Long compId) { + return compilationAdminService.deleteCompilation(compId); + } + + @Override + public CompilationDto patchCompilation(Long compId, UpdateCompilationDto updateCompilationDto) { + return compilationAdminService.updateCompilation(compId, updateCompilationDto); + } + +} \ No newline at end of file diff --git a/core/event-service/src/main/java/ru/practicum/compilation/controller/CompilationPublicController.java b/core/event-service/src/main/java/ru/practicum/compilation/controller/CompilationPublicController.java new file mode 100644 index 0000000..796aec5 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/compilation/controller/CompilationPublicController.java @@ -0,0 +1,29 @@ +package ru.practicum.compilation.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.api.compilation.CompilationPublicApi; +import ru.practicum.compilation.service.CompilationPublicService; +import ru.practicum.dto.compilation.CompilationDto; + +import java.util.Collection; + +@RestController +@Validated +@RequiredArgsConstructor +public class CompilationPublicController implements CompilationPublicApi { + + private final CompilationPublicService compilationPublicService; + + @Override + public Collection getCompilation(Boolean pinned, int from, int size) { + return compilationPublicService.readAllCompilations(pinned, from, size); + } + + @Override + public CompilationDto getCompilationById(Long compId) { + return compilationPublicService.readCompilationById(compId); + } + +} \ No newline at end of file diff --git a/core/event-service/src/main/java/ru/practicum/compilation/dal/Compilation.java b/core/event-service/src/main/java/ru/practicum/compilation/dal/Compilation.java new file mode 100644 index 0000000..e5fa0c6 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/compilation/dal/Compilation.java @@ -0,0 +1,45 @@ +package ru.practicum.compilation.dal; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; +import lombok.*; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; +import ru.practicum.event.dal.Event; + +import java.util.Set; + +@Getter +@Setter +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString +@Entity +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "compilations") +public class Compilation { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @EqualsAndHashCode.Include + @Column(name = "id") + private Long id; + + @Column(name = "pinned", nullable = false) + private Boolean pinned; + + @Size(min = 1, max = 50) + @NotEmpty + @Column(name = "title", length = 50, nullable = false) + private String title; + + @ManyToMany(cascade = CascadeType.ALL) + @JoinTable(name = "compilations_events", + joinColumns = @JoinColumn(name = "compilations_id", nullable = false), + inverseJoinColumns = @JoinColumn(name = "events_id", nullable = false)) + @OnDelete(action = OnDeleteAction.CASCADE) + private Set events; + +} \ No newline at end of file diff --git a/core/event-service/src/main/java/ru/practicum/compilation/dal/CompilationRepository.java b/core/event-service/src/main/java/ru/practicum/compilation/dal/CompilationRepository.java new file mode 100644 index 0000000..6ac7a0f --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/compilation/dal/CompilationRepository.java @@ -0,0 +1,11 @@ +package ru.practicum.compilation.dal; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CompilationRepository extends JpaRepository { + + Page findAllByPinned(Boolean pinned, Pageable pageable); + +} diff --git a/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminService.java b/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationAdminService.java similarity index 54% rename from core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminService.java rename to core/event-service/src/main/java/ru/practicum/compilation/service/CompilationAdminService.java index ad82c21..718d71a 100644 --- a/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminService.java +++ b/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationAdminService.java @@ -1,15 +1,15 @@ package ru.practicum.compilation.service; -import ru.practicum.compilation.dto.CompilationDto; -import ru.practicum.compilation.dto.NewCompilationDto; -import ru.practicum.compilation.dto.UpdateCompilationDto; +import ru.practicum.dto.compilation.CompilationDto; +import ru.practicum.dto.compilation.NewCompilationDto; +import ru.practicum.dto.compilation.UpdateCompilationDto; public interface CompilationAdminService { CompilationDto createCompilation(NewCompilationDto request); - void deleteCompilation(Long compId); + String deleteCompilation(Long compId); CompilationDto updateCompilation(Long compId, UpdateCompilationDto updateCompilationDto); -} \ No newline at end of file +} diff --git a/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationAdminServiceImpl.java b/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationAdminServiceImpl.java new file mode 100644 index 0000000..7dcc985 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationAdminServiceImpl.java @@ -0,0 +1,102 @@ +package ru.practicum.compilation.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; +import ru.practicum.client.UserClientHelper; +import ru.practicum.compilation.dal.Compilation; +import ru.practicum.compilation.dal.CompilationRepository; +import ru.practicum.dto.compilation.CompilationDto; +import ru.practicum.dto.compilation.NewCompilationDto; +import ru.practicum.dto.compilation.UpdateCompilationDto; +import ru.practicum.dto.user.UserShortDto; +import ru.practicum.event.dal.Event; +import ru.practicum.event.dal.EventRepository; +import ru.practicum.exception.NotFoundException; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CompilationAdminServiceImpl implements CompilationAdminService { + + private final TransactionTemplate transactionTemplate; + private final CompilationRepository compilationRepository; + private final EventRepository eventRepository; + + private final UserClientHelper userClientHelper; + + @Override + public CompilationDto createCompilation(NewCompilationDto newCompilationDto) { + Set events = new HashSet<>(); + Map userMap = new HashMap<>(); + + if (newCompilationDto.getPinned() == null) newCompilationDto.setPinned(false); + + if (newCompilationDto.getEvents() != null && !newCompilationDto.getEvents().isEmpty()) { + events = transactionTemplate.execute(status -> { + return new HashSet<>(eventRepository.findAllById(newCompilationDto.getEvents())); + }); + Set userIds = events.stream().map(Event::getInitiatorId).collect(Collectors.toSet()); + userMap = userClientHelper.retrieveUserShortDtoMapByUserIdList(userIds); + } + + Set eventsFinal = events; + Map userMapFinal = userMap; + + return transactionTemplate.execute(status -> { + Compilation compilation = Compilation.builder() + .pinned(newCompilationDto.getPinned()) + .title(newCompilationDto.getTitle()) + .events(eventsFinal) + .build(); + compilationRepository.save(compilation); + return CompilationMapper.toCompilationDto(compilation, userMapFinal); + }); + } + + @Override + @Transactional + public String deleteCompilation(Long compId) { + if (!compilationRepository.existsById(compId)) throw new NotFoundException("Not found Compilation " + compId); + compilationRepository.deleteById(compId); + return "Compilation deleted: " + compId; + } + + @Override + public CompilationDto updateCompilation(Long compId, UpdateCompilationDto updateCompilationDto) { + Set userIds = transactionTemplate.execute(status -> { + Compilation compilation = compilationRepository.findById(compId) + .orElseThrow(() -> new NotFoundException("Not found Compilation " + compId)); + return compilation.getEvents().stream().map(Event::getInitiatorId).collect(Collectors.toSet()); + }); + + Map userMap = userClientHelper.retrieveUserShortDtoMapByUserIdList(userIds); + + return transactionTemplate.execute(status -> { + Compilation compilation = compilationRepository.findById(compId) + .orElseThrow(() -> new NotFoundException("Not found Compilation " + compId)); + + if (updateCompilationDto.getTitle() != null) { + compilation.setTitle(updateCompilationDto.getTitle()); + } + if (updateCompilationDto.getPinned() != null) { + compilation.setPinned(updateCompilationDto.getPinned()); + } + if (updateCompilationDto.getEvents() != null && !updateCompilationDto.getEvents().isEmpty()) { + Set events = new HashSet<>(eventRepository.findAllById(updateCompilationDto.getEvents())); + compilation.setEvents(events); + } + compilationRepository.save(compilation); + return CompilationMapper.toCompilationDto(compilation, userMap); + }); + } + +} \ No newline at end of file diff --git a/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationMapper.java b/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationMapper.java new file mode 100644 index 0000000..8c961d6 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationMapper.java @@ -0,0 +1,27 @@ +package ru.practicum.compilation.service; + +import ru.practicum.compilation.dal.Compilation; +import ru.practicum.dto.compilation.CompilationDto; +import ru.practicum.dto.event.EventShortDto; +import ru.practicum.dto.user.UserShortDto; +import ru.practicum.event.service.EventMapper; + +import java.util.List; +import java.util.Map; + +public class CompilationMapper { + + public static CompilationDto toCompilationDto(Compilation compilation, Map userMap) { + List eventShortDtoList = compilation.getEvents().stream() + .map(e -> EventMapper.toEventShortDto(e, userMap.get(e.getInitiatorId()), 0L, 0L)) + .toList(); + + return CompilationDto.builder() + .id(compilation.getId()) + .pinned(compilation.getPinned()) + .title(compilation.getTitle()) + .events(eventShortDtoList) + .build(); + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicService.java b/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationPublicService.java similarity index 82% rename from core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicService.java rename to core/event-service/src/main/java/ru/practicum/compilation/service/CompilationPublicService.java index ab3283e..0977bb3 100644 --- a/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicService.java +++ b/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationPublicService.java @@ -1,6 +1,6 @@ package ru.practicum.compilation.service; -import ru.practicum.compilation.dto.CompilationDto; +import ru.practicum.dto.compilation.CompilationDto; import java.util.List; @@ -10,4 +10,4 @@ public interface CompilationPublicService { List readAllCompilations(Boolean pinned, int from, int size); -} \ No newline at end of file +} diff --git a/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationPublicServiceImpl.java b/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationPublicServiceImpl.java new file mode 100644 index 0000000..527e184 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/compilation/service/CompilationPublicServiceImpl.java @@ -0,0 +1,73 @@ +package ru.practicum.compilation.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; +import ru.practicum.client.UserClientHelper; +import ru.practicum.compilation.dal.Compilation; +import ru.practicum.compilation.dal.CompilationRepository; +import ru.practicum.dto.compilation.CompilationDto; +import ru.practicum.dto.user.UserShortDto; +import ru.practicum.event.dal.Event; +import ru.practicum.exception.NotFoundException; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CompilationPublicServiceImpl implements CompilationPublicService { + + private final TransactionTemplate transactionTemplate; + private final CompilationRepository compilationRepository; + + private final UserClientHelper userClientHelper; + + @Override + public CompilationDto readCompilationById(Long compId) { + Compilation compilation = transactionTemplate.execute(status -> { + return compilationRepository.findById(compId) + .orElseThrow(() -> new NotFoundException("Compilation not found")); + }); + + Map userMap = new HashMap<>(); + if (compilation.getEvents() != null && !compilation.getEvents().isEmpty()) { + Set userIds = compilation.getEvents().stream().map(Event::getInitiatorId).collect(Collectors.toSet()); + userMap = userClientHelper.retrieveUserShortDtoMapByUserIdList(userIds); + } + + return CompilationMapper.toCompilationDto(compilation, userMap); + } + + @Override + public List readAllCompilations(Boolean pinned, int from, int size) { + List compilations = transactionTemplate.execute(status -> { + Pageable pageable = PageRequest.of(from / size, size, Sort.Direction.ASC, "id"); + if (pinned == null) { + return compilationRepository.findAll(pageable).getContent(); + } else { + return compilationRepository.findAllByPinned(pinned, pageable).getContent(); + } + }); + if (compilations == null || compilations.isEmpty()) return List.of(); + + Set userIds = compilations.stream() + .flatMap(c -> c.getEvents().stream()) + .map(Event::getInitiatorId) + .collect(Collectors.toSet()); + Map userMap = userClientHelper.retrieveUserShortDtoMapByUserIdList(userIds); + + return compilations.stream() + .map(c -> CompilationMapper.toCompilationDto(c, userMap)) + .toList(); + } + +} \ No newline at end of file diff --git a/core/event-service/src/main/java/ru/practicum/event/controller/EventAdminController.java b/core/event-service/src/main/java/ru/practicum/event/controller/EventAdminController.java new file mode 100644 index 0000000..2cba963 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/event/controller/EventAdminController.java @@ -0,0 +1,46 @@ +package ru.practicum.event.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.api.event.EventAdminApi; +import ru.practicum.dto.event.EventAdminParams; +import ru.practicum.dto.event.EventFullDto; +import ru.practicum.dto.event.State; +import ru.practicum.dto.event.UpdateEventDto; +import ru.practicum.event.service.EventAdminService; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +@RestController +@RequiredArgsConstructor +@Validated +public class EventAdminController implements EventAdminApi { + + private final EventAdminService eventAdminService; + + // Поиск событий + @Override + public Collection getAllEventsByParams(List users, List states, List categories, + LocalDateTime rangeStart, LocalDateTime rangeEnd, Integer from, Integer size) { + EventAdminParams params = EventAdminParams.builder() + .users(users) + .states(states) + .categories(categories) + .rangeStart(rangeStart) + .rangeEnd(rangeEnd) + .from(from) + .size(size) + .build(); + return eventAdminService.getAllEventsByParams(params); + } + + // Редактирование данных события и его статуса (отклонение/публикация). + @Override + public EventFullDto updateEventByAdmin(Long eventId, UpdateEventDto updateEventDto) { + return eventAdminService.updateEventByAdmin(eventId, updateEventDto); + } + +} \ No newline at end of file diff --git a/core/event-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java b/core/event-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java new file mode 100644 index 0000000..c933d10 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java @@ -0,0 +1,46 @@ +package ru.practicum.event.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.api.event.EventPrivateApi; +import ru.practicum.dto.event.EventFullDto; +import ru.practicum.dto.event.EventShortDto; +import ru.practicum.dto.event.NewEventDto; +import ru.practicum.dto.event.UpdateEventDto; +import ru.practicum.event.service.EventPrivateService; + +import java.util.Collection; + +@RestController +@RequiredArgsConstructor +@Validated +public class EventPrivateController implements EventPrivateApi { + + private final EventPrivateService eventPrivateService; + + // Добавление нового события + @Override + public EventFullDto addNewEventByUser(Long userId, NewEventDto newEventDto) { + return eventPrivateService.addEvent(userId, newEventDto); + } + + // Получение событий, добавленных текущим пользователем + @Override + public Collection getAllEventsByUserId(Long userId, Integer from, Integer size) { + return eventPrivateService.getEventsByUserId(userId, from, size); + } + + // Получение полной информации о событии добавленном текущим пользователем + @Override + public EventFullDto getEventByUserIdAndEventId(Long userId, Long eventId) { + return eventPrivateService.getEventByUserIdAndEventId(userId, eventId); + } + + // Изменение события добавленного текущим пользователем + @Override + public EventFullDto updateEventByUserIdAndEventId(Long userId, Long eventId, UpdateEventDto updateEventDto) { + return eventPrivateService.updateEventByUserIdAndEventId(userId, eventId, updateEventDto); + } + +} \ No newline at end of file diff --git a/core/event-service/src/main/java/ru/practicum/event/controller/EventPublicController.java b/core/event-service/src/main/java/ru/practicum/event/controller/EventPublicController.java new file mode 100644 index 0000000..f249745 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/event/controller/EventPublicController.java @@ -0,0 +1,71 @@ +package ru.practicum.event.controller; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.api.event.EventPublicApi; +import ru.practicum.dto.event.*; +import ru.practicum.event.service.EventPublicService; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +@RestController +@RequiredArgsConstructor +@Validated +public class EventPublicController implements EventPublicApi { + + private final EventPublicService eventPublicService; + + // Получение событий с возможностью фильтрации + @Override + public List getAllEventsByParams( + String text, + List categories, + Boolean paid, + LocalDateTime rangeStart, + LocalDateTime rangeEnd, + Boolean onlyAvailable, + EventSort eventSort, + Integer from, + Integer size, + HttpServletRequest request + ) { + EventParams params = EventParams.builder() + .text(text) + .categories(categories) + .paid(paid) + .rangeStart(rangeStart) + .rangeEnd(rangeEnd) + .onlyAvailable(onlyAvailable) + .eventSort(eventSort) + .from(from) + .size(size) + .build(); + return eventPublicService.getAllEventsByParams(params, request); + } + + // Получение подробной информации об опубликованном событии по его идентификатору + @Override + public EventFullDto getInformationAboutEventByEventId(Long id, HttpServletRequest request) { + return eventPublicService.getEventById(id, request); + } + + @Override + public EventCommentDto getEventCommentDto(Long id) { + return eventPublicService.getEventCommentDto(id); + } + + @Override + public Collection getEventCommentDtoList(Collection ids) { + return eventPublicService.getEventCommentDtoList(ids); + } + + @Override + public EventInteractionDto getEventInteractionDto(Long id) { + return eventPublicService.getEventInteractionDto(id); + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/event/model/Event.java b/core/event-service/src/main/java/ru/practicum/event/dal/Event.java similarity index 68% rename from core/main-service/src/main/java/ru/practicum/event/model/Event.java rename to core/event-service/src/main/java/ru/practicum/event/dal/Event.java index fb8ea7c..74defe2 100644 --- a/core/main-service/src/main/java/ru/practicum/event/model/Event.java +++ b/core/event-service/src/main/java/ru/practicum/event/dal/Event.java @@ -1,36 +1,40 @@ -package ru.practicum.event.model; +package ru.practicum.event.dal; import jakarta.persistence.*; import lombok.*; -import ru.practicum.category.model.Category; -import ru.practicum.comment.model.Comment; -import ru.practicum.event.dto.State; -import ru.practicum.request.model.Request; -import ru.practicum.user.model.User; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; +import ru.practicum.category.dal.Category; +import ru.practicum.dto.event.State; import java.time.LocalDateTime; -import java.util.List; @Getter @Setter +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString +@Entity @Builder @NoArgsConstructor @AllArgsConstructor -@Entity -@Table(name = "events") +@Table(name = "events", indexes = { + @Index(name = "idx_events_initiator_id", columnList = "initiator_id"), + @Index(name = "idx_events_categories_id", columnList = "categories_id") +}) public class Event { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @EqualsAndHashCode.Include @Column(name = "id") private Long id; - @ManyToOne - @JoinColumn(name = "initiator", nullable = false) - private User initiator; + @Column(name = "initiator_id", nullable = false) + private Long initiatorId; @ManyToOne @JoinColumn(name = "categories_id", nullable = false) + @OnDelete(action = OnDeleteAction.RESTRICT) private Category category; @Column(name = "title", length = 120, nullable = false) @@ -67,10 +71,4 @@ public class Event { @Column(name = "created_on", nullable = false) private LocalDateTime createdOn; - @OneToMany(mappedBy = "event", fetch = FetchType.LAZY) - private List requests; - - @OneToMany(mappedBy = "event", fetch = FetchType.LAZY) - private List comments; - -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java b/core/event-service/src/main/java/ru/practicum/event/dal/EventRepository.java similarity index 73% rename from core/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java rename to core/event-service/src/main/java/ru/practicum/event/dal/EventRepository.java index 9ccec1d..c69bb3d 100644 --- a/core/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java +++ b/core/event-service/src/main/java/ru/practicum/event/dal/EventRepository.java @@ -1,18 +1,15 @@ -package ru.practicum.event.repository; +package ru.practicum.event.dal; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import ru.practicum.event.dto.State; -import ru.practicum.event.model.Event; +import ru.practicum.dto.event.State; import java.util.List; import java.util.Optional; public interface EventRepository extends JpaRepository, JpaSpecificationExecutor { - Optional findByIdAndInitiatorId(Long eventId, Long initiatorId); - List findByInitiatorId(Long initiatorId, Pageable pageable); Optional findByIdAndState(Long id, State state); diff --git a/core/main-service/src/main/java/ru/practicum/event/repository/JpaSpecifications.java b/core/event-service/src/main/java/ru/practicum/event/dal/JpaSpecifications.java similarity index 70% rename from core/main-service/src/main/java/ru/practicum/event/repository/JpaSpecifications.java rename to core/event-service/src/main/java/ru/practicum/event/dal/JpaSpecifications.java index 4ca46d0..866a018 100644 --- a/core/main-service/src/main/java/ru/practicum/event/repository/JpaSpecifications.java +++ b/core/event-service/src/main/java/ru/practicum/event/dal/JpaSpecifications.java @@ -1,14 +1,9 @@ -package ru.practicum.event.repository; +package ru.practicum.event.dal; -import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.Predicate; import org.springframework.data.jpa.domain.Specification; -import ru.practicum.event.dto.EventAdminParams; -import ru.practicum.event.dto.EventParams; -import ru.practicum.event.model.Event; -import ru.practicum.request.dto.ParticipationRequestStatus; -import ru.practicum.request.model.Request; +import ru.practicum.dto.event.EventAdminParams; +import ru.practicum.dto.event.EventParams; import java.util.ArrayList; import java.util.List; @@ -20,7 +15,7 @@ public static Specification adminFilters(EventAdminParams params) { List predicates = new ArrayList<>(); if (params.getUsers() != null && !params.getUsers().isEmpty()) - predicates.add(root.get("initiator").get("id").in(params.getUsers())); + predicates.add(root.get("initiatorId").in(params.getUsers())); if (params.getStates() != null && !params.getStates().isEmpty()) predicates.add(root.get("state").in(params.getStates())); @@ -60,19 +55,9 @@ public static Specification publicFilters(EventParams params) { if (params.getRangeEnd() != null) predicates.add(cb.lessThanOrEqualTo(root.get("eventDate"), params.getRangeEnd())); - if (params.getOnlyAvailable() == true) { - Join requestJoin = root.join("requests", JoinType.LEFT); - requestJoin.on(cb.equal(requestJoin.get("status"), ParticipationRequestStatus.CONFIRMED)); - query.groupBy(root.get("id")); - - Predicate unlimitedPredicate = cb.equal(root.get("participantLimit"), 0); - Predicate hasFreeSeatsPredicate = cb.greaterThan(root.get("participantLimit"), cb.count(requestJoin)); - query.having(cb.or(unlimitedPredicate, hasFreeSeatsPredicate)); - } - return cb.and(predicates.toArray(new Predicate[0])); }; } -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/model/Location.java b/core/event-service/src/main/java/ru/practicum/event/dal/Location.java similarity index 76% rename from core/main-service/src/main/java/ru/practicum/event/model/Location.java rename to core/event-service/src/main/java/ru/practicum/event/dal/Location.java index 44cbad3..9c52841 100644 --- a/core/main-service/src/main/java/ru/practicum/event/model/Location.java +++ b/core/event-service/src/main/java/ru/practicum/event/dal/Location.java @@ -1,18 +1,20 @@ -package ru.practicum.event.model; +package ru.practicum.event.dal; import jakarta.persistence.Embeddable; import lombok.*; @Getter @Setter -@Embeddable +@EqualsAndHashCode +@ToString @Builder -@AllArgsConstructor @NoArgsConstructor +@AllArgsConstructor +@Embeddable public class Location { private Float lat; private Float lon; -} \ No newline at end of file +} diff --git a/core/event-service/src/main/java/ru/practicum/event/dal/View.java b/core/event-service/src/main/java/ru/practicum/event/dal/View.java new file mode 100644 index 0000000..4cce663 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/event/dal/View.java @@ -0,0 +1,33 @@ +package ru.practicum.event.dal; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + +@Getter +@Setter +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString +@Entity +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "views", indexes = {@Index(name = "idx_views_event_id", columnList = "event_id")}) +public class View { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @EqualsAndHashCode.Include + @Column(name = "id") + private Long id; + + @ManyToOne + @JoinColumn(name = "event_id", nullable = false) + @OnDelete(action = OnDeleteAction.RESTRICT) + private Event event; + + @Column(name = "ip", length = 15, nullable = false) + private String ip; + +} diff --git a/core/main-service/src/main/java/ru/practicum/event/repository/ViewRepository.java b/core/event-service/src/main/java/ru/practicum/event/dal/ViewRepository.java similarity index 89% rename from core/main-service/src/main/java/ru/practicum/event/repository/ViewRepository.java rename to core/event-service/src/main/java/ru/practicum/event/dal/ViewRepository.java index f94c538..2d2110e 100644 --- a/core/main-service/src/main/java/ru/practicum/event/repository/ViewRepository.java +++ b/core/event-service/src/main/java/ru/practicum/event/dal/ViewRepository.java @@ -1,9 +1,8 @@ -package ru.practicum.event.repository; +package ru.practicum.event.dal; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import ru.practicum.event.model.View; import java.util.List; @@ -23,4 +22,4 @@ List countsByEventIds( boolean existsByEventIdAndIp(Long eventId, String ip); -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/event/service/EventAdminService.java b/core/event-service/src/main/java/ru/practicum/event/service/EventAdminService.java similarity index 65% rename from core/main-service/src/main/java/ru/practicum/event/service/EventAdminService.java rename to core/event-service/src/main/java/ru/practicum/event/service/EventAdminService.java index b1d6495..5305a85 100644 --- a/core/main-service/src/main/java/ru/practicum/event/service/EventAdminService.java +++ b/core/event-service/src/main/java/ru/practicum/event/service/EventAdminService.java @@ -1,8 +1,8 @@ package ru.practicum.event.service; -import ru.practicum.event.dto.EventAdminParams; -import ru.practicum.event.dto.EventFullDto; -import ru.practicum.event.dto.UpdateEventDto; +import ru.practicum.dto.event.EventAdminParams; +import ru.practicum.dto.event.EventFullDto; +import ru.practicum.dto.event.UpdateEventDto; import java.util.List; @@ -10,4 +10,4 @@ public interface EventAdminService { List getAllEventsByParams(EventAdminParams eventAdminParams); EventFullDto updateEventByAdmin(Long eventId, UpdateEventDto updateEventDto); -} \ No newline at end of file +} diff --git a/core/event-service/src/main/java/ru/practicum/event/service/EventAdminServiceImpl.java b/core/event-service/src/main/java/ru/practicum/event/service/EventAdminServiceImpl.java new file mode 100644 index 0000000..6481c77 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/event/service/EventAdminServiceImpl.java @@ -0,0 +1,133 @@ +package ru.practicum.event.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; +import ru.practicum.category.dal.Category; +import ru.practicum.category.dal.CategoryRepository; +import ru.practicum.client.RequestClientHelper; +import ru.practicum.client.UserClientHelper; +import ru.practicum.dto.event.*; +import ru.practicum.dto.user.UserShortDto; +import ru.practicum.event.dal.Event; +import ru.practicum.event.dal.EventRepository; +import ru.practicum.event.dal.JpaSpecifications; +import ru.practicum.event.dal.ViewRepository; +import ru.practicum.exception.ConflictException; +import ru.practicum.exception.NotFoundException; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class EventAdminServiceImpl implements EventAdminService { + + private final TransactionTemplate transactionTemplate; + private final EventRepository eventRepository; + private final CategoryRepository categoryRepository; + private final ViewRepository viewRepository; + + private final RequestClientHelper requestClientHelper; + private final UserClientHelper userClientHelper; + + // Поиск событий + @Override + public List getAllEventsByParams(EventAdminParams params) { + Page events = transactionTemplate.execute(status -> { + Pageable pageable = PageRequest.of(params.getFrom() / params.getSize(), params.getSize()); + return eventRepository.findAll(JpaSpecifications.adminFilters(params), pageable); + }); + if (events == null) return List.of(); + + Set userIds = events.stream().map(Event::getInitiatorId).collect(Collectors.toSet()); + List eventIds = events.stream().map(Event::getId).toList(); + + Map userMap = userClientHelper.retrieveUserShortDtoMapByUserIdList(userIds); + Map confirmedRequestsMap = requestClientHelper.retrieveConfirmedRequestsMapByEventIdList(eventIds); + + Map viewsMap = viewRepository.countsByEventIds(eventIds) + .stream() + .collect(Collectors.toMap( + r -> (Long) r[0], + r -> (Long) r[1] + )); + + return events.stream() + .map(e -> EventMapper.toEventFullDto( + e, + userMap.get(e.getInitiatorId()), + confirmedRequestsMap.get(e.getId()), + viewsMap.get(e.getId()) + )) + .toList(); + } + + // Редактирование данных события и его статуса (отклонение/публикация). + @Override + public EventFullDto updateEventByAdmin(Long eventId, UpdateEventDto updateEventDto) { + Long initiatorId = transactionTemplate.execute(status -> { + return eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Not found Event " + eventId)) + .getInitiatorId(); + }); + + UserShortDto userShortDto = userClientHelper.retrieveUserShortDtoByUserId(initiatorId); + Map confirmedRequestsMap = requestClientHelper.retrieveConfirmedRequestsMapByEventIdList(List.of(eventId)); + + return transactionTemplate.execute(status -> { + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); + + if (updateEventDto.getCategory() != null) { + Category category = categoryRepository.findById(updateEventDto.getCategory()) + .orElseThrow(() -> new NotFoundException("Category with id=" + updateEventDto.getCategory() + " not found")); + event.setCategory(category); + } + + if (updateEventDto.getTitle() != null) event.setTitle(updateEventDto.getTitle()); + if (updateEventDto.getAnnotation() != null) event.setAnnotation(updateEventDto.getAnnotation()); + if (updateEventDto.getDescription() != null) event.setDescription(updateEventDto.getDescription()); + if (updateEventDto.getLocation() != null) + event.setLocation(LocationMapper.toEntity(updateEventDto.getLocation())); + if (updateEventDto.getPaid() != null) event.setPaid(updateEventDto.getPaid()); + if (updateEventDto.getParticipantLimit() != null) + event.setParticipantLimit(updateEventDto.getParticipantLimit()); + if (updateEventDto.getRequestModeration() != null) + event.setRequestModeration(updateEventDto.getRequestModeration()); + if (updateEventDto.getEventDate() != null) event.setEventDate(updateEventDto.getEventDate()); + + if (Objects.equals(updateEventDto.getStateAction(), StateAction.REJECT_EVENT)) { + // событие можно отклонить, только если оно еще не опубликовано (Ожидается код ошибки 409) + if (Objects.equals(event.getState(), State.PUBLISHED)) { + throw new ConflictException("Event in PUBLISHED state can not be rejected"); + } + event.setState(State.CANCELED); + } else if (Objects.equals(updateEventDto.getStateAction(), StateAction.PUBLISH_EVENT)) { + // дата начала изменяемого события должна быть не ранее чем за час от даты публикации. (Ожидается код ошибки 409) + if (LocalDateTime.now().plusHours(1).isAfter(event.getEventDate())) { + throw new ConflictException("Event time must be at least 1 hours from publish time"); + } + // событие можно публиковать, только если оно в состоянии ожидания публикации (Ожидается код ошибки 409) + if (!Objects.equals(event.getState(), State.PENDING)) { + throw new ConflictException("Event should be in PENDING state"); + } + event.setState(State.PUBLISHED); + event.setPublishedOn(LocalDateTime.now()); + } + + eventRepository.save(event); + + Long views = viewRepository.countByEventId(eventId); + return EventMapper.toEventFullDto(event, userShortDto, confirmedRequestsMap.get(eventId), views); + }); + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java b/core/event-service/src/main/java/ru/practicum/event/service/EventMapper.java similarity index 65% rename from core/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java rename to core/event-service/src/main/java/ru/practicum/event/service/EventMapper.java index c57915b..0097075 100644 --- a/core/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java +++ b/core/event-service/src/main/java/ru/practicum/event/service/EventMapper.java @@ -1,23 +1,23 @@ -package ru.practicum.event.mapper; +package ru.practicum.event.service; -import ru.practicum.category.mapper.CategoryMapper; -import ru.practicum.category.model.Category; -import ru.practicum.event.dto.*; -import ru.practicum.event.model.Event; -import ru.practicum.user.mapper.UserMapper; -import ru.practicum.user.model.User; +import ru.practicum.category.dal.Category; +import ru.practicum.category.service.CategoryMapper; +import ru.practicum.dto.event.*; +import ru.practicum.dto.user.UserShortDto; +import ru.practicum.event.dal.Event; import java.time.LocalDateTime; public class EventMapper { - public static Event toEvent( + + public static Event toNewEvent( NewEventDto newEventDto, - User initiator, + Long userId, Category category ) { return Event.builder() - .initiator(initiator) + .initiatorId(userId) .category(category) .title(newEventDto.getTitle()) .annotation(newEventDto.getAnnotation()) @@ -35,13 +35,14 @@ public static Event toEvent( public static EventFullDto toEventFullDto( Event event, + UserShortDto userShortDto, Long confirmedRequests, Long views ) { if (confirmedRequests == null) confirmedRequests = 0L; return EventFullDto.builder() .id(event.getId()) - .initiator(UserMapper.toUserShortDto(event.getInitiator())) + .initiator(userShortDto) .category(CategoryMapper.toCategoryDto(event.getCategory())) .title(event.getTitle()) .annotation(event.getAnnotation()) @@ -61,13 +62,15 @@ public static EventFullDto toEventFullDto( public static EventShortDto toEventShortDto( Event event, + UserShortDto userShortDto, Long confirmedRequests, Long views ) { if (confirmedRequests == null) confirmedRequests = 0L; + if (views == null) views = 0L; return EventShortDto.builder() .id(event.getId()) - .initiator(UserMapper.toUserShortDto(event.getInitiator())) + .initiator(userShortDto) .category(CategoryMapper.toCategoryDto(event.getCategory())) .title(event.getTitle()) .annotation(event.getAnnotation()) @@ -82,6 +85,27 @@ public static EventCommentDto toEventComment(Event event) { return EventCommentDto.builder() .id(event.getId()) .title(event.getTitle()) + .state(event.getState()) + .build(); + } + + public static EventInteractionDto toInteractionDto(Event event) { + return EventInteractionDto.builder() + .id(event.getId()) + .initiatorId(event.getInitiatorId()) + .categoryId(event.getCategory().getId()) + .title(event.getTitle()) + .annotation(event.getAnnotation()) + .description(event.getDescription()) + .state(event.getState()) + .location(LocationMapper.toDto(event.getLocation())) + .participantLimit(event.getParticipantLimit()) + .requestModeration(event.getRequestModeration()) + .paid(event.getPaid()) + .eventDate(event.getEventDate()) + .publishedOn(event.getPublishedOn()) + .createdOn(event.getCreatedOn()) .build(); } -} \ No newline at end of file + +} diff --git a/core/main-service/src/main/java/ru/practicum/event/service/EventPrivateService.java b/core/event-service/src/main/java/ru/practicum/event/service/EventPrivateService.java similarity index 56% rename from core/main-service/src/main/java/ru/practicum/event/service/EventPrivateService.java rename to core/event-service/src/main/java/ru/practicum/event/service/EventPrivateService.java index 1fc1b18..479aecd 100644 --- a/core/main-service/src/main/java/ru/practicum/event/service/EventPrivateService.java +++ b/core/event-service/src/main/java/ru/practicum/event/service/EventPrivateService.java @@ -1,9 +1,9 @@ package ru.practicum.event.service; -import ru.practicum.event.dto.EventFullDto; -import ru.practicum.event.dto.EventShortDto; -import ru.practicum.event.dto.NewEventDto; -import ru.practicum.event.dto.UpdateEventDto; +import ru.practicum.dto.event.EventFullDto; +import ru.practicum.dto.event.EventShortDto; +import ru.practicum.dto.event.NewEventDto; +import ru.practicum.dto.event.UpdateEventDto; import java.util.List; @@ -13,8 +13,8 @@ public interface EventPrivateService { EventFullDto getEventByUserIdAndEventId(Long userId, Long eventId); - List getEventsByUserId(Long userId, Long from, Long size); + List getEventsByUserId(Long userId, Integer from, Integer size); EventFullDto updateEventByUserIdAndEventId(Long userId, Long eventId, UpdateEventDto newEventDto); -} \ No newline at end of file +} diff --git a/core/event-service/src/main/java/ru/practicum/event/service/EventPrivateServiceImpl.java b/core/event-service/src/main/java/ru/practicum/event/service/EventPrivateServiceImpl.java new file mode 100644 index 0000000..be1a315 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/event/service/EventPrivateServiceImpl.java @@ -0,0 +1,160 @@ +package ru.practicum.event.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; +import ru.practicum.category.dal.Category; +import ru.practicum.category.dal.CategoryRepository; +import ru.practicum.client.RequestClientHelper; +import ru.practicum.client.UserClientHelper; +import ru.practicum.dto.event.*; +import ru.practicum.dto.user.UserShortDto; +import ru.practicum.event.dal.Event; +import ru.practicum.event.dal.EventRepository; +import ru.practicum.event.dal.ViewRepository; +import ru.practicum.exception.ConflictException; +import ru.practicum.exception.NotFoundException; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class EventPrivateServiceImpl implements EventPrivateService { + + private final TransactionTemplate transactionTemplate; + private final CategoryRepository categoryRepository; + private final EventRepository eventRepository; + private final ViewRepository viewRepository; + + private final UserClientHelper userClientHelper; + private final RequestClientHelper requestClientHelper; + + // Добавление нового события + @Override + public EventFullDto addEvent(Long userId, NewEventDto newEventDto) { + UserShortDto userShortDto = userClientHelper.retrieveUserShortDtoByUserIdOrFall(userId); + + return transactionTemplate.execute(status -> { + Category category = categoryRepository.findById(newEventDto.getCategory()) + .orElseThrow(() -> new NotFoundException("Not found Category " + newEventDto.getCategory())); + + Event newEvent = EventMapper.toNewEvent(newEventDto, userId, category); + eventRepository.save(newEvent); + return EventMapper.toEventFullDto(newEvent, userShortDto, 0L, 0L); + }); + } + + // Получение полной информации о событии добавленном текущим пользователем + @Override + public EventFullDto getEventByUserIdAndEventId(Long userId, Long eventId) { + Event event = transactionTemplate.execute(status -> { + return eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Not found Event " + eventId)); + }); + + if (!Objects.equals(userId, event.getInitiatorId())) + throw new ConflictException("User " + userId + " is not an initiator of event " + eventId, "Forbidden action"); + + Long views = transactionTemplate.execute(status -> { + return viewRepository.countByEventId(eventId); + }); + + UserShortDto userShortDto = userClientHelper.retrieveUserShortDtoByUserId(userId); + Map confirmedRequestsMap = requestClientHelper.retrieveConfirmedRequestsMapByEventIdList(List.of(eventId)); + + return EventMapper.toEventFullDto(event, userShortDto, confirmedRequestsMap.get(eventId), views); + } + + // Получение событий, добавленных текущим пользователем + @Override + public List getEventsByUserId(Long userId, Integer from, Integer size) { + UserShortDto userShortDto = userClientHelper.retrieveUserShortDtoByUserId(userId); + + List events = transactionTemplate.execute(status -> { + Pageable pageable = PageRequest.of(from / size, size, Sort.by("eventDate").descending()); + return eventRepository.findByInitiatorId(userId, pageable); + }); + if (events == null || events.isEmpty()) return List.of(); + + List eventIds = events.stream().map(Event::getId).toList(); + Map confirmedRequestsMap = requestClientHelper.retrieveConfirmedRequestsMapByEventIdList(eventIds); + + Map viewsMap = transactionTemplate.execute(status -> { + return viewRepository.countsByEventIds(eventIds) + .stream() + .collect(Collectors.toMap( + r -> (Long) r[0], + r -> (Long) r[1] + )); + }); + + return events.stream() + .map(e -> EventMapper.toEventShortDto( + e, + userShortDto, + confirmedRequestsMap.get(e.getId()), + viewsMap.get(e.getId()) + )) + .toList(); + } + + // Изменение события добавленного текущим пользователем + @Override + public EventFullDto updateEventByUserIdAndEventId(Long userId, Long eventId, UpdateEventDto updateEventDto) { + UserShortDto userShortDto = userClientHelper.retrieveUserShortDtoByUserId(userId); + Map confirmedRequestsMap = requestClientHelper.retrieveConfirmedRequestsMapByEventIdList(List.of(eventId)); + + return transactionTemplate.execute(status -> { + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); + + if (!Objects.equals(userId, event.getInitiatorId())) + throw new ConflictException("User " + userId + " is not an initiator of event " + eventId, "Forbidden action"); + + // изменить можно только отмененные события или события в состоянии ожидания модерации (Ожидается код ошибки 409) + if (event.getState() != State.PENDING && event.getState() != State.CANCELED) + throw new ConflictException("Only pending or canceled events can be changed"); + + // дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента (Ожидается код ошибки 409) + if (updateEventDto.getEventDate() != null && + updateEventDto.getEventDate().isBefore(LocalDateTime.now().plusHours(2))) + throw new ConflictException("Event date must be at least 2 hours from now"); + + // если все хорошо, изменяем обновленные данные: + if (updateEventDto.getCategory() != null) { + Category category = categoryRepository.findById(updateEventDto.getCategory()) + .orElseThrow(() -> new NotFoundException("Category with id=" + updateEventDto.getCategory() + " not found")); + event.setCategory(category); + } + if (updateEventDto.getTitle() != null) event.setTitle(updateEventDto.getTitle()); + if (updateEventDto.getAnnotation() != null) event.setAnnotation(updateEventDto.getAnnotation()); + if (updateEventDto.getDescription() != null) event.setDescription(updateEventDto.getDescription()); + if (updateEventDto.getLocation() != null) + event.setLocation(LocationMapper.toEntity(updateEventDto.getLocation())); + if (updateEventDto.getPaid() != null) event.setPaid(updateEventDto.getPaid()); + if (updateEventDto.getParticipantLimit() != null) + event.setParticipantLimit(updateEventDto.getParticipantLimit()); + if (updateEventDto.getRequestModeration() != null) + event.setRequestModeration(updateEventDto.getRequestModeration()); + if (updateEventDto.getEventDate() != null) event.setEventDate(updateEventDto.getEventDate()); + if (Objects.equals(updateEventDto.getStateAction(), StateAction.CANCEL_REVIEW)) { + event.setState(State.CANCELED); + } else if (Objects.equals(updateEventDto.getStateAction(), StateAction.SEND_TO_REVIEW)) { + event.setState(State.PENDING); + } + + eventRepository.save(event); + + Long views = viewRepository.countByEventId(eventId); + return EventMapper.toEventFullDto(event, userShortDto, confirmedRequestsMap.get(eventId), views); + }); + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/event/service/EventPublicService.java b/core/event-service/src/main/java/ru/practicum/event/service/EventPublicService.java similarity index 55% rename from core/main-service/src/main/java/ru/practicum/event/service/EventPublicService.java rename to core/event-service/src/main/java/ru/practicum/event/service/EventPublicService.java index 08086bc..aeb4aa4 100644 --- a/core/main-service/src/main/java/ru/practicum/event/service/EventPublicService.java +++ b/core/event-service/src/main/java/ru/practicum/event/service/EventPublicService.java @@ -1,10 +1,9 @@ package ru.practicum.event.service; import jakarta.servlet.http.HttpServletRequest; -import ru.practicum.event.dto.EventFullDto; -import ru.practicum.event.dto.EventParams; -import ru.practicum.event.dto.EventShortDto; +import ru.practicum.dto.event.*; +import java.util.Collection; import java.util.List; public interface EventPublicService { @@ -13,4 +12,10 @@ public interface EventPublicService { EventFullDto getEventById(Long id, HttpServletRequest request); -} \ No newline at end of file + EventCommentDto getEventCommentDto(Long id); + + Collection getEventCommentDtoList(Collection ids); + + EventInteractionDto getEventInteractionDto(Long id); + +} diff --git a/core/event-service/src/main/java/ru/practicum/event/service/EventPublicServiceImpl.java b/core/event-service/src/main/java/ru/practicum/event/service/EventPublicServiceImpl.java new file mode 100644 index 0000000..49b67a0 --- /dev/null +++ b/core/event-service/src/main/java/ru/practicum/event/service/EventPublicServiceImpl.java @@ -0,0 +1,166 @@ +package ru.practicum.event.service; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; +import ru.practicum.EventHitDto; +import ru.practicum.client.RequestClientHelper; +import ru.practicum.client.UserClientHelper; +import ru.practicum.dto.event.*; +import ru.practicum.dto.user.UserShortDto; +import ru.practicum.event.dal.*; +import ru.practicum.ewm.client.StatClient; +import ru.practicum.exception.BadRequestException; +import ru.practicum.exception.NotFoundException; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class EventPublicServiceImpl implements EventPublicService { + + private final TransactionTemplate transactionTemplate; + private final EventRepository eventRepository; + private final ViewRepository viewRepository; + + private final UserClientHelper userClientHelper; + private final RequestClientHelper requestClientHelper; + + private final StatClient statClient; + + // Получение событий с возможностью фильтрации + @Override + public List getAllEventsByParams(EventParams params, HttpServletRequest request) { + if (params.getRangeStart() != null && params.getRangeEnd() != null && params.getRangeEnd().isBefore(params.getRangeStart())) + throw new BadRequestException("rangeStart should be before rangeEnd"); + + // если в запросе не указан диапазон дат, то нужно выгружать события, которые произойдут позже текущего времени + if (params.getRangeStart() == null) { + params.setRangeStart(LocalDateTime.now()); + params.setRangeEnd(null); + } + + List events = transactionTemplate.execute(status -> { + Sort sort = Sort.by(Sort.Direction.ASC, "eventDate"); + if (EventSort.VIEWS.equals(params.getEventSort())) sort = Sort.by(Sort.Direction.DESC, "views"); + PageRequest pageRequest = PageRequest.of(params.getFrom() / params.getSize(), params.getSize(), sort); + return eventRepository.findAll(JpaSpecifications.publicFilters(params), pageRequest).getContent(); + }); + if (events == null) return List.of(); + + Set userIds = events.stream().map(Event::getInitiatorId).collect(Collectors.toSet()); + List eventIds = events.stream().map(Event::getId).toList(); + + Map userMap = userClientHelper.retrieveUserShortDtoMapByUserIdList(userIds); + // информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие + Map confirmedRequestsMap = requestClientHelper.retrieveConfirmedRequestsMapByEventIdList(eventIds); + + if (params.getOnlyAvailable() == true && !confirmedRequestsMap.isEmpty()) { + events = events.stream() + .filter(e -> { + if (Objects.equals(e.getParticipantLimit(), 0L)) return true; + Long confirmedRequests = confirmedRequestsMap.get(e.getId()); + if (confirmedRequests == null) return true; + return confirmedRequests < e.getParticipantLimit(); + }).toList(); + } + + Map viewsMap = Optional.ofNullable( + transactionTemplate.execute(status -> { + return viewRepository.countsByEventIds(eventIds) + .stream() + .collect(Collectors.toMap( + r -> (Long) r[0], + r -> (Long) r[1] + )); + }) + ).orElse(Map.of()); + + // информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики + statClient.hit(EventHitDto.builder() + .ip(request.getRemoteAddr()) + .uri(request.getRequestURI()) + .app("ewm-main-service") + .timestamp(LocalDateTime.now()) + .build()); + + return events.stream() + .map(e -> EventMapper.toEventShortDto( + e, + userMap.get(e.getInitiatorId()), + confirmedRequestsMap.get(e.getId()), + viewsMap.get(e.getId()) + )) + .toList(); + } + + // Получение подробной информации об опубликованном событии по его идентификатору + @Override + public EventFullDto getEventById(Long eventId, HttpServletRequest request) { + Event event = transactionTemplate.execute(status -> { + // событие должно быть опубликовано + return eventRepository.findByIdAndState(eventId, State.PUBLISHED) + .orElseThrow(() -> new NotFoundException("Event not found")); + }); + + Long views = transactionTemplate.execute(status -> { + Long viewsBefore = viewRepository.countByEventId(eventId); + // делаем новый уникальный просмотр + if (!viewRepository.existsByEventIdAndIp(eventId, request.getRemoteAddr())) { + View view = View.builder() + .event(event) + .ip(request.getRemoteAddr()) + .build(); + viewRepository.save(view); + } + return viewsBefore; + }); + + // информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики + statClient.hit(EventHitDto.builder() + .ip(request.getRemoteAddr()) + .uri(request.getRequestURI()) + .app("ewm-main-service") + .timestamp(LocalDateTime.now()) + .build()); + + UserShortDto userShortDto = userClientHelper.retrieveUserShortDtoByUserId(event.getInitiatorId()); + // информация о событии должна включать в себя количество просмотров и количество подтвержденных запросов + Map confirmedRequestsMap = requestClientHelper.retrieveConfirmedRequestsMapByEventIdList(List.of(eventId)); + + return EventMapper.toEventFullDto(event, userShortDto, confirmedRequestsMap.get(eventId), views); + } + + @Override + @Transactional(readOnly = true) + public EventCommentDto getEventCommentDto(Long eventId) { + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Not found Event " + eventId)); + return EventMapper.toEventComment(event); + } + + @Override + @Transactional(readOnly = true) + public Collection getEventCommentDtoList(Collection ids) { + if (ids == null || ids.isEmpty()) return List.of(); + List events = eventRepository.findAllById(ids); + return events.stream() + .map(EventMapper::toEventComment) + .toList(); + } + + @Override + @Transactional(readOnly = true) + public EventInteractionDto getEventInteractionDto(Long eventId) { + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Not found Event " + eventId)); + return EventMapper.toInteractionDto(event); + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/event/mapper/LocationMapper.java b/core/event-service/src/main/java/ru/practicum/event/service/LocationMapper.java similarity index 80% rename from core/main-service/src/main/java/ru/practicum/event/mapper/LocationMapper.java rename to core/event-service/src/main/java/ru/practicum/event/service/LocationMapper.java index 7d4f61a..6ec2ff3 100644 --- a/core/main-service/src/main/java/ru/practicum/event/mapper/LocationMapper.java +++ b/core/event-service/src/main/java/ru/practicum/event/service/LocationMapper.java @@ -1,7 +1,7 @@ -package ru.practicum.event.mapper; +package ru.practicum.event.service; -import ru.practicum.event.dto.LocationDto; -import ru.practicum.event.model.Location; +import ru.practicum.dto.event.LocationDto; +import ru.practicum.event.dal.Location; public class LocationMapper { @@ -21,4 +21,4 @@ public static LocationDto toDto(Location location) { .build(); } -} \ No newline at end of file +} diff --git a/core/event-service/src/main/resources/application.yaml b/core/event-service/src/main/resources/application.yaml new file mode 100644 index 0000000..2f51ff5 --- /dev/null +++ b/core/event-service/src/main/resources/application.yaml @@ -0,0 +1,25 @@ +spring: + application: + name: event-service + config: + import: "configserver:" + cloud: + config: + discovery: + enabled: true + serviceId: config-server + fail-fast: true + retry: + useRandomPolicy: true + max-interval: 10000 + max-attempts: 100 + +eureka: + client: + registerWithEureka: true + serviceUrl: + defaultZone: http://localhost:8761/eureka/ + instance: + instance-id: ${spring.application.name}${random.int} + preferIpAddress: false + hostname: localhost diff --git a/core/event-service/src/main/resources/schema.sql b/core/event-service/src/main/resources/schema.sql new file mode 100644 index 0000000..e4b36ac --- /dev/null +++ b/core/event-service/src/main/resources/schema.sql @@ -0,0 +1 @@ +CREATE SCHEMA IF NOT EXISTS event_service; diff --git a/core/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java b/core/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java deleted file mode 100644 index 8d5b8d7..0000000 --- a/core/main-service/src/main/java/ru/practicum/category/controller/CategoryAdminController.java +++ /dev/null @@ -1,53 +0,0 @@ -package ru.practicum.category.controller; - -import jakarta.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.validation.BindingResult; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import ru.practicum.category.dto.CategoryDto; -import ru.practicum.category.service.CategoryAdminService; -import ru.practicum.validation.CreateOrUpdateValidator; - -@RestController -@RequiredArgsConstructor -@Validated -@RequestMapping(path = "/admin/categories") -@Slf4j -public class CategoryAdminController { - - private final CategoryAdminService categoryAdminService; - - @PostMapping - @ResponseStatus(HttpStatus.CREATED) - public CategoryDto addCategory( - @RequestBody @Validated(CreateOrUpdateValidator.Create.class) - CategoryDto requestCategory, - BindingResult bindingResult - ) { - log.info("Calling the POST request to /admin/categories endpoint"); - if (bindingResult.hasErrors()) { - log.error("Validation error with category name"); - throw new IllegalArgumentException("Validation failed"); - } - return categoryAdminService.createCategory(requestCategory); - } - - @DeleteMapping("/{catId}") - @ResponseStatus(HttpStatus.NO_CONTENT) - public void deleteCategories(@PathVariable @Positive Long catId) { - log.info("Calling the DELETE request to /admin/categories/{catId} endpoint"); - categoryAdminService.deleteCategory(catId); - } - - @PatchMapping("/{catId}") - public CategoryDto updateCategory( - @PathVariable Long catId, - @RequestBody @Validated(CreateOrUpdateValidator.Update.class) CategoryDto categoryDto - ) { - log.info("Calling the PATCH request to /admin/categories/{catId} endpoint"); - return categoryAdminService.updateCategory(catId, categoryDto); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java b/core/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java deleted file mode 100644 index 8ee6349..0000000 --- a/core/main-service/src/main/java/ru/practicum/category/controller/CategoryPublicController.java +++ /dev/null @@ -1,40 +0,0 @@ -package ru.practicum.category.controller; - -import jakarta.validation.constraints.Positive; -import jakarta.validation.constraints.PositiveOrZero; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import ru.practicum.category.dto.CategoryDto; -import ru.practicum.category.service.CategoryPublicService; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -@Validated -@RequestMapping(path = "/categories") -@Slf4j -public class CategoryPublicController { - - private final CategoryPublicService service; - - @GetMapping - public ResponseEntity> readAllCategories( - @RequestParam(defaultValue = "0") @PositiveOrZero int from, - @RequestParam(defaultValue = "10") @Positive int size - ) { - log.info("Calling the POST request to - /categories - endpoint"); - return ResponseEntity.ok(service.readAllCategories(from, size)); - } - - @GetMapping("/{catId}") - public ResponseEntity readCategoryById( - @PathVariable Long catId - ) { - log.info("Calling the GET request to - /categories/{catId} - endpoint"); - return ResponseEntity.ok(service.readCategoryById(catId)); - } -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java b/core/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java deleted file mode 100644 index 1be0a1b..0000000 --- a/core/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java +++ /dev/null @@ -1,63 +0,0 @@ -package ru.practicum.comment.controller; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import ru.practicum.comment.dto.CommentDto; -import ru.practicum.comment.service.CommentAdminService; - -import java.util.List; - -@RestController -@RequestMapping(path = "/admin") -@RequiredArgsConstructor -@Slf4j -public class CommentAdminController { - - private final CommentAdminService service; - - @GetMapping("/comments/search") - public ResponseEntity> search(@RequestParam @NotBlank String text, - @RequestParam(defaultValue = "0") int from, - @RequestParam(defaultValue = "10") int size) { - log.info("Calling the GET request to /admin/comment/search endpoint"); - return ResponseEntity.ok(service.search(text, from, size)); - } - - @GetMapping("users/{userId}/comments") - public ResponseEntity> get(@PathVariable @Positive Long userId, - @RequestParam(defaultValue = "0") int from, - @RequestParam(defaultValue = "10") int size) { - log.info("Calling the GET request to admin/users/{userId}/comment endpoint"); - return ResponseEntity.ok(service.findAllByUserId(userId, from, size)); - } - - @DeleteMapping("comments/{comId}") - public ResponseEntity delete(@PathVariable @Positive Long comId) { - log.info("Calling the GET request to admin/comment/{comId} endpoint"); - service.delete(comId); - return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); - } - - @PatchMapping("/comments/{comId}/approve") - public ResponseEntity approveComment(@PathVariable @Positive Long comId) { - log.info("Calling the PATCH request to /admin/comment/{comId}/approve endpoint"); - CommentDto commentDto = service.approveComment(comId); - return ResponseEntity - .status(HttpStatus.OK) - .body(commentDto); - } - - @PatchMapping("/comments/{comId}/reject") - public ResponseEntity rejectComment(@PathVariable @Positive Long comId) { - log.info("Calling the PATCH request to /admin/comment/{comId}/reject endpoint"); - CommentDto commentDto = service.rejectComment(comId); - return ResponseEntity - .status(HttpStatus.OK) - .body(commentDto); - } -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java b/core/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java deleted file mode 100644 index bac14f2..0000000 --- a/core/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.practicum.comment.controller; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import ru.practicum.comment.dto.CommentCreateDto; -import ru.practicum.comment.dto.CommentDto; -import ru.practicum.comment.service.CommentPrivateService; - -@RestController -@Validated -@RequiredArgsConstructor -@Slf4j -public class CommentPrivateController { - - private final CommentPrivateService service; - - @PostMapping("/users/{userId}/events/{eventId}/comments") - public ResponseEntity create(@PathVariable @Positive Long userId, - @PathVariable @Positive Long eventId, - @RequestBody @Valid CommentCreateDto commentCreateDto) { - log.info("Calling the GET request to /users/{userId}/events/{eventId}/comment endpoint"); - return ResponseEntity.status(HttpStatus.CREATED) - .body(service.createComment(userId, eventId, commentCreateDto)); - } - - @DeleteMapping("/users/{userId}/comments/{comId}") - public ResponseEntity delete(@PathVariable @Positive Long userId, - @PathVariable @Positive Long comId) { - log.info("Calling the GET request to /users/{userId}/comment/{comId} endpoint"); - service.deleteComment(userId, comId); - return ResponseEntity - .status(HttpStatus.NO_CONTENT) - .body("Comment deleted by user: " + comId); - } - - @PatchMapping("/users/{userId}/comments/{comId}") - public ResponseEntity patch(@PathVariable @Positive Long userId, - @PathVariable @Positive Long comId, - @RequestBody @Valid CommentCreateDto commentCreateDto) { - log.info("Calling the PATCH request to users/{userId}/comment/{comId} endpoint"); - return ResponseEntity.ok(service.patchComment(userId, comId, commentCreateDto)); - } -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java b/core/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java deleted file mode 100644 index 5a97d7b..0000000 --- a/core/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.practicum.comment.controller; - -import jakarta.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import ru.practicum.comment.dto.CommentDto; -import ru.practicum.comment.dto.CommentShortDto; -import ru.practicum.comment.service.CommentPublicService; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -@Slf4j -public class CommentPublicController { - - private final CommentPublicService service; - - @GetMapping("/comments/{comId}") - public ResponseEntity getById(@PathVariable @Positive Long comId) { - log.info("Calling the GET request to /comments/{comId} endpoint"); - return ResponseEntity.ok(service.getComment(comId)); - } - - @GetMapping("/events/{eventId}/comments") - public ResponseEntity> getByEventId(@PathVariable @Positive Long eventId, - @RequestParam(defaultValue = "0") int from, - @RequestParam(defaultValue = "10") int size) { - log.info("Calling the GET request to /events/{eventId}/comments"); - return ResponseEntity.ok(service.getCommentsByEvent(eventId, from, size)); - } - - @GetMapping("/events/{eventId}/comments/{commentId}") - public ResponseEntity getByEventAndCommentId(@PathVariable @Positive Long eventId, - @PathVariable @Positive Long commentId) { - log.info("Calling the GET request to /events/{eventId}/comments/{commentId}"); - return ResponseEntity.ok(service.getCommentByEventAndCommentId(eventId, commentId)); - } - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java b/core/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java deleted file mode 100644 index b666a10..0000000 --- a/core/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.practicum.comment.mapper; - -import ru.practicum.comment.dto.CommentCreateDto; -import ru.practicum.comment.dto.CommentDto; -import ru.practicum.comment.dto.CommentShortDto; -import ru.practicum.comment.model.Comment; -import ru.practicum.event.mapper.EventMapper; -import ru.practicum.user.mapper.UserMapper; - -import java.util.List; -import java.util.stream.Collectors; - -public class CommentMapper { - - public static Comment toComment(CommentCreateDto commentDto) { - return Comment.builder() - .text(commentDto.getText()) - .build(); - } - - public static CommentDto toCommentDto(Comment comment) { - return CommentDto.builder() - .id(comment.getId()) - .author(UserMapper.toDto(comment.getAuthor())) - .event(EventMapper.toEventComment(comment.getEvent())) - .createTime(comment.getCreateTime()) - .text(comment.getText()) - .approved(comment.getApproved()) - .build(); - } - - public static List toListCommentDto(List list) { - return list.stream().map(CommentMapper::toCommentDto).collect(Collectors.toList()); - } - - public static CommentShortDto toCommentShortDto(Comment comment) { - return CommentShortDto.builder() - .author(UserMapper.toDto(comment.getAuthor())) - .createTime(comment.getText()) - .id(comment.getId()) - .text(comment.getText()) - .build(); - } - - public static List toListCommentShortDto(List list) { - return list.stream().map(CommentMapper::toCommentShortDto).collect(Collectors.toList()); - } - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java b/core/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java deleted file mode 100644 index 21bbc6d..0000000 --- a/core/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.practicum.comment.repository; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import ru.practicum.comment.dto.CommentCountDto; -import ru.practicum.comment.model.Comment; - -import java.util.List; -import java.util.Optional; - -public interface CommentRepository extends JpaRepository { - - Page findAllByEventId(Long eventId, Pageable pageable); - - @Query("select new ru.practicum.comment.dto.CommentCountDto(c.event.id, count(c.id)) " + - "from Comment as c " + - "where c.event.id in ?1 " + - "group by c.event.id") - List findAllCommentCount(List listEventId); - - @Query("select c " + - "from Comment as c " + - "where c.text ilike concat('%', ?1, '%')") - Page findAllByText(String text, Pageable pageable); - - Page findAllByAuthorId(Long userId, Pageable pageable); - - Optional findByEventIdAndId(Long eventId, Long commentId); -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java b/core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java deleted file mode 100644 index 38e9dd4..0000000 --- a/core/main-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java +++ /dev/null @@ -1,85 +0,0 @@ -package ru.practicum.comment.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.comment.dto.CommentDto; -import ru.practicum.comment.mapper.CommentMapper; -import ru.practicum.comment.model.Comment; -import ru.practicum.comment.repository.CommentRepository; -import ru.practicum.exception.NotFoundException; -import ru.practicum.user.repository.UserRepository; -import java.util.List; - -@Service -@RequiredArgsConstructor -@Slf4j -public class CommentAdminServiceImpl implements CommentAdminService { - - private final CommentRepository repository; - private final UserRepository userRepository; - - @Override - @Transactional - public void delete(Long comId) { - log.info("admin delete - invoked for comment ID: {}", comId); - if (!repository.existsById(comId)) { - log.error("Comment with id = {} not found", comId); - throw new NotFoundException("Comment not found"); - } - log.info("Result: comment with id = {} deleted", comId); - repository.deleteById(comId); - } - - @Override - public List search(String text, int from, int size) { - log.info("admin search - invoked with text='{}', from={}, size={}", text, from, size); - Pageable pageable = PageRequest.of(from / size, size); - Page page = repository.findAllByText(text, pageable); - List list = page.getContent(); - log.info("Result: found {} comments for search query '{}'", list.size(), text); - return CommentMapper.toListCommentDto(list); - } - - @Override - public List findAllByUserId(Long userId, int from, int size) { - log.info("admin findAllByUserId - invoked for user ID: {}, from={}, size={}", userId, from, size); - if (!userRepository.existsById(userId)) { - log.error("User with id = {} not found", userId); - throw new NotFoundException("User not found"); - } - Pageable pageable = PageRequest.of(from / size, size); - Page page = repository.findAllByAuthorId(userId, pageable); - List list = page.getContent(); - log.info("Result: user ID {} has {} comments", userId, list.size()); - return CommentMapper.toListCommentDto(list); - } - - @Override - @Transactional - public CommentDto approveComment(Long comId) { - log.info("approveComment - invoked for comment ID: {}", comId); - Comment comment = repository.findById(comId) - .orElseThrow(() -> new NotFoundException("Comment not found")); - comment.setApproved(true); - repository.save(comment); - log.info("Result: comment with id = {} approved successfully", comId); - return CommentMapper.toCommentDto(comment); - } - - @Override - @Transactional - public CommentDto rejectComment(Long comId) { - log.info("rejectComment - invoked for comment ID: {}", comId); - Comment comment = repository.findById(comId) - .orElseThrow(() -> new NotFoundException("Comment not found")); - comment.setApproved(false); - repository.save(comment); - log.info("Result: comment with id = {} rejected successfully", comId); - return CommentMapper.toCommentDto(comment); - } -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateServiceImpl.java b/core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateServiceImpl.java deleted file mode 100644 index c5e47c7..0000000 --- a/core/main-service/src/main/java/ru/practicum/comment/service/CommentPrivateServiceImpl.java +++ /dev/null @@ -1,110 +0,0 @@ -package ru.practicum.comment.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.comment.dto.CommentCreateDto; -import ru.practicum.comment.dto.CommentDto; -import ru.practicum.comment.mapper.CommentMapper; -import ru.practicum.comment.model.Comment; -import ru.practicum.comment.repository.CommentRepository; -import ru.practicum.event.dto.State; -import ru.practicum.event.model.Event; -import ru.practicum.event.repository.EventRepository; -import ru.practicum.exception.ConflictException; -import ru.practicum.exception.NotFoundException; -import ru.practicum.user.model.User; -import ru.practicum.user.repository.UserRepository; - -import java.time.LocalDateTime; - -@Service -@RequiredArgsConstructor -@Slf4j -public class CommentPrivateServiceImpl implements CommentPrivateService { - - private final CommentRepository repository; - private final UserRepository userRepository; - private final EventRepository eventRepository; - - @Override - @Transactional - public CommentDto createComment(Long userId, Long eventId, CommentCreateDto commentDto) { - log.info("createComment - invoked for user ID: {}, event ID: {}", userId, eventId); - - Comment comment = CommentMapper.toComment(commentDto); - - User author = userRepository.findById(userId) - .orElseThrow(() -> { - log.error("User with id = {} not registered", userId); - return new NotFoundException("Please register first then you can comment"); - }); - - Event event = eventRepository.findById(eventId) - .orElseThrow(() -> { - log.error("Event with id = {} does not exist", eventId); - return new NotFoundException("Event not found"); - }); - - if (!event.getState().equals(State.PUBLISHED)) { - log.error("Event ID {} has state = {}, expected PUBLISHED", eventId, event.getState()); - throw new ConflictException("Event not published, you can't comment it"); - } - - comment.setAuthor(author); - comment.setEvent(event); - comment.setApproved(true); - comment.setCreateTime(LocalDateTime.now().withNano(0)); - - log.info("Result: new comment created for user ID: {}, event ID: {}, comment ID: {}", - userId, eventId, comment.getId()); - - return CommentMapper.toCommentDto(repository.save(comment)); - } - - @Override - @Transactional - public void deleteComment(Long userId, Long comId) { - log.info("deleteComment - invoked by user ID: {}, for comment ID: {}", userId, comId); - - Comment comment = repository.findById(comId) - .orElseThrow(() -> { - log.error("Comment with id = {} does not exist", comId); - return new NotFoundException("Comment not found"); - }); - - if (!comment.getAuthor().getId().equals(userId)) { - log.error("Unauthorized access: user ID {} tried to delete comment ID {}, but author is ID {}", - userId, comId, comment.getAuthor().getId()); - throw new ConflictException("You didn't write this comment and can't delete it"); - } - - log.info("Result: comment with id = {} deleted by user ID {}", comId, userId); - repository.deleteById(comId); - } - - @Override - @Transactional - public CommentDto patchComment(Long userId, Long comId, CommentCreateDto commentCreateDto) { - log.info("patchComment - invoked by user ID: {}, for comment ID: {}", userId, comId); - - Comment comment = repository.findById(comId) - .orElseThrow(() -> { - log.error("Comment with id = {} does not exist", comId); - return new NotFoundException("Comment not found"); - }); - - if (!comment.getAuthor().getId().equals(userId)) { - log.error("Unauthorized access: user ID {} tried to patch comment ID {}, but author is ID {}", - userId, comId, comment.getAuthor().getId()); - throw new ConflictException("You didn't write this comment and can't patch it"); - } - - comment.setText(commentCreateDto.getText()); - comment.setPatchTime(LocalDateTime.now().withNano(0)); - - log.info("Result: comment with id = {} updated by user ID {}", comId, userId); - return CommentMapper.toCommentDto(comment); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicServiceImpl.java b/core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicServiceImpl.java deleted file mode 100644 index 94a8949..0000000 --- a/core/main-service/src/main/java/ru/practicum/comment/service/CommentPublicServiceImpl.java +++ /dev/null @@ -1,102 +0,0 @@ -package ru.practicum.comment.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.comment.dto.CommentDto; -import ru.practicum.comment.dto.CommentShortDto; -import ru.practicum.comment.mapper.CommentMapper; -import ru.practicum.comment.model.Comment; -import ru.practicum.comment.repository.CommentRepository; -import ru.practicum.event.repository.EventRepository; -import ru.practicum.exception.ForbiddenException; -import ru.practicum.exception.NotFoundException; - -import java.util.List; -import java.util.stream.Collectors; - -import static ru.practicum.util.Util.createPageRequestAsc; - -@Service -@RequiredArgsConstructor -@Slf4j -public class CommentPublicServiceImpl implements CommentPublicService { - - private final CommentRepository repository; - private final EventRepository eventRepository; - - @Override - public CommentDto getComment(Long comId) { - log.info("getComment - invoked for comment ID: {}", comId); - - Comment comment = repository.findById(comId) - .orElseThrow(() -> { - log.error("Comment with ID {} not found", comId); - return new NotFoundException("Comment not found"); - }); - - if (!comment.isApproved()) { - log.warn("Comment with ID {} is not approved (current state: {})", - comId, comment.isApproved()); - throw new ForbiddenException("Comment is not approved"); - } - - log.info("Result: successfully retrieved approved comment with ID {}", comId); - return CommentMapper.toCommentDto(comment); - } - - @Override - public List getCommentsByEvent(Long eventId, int from, int size) { - log.info("getCommentsByEvent - invoked for event ID: {}, from: {}, size: {}", - eventId, from, size); - - if (!eventRepository.existsById(eventId)) { - log.error("Event with ID {} does not exist", eventId); - throw new NotFoundException("Event not found"); - } - - Pageable pageable = createPageRequestAsc("createTime", from, size); - Page commentsPage = repository.findAllByEventId(eventId, pageable); - List comments = commentsPage.getContent(); - - List approvedComments = comments.stream() - .filter(Comment::isApproved) - .collect(Collectors.toList()); - - log.info("Result: retrieved {} approved comments for event ID {} (requested {} items, offset {})", - approvedComments.size(), eventId, size, from); - - return CommentMapper.toListCommentShortDto(approvedComments); - } - - @Override - public CommentDto getCommentByEventAndCommentId(Long eventId, Long commentId) { - log.info("getCommentByEventAndCommentId - invoked for event ID: {}, comment ID: {}", - eventId, commentId); - - Comment comment = repository.findById(commentId) - .orElseThrow(() -> { - log.error("Comment with ID {} not found", commentId); - return new NotFoundException("Comment not found"); - }); - - if (!comment.getEvent().getId().equals(eventId)) { - log.error("Comment ID {} does not belong to event ID {} (belongs to event ID {})", - commentId, eventId, comment.getEvent().getId()); - throw new NotFoundException("Comment not found for the specified event"); - } - - if (!comment.isApproved()) { - log.warn("Comment ID {} is not approved (cannot be accessed)", commentId); - throw new ForbiddenException("Comment is not approved"); - } - - log.info("Result: successfully retrieved comment ID {} for event ID {}", - commentId, eventId); - - return CommentMapper.toCommentDto(comment); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationAdminController.java b/core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationAdminController.java deleted file mode 100644 index 1ecb2a9..0000000 --- a/core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationAdminController.java +++ /dev/null @@ -1,55 +0,0 @@ -package ru.practicum.compilation.controller; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import ru.practicum.compilation.dto.CompilationDto; -import ru.practicum.compilation.dto.NewCompilationDto; -import ru.practicum.compilation.dto.UpdateCompilationDto; -import ru.practicum.compilation.service.CompilationAdminService; - -@RestController -@Validated -@RequestMapping("/admin/compilations") -@RequiredArgsConstructor -@Slf4j -public class CompilationAdminController { - - private final CompilationAdminService compilationAdminService; - - @PostMapping - public ResponseEntity postCompilations( - @RequestBody @Valid NewCompilationDto newCompilationDto - ) { - log.info("Calling the POST request to /admin/compilations endpoint"); - return ResponseEntity - .status(HttpStatus.CREATED) - .body(compilationAdminService.createCompilation(newCompilationDto)); - } - - @DeleteMapping("/{compId}") - public ResponseEntity deleteCompilation( - @PathVariable Long compId - ) { - log.info("Calling the DELETE request to /admin/endpoint/{compId}"); - compilationAdminService.deleteCompilation(compId); - return ResponseEntity - .status(HttpStatus.NO_CONTENT) - .body("Compilation deleted: " + compId); - } - - @PatchMapping("/{compId}") - public ResponseEntity patchCompilation( - @PathVariable Long compId, - @RequestBody @Valid UpdateCompilationDto updateCompilationDto - ) { - log.info("Calling the PATCH request to /admin/compilations/{compId} endpoint"); - return ResponseEntity - .status(HttpStatus.OK) - .body(compilationAdminService.updateCompilation(compId, updateCompilationDto)); - } -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationPublicController.java b/core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationPublicController.java deleted file mode 100644 index 3021f0a..0000000 --- a/core/main-service/src/main/java/ru/practicum/compilation/controller/CompilationPublicController.java +++ /dev/null @@ -1,44 +0,0 @@ -package ru.practicum.compilation.controller; - -import jakarta.validation.constraints.Positive; -import jakarta.validation.constraints.PositiveOrZero; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import ru.practicum.compilation.dto.CompilationDto; -import ru.practicum.compilation.service.CompilationPublicService; - -import java.util.List; - -@RestController -@Validated -@RequestMapping("/compilations") -@RequiredArgsConstructor -@Slf4j -public class CompilationPublicController { - - private final CompilationPublicService compilationPublicService; - - @GetMapping - public ResponseEntity> getCompilation( - @RequestParam(required = false) Boolean pinned, - @RequestParam(defaultValue = "0") @PositiveOrZero int from, - @RequestParam(defaultValue = "10") @Positive int size - ) { - log.info("Calling the GET request to /compilations endpoint"); - List list = compilationPublicService.readAllCompilations(pinned, from, size); - return ResponseEntity.ok(list); - } - - @GetMapping("/{compId}") - public ResponseEntity getCompilationById( - @PathVariable Long compId - ) { - log.info("Calling the GET request to /compilations/{compId} endpoint"); - CompilationDto response = compilationPublicService.readCompilationById(compId); - return ResponseEntity.ok(response); - } - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/compilation/mapper/CompilationMapper.java b/core/main-service/src/main/java/ru/practicum/compilation/mapper/CompilationMapper.java deleted file mode 100644 index 9635192..0000000 --- a/core/main-service/src/main/java/ru/practicum/compilation/mapper/CompilationMapper.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.practicum.compilation.mapper; - -import ru.practicum.compilation.dto.CompilationDto; -import ru.practicum.compilation.model.Compilation; -import ru.practicum.event.dto.EventShortDto; -import ru.practicum.event.mapper.EventMapper; - -import java.util.List; -import java.util.stream.Collectors; - -public class CompilationMapper { - - public static CompilationDto toCompilationDto(Compilation compilation) { - List eventShortDtoList = compilation.getEvents().stream() - .map(event -> - EventMapper.toEventShortDto(event, 0L, 0L) - ).collect(Collectors.toList()); - - return CompilationDto.builder() - .id(compilation.getId()) - .pinned(compilation.getPinned()) - .title(compilation.getTitle()) - .events(eventShortDtoList) - .build(); - } - - public static List toCompilationDtoList(List compilations) { - return compilations.stream() - .map(CompilationMapper::toCompilationDto) - .collect(Collectors.toList()); - } - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/compilation/model/Compilation.java b/core/main-service/src/main/java/ru/practicum/compilation/model/Compilation.java deleted file mode 100644 index e4e4004..0000000 --- a/core/main-service/src/main/java/ru/practicum/compilation/model/Compilation.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.practicum.compilation.model; - -import jakarta.persistence.*; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.Size; -import lombok.*; -import ru.practicum.event.model.Event; - -import java.util.Set; - -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Entity -@Table(name = "compilations") -public class Compilation { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @Column(name = "pinned") - private Boolean pinned; - - @Column(name = "title") - @Size(min = 1, max = 50) - @NotEmpty - private String title; - - @ManyToMany - @JoinTable(name = "compilations_events", - joinColumns = @JoinColumn(name = "compilations_id"), - inverseJoinColumns = @JoinColumn(name = "events_id")) - private Set events; - - @Override - public String toString() { - return "Compilations{" + - "id=" + id + - ", pinned=" + pinned + - ", title='" + title + '\'' + - ", events=" + events + - '}'; - } - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/compilation/repository/CompilationRepository.java b/core/main-service/src/main/java/ru/practicum/compilation/repository/CompilationRepository.java deleted file mode 100644 index 2d9e074..0000000 --- a/core/main-service/src/main/java/ru/practicum/compilation/repository/CompilationRepository.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.practicum.compilation.repository; - -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import ru.practicum.compilation.model.Compilation; - -import java.util.List; - -public interface CompilationRepository extends JpaRepository { - - @Query("SELECT c " + - "FROM Compilation c " + - "WHERE c.pinned = ?1") - List findAllByPinned(Boolean pinned, Pageable pageable); - - boolean existsByTitle(String title); - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminServiceImpl.java b/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminServiceImpl.java deleted file mode 100644 index 69d6ed6..0000000 --- a/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationAdminServiceImpl.java +++ /dev/null @@ -1,98 +0,0 @@ -package ru.practicum.compilation.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.compilation.dto.CompilationDto; -import ru.practicum.compilation.dto.NewCompilationDto; -import ru.practicum.compilation.dto.UpdateCompilationDto; -import ru.practicum.compilation.mapper.CompilationMapper; -import ru.practicum.compilation.model.Compilation; -import ru.practicum.compilation.repository.CompilationRepository; -import ru.practicum.event.model.Event; -import ru.practicum.event.repository.EventRepository; -import ru.practicum.exception.NotFoundException; - -import java.util.HashSet; -import java.util.Set; - -@Service -@Transactional -@RequiredArgsConstructor -@Slf4j -public class CompilationAdminServiceImpl implements CompilationAdminService { - - private final CompilationRepository compilationRepository; - private final EventRepository eventRepository; - - @Override - public CompilationDto createCompilation(NewCompilationDto request) { - log.info("createCompilation - invoked. Title: '{}', pinned: {}, eventCount: {}", - request.getTitle(), request.getPinned(), - (request.getEvents() != null ? request.getEvents().size() : 0)); - - Set events = (request.getEvents() != null && !request.getEvents().isEmpty()) - ? new HashSet<>(eventRepository.findAllById(request.getEvents())) - : new HashSet<>(); - - Compilation compilation = Compilation.builder() - .pinned(request.getPinned() != null && request.getPinned()) - .title(request.getTitle()) - .events(events) - .build(); - - Compilation savedCompilation = compilationRepository.save(compilation); - log.info("Result: compilation created with ID: {}, title: '{}'", - savedCompilation.getId(), savedCompilation.getTitle()); - return CompilationMapper.toCompilationDto(savedCompilation); - } - - @Override - public void deleteCompilation(Long compId) { - log.info("deleteCompilation - invoked for compilation ID: {}", compId); - - if (!compilationRepository.existsById(compId)) { - log.error("Compilation with ID {} not found", compId); - throw new NotFoundException("Compilation not found"); - } - - compilationRepository.deleteById(compId); - log.info("Result: compilation with ID {} deleted successfully", compId); - } - - @Override - public CompilationDto updateCompilation(Long compId, UpdateCompilationDto updateCompilationDto) { - log.info("updateCompilation - invoked for ID: {}. Changes - title: {}, pinned: {}, eventCount: {}", - compId, - updateCompilationDto.getTitle(), - updateCompilationDto.getPinned(), - (updateCompilationDto.getEvents() != null ? updateCompilationDto.getEvents().size() : "unchanged")); - - Compilation compilation = compilationRepository.findById(compId) - .orElseThrow(() -> { - log.error("Compilation with ID {} not found", compId); - return new NotFoundException("Compilation not found"); - }); - - if (updateCompilationDto.getTitle() != null) { - compilation.setTitle(updateCompilationDto.getTitle()); - } - if (updateCompilationDto.getPinned() != null) { - compilation.setPinned(updateCompilationDto.getPinned()); - } - if (updateCompilationDto.getEvents() != null && !updateCompilationDto.getEvents().isEmpty()) { - HashSet events = new HashSet<>(eventRepository.findAllById(updateCompilationDto.getEvents())); - compilation.setEvents(events); - } - - Compilation updatedCompilation = compilationRepository.save(compilation); - log.info("Result: compilation ID {} updated successfully. New title: '{}', pinned: {}, eventCount: {}", - updatedCompilation.getId(), - updatedCompilation.getTitle(), - updatedCompilation.getPinned(), - updatedCompilation.getEvents().size()); - - return CompilationMapper.toCompilationDto(updatedCompilation); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicServiceImpl.java b/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicServiceImpl.java deleted file mode 100644 index f4ef0e0..0000000 --- a/core/main-service/src/main/java/ru/practicum/compilation/service/CompilationPublicServiceImpl.java +++ /dev/null @@ -1,56 +0,0 @@ -package ru.practicum.compilation.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; -import ru.practicum.compilation.dto.CompilationDto; -import ru.practicum.compilation.mapper.CompilationMapper; -import ru.practicum.compilation.model.Compilation; -import ru.practicum.compilation.repository.CompilationRepository; -import ru.practicum.exception.NotFoundException; - -import java.util.List; - -@Service -@RequiredArgsConstructor -@Slf4j -public class CompilationPublicServiceImpl implements CompilationPublicService { - - private final CompilationRepository compilationRepository; - - @Override - public CompilationDto readCompilationById(Long compId) { - log.info("readCompilationById - invoked for compilation ID: {}", compId); - - Compilation compilation = compilationRepository.findById(compId) - .orElseThrow(() -> { - log.error("Compilation with ID {} not found", compId); - return new NotFoundException("Compilation not found"); - }); - - log.info("Result: compilation ID {} retrieved successfully (title: '{}')", - compId, compilation.getTitle()); - - return CompilationMapper.toCompilationDto(compilation); - } - - @Override - public List readAllCompilations(Boolean pinned, int from, int size) { - log.info("readAllCompilations - invoked. Pinned: {}, from: {}, size: {}", - pinned, from, size); - - Pageable pageable = PageRequest.of(from, size, Sort.Direction.ASC, "id"); - List compilations = (pinned == null) - ? compilationRepository.findAll(pageable).getContent() - : compilationRepository.findAllByPinned(pinned, pageable); - - int resultSize = compilations.size(); - log.info("Result: retrieved {} compilations (pinned={}, from={}, size={})", - resultSize, pinned, from, size); - - return CompilationMapper.toCompilationDtoList(compilations); - } -} diff --git a/core/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java b/core/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java deleted file mode 100644 index 6097494..0000000 --- a/core/main-service/src/main/java/ru/practicum/event/controller/EventAdminController.java +++ /dev/null @@ -1,66 +0,0 @@ -package ru.practicum.event.controller; - -import com.fasterxml.jackson.annotation.JsonFormat; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Positive; -import jakarta.validation.constraints.PositiveOrZero; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import ru.practicum.event.dto.EventAdminParams; -import ru.practicum.event.dto.EventFullDto; -import ru.practicum.event.dto.State; -import ru.practicum.event.dto.UpdateEventDto; -import ru.practicum.event.service.EventAdminService; - -import java.time.LocalDateTime; -import java.util.Collection; -import java.util.List; - -@RestController -@RequestMapping("/admin/events") -@RequiredArgsConstructor -@Slf4j -@Validated -public class EventAdminController { - - private final EventAdminService eventAdminService; - - // Поиск событий - @GetMapping - Collection getAllEventsByParams( - @RequestParam(required = false) List users, - @RequestParam(required = false) List states, - @RequestParam(required = false) List categories, - @RequestParam(required = false) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, - @RequestParam(required = false) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, - @RequestParam(defaultValue = "0") @PositiveOrZero Long from, - @RequestParam(defaultValue = "10") @Positive Long size - ) { - EventAdminParams params = EventAdminParams.builder() - .users(users) - .states(states) - .categories(categories) - .rangeStart(rangeStart) - .rangeEnd(rangeEnd) - .from(from) - .size(size) - .build(); - - log.info("Calling to endpoint /admin/events GetMapping for params: " + params.toString()); - return eventAdminService.getAllEventsByParams(params); - } - - // Редактирование данных события и его статуса (отклонение/публикация). - @PatchMapping("/{eventId}") - EventFullDto updateEventByAdmin( - @PathVariable Long eventId, - @RequestBody @Valid UpdateEventDto updateEventDto - ) { - log.info("Calling to endpoint /admin/events/{eventId} PatchMapping for eventId: " + eventId + "." - + " UpdateEvent: " + updateEventDto.toString()); - return eventAdminService.updateEventByAdmin(eventId, updateEventDto); - } - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java b/core/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java deleted file mode 100644 index ac70da1..0000000 --- a/core/main-service/src/main/java/ru/practicum/event/controller/EventPrivateController.java +++ /dev/null @@ -1,74 +0,0 @@ -package ru.practicum.event.controller; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import ru.practicum.event.dto.EventFullDto; -import ru.practicum.event.dto.EventShortDto; -import ru.practicum.event.dto.NewEventDto; -import ru.practicum.event.dto.UpdateEventDto; -import ru.practicum.event.service.EventPrivateService; - -import java.util.Collection; - -@RestController -@RequestMapping("/users/{userId}/events") -@RequiredArgsConstructor -@Slf4j -@Validated -public class EventPrivateController { - - private final EventPrivateService eventPrivateService; - - // Добавление нового события - @PostMapping - @ResponseStatus(HttpStatus.CREATED) - EventFullDto addNewEventByUser( - @PathVariable @Positive Long userId, - @Valid @RequestBody NewEventDto newEventDto - ) { - log.info("Calling to endpoint /users/{userId}/events PostMapping for userId: " + userId); - return eventPrivateService.addEvent(userId, newEventDto); - } - - // Получение событий, добавленных текущим пользователем - @GetMapping - Collection getAllEventsByUserId( - @PathVariable @Positive Long userId, - @RequestParam(defaultValue = "0") Long from, - @RequestParam(defaultValue = "10") Long size - ) { - log.info("Calling to endpoint /users/{userId}/events GetMapping for userId: " + userId); - return eventPrivateService.getEventsByUserId(userId, from, size); - } - - // Получение полной информации о событии добавленном текущим пользователем - @GetMapping("/{eventId}") - EventFullDto getEventByUserIdAndEventId( - @PathVariable @Positive Long userId, - @PathVariable @Positive Long eventId - ) { - log.info("Calling to endpoint /users/{userId}/events/{eventId} GetMapping for userId: " - + userId + " and eventId: " + eventId); - return eventPrivateService.getEventByUserIdAndEventId(userId, eventId); - } - - // Изменение события добавленного текущим пользователем - @PatchMapping("/{eventId}") - EventFullDto updateEventByUserIdAndEventId( - @PathVariable @Positive Long userId, - @PathVariable @Positive Long eventId, - @Valid @RequestBody UpdateEventDto updateEventDto - ) { - log.info("Calling to endpoint /users/{userId}/events/{eventId} PatchMapping for userId: " + userId - + " and eventId: " + eventId + "." - + "Information by eventDto: " + updateEventDto.toString()); - return eventPrivateService.updateEventByUserIdAndEventId(userId, eventId, updateEventDto); - } - - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java b/core/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java deleted file mode 100644 index 7a6eeb9..0000000 --- a/core/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java +++ /dev/null @@ -1,65 +0,0 @@ -package ru.practicum.event.controller; - -import com.fasterxml.jackson.annotation.JsonFormat; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; -import ru.practicum.event.dto.EventFullDto; -import ru.practicum.event.dto.EventParams; -import ru.practicum.event.dto.EventShortDto; -import ru.practicum.event.dto.EventSort; -import ru.practicum.event.service.EventPublicService; - -import java.time.LocalDateTime; -import java.util.List; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/events") -@Slf4j -public class EventPublicController { - - private final EventPublicService eventPublicService; - - // Получение событий с возможностью фильтрации - @GetMapping - List getAllEventsByParams( - @RequestParam(required = false) String text, - @RequestParam(required = false) List categories, - @RequestParam(required = false) Boolean paid, - @RequestParam(required = false) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeStart, - @RequestParam(required = false) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime rangeEnd, - @RequestParam(defaultValue = "false") Boolean onlyAvailable, - @RequestParam(defaultValue = "EVENT_DATE") EventSort eventSort, - @RequestParam(defaultValue = "0") Long from, - @RequestParam(defaultValue = "10") Long size, - HttpServletRequest request - ) { - EventParams params = EventParams.builder() - .text(text) - .categories(categories) - .paid(paid) - .rangeStart(rangeStart) - .rangeEnd(rangeEnd) - .onlyAvailable(onlyAvailable) - .eventSort(eventSort) - .from(from) - .size(size) - .build(); - log.info("Calling to endpoint /events GetMapping for params: " + params.toString()); - return eventPublicService.getAllEventsByParams(params, request); - } - - // Получение подробной информации об опубликованном событии по его идентификатору - @GetMapping("/{id}") - EventFullDto getInformationAboutEventByEventId( - @PathVariable @Positive Long id, - HttpServletRequest request - ) { - log.info("Calling to endpoint /events/{id} GetMapping for eventId: " + id); - return eventPublicService.getEventById(id, request); - } - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/event/model/View.java b/core/main-service/src/main/java/ru/practicum/event/model/View.java deleted file mode 100644 index 0945701..0000000 --- a/core/main-service/src/main/java/ru/practicum/event/model/View.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.practicum.event.model; - -import jakarta.persistence.*; -import lombok.*; - -@Getter -@Setter -@Builder -@AllArgsConstructor -@NoArgsConstructor -@Entity -@Table(name = "views") -public class View { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @ManyToOne - @JoinColumn(name = "event_id") - private Event event; - - @Column(name = "ip", length = 15, nullable = false) - private String ip; - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/event/service/EventAdminServiceImpl.java b/core/main-service/src/main/java/ru/practicum/event/service/EventAdminServiceImpl.java deleted file mode 100644 index 551b6ed..0000000 --- a/core/main-service/src/main/java/ru/practicum/event/service/EventAdminServiceImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -package ru.practicum.event.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.category.model.Category; -import ru.practicum.category.repository.CategoryRepository; -import ru.practicum.event.dto.*; -import ru.practicum.event.mapper.EventMapper; -import ru.practicum.event.mapper.LocationMapper; -import ru.practicum.event.model.Event; -import ru.practicum.event.repository.EventRepository; -import ru.practicum.event.repository.JpaSpecifications; -import ru.practicum.event.repository.ViewRepository; -import ru.practicum.exception.ConflictException; -import ru.practicum.exception.NotFoundException; -import ru.practicum.request.dto.ParticipationRequestStatus; -import ru.practicum.request.repository.RequestRepository; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -public class EventAdminServiceImpl implements EventAdminService { - - private final EventRepository eventRepository; - private final CategoryRepository categoryRepository; - private final RequestRepository requestRepository; - private final ViewRepository viewRepository; - - // Поиск событий - @Override - public List getAllEventsByParams(EventAdminParams params) { - Pageable pageable = PageRequest.of( - params.getFrom().intValue() / params.getSize().intValue(), - params.getSize().intValue() - ); - List events = eventRepository.findAll(JpaSpecifications.adminFilters(params), pageable).getContent(); - - List eventIds = events.stream().map(Event::getId).toList(); - Map confirmedRequestsMap = requestRepository.getConfirmedRequestsByEventIds(eventIds) - .stream() - .collect(Collectors.toMap( - r -> (Long) r[0], - r -> (Long) r[1] - )); - Map viewsMap = viewRepository.countsByEventIds(eventIds) - .stream() - .collect(Collectors.toMap( - r -> (Long) r[0], - r -> (Long) r[1] - )); - - return events.stream() - .map(e -> EventMapper.toEventFullDto(e, confirmedRequestsMap.get(e.getId()), viewsMap.get(e.getId()))) - .toList(); - } - - // Редактирование данных события и его статуса (отклонение/публикация). - @Override - @Transactional(readOnly = false) - public EventFullDto updateEventByAdmin(Long eventId, UpdateEventDto updateEventDto) { - Event event = eventRepository.findById(eventId) - .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); - - if (updateEventDto.getCategory() != null) { - Category category = categoryRepository.findById(updateEventDto.getCategory()) - .orElseThrow(() -> new NotFoundException("Category with id=" + updateEventDto.getCategory() + " not found")); - event.setCategory(category); - } - - if (updateEventDto.getTitle() != null) event.setTitle(updateEventDto.getTitle()); - if (updateEventDto.getAnnotation() != null) event.setAnnotation(updateEventDto.getAnnotation()); - if (updateEventDto.getDescription() != null) event.setDescription(updateEventDto.getDescription()); - if (updateEventDto.getLocation() != null) - event.setLocation(LocationMapper.toEntity(updateEventDto.getLocation())); - if (updateEventDto.getPaid() != null) event.setPaid(updateEventDto.getPaid()); - if (updateEventDto.getParticipantLimit() != null) - event.setParticipantLimit(updateEventDto.getParticipantLimit()); - if (updateEventDto.getRequestModeration() != null) - event.setRequestModeration(updateEventDto.getRequestModeration()); - if (updateEventDto.getEventDate() != null) event.setEventDate(updateEventDto.getEventDate()); - - if (Objects.equals(updateEventDto.getStateAction(), StateAction.REJECT_EVENT)) { - // событие можно отклонить, только если оно еще не опубликовано (Ожидается код ошибки 409) - if (Objects.equals(event.getState(), State.PUBLISHED)) { - throw new ConflictException("Event in PUBLISHED state can not be rejected"); - } - event.setState(State.CANCELED); - } else if (Objects.equals(updateEventDto.getStateAction(), StateAction.PUBLISH_EVENT)) { - // дата начала изменяемого события должна быть не ранее чем за час от даты публикации. (Ожидается код ошибки 409) - if (LocalDateTime.now().plusHours(1).isAfter(event.getEventDate())) { - throw new ConflictException("Event time must be at least 1 hours from publish time"); - } - // событие можно публиковать, только если оно в состоянии ожидания публикации (Ожидается код ошибки 409) - if (!Objects.equals(event.getState(), State.PENDING)) { - throw new ConflictException("Event should be in PENDING state"); - } - event.setState(State.PUBLISHED); - event.setPublishedOn(LocalDateTime.now()); - } - - eventRepository.save(event); - Long confirmedRequests = requestRepository.countByEventIdAndStatus(eventId, ParticipationRequestStatus.CONFIRMED); - Long views = viewRepository.countByEventId(eventId); - return EventMapper.toEventFullDto(event, confirmedRequests, views); - } - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/event/service/EventPrivateServiceImpl.java b/core/main-service/src/main/java/ru/practicum/event/service/EventPrivateServiceImpl.java deleted file mode 100644 index ad80c86..0000000 --- a/core/main-service/src/main/java/ru/practicum/event/service/EventPrivateServiceImpl.java +++ /dev/null @@ -1,158 +0,0 @@ -package ru.practicum.event.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.category.model.Category; -import ru.practicum.category.repository.CategoryRepository; -import ru.practicum.event.dto.*; -import ru.practicum.event.mapper.EventMapper; -import ru.practicum.event.mapper.LocationMapper; -import ru.practicum.event.model.Event; -import ru.practicum.event.repository.EventRepository; -import ru.practicum.event.repository.ViewRepository; -import ru.practicum.exception.ConflictException; -import ru.practicum.exception.NotFoundException; -import ru.practicum.request.dto.ParticipationRequestStatus; -import ru.practicum.request.repository.RequestRepository; -import ru.practicum.user.model.User; -import ru.practicum.user.repository.UserRepository; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -public class EventPrivateServiceImpl implements EventPrivateService { - - private final UserRepository userRepository; - private final CategoryRepository categoryRepository; - private final EventRepository eventRepository; - private final RequestRepository requestRepository; - private final ViewRepository viewRepository; - - // Добавление нового события - @Override - @Transactional(readOnly = false) - public EventFullDto addEvent(Long userId, NewEventDto newEventDto) { - User initiator = userRepository.findById(userId) - .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")); - - Event newEvent = EventMapper.toEvent(newEventDto, initiator, category); - eventRepository.save(newEvent); - return EventMapper.toEventFullDto(newEvent, 0L, 0L); - } - - // Получение полной информации о событии добавленном текущим пользователем - @Override - public EventFullDto getEventByUserIdAndEventId(Long userId, Long eventId) { - User initiator = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("User with id=" + userId + " was not found")); - Event event = eventRepository.findById(eventId) - .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); - - if (!Objects.equals(initiator.getId(), event.getInitiator().getId())) { - throw new ConflictException("User " + userId + " is not an initiator of event " + eventId, "Forbidden action"); - } - - Long confirmedRequests = requestRepository.countByEventIdAndStatus(event.getId(), ParticipationRequestStatus.CONFIRMED); - Long views = viewRepository.countByEventId(eventId); - return EventMapper.toEventFullDto(event, confirmedRequests, views); - } - - // Получение событий, добавленных текущим пользователем - @Override - public List getEventsByUserId(Long userId, Long from, Long size) { - User initiator = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("User with id=" + userId + " was not found")); - - Pageable pageable = PageRequest.of( - from.intValue() / size.intValue(), - size.intValue(), - Sort.by("eventDate").descending() - ); - - List events = eventRepository.findByInitiatorId(userId, pageable); - List eventIds = events.stream().map(Event::getId).toList(); - Map confirmedRequestsMap = requestRepository.getConfirmedRequestsByEventIds(eventIds) - .stream() - .collect(Collectors.toMap( - r -> (Long) r[0], - r -> (Long) r[1] - )); - Map viewsMap = viewRepository.countsByEventIds(eventIds) - .stream() - .collect(Collectors.toMap( - r -> (Long) r[0], - r -> (Long) r[1] - )); - - return events.stream() - .map(e -> EventMapper.toEventShortDto(e, confirmedRequestsMap.get(e.getId()), viewsMap.get(e.getId()))) - .toList(); - } - - // Изменение события добавленного текущим пользователем - @Override - @Transactional(readOnly = false) - public EventFullDto updateEventByUserIdAndEventId(Long userId, Long eventId, UpdateEventDto updateEventDto) { - User initiator = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("User with id=" + userId + " was not found")); - Event event = eventRepository.findById(eventId) - .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); - - if (!Objects.equals(initiator.getId(), event.getInitiator().getId())) { - throw new ConflictException("User " + userId + " is not an initiator of event " + eventId, "Forbidden action"); - } - - // изменить можно только отмененные события или события в состоянии ожидания модерации (Ожидается код ошибки 409) - if (event.getState() != State.PENDING && event.getState() != State.CANCELED) { - throw new ConflictException("Only pending or canceled events can be changed"); - } - - // дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента (Ожидается код ошибки 409) - if (updateEventDto.getEventDate() != null && - updateEventDto.getEventDate().isBefore(LocalDateTime.now().plusHours(2))) { - throw new ConflictException("Event date must be at least 2 hours from now"); - } - - if (updateEventDto.getCategory() != null) { - Category category = categoryRepository.findById(updateEventDto.getCategory()) - .orElseThrow(() -> new NotFoundException("Category with id=" + updateEventDto.getCategory() + " not found")); - event.setCategory(category); - } - - if (updateEventDto.getTitle() != null) event.setTitle(updateEventDto.getTitle()); - if (updateEventDto.getAnnotation() != null) event.setAnnotation(updateEventDto.getAnnotation()); - if (updateEventDto.getDescription() != null) event.setDescription(updateEventDto.getDescription()); - if (updateEventDto.getLocation() != null) - event.setLocation(LocationMapper.toEntity(updateEventDto.getLocation())); - if (updateEventDto.getPaid() != null) event.setPaid(updateEventDto.getPaid()); - if (updateEventDto.getParticipantLimit() != null) - event.setParticipantLimit(updateEventDto.getParticipantLimit()); - if (updateEventDto.getRequestModeration() != null) - event.setRequestModeration(updateEventDto.getRequestModeration()); - if (updateEventDto.getEventDate() != null) event.setEventDate(updateEventDto.getEventDate()); - - if (Objects.equals(updateEventDto.getStateAction(), StateAction.CANCEL_REVIEW)) { - event.setState(State.CANCELED); - } else if (Objects.equals(updateEventDto.getStateAction(), StateAction.SEND_TO_REVIEW)) { - event.setState(State.PENDING); - } - - eventRepository.save(event); - Long confirmedRequests = requestRepository.countByEventIdAndStatus(event.getId(), ParticipationRequestStatus.CONFIRMED); - Long views = viewRepository.countByEventId(eventId); - return EventMapper.toEventFullDto(event, confirmedRequests, views); - } - - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/event/service/EventPublicServiceImpl.java b/core/main-service/src/main/java/ru/practicum/event/service/EventPublicServiceImpl.java deleted file mode 100644 index 40ffb3f..0000000 --- a/core/main-service/src/main/java/ru/practicum/event/service/EventPublicServiceImpl.java +++ /dev/null @@ -1,117 +0,0 @@ -package ru.practicum.event.service; - -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.EventHitDto; -import ru.practicum.event.dto.*; -import ru.practicum.event.mapper.EventMapper; -import ru.practicum.event.model.Event; -import ru.practicum.event.model.View; -import ru.practicum.event.repository.EventRepository; -import ru.practicum.event.repository.JpaSpecifications; -import ru.practicum.event.repository.ViewRepository; -import ru.practicum.ewm.client.StatClient; -import ru.practicum.exception.BadRequestException; -import ru.practicum.exception.NotFoundException; -import ru.practicum.request.dto.ParticipationRequestStatus; -import ru.practicum.request.repository.RequestRepository; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -public class EventPublicServiceImpl implements EventPublicService { - - private final StatClient statClient; - private final EventRepository eventRepository; - private final RequestRepository requestRepository; - private final ViewRepository viewRepository; - - // Получение событий с возможностью фильтрации - @Override - public List getAllEventsByParams(EventParams params, HttpServletRequest request) { - - if (params.getRangeStart() != null && params.getRangeEnd() != null && params.getRangeEnd().isBefore(params.getRangeStart())) { - throw new BadRequestException("rangeStart should be before rangeEnd"); - } - - // если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, которые произойдут позже текущей даты и времени - if (params.getRangeStart() == null) params.setRangeStart(LocalDateTime.now()); - - // сортировочка и пагинация - Sort sort = Sort.by(Sort.Direction.ASC, "eventDate"); - if (EventSort.VIEWS.equals(params.getEventSort())) sort = Sort.by(Sort.Direction.DESC, "views"); - PageRequest pageRequest = PageRequest.of(params.getFrom().intValue() / params.getSize().intValue(), - params.getSize().intValue(), sort); - - Page events = eventRepository.findAll(JpaSpecifications.publicFilters(params), pageRequest); - List eventIds = events.stream().map(Event::getId).toList(); - - // информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие - Map confirmedRequestsMap = requestRepository.getConfirmedRequestsByEventIds(eventIds) - .stream() - .collect(Collectors.toMap( - r -> (Long) r[0], - r -> (Long) r[1] - )); - Map viewsMap = viewRepository.countsByEventIds(eventIds) - .stream() - .collect(Collectors.toMap( - r -> (Long) r[0], - r -> (Long) r[1] - )); - - // информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики - statClient.hit(EventHitDto.builder() - .ip(request.getRemoteAddr()) - .uri(request.getRequestURI()) - .app("ewm-main-service") - .timestamp(LocalDateTime.now()) - .build()); - - return events.stream() - .map(e -> EventMapper.toEventShortDto(e, confirmedRequestsMap.get(e.getId()), viewsMap.get(e.getId()))) - .toList(); - } - - // Получение подробной информации об опубликованном событии по его идентификатору - @Override - @Transactional(readOnly = false) - public EventFullDto getEventById(Long eventId, HttpServletRequest request) { - // событие должно быть опубликовано - Event event = eventRepository.findByIdAndState(eventId, State.PUBLISHED) - .orElseThrow(() -> new NotFoundException("Event not found")); - - // информация о событии должна включать в себя количество просмотров и количество подтвержденных запросов - Long confirmedRequests = requestRepository.countByEventIdAndStatus(eventId, ParticipationRequestStatus.CONFIRMED); - Long views = viewRepository.countByEventId(eventId); - - // делаем новый уникальный просмотр - if (!viewRepository.existsByEventIdAndIp(eventId, request.getRemoteAddr())) { - View view = View.builder() - .event(event) - .ip(request.getRemoteAddr()) - .build(); - viewRepository.save(view); - } - - // информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики - statClient.hit(EventHitDto.builder() - .ip(request.getRemoteAddr()) - .uri(request.getRequestURI()) - .app("ewm-main-service") - .timestamp(LocalDateTime.now()) - .build()); - - return EventMapper.toEventFullDto(event, confirmedRequests, views); - } - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/request/mapper/RequestMapper.java b/core/main-service/src/main/java/ru/practicum/request/mapper/RequestMapper.java deleted file mode 100644 index 7a0540e..0000000 --- a/core/main-service/src/main/java/ru/practicum/request/mapper/RequestMapper.java +++ /dev/null @@ -1,30 +0,0 @@ -package ru.practicum.request.mapper; - -import ru.practicum.event.model.Event; -import ru.practicum.request.dto.ParticipationRequestDto; -import ru.practicum.request.model.Request; -import ru.practicum.user.model.User; - -public class RequestMapper { - - public static ParticipationRequestDto toDto(Request request) { - ParticipationRequestDto dto = new ParticipationRequestDto(); - dto.setId(request.getId()); - dto.setRequester(request.getRequester().getId()); - dto.setEvent(request.getEvent().getId()); - dto.setStatus(request.getStatus()); - dto.setCreated(request.getCreated()); - return dto; - } - - public static Request toEntity(ParticipationRequestDto dto, User requester, Event event) { - Request request = new Request(); - request.setId(dto.getId()); - request.setRequester(requester); - request.setEvent(event); - request.setStatus(dto.getStatus()); - request.setCreated(dto.getCreated()); - return request; - } - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/request/model/Request.java b/core/main-service/src/main/java/ru/practicum/request/model/Request.java deleted file mode 100644 index 5b3ca06..0000000 --- a/core/main-service/src/main/java/ru/practicum/request/model/Request.java +++ /dev/null @@ -1,40 +0,0 @@ -package ru.practicum.request.model; - -import jakarta.persistence.*; -import lombok.*; -import ru.practicum.event.model.Event; -import ru.practicum.request.dto.ParticipationRequestStatus; -import ru.practicum.user.model.User; - -import java.time.LocalDateTime; - -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Entity -@Table(name = "requests") -public class Request { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @ManyToOne - @JoinColumn(name = "requester_id") - private User requester; - - @ManyToOne - @JoinColumn(name = "event_id") - private Event event; - - @Enumerated(EnumType.STRING) - @Column(name = "status") - private ParticipationRequestStatus status; - - @Column(name = "created_at") - private LocalDateTime created; - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/request/service/RequestService.java b/core/main-service/src/main/java/ru/practicum/request/service/RequestService.java deleted file mode 100644 index 94884f2..0000000 --- a/core/main-service/src/main/java/ru/practicum/request/service/RequestService.java +++ /dev/null @@ -1,197 +0,0 @@ -package ru.practicum.request.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.event.dto.State; -import ru.practicum.event.model.Event; -import ru.practicum.event.repository.EventRepository; -import ru.practicum.exception.ConflictException; -import ru.practicum.exception.NotFoundException; -import ru.practicum.request.dto.EventRequestStatusUpdateRequestDto; -import ru.practicum.request.dto.EventRequestStatusUpdateResultDto; -import ru.practicum.request.dto.ParticipationRequestDto; -import ru.practicum.request.dto.ParticipationRequestStatus; -import ru.practicum.request.mapper.RequestMapper; -import ru.practicum.request.model.Request; -import ru.practicum.request.repository.RequestRepository; -import ru.practicum.user.model.User; -import ru.practicum.user.repository.UserRepository; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; - -@Service -@RequiredArgsConstructor -public class RequestService { - - private final RequestRepository requestRepository; - private final UserRepository userRepository; - private final EventRepository eventRepository; - - // ЗАЯВКИ ТЕКУЩЕГО ПОЛЬЗОВАТЕЛЯ - - // Добавление запроса от текущего пользователя на участие в событии - @Transactional(readOnly = false) - public ParticipationRequestDto addRequest(Long userId, Long eventId) { - User requester = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("User with id=" + userId + " was not found")); - Event event = eventRepository.findById(eventId) - .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); - - // нельзя добавить повторный запрос (Ожидается код ошибки 409) - if (requestRepository.existsByRequesterIdAndEventId(userId, eventId)) { - throw new ConflictException("User tries to make duplicate request", "Forbidden action"); - } - - // инициатор события не может добавить запрос на участие в своём событии (Ожидается код ошибки 409) - if (Objects.equals(requester.getId(), event.getInitiator().getId())) { - throw new ConflictException("User tries to request for his own event", "Forbidden action"); - } - - // нельзя участвовать в неопубликованном событии (Ожидается код ошибки 409) - if (event.getState() != State.PUBLISHED) { - throw new ConflictException("User tries to request for non-published event", "Forbidden action"); - } - - // если у события достигнут лимит запросов на участие - необходимо вернуть ошибку (Ожидается код ошибки 409) - long confirmedRequestCount = requestRepository.countByEventIdAndStatus(eventId, ParticipationRequestStatus.CONFIRMED); - if (event.getParticipantLimit() > 0 && confirmedRequestCount >= event.getParticipantLimit()) { - throw new ConflictException("Participants limit is already reached", "Forbidden action"); - } - - // если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного - ParticipationRequestStatus newRequestStatus = ParticipationRequestStatus.PENDING; - if (!event.getRequestModeration()) newRequestStatus = ParticipationRequestStatus.CONFIRMED; - if (Objects.equals(event.getParticipantLimit(), 0L)) newRequestStatus = ParticipationRequestStatus.CONFIRMED; - - Request newRequest = Request.builder() - .requester(requester) - .event(event) - .status(newRequestStatus) - .created(LocalDateTime.now()) - .build(); - requestRepository.save(newRequest); - return RequestMapper.toDto(newRequest); - } - - // Отмена своего запроса на участие в событии - @Transactional(readOnly = false) - public ParticipationRequestDto cancelRequest(Long userId, Long requestId) { - User requester = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("User with id=" + userId + " was not found")); - Request existingRequest = requestRepository.findById(requestId) - .orElseThrow(() -> new NotFoundException("Request with id=" + requestId + " was not found")); - - existingRequest.setStatus(ParticipationRequestStatus.CANCELED); - requestRepository.save(existingRequest); - return RequestMapper.toDto(existingRequest); - } - - // Получение информации о заявках текущего пользователя на участие в чужих событиях - public Collection findRequesterRequests(Long userId) { - return requestRepository.findByRequesterId(userId).stream() - .filter(Objects::nonNull) - .map(RequestMapper::toDto) - .toList(); - } - - // ЗАЯВКИ НА КОНКРЕТНОЕ СОБЫТИЕ - - // Получение информации о запросах на участие в событии текущего пользователя - public Collection findEventRequests(Long userId, Long eventId) { - User initiator = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("User with id=" + userId + " was not found")); - Event event = eventRepository.findById(eventId) - .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); - - // проверка что юзер - инициатор события - if (!Objects.equals(initiator.getId(), event.getInitiator().getId())) { - throw new ConflictException("User " + userId + " is not an initiator of event " + eventId, "Forbidden action"); - } - - return requestRepository.findByEventId(eventId).stream() - .filter(Objects::nonNull) - .map(RequestMapper::toDto) - .toList(); - } - - // Изменение статуса (подтверждена, отменена) заявок на участие в событии текущего пользователя - @Transactional(readOnly = false) - public EventRequestStatusUpdateResultDto moderateRequest( - Long userId, - Long eventId, - EventRequestStatusUpdateRequestDto updateRequestDto - ) { - User initiator = userRepository.findById(userId) - .orElseThrow(() -> new NotFoundException("User with id=" + userId + " was not found")); - Event event = eventRepository.findById(eventId) - .orElseThrow(() -> new NotFoundException("Event with id=" + eventId + " was not found")); - - // проверка что юзер - инициатор события - if (!Objects.equals(initiator.getId(), event.getInitiator().getId())) { - throw new ConflictException("User " + userId + " is not an initiator of event " + eventId, "Forbidden action"); - } - - // если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется - if (event.getParticipantLimit() < 1 || !event.getRequestModeration()) { - return new EventRequestStatusUpdateResultDto(); - } - - // статус можно изменить только у заявок, находящихся в состоянии ожидания (Ожидается код ошибки 409) - List requests = requestRepository.findAllById(updateRequestDto.getRequestIds()); - for (Request request : requests) { - if (request.getStatus() != ParticipationRequestStatus.PENDING) { - throw new ConflictException("Request " + request.getId() + " must have status PENDING", "Incorrectly made request"); - } - } - - List requestsToConfirm = new ArrayList<>(); - List requestsToReject = new ArrayList<>(); - - if (updateRequestDto.getStatus() == ParticipationRequestStatus.CONFIRMED) { - - long confirmedRequestCount = requestRepository.countByEventIdAndStatus(eventId, ParticipationRequestStatus.CONFIRMED); - - if (confirmedRequestCount >= event.getParticipantLimit()) { - // нельзя подтвердить заявку, если уже достигнут лимит по заявкам на данное событие (Ожидается код ошибки 409) - throw new ConflictException("The participant limit has been reached for event " + eventId, "Forbidden action"); - } else if (updateRequestDto.getRequestIds().size() < event.getParticipantLimit() - confirmedRequestCount) { - requestsToConfirm = updateRequestDto.getRequestIds(); - requestRepository.updateStatusByIds(requestsToConfirm, ParticipationRequestStatus.CONFIRMED); - } else { - long freeSeats = event.getParticipantLimit() - confirmedRequestCount; - requestsToConfirm = updateRequestDto.getRequestIds().stream() - .limit(freeSeats) - .toList(); - requestsToReject = updateRequestDto.getRequestIds().stream() - .skip(freeSeats) - .toList(); - requestRepository.updateStatusByIds(requestsToConfirm, ParticipationRequestStatus.CONFIRMED); - // если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки необходимо отклонить - requestRepository.setStatusToRejectForAllPending(eventId); - } - - } else if (updateRequestDto.getStatus() == ParticipationRequestStatus.REJECTED) { - requestsToReject = updateRequestDto.getRequestIds(); - requestRepository.updateStatusByIds(requestsToReject, ParticipationRequestStatus.REJECTED); - } else { - throw new ConflictException("Only CONFIRMED and REJECTED statuses are allowed", "Forbidden action"); - } - - EventRequestStatusUpdateResultDto resultDto = new EventRequestStatusUpdateResultDto(); - List confirmedRequests = requestRepository.findAllById(requestsToConfirm).stream() - .map(RequestMapper::toDto) - .toList(); - resultDto.setConfirmedRequests(confirmedRequests); - List rejectedRequests = requestRepository.findAllById(requestsToReject).stream() - .map(RequestMapper::toDto) - .toList(); - resultDto.setRejectedRequests(rejectedRequests); - return resultDto; - } - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/user/controller/UserController.java b/core/main-service/src/main/java/ru/practicum/user/controller/UserController.java deleted file mode 100644 index 31d2fce..0000000 --- a/core/main-service/src/main/java/ru/practicum/user/controller/UserController.java +++ /dev/null @@ -1,53 +0,0 @@ -package ru.practicum.user.controller; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import ru.practicum.user.dto.NewUserRequestDto; -import ru.practicum.user.dto.UserDto; -import ru.practicum.user.service.UserService; - -import java.util.Collection; -import java.util.List; - -@RestController -@RequiredArgsConstructor -@Validated -public class UserController { - - private final UserService userService; - - // MODIFY OPS - - @PostMapping("/admin/users") - @ResponseStatus(HttpStatus.CREATED) - public UserDto createUser( - @RequestBody @Valid NewUserRequestDto newUserRequestDto - ) { - return userService.create(newUserRequestDto); - } - - @DeleteMapping("/admin/users/{userId}") - @ResponseStatus(HttpStatus.NO_CONTENT) - public void deleteUser( - @PathVariable @Positive(message = "User Id not valid") Long userId - ) { - userService.delete(userId); - } - - // GET COLLECTION - - @GetMapping("/admin/users") - public Collection getUsers( - @RequestParam(required = false) List ids, - @RequestParam(defaultValue = "0") Integer from, - @RequestParam(defaultValue = "10") Integer size - ) { - return userService.findByIdListWithOffsetAndLimit(ids, from, size); - } - - -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/util/Util.java b/core/main-service/src/main/java/ru/practicum/util/Util.java deleted file mode 100644 index a14e42f..0000000 --- a/core/main-service/src/main/java/ru/practicum/util/Util.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.practicum.util; - -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; - -public class Util { - - private Util() { - } - - public static PageRequest createPageRequestAsc(int from, int size) { - return PageRequest.of(from, size, Sort.Direction.ASC, "id"); - } - - public static PageRequest createPageRequestDesc(String sortBy, int from, int size) { - return PageRequest.of(from > 0 ? from / size : 0, size, Sort.by(sortBy).descending()); - } - - public static PageRequest createPageRequestAsc(String sortBy, int from, int size) { - return PageRequest.of(from > 0 ? from / size : 0, size, Sort.by(sortBy).ascending()); - } - -} \ No newline at end of file diff --git a/core/main-service/src/main/resources/schema.sql b/core/main-service/src/main/resources/schema.sql deleted file mode 100644 index eb81edb..0000000 --- a/core/main-service/src/main/resources/schema.sql +++ /dev/null @@ -1,76 +0,0 @@ - -CREATE TABLE IF NOT EXISTS users ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - email VARCHAR(254) NOT NULL UNIQUE, - name VARCHAR(250) NOT NULL -); - -CREATE TABLE IF NOT EXISTS categories ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - cat_name VARCHAR(50) NOT NULL UNIQUE -); - - -CREATE TABLE IF NOT EXISTS events ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - initiator BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - categories_id BIGINT NOT NULL REFERENCES categories(id) ON DELETE RESTRICT, - title VARCHAR(120) NOT NULL, - annotation VARCHAR(2000) NOT NULL, - description VARCHAR(7000) NOT NULL, - state VARCHAR(20) NOT NULL, - lat FLOAT, - lon FLOAT, - participant_limit BIGINT NOT NULL, - request_moderation BOOLEAN NOT NULL, - paid BOOLEAN NOT NULL, - event_date TIMESTAMP NOT NULL, - published_on TIMESTAMP, - created_on TIMESTAMP NOT NULL -); - -CREATE TABLE IF NOT EXISTS views ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - event_id BIGINT NOT NULL REFERENCES events(id) ON DELETE CASCADE ON UPDATE RESTRICT, - ip VARCHAR(15) NOT NULL -); - -CREATE TABLE IF NOT EXISTS requests ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - requester_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE ON UPDATE RESTRICT, - event_id BIGINT NOT NULL REFERENCES events(id) ON DELETE CASCADE ON UPDATE RESTRICT, - status VARCHAR(15) NOT NULL, - created_at TIMESTAMP NOT NULL -); - -CREATE TABLE IF NOT EXISTS compilations ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - pinned BOOLEAN NOT NULL, - title VARCHAR(50) NOT NULL -); - -CREATE TABLE IF NOT EXISTS compilations_events ( - compilations_id BIGINT NOT NULL, - events_id BIGINT NOT NULL, - - CONSTRAINT pk_compilations_events PRIMARY KEY (compilations_id, events_id), - CONSTRAINT fk_compilations FOREIGN KEY (compilations_id) REFERENCES compilations (id) ON DELETE CASCADE, - CONSTRAINT fk_events FOREIGN KEY (events_id) REFERENCES events (id) ON DELETE CASCADE -); - -CREATE TABLE IF NOT EXISTS comments ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - textual_content VARCHAR(1000) NOT NULL, - author_id BIGINT NOT NULL, - event_id BIGINT NOT NULL, - create_time TIMESTAMP NOT NULL, - patch_time TIMESTAMP, - approved BOOLEAN DEFAULT TRUE NOT NULL, - - CONSTRAINT pk_comment PRIMARY KEY (id), - CONSTRAINT fk_user_comment FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE CASCADE, - CONSTRAINT fk_event_comment FOREIGN KEY (event_id) REFERENCES events (id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS idx_textual_content ON comments (textual_content); -CREATE INDEX IF NOT EXISTS idx_event_id_comment_id ON comments (event_id, id); \ No newline at end of file diff --git a/core/main-service/src/test/java/ru/practicum/client/StatClientTest.java b/core/main-service/src/test/java/ru/practicum/client/StatClientTest.java deleted file mode 100644 index d66d7c9..0000000 --- a/core/main-service/src/test/java/ru/practicum/client/StatClientTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package ru.practicum.client; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.context.SpringBootTest; -import ru.practicum.EventHitDto; -import ru.practicum.EventStatsResponseDto; -import ru.practicum.ewm.client.StatClient; - -import java.time.LocalDateTime; -import java.util.Collection; - -@Disabled("Выполнять только при запущенных Discovery and Config servers") -@AutoConfigureTestDatabase -@SpringBootTest -public class StatClientTest { - - @Autowired - StatClient statClient; - - @Test - void simpleClientTest() { - System.out.println(1); - LocalDateTime now = LocalDateTime.now(); - EventHitDto hit = EventHitDto.builder() - .app("app1") - .uri("/events/2") - .ip("192.168.1.2") - .timestamp(now.minusHours(2)) - .build(); - LocalDateTime start = LocalDateTime.now().minusDays(1); - LocalDateTime end = LocalDateTime.now().plusDays(1); - - statClient.hit(hit); - - Collection eventStatsResponseDtoCollection = statClient.stats(start, end, null, true); - System.out.println(eventStatsResponseDtoCollection.size() + " уникальных:"); - for (EventStatsResponseDto ev : eventStatsResponseDtoCollection) System.out.println(ev); - - Collection eventStatsResponseDtoCollection2 = statClient.stats(start, end, null, false); - System.out.println(eventStatsResponseDtoCollection2.size() + " всего:"); - for (EventStatsResponseDto ev : eventStatsResponseDtoCollection2) System.out.println(ev); - - } - -} diff --git a/core/main-service/src/test/java/ru/practicum/controller/UserControllerTest.java b/core/main-service/src/test/java/ru/practicum/controller/UserControllerTest.java deleted file mode 100644 index 0a190f5..0000000 --- a/core/main-service/src/test/java/ru/practicum/controller/UserControllerTest.java +++ /dev/null @@ -1,426 +0,0 @@ -package ru.practicum.controller; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.http.MediaType; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -import org.springframework.test.web.servlet.MockMvc; -import ru.practicum.exception.ConflictException; -import ru.practicum.exception.NotFoundException; -import ru.practicum.user.controller.UserController; -import ru.practicum.user.dto.NewUserRequestDto; -import ru.practicum.user.dto.UserDto; -import ru.practicum.user.service.UserService; - -import java.util.Arrays; -import java.util.List; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@Disabled("Выполнять только при запущенных Discovery and Config servers") -@WebMvcTest(UserController.class) -class UserControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @MockitoBean - private UserService userService; - - // CREATE USER TESTS - - @Test - void createUser_WithValidData_ReturnsCreatedUser() throws Exception { - // Given - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("test@example.com") - .name("Test User") - .build(); - - UserDto expectedUser = UserDto.builder() - .id(1L) - .email("test@example.com") - .name("Test User") - .build(); - - when(userService.create(any(NewUserRequestDto.class))).thenReturn(expectedUser); - - // When & Then - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(requestDto))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id").value(1L)) - .andExpect(jsonPath("$.email").value("test@example.com")) - .andExpect(jsonPath("$.name").value("Test User")); - - verify(userService).create(any(NewUserRequestDto.class)); - } - - @Test - void createUser_WithDuplicateEmail_ReturnsConflict() throws Exception { - // Given - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("duplicate@example.com") - .name("Test User") - .build(); - - when(userService.create(any(NewUserRequestDto.class))) - .thenThrow(new ConflictException("User with email duplicate@example.com already exists", - "Integrity constraint has been violated")); - - // When & Then - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(requestDto))) - .andExpect(status().isConflict()); - - verify(userService).create(any(NewUserRequestDto.class)); - } - - @Test - void createUser_WithBlankEmail_ReturnsBadRequest() throws Exception { - // Given - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("") - .name("Test User") - .build(); - - // When & Then - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(requestDto))) - .andExpect(status().isBadRequest()); - - verify(userService, never()).create(any(NewUserRequestDto.class)); - } - - @Test - void createUser_WithInvalidEmail_ReturnsBadRequest() throws Exception { - // Given - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("invalid-email") - .name("Test User") - .build(); - - // When & Then - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(requestDto))) - .andExpect(status().isBadRequest()); - - verify(userService, never()).create(any(NewUserRequestDto.class)); - } - - @Test - void createUser_WithTooShortEmail_ReturnsBadRequest() throws Exception { - // Given - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("a@b.c") // 5 characters, minimum is 6 - .name("Test User") - .build(); - - // When & Then - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(requestDto))) - .andExpect(status().isBadRequest()); - - verify(userService, never()).create(any(NewUserRequestDto.class)); - } - - @Test - void createUser_WithTooLongEmail_ReturnsBadRequest() throws Exception { - // Given - String longEmail = "a".repeat(250) + "@example.com"; // More than 254 characters - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email(longEmail) - .name("Test User") - .build(); - - // When & Then - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(requestDto))) - .andExpect(status().isBadRequest()); - - verify(userService, never()).create(any(NewUserRequestDto.class)); - } - - @Test - void createUser_WithBlankName_ReturnsBadRequest() throws Exception { - // Given - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("test@example.com") - .name("") - .build(); - - // When & Then - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(requestDto))) - .andExpect(status().isBadRequest()); - - verify(userService, never()).create(any(NewUserRequestDto.class)); - } - - @Test - void createUser_WithTooShortName_ReturnsBadRequest() throws Exception { - // Given - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("test@example.com") - .name("A") // 1 character, minimum is 2 - .build(); - - // When & Then - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(requestDto))) - .andExpect(status().isBadRequest()); - - verify(userService, never()).create(any(NewUserRequestDto.class)); - } - - @Test - void createUser_WithTooLongName_ReturnsBadRequest() throws Exception { - // Given - String longName = "A".repeat(251); // More than 250 characters - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("test@example.com") - .name(longName) - .build(); - - // When & Then - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(requestDto))) - .andExpect(status().isBadRequest()); - - verify(userService, never()).create(any(NewUserRequestDto.class)); - } - - @Test - void createUser_WithNullEmail_ReturnsBadRequest() throws Exception { - // Given - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email(null) - .name("Test User") - .build(); - - // When & Then - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(requestDto))) - .andExpect(status().isBadRequest()); - - verify(userService, never()).create(any(NewUserRequestDto.class)); - } - - @Test - void createUser_WithNullName_ReturnsBadRequest() throws Exception { - // Given - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("test@example.com") - .name(null) - .build(); - - // When & Then - mockMvc.perform(post("/admin/users") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(requestDto))) - .andExpect(status().isBadRequest()); - - verify(userService, never()).create(any(NewUserRequestDto.class)); - } - - // DELETE USER TESTS - - @Test - void deleteUser_WithValidId_ReturnsNoContent() throws Exception { - // Given - Long userId = 1L; - doNothing().when(userService).delete(userId); - - // When & Then - mockMvc.perform(delete("/admin/users/{userId}", userId)) - .andExpect(status().isNoContent()); - - verify(userService).delete(userId); - } - - @Test - void deleteUser_WithNonExistentId_ReturnsNotFound() throws Exception { - // Given - Long userId = 999L; - doThrow(new NotFoundException("User with id=" + userId + " was not found")) - .when(userService).delete(userId); - - // When & Then - mockMvc.perform(delete("/admin/users/{userId}", userId)) - .andExpect(status().isNotFound()); - - verify(userService).delete(userId); - } - - @Test - void deleteUser_WithInvalidId_ReturnsBadRequest() throws Exception { - // Given - Long invalidUserId = -1L; - - // When & Then - mockMvc.perform(delete("/admin/users/{userId}", invalidUserId)) - .andExpect(status().isBadRequest()); - - verify(userService, never()).delete(any(Long.class)); - } - - @Test - void deleteUser_WithZeroId_ReturnsBadRequest() throws Exception { - // Given - Long invalidUserId = 0L; - - // When & Then - mockMvc.perform(delete("/admin/users/{userId}", invalidUserId)) - .andExpect(status().isBadRequest()); - - verify(userService, never()).delete(any(Long.class)); - } - - // GET USERS TESTS - - @Test - void getUsers_WithoutParameters_ReturnsDefaultPagedUsers() throws Exception { - // Given - List expectedUsers = Arrays.asList( - UserDto.builder().id(1L).email("user1@example.com").name("User 1").build(), - UserDto.builder().id(2L).email("user2@example.com").name("User 2").build() - ); - - when(userService.findByIdListWithOffsetAndLimit(null, 0, 10)) - .thenReturn(expectedUsers); - - // When & Then - mockMvc.perform(get("/admin/users")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.length()").value(2)) - .andExpect(jsonPath("$[0].id").value(1L)) - .andExpect(jsonPath("$[0].email").value("user1@example.com")) - .andExpect(jsonPath("$[0].name").value("User 1")) - .andExpect(jsonPath("$[1].id").value(2L)) - .andExpect(jsonPath("$[1].email").value("user2@example.com")) - .andExpect(jsonPath("$[1].name").value("User 2")); - - verify(userService).findByIdListWithOffsetAndLimit(null, 0, 10); - } - - @Test - void getUsers_WithCustomPagination_ReturnsPagedUsers() throws Exception { - // Given - List expectedUsers = Arrays.asList( - UserDto.builder().id(3L).email("user3@example.com").name("User 3").build() - ); - - when(userService.findByIdListWithOffsetAndLimit(null, 5, 5)) - .thenReturn(expectedUsers); - - // When & Then - mockMvc.perform(get("/admin/users") - .param("from", "5") - .param("size", "5")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.length()").value(1)) - .andExpect(jsonPath("$[0].id").value(3L)) - .andExpect(jsonPath("$[0].email").value("user3@example.com")) - .andExpect(jsonPath("$[0].name").value("User 3")); - - verify(userService).findByIdListWithOffsetAndLimit(null, 5, 5); - } - - @Test - void getUsers_WithSpecificIds_ReturnsFilteredUsers() throws Exception { - // Given - List userIds = Arrays.asList(1L, 3L); - List expectedUsers = Arrays.asList( - UserDto.builder().id(1L).email("user1@example.com").name("User 1").build(), - UserDto.builder().id(3L).email("user3@example.com").name("User 3").build() - ); - - when(userService.findByIdListWithOffsetAndLimit(userIds, 0, 10)) - .thenReturn(expectedUsers); - - // When & Then - mockMvc.perform(get("/admin/users") - .param("ids", "1,3")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.length()").value(2)) - .andExpect(jsonPath("$[0].id").value(1L)) - .andExpect(jsonPath("$[1].id").value(3L)); - - verify(userService).findByIdListWithOffsetAndLimit(userIds, 0, 10); - } - - @Test - void getUsers_WithEmptyResult_ReturnsEmptyList() throws Exception { - // Given - when(userService.findByIdListWithOffsetAndLimit(null, 0, 10)) - .thenReturn(Arrays.asList()); - - // When & Then - mockMvc.perform(get("/admin/users")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.length()").value(0)); - - verify(userService).findByIdListWithOffsetAndLimit(null, 0, 10); - } - - @Test - void getUsers_WithSingleId_ReturnsFilteredUser() throws Exception { - // Given - List userIds = Arrays.asList(1L); - List expectedUsers = Arrays.asList( - UserDto.builder().id(1L).email("user1@example.com").name("User 1").build() - ); - - when(userService.findByIdListWithOffsetAndLimit(userIds, 0, 10)) - .thenReturn(expectedUsers); - - // When & Then - mockMvc.perform(get("/admin/users") - .param("ids", "1")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.length()").value(1)) - .andExpect(jsonPath("$[0].id").value(1L)) - .andExpect(jsonPath("$[0].email").value("user1@example.com")) - .andExpect(jsonPath("$[0].name").value("User 1")); - - verify(userService).findByIdListWithOffsetAndLimit(userIds, 0, 10); - } - - @Test - void getUsers_WithNonExistentIds_ReturnsEmptyList() throws Exception { - // Given - List userIds = Arrays.asList(999L, 1000L); - - when(userService.findByIdListWithOffsetAndLimit(userIds, 0, 10)) - .thenReturn(Arrays.asList()); - - // When & Then - mockMvc.perform(get("/admin/users") - .param("ids", "999,1000")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.length()").value(0)); - - verify(userService).findByIdListWithOffsetAndLimit(userIds, 0, 10); - } - -} \ No newline at end of file diff --git a/core/main-service/src/test/java/ru/practicum/service/UserServiceIntegrationTest.java b/core/main-service/src/test/java/ru/practicum/service/UserServiceIntegrationTest.java deleted file mode 100644 index c241a5b..0000000 --- a/core/main-service/src/test/java/ru/practicum/service/UserServiceIntegrationTest.java +++ /dev/null @@ -1,365 +0,0 @@ -package ru.practicum.service; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.transaction.annotation.Transactional; -import ru.practicum.exception.ConflictException; -import ru.practicum.exception.NotFoundException; -import ru.practicum.user.dto.NewUserRequestDto; -import ru.practicum.user.dto.UserDto; -import ru.practicum.user.model.User; -import ru.practicum.user.repository.UserRepository; -import ru.practicum.user.service.UserService; - -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -@Disabled("Выполнять только при запущенных Discovery and Config servers") -@SpringBootTest -@AutoConfigureTestDatabase -@Transactional -class UserServiceIntegrationTest { - - @Autowired - private UserService userService; - - @Autowired - private UserRepository userRepository; - - @PersistenceContext - private EntityManager entityManager; - - @BeforeEach - void setUp() { - userRepository.deleteAll(); - entityManager.flush(); - entityManager.clear(); - } - - // CREATE USER TESTS - - @Test - void create_WithValidData_ShouldSaveUserAndReturnDto() { - // Given - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("test@example.com") - .name("Test User") - .build(); - - // When - UserDto result = userService.create(requestDto); - - // Then - assertThat(result).isNotNull(); - assertThat(result.getId()).isNotNull(); - assertThat(result.getEmail()).isEqualTo("test@example.com"); - assertThat(result.getName()).isEqualTo("Test User"); - - // Verify user was saved in database - User savedUser = userRepository.findById(result.getId()).orElse(null); - assertThat(savedUser).isNotNull(); - assertThat(savedUser.getEmail()).isEqualTo("test@example.com"); - assertThat(savedUser.getName()).isEqualTo("Test User"); - } - - @Test - void create_WithDuplicateEmail_ShouldThrowConflictException() { - // Given - User existingUser = User.builder() - .email("duplicate@example.com") - .name("Existing User") - .build(); - userRepository.save(existingUser); - - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("duplicate@example.com") - .name("New User") - .build(); - - // When & Then - assertThatThrownBy(() -> userService.create(requestDto)) - .isInstanceOf(ConflictException.class) - .hasMessageContaining("duplicate@example.com"); - - // Verify only one user exists - List users = userRepository.findAll(); - assertThat(users).hasSize(1); - assertThat(users.get(0).getName()).isEqualTo("Existing User"); - } - - @Test - void create_WithCaseInsensitiveEmail_ShouldWorkCorrectly() { - // Given - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("TeSt@ExAmPLE.COM") - .name("Test User") - .build(); - - // When - UserDto result = userService.create(requestDto); - - // Then - assertThat(result.getEmail()).isEqualTo("TeSt@ExAmPLE.COM"); - - // Verify saved in database - User savedUser = userRepository.findById(result.getId()).orElse(null); - assertThat(savedUser.getEmail()).isEqualTo("TeSt@ExAmPLE.COM"); - } - - @Test - void create_WithMinimalValidData_ShouldCreateUser() { - // Given - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email("ab@c.de") // 6 characters - minimum valid - .name("AB") // 2 characters - minimum valid - .build(); - - // When - UserDto result = userService.create(requestDto); - - // Then - assertThat(result).isNotNull(); - assertThat(result.getId()).isNotNull(); - assertThat(result.getEmail()).isEqualTo("ab@c.de"); - assertThat(result.getName()).isEqualTo("AB"); - } - - @Test - void create_WithMaximalValidData_ShouldCreateUser() { - // Given - String maxEmail = "a".repeat(245) + "@test.com"; // 254 characters - maximum valid - String maxName = "A".repeat(250); // 250 characters - maximum valid - - NewUserRequestDto requestDto = NewUserRequestDto.builder() - .email(maxEmail) - .name(maxName) - .build(); - - // When - UserDto result = userService.create(requestDto); - - // Then - assertThat(result).isNotNull(); - assertThat(result.getId()).isNotNull(); - assertThat(result.getEmail()).isEqualTo(maxEmail); - assertThat(result.getName()).isEqualTo(maxName); - } - - // DELETE USER TESTS - - @Test - void delete_WithExistingUserId_ShouldDeleteUser() { - // Given - User user = User.builder() - .email("todelete@example.com") - .name("User To Delete") - .build(); - User savedUser = userRepository.save(user); - Long userId = savedUser.getId(); - - // When - userService.delete(userId); - - // Then - assertThat(userRepository.findById(userId)).isEmpty(); - assertThat(userRepository.count()).isEqualTo(0); - } - - @Test - void delete_WithNonExistentUserId_ShouldThrowNotFoundException() { - // Given - Long nonExistentId = 999L; - - // When & Then - assertThatThrownBy(() -> userService.delete(nonExistentId)) - .isInstanceOf(NotFoundException.class); - - // Verify database is unchanged - assertThat(userRepository.count()).isEqualTo(0); - } - - @Test - void delete_WithMultipleUsers_ShouldDeleteOnlySpecificUser() { - // Given - User user1 = userRepository.save(User.builder() - .email("user1@example.com") - .name("User 1") - .build()); - User user2 = userRepository.save(User.builder() - .email("user2@example.com") - .name("User 2") - .build()); - User user3 = userRepository.save(User.builder() - .email("user3@example.com") - .name("User 3") - .build()); - - // When - userService.delete(user2.getId()); - - // Then - assertThat(userRepository.findById(user1.getId())).isPresent(); - assertThat(userRepository.findById(user2.getId())).isEmpty(); - assertThat(userRepository.findById(user3.getId())).isPresent(); - assertThat(userRepository.count()).isEqualTo(2); - } - - // FIND BY ID LIST WITH OFFSET AND LIMIT TESTS - - @Test - void findByIdListWithOffsetAndLimit_WithNullIdList_ShouldReturnPagedResults() { - // Given - createTestUsers(15); - - // When - List result = userService.findByIdListWithOffsetAndLimit(null, 0, 10); - - // Then - assertThat(result).hasSize(10); - assertThat(result).extracting(UserDto::getEmail) - .contains("user0@example.com", "user1@example.com", "user9@example.com"); - } - - @Test - void findByIdListWithOffsetAndLimit_WithEmptyIdList_ShouldReturnPagedResults() { - // Given - createTestUsers(5); - - // When - List result = userService.findByIdListWithOffsetAndLimit(List.of(), 0, 10); - - // Then - assertThat(result).hasSize(5); - } - - @Test - void findByIdListWithOffsetAndLimit_WithOffsetAndLimit_ShouldReturnCorrectPage() { - // Given - createTestUsers(15); - - // When - List result = userService.findByIdListWithOffsetAndLimit(null, 1, 5); - - // Then - assertThat(result).hasSize(5); - assertThat(result).extracting(UserDto::getEmail) - .contains("user0@example.com", "user1@example.com", "user4@example.com"); - } - - @Test - void findByIdListWithOffsetAndLimit_WithSpecificIds_ShouldReturnFilteredResults() { - // Given - List users = createTestUsers(10); - List specificIds = Arrays.asList( - users.get(0).getId(), - users.get(2).getId(), - users.get(4).getId() - ); - - // When - List result = userService.findByIdListWithOffsetAndLimit(specificIds, 0, 10); - - // Then - assertThat(result).hasSize(3); - assertThat(result).extracting(UserDto::getId) - .containsExactlyInAnyOrderElementsOf(specificIds); - } - - @Test - void findByIdListWithOffsetAndLimit_WithNonExistentIds_ShouldReturnEmptyList() { - // Given - createTestUsers(5); - List nonExistentIds = Arrays.asList(999L, 1000L, 1001L); - - // When - List result = userService.findByIdListWithOffsetAndLimit(nonExistentIds, 0, 10); - - // Then - assertThat(result).isEmpty(); - } - - @Test - void findByIdListWithOffsetAndLimit_WithMixedExistentAndNonExistentIds_ShouldReturnOnlyExistentUsers() { - // Given - List users = createTestUsers(3); - List mixedIds = Arrays.asList( - users.get(0).getId(), - 999L, // non-existent - users.get(1).getId(), - 1000L // non-existent - ); - - // When - List result = userService.findByIdListWithOffsetAndLimit(mixedIds, 0, 10); - - // Then - assertThat(result).hasSize(2); - assertThat(result).extracting(UserDto::getId) - .containsExactlyInAnyOrder(users.get(0).getId(), users.get(1).getId()); - } - - @Test - void findByIdListWithOffsetAndLimit_WithEmptyDatabase_ShouldReturnEmptyList() { - // Given - empty database - - // When - List result = userService.findByIdListWithOffsetAndLimit(null, 0, 10); - - // Then - assertThat(result).isEmpty(); - } - - @Test - void findByIdListWithOffsetAndLimit_WithLargeOffset_ShouldReturnEmptyList() { - // Given - createTestUsers(5); - - // When - List result = userService.findByIdListWithOffsetAndLimit(null, 10, 10); - - // Then - assertThat(result).isEmpty(); - } - - @Test - void findByIdListWithOffsetAndLimit_WithSingleUser_ShouldReturnSingleResult() { - // Given - User user = userRepository.save(User.builder() - .email("single@example.com") - .name("Single User") - .build()); - - // When - List result = userService.findByIdListWithOffsetAndLimit(null, 0, 10); - - // Then - assertThat(result).hasSize(1); - assertThat(result.get(0).getId()).isEqualTo(user.getId()); - assertThat(result.get(0).getEmail()).isEqualTo("single@example.com"); - assertThat(result.get(0).getName()).isEqualTo("Single User"); - } - - // Helper methods - - private List createTestUsers(int count) { - List users = new java.util.ArrayList<>(); - for (int i = 0; i < count; i++) { - User user = User.builder() - .email("user" + i + "@example.com") - .name("User " + i) - .build(); - users.add(userRepository.save(user)); - } - return users; - } - -} \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 0ece0d5..8069c5d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,6 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + ru.practicum explore-with-me @@ -13,7 +14,11 @@ pom - main-service + core-common + event-service + user-service + comment-service + request-service \ No newline at end of file diff --git a/core/request-service/Dockerfile b/core/request-service/Dockerfile new file mode 100644 index 0000000..16eef7a --- /dev/null +++ b/core/request-service/Dockerfile @@ -0,0 +1,4 @@ +FROM amazoncorretto:21-alpine +LABEL authors="Слава" +COPY target/*.jar app.jar +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/core/request-service/pom.xml b/core/request-service/pom.xml new file mode 100644 index 0000000..417a2b5 --- /dev/null +++ b/core/request-service/pom.xml @@ -0,0 +1,136 @@ + + + 4.0.0 + + + ru.practicum + core + 0.0.1-SNAPSHOT + + + comment-service + + + + + + + ru.practicum + core-common + 0.0.1-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + javax.validation + validation-api + 2.0.1.Final + + + + + + org.postgresql + postgresql + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + + org.springframework.cloud + spring-cloud-starter-config + + + + org.springframework.retry + spring-retry + + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-resilience4j + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + + + + diff --git a/core/request-service/src/main/java/ru/practicum/RequestServiceApplication.java b/core/request-service/src/main/java/ru/practicum/RequestServiceApplication.java new file mode 100644 index 0000000..6599c25 --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/RequestServiceApplication.java @@ -0,0 +1,15 @@ +package ru.practicum; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +@EnableFeignClients +@SpringBootApplication +public class RequestServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(RequestServiceApplication.class, args); + } + +} diff --git a/core/request-service/src/main/java/ru/practicum/client/EventClient.java b/core/request-service/src/main/java/ru/practicum/client/EventClient.java new file mode 100644 index 0000000..a808401 --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/client/EventClient.java @@ -0,0 +1,8 @@ +package ru.practicum.client; + +import org.springframework.cloud.openfeign.FeignClient; +import ru.practicum.api.event.EventAllApi; + +@FeignClient(name = "event-service") +public interface EventClient extends EventAllApi { +} diff --git a/core/request-service/src/main/java/ru/practicum/client/EventClientHelper.java b/core/request-service/src/main/java/ru/practicum/client/EventClientHelper.java new file mode 100644 index 0000000..633a138 --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/client/EventClientHelper.java @@ -0,0 +1,13 @@ +package ru.practicum.client; + +import org.springframework.stereotype.Component; +import ru.practicum.api.event.EventAllApi; + +@Component +public class EventClientHelper extends EventClientAbstractHelper { + + public EventClientHelper(EventAllApi eventApiClient) { + super(eventApiClient); + } + +} diff --git a/core/request-service/src/main/java/ru/practicum/client/UserClient.java b/core/request-service/src/main/java/ru/practicum/client/UserClient.java new file mode 100644 index 0000000..50ca2b2 --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/client/UserClient.java @@ -0,0 +1,8 @@ +package ru.practicum.client; + +import org.springframework.cloud.openfeign.FeignClient; +import ru.practicum.api.user.UserApi; + +@FeignClient(name = "user-service") +public interface UserClient extends UserApi { +} diff --git a/core/request-service/src/main/java/ru/practicum/client/UserClientHelper.java b/core/request-service/src/main/java/ru/practicum/client/UserClientHelper.java new file mode 100644 index 0000000..41cb654 --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/client/UserClientHelper.java @@ -0,0 +1,13 @@ +package ru.practicum.client; + +import org.springframework.stereotype.Component; +import ru.practicum.api.user.UserApi; + +@Component +public class UserClientHelper extends UserClientAbstractHelper { + + public UserClientHelper(UserApi userApiClient) { + super(userApiClient); + } + +} \ No newline at end of file diff --git a/core/request-service/src/main/java/ru/practicum/request/controller/RequestController.java b/core/request-service/src/main/java/ru/practicum/request/controller/RequestController.java new file mode 100644 index 0000000..85eff65 --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/request/controller/RequestController.java @@ -0,0 +1,62 @@ +package ru.practicum.request.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.api.request.RequestApi; +import ru.practicum.dto.request.EventRequestStatusUpdateRequestDto; +import ru.practicum.dto.request.EventRequestStatusUpdateResultDto; +import ru.practicum.dto.request.ParticipationRequestDto; +import ru.practicum.request.service.RequestService; + +import java.util.Collection; +import java.util.Map; + +@RestController +@RequiredArgsConstructor +@Validated +public class RequestController implements RequestApi { + + private final RequestService requestService; + + // ЗАЯВКИ ТЕКУЩЕГО ПОЛЬЗОВАТЕЛЯ + + // Добавление запроса от текущего пользователя на участие в событии + @Override + public ParticipationRequestDto addRequest(Long userId, Long eventId) { + return requestService.addRequest(userId, eventId); + } + + // Отмена своего запроса на участие в событии + @Override + public ParticipationRequestDto cancelRequest(Long userId, Long requestId) { + return requestService.cancelRequest(userId, requestId); + } + + // Получение информации о заявках текущего пользователя на участие в чужих событиях + @Override + public Collection getRequesterRequests(Long userId) { + return requestService.findRequesterRequests(userId); + } + + // ЗАЯВКИ НА КОНКРЕТНОЕ СОБЫТИЕ + + // Изменение статуса (подтверждена, отменена) заявок на участие в событии текущего пользователя + @Override + public EventRequestStatusUpdateResultDto moderateRequest(Long userId, Long eventId, + EventRequestStatusUpdateRequestDto updateRequestDto) { + return requestService.moderateRequest(userId, eventId, updateRequestDto); + } + + // Получение информации о запросах на участие в событии текущего пользователя + @Override + public Collection getEventRequests(Long userId, Long eventId) { + return requestService.findEventRequests(userId, eventId); + } + + @Override + public Map getConfirmedRequestsByEventIds(Collection eventIds) { + return requestService.getConfirmedRequestsByEventIds(eventIds); + } + +} \ No newline at end of file diff --git a/core/request-service/src/main/java/ru/practicum/request/dal/Request.java b/core/request-service/src/main/java/ru/practicum/request/dal/Request.java new file mode 100644 index 0000000..0bcfa37 --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/request/dal/Request.java @@ -0,0 +1,43 @@ +package ru.practicum.request.dal; + +import jakarta.persistence.*; +import lombok.*; +import ru.practicum.dto.request.ParticipationRequestStatus; + +import java.time.LocalDateTime; + +@Getter +@Setter +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString +@Entity +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "requests", indexes = { + @Index(name = "idx_requests_requester_id", columnList = "requester_id"), + @Index(name = "idx_requests_event_id", columnList = "event_id") +}) + +public class Request { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @EqualsAndHashCode.Include + @Column(name = "id") + private Long id; + + @Column(name = "requester_id", nullable = false) + private Long requesterId; + + @Column(name = "event_id", nullable = false) + private Long eventId; + + @Enumerated(EnumType.STRING) + @Column(name = "status", length = 15, nullable = false) + private ParticipationRequestStatus status; + + @Column(name = "created_at", nullable = false) + private LocalDateTime created; + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/request/repository/RequestRepository.java b/core/request-service/src/main/java/ru/practicum/request/dal/RequestRepository.java similarity index 76% rename from core/main-service/src/main/java/ru/practicum/request/repository/RequestRepository.java rename to core/request-service/src/main/java/ru/practicum/request/dal/RequestRepository.java index 1926350..02251a8 100644 --- a/core/main-service/src/main/java/ru/practicum/request/repository/RequestRepository.java +++ b/core/request-service/src/main/java/ru/practicum/request/dal/RequestRepository.java @@ -1,12 +1,12 @@ -package ru.practicum.request.repository; +package ru.practicum.request.dal; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import ru.practicum.request.dto.ParticipationRequestStatus; -import ru.practicum.request.model.Request; +import ru.practicum.dto.request.ParticipationRequestStatus; +import java.util.Collection; import java.util.List; public interface RequestRepository extends JpaRepository { @@ -30,7 +30,7 @@ void updateStatusByIds( @Query(""" UPDATE Request r SET r.status = 'REJECTED' - WHERE r.event.id = :eventId + WHERE r.eventId = :eventId AND r.status = 'PENDING' """) void setStatusToRejectForAllPending( @@ -38,13 +38,14 @@ void setStatusToRejectForAllPending( ); @Query(""" - SELECT r.event.id, count(r) + SELECT r.eventId, count(r) FROM Request r - WHERE r.event.id IN :eventIds - GROUP BY r.event.id + WHERE r.eventId IN :eventIds + AND r.status = 'CONFIRMED' + GROUP BY r.eventId """) List getConfirmedRequestsByEventIds( - @Param("eventIds") List eventIds + @Param("eventIds") Collection eventIds ); -} \ No newline at end of file +} diff --git a/core/request-service/src/main/java/ru/practicum/request/service/RequestMapper.java b/core/request-service/src/main/java/ru/practicum/request/service/RequestMapper.java new file mode 100644 index 0000000..24393fb --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/request/service/RequestMapper.java @@ -0,0 +1,18 @@ +package ru.practicum.request.service; + +import ru.practicum.dto.request.ParticipationRequestDto; +import ru.practicum.request.dal.Request; + +public class RequestMapper { + + public static ParticipationRequestDto toDto(Request request) { + ParticipationRequestDto dto = new ParticipationRequestDto(); + dto.setId(request.getId()); + dto.setRequester(request.getRequesterId()); + dto.setEvent(request.getEventId()); + dto.setStatus(request.getStatus()); + dto.setCreated(request.getCreated()); + return dto; + } + +} diff --git a/core/request-service/src/main/java/ru/practicum/request/service/RequestService.java b/core/request-service/src/main/java/ru/practicum/request/service/RequestService.java new file mode 100644 index 0000000..402e236 --- /dev/null +++ b/core/request-service/src/main/java/ru/practicum/request/service/RequestService.java @@ -0,0 +1,194 @@ +package ru.practicum.request.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; +import ru.practicum.client.EventClientAbstractHelper; +import ru.practicum.client.UserClientHelper; +import ru.practicum.dto.event.EventInteractionDto; +import ru.practicum.dto.event.State; +import ru.practicum.dto.request.EventRequestStatusUpdateRequestDto; +import ru.practicum.dto.request.EventRequestStatusUpdateResultDto; +import ru.practicum.dto.request.ParticipationRequestDto; +import ru.practicum.dto.request.ParticipationRequestStatus; +import ru.practicum.exception.ConflictException; +import ru.practicum.exception.NotFoundException; +import ru.practicum.request.dal.Request; +import ru.practicum.request.dal.RequestRepository; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class RequestService { + + private final TransactionTemplate transactionTemplate; + private final RequestRepository requestRepository; + + private final UserClientHelper userClientHelper; + private final EventClientAbstractHelper eventClientHelper; + + // ЗАЯВКИ ТЕКУЩЕГО ПОЛЬЗОВАТЕЛЯ + + // Добавление запроса от текущего пользователя на участие в событии + public ParticipationRequestDto addRequest(Long userId, Long eventId) { + userClientHelper.retrieveUserShortDtoByUserIdOrFall(userId); + + EventInteractionDto eventDto = eventClientHelper.retrieveEventInteractionDtoByEventIdOrFall(eventId); + + return transactionTemplate.execute(status -> { + // нельзя добавить повторный запрос (Ожидается код ошибки 409) + if (requestRepository.existsByRequesterIdAndEventId(userId, eventId)) + throw new ConflictException("User tries to make duplicate request", "Forbidden action"); + + // инициатор события не может добавить запрос на участие в своём событии (Ожидается код ошибки 409) + if (Objects.equals(userId, eventDto.getInitiatorId())) + throw new ConflictException("User tries to request for his own event", "Forbidden action"); + + // нельзя участвовать в неопубликованном событии (Ожидается код ошибки 409) + if (eventDto.getState() != State.PUBLISHED) + throw new ConflictException("User tries to request for non-published event", "Forbidden action"); + + // если у события достигнут лимит запросов на участие - необходимо вернуть ошибку (Ожидается код ошибки 409) + long confirmedRequestCount = requestRepository.countByEventIdAndStatus(eventId, ParticipationRequestStatus.CONFIRMED); + if (eventDto.getParticipantLimit() > 0 && confirmedRequestCount >= eventDto.getParticipantLimit()) + throw new ConflictException("Participants limit is already reached", "Forbidden action"); + + // если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного + ParticipationRequestStatus newRequestStatus = ParticipationRequestStatus.PENDING; + if (!eventDto.getRequestModeration()) newRequestStatus = ParticipationRequestStatus.CONFIRMED; + if (Objects.equals(eventDto.getParticipantLimit(), 0L)) + newRequestStatus = ParticipationRequestStatus.CONFIRMED; + + Request newRequest = Request.builder() + .requesterId(userId) + .eventId(eventId) + .status(newRequestStatus) + .created(LocalDateTime.now()) + .build(); + requestRepository.save(newRequest); + return RequestMapper.toDto(newRequest); + }); + } + + // Отмена своего запроса на участие в событии + @Transactional + public ParticipationRequestDto cancelRequest(Long userId, Long requestId) { + Request request = requestRepository.findById(requestId) + .orElseThrow(() -> new NotFoundException("Not found Request " + requestId)); + + if (!Objects.equals(request.getRequesterId(), userId)) + throw new ConflictException("User can cancel only his own event", "Forbidden action"); + + request.setStatus(ParticipationRequestStatus.CANCELED); + requestRepository.save(request); + return RequestMapper.toDto(request); + } + + // Получение информации о заявках текущего пользователя на участие в чужих событиях + @Transactional(readOnly = true) + public Collection findRequesterRequests(Long userId) { + return requestRepository.findByRequesterId(userId).stream() + .map(RequestMapper::toDto) + .toList(); + } + + // ЗАЯВКИ НА КОНКРЕТНОЕ СОБЫТИЕ + + // Получение информации о запросах на участие в событии, инициированном текущим пользователем + public Collection findEventRequests(Long userId, Long eventId) { + EventInteractionDto eventDto = eventClientHelper.retrieveEventInteractionDtoByEventIdOrFall(eventId); + + // проверка что юзер - инициатор события + if (!Objects.equals(userId, eventDto.getInitiatorId())) + throw new ConflictException("User " + userId + " is not an initiator of event " + eventId, "Forbidden action"); + + return requestRepository.findByEventId(eventId).stream() + .map(RequestMapper::toDto) + .toList(); + } + + // Изменение статуса (подтверждена, отменена) заявок на участие в событии текущего пользователя + public EventRequestStatusUpdateResultDto moderateRequest( + Long userId, + Long eventId, + EventRequestStatusUpdateRequestDto updateRequestDto + ) { + EventInteractionDto eventDto = eventClientHelper.retrieveEventInteractionDtoByEventIdOrFall(eventId); + + // проверка что юзер - инициатор события + if (!Objects.equals(userId, eventDto.getInitiatorId())) + throw new ConflictException("User " + userId + " is not an initiator of event " + eventId, "Forbidden action"); + + // если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется + if (eventDto.getParticipantLimit() < 1 || !eventDto.getRequestModeration()) + return new EventRequestStatusUpdateResultDto(); + + return transactionTemplate.execute(status -> { + // статус можно изменить только у заявок, находящихся в состоянии ожидания (Ожидается код ошибки 409) + List requests = requestRepository.findAllById(updateRequestDto.getRequestIds()); + for (Request request : requests) { + if (!Objects.equals(request.getStatus(), ParticipationRequestStatus.PENDING)) + throw new ConflictException("Request " + request.getId() + " must have status PENDING", "Incorrectly made request"); + } + + List requestsToConfirm = new ArrayList<>(); + List requestsToReject = new ArrayList<>(); + + if (Objects.equals(updateRequestDto.getStatus(), ParticipationRequestStatus.CONFIRMED)) { + + long confirmedRequestCount = requestRepository.countByEventIdAndStatus(eventId, ParticipationRequestStatus.CONFIRMED); + + if (confirmedRequestCount >= eventDto.getParticipantLimit()) { + // нельзя подтвердить заявку, если уже достигнут лимит по заявкам на данное событие (Ожидается код ошибки 409) + throw new ConflictException("The participant limit has been reached for event " + eventId, "Forbidden action"); + } else if (updateRequestDto.getRequestIds().size() < eventDto.getParticipantLimit() - confirmedRequestCount) { + requestsToConfirm = updateRequestDto.getRequestIds(); + requestRepository.updateStatusByIds(requestsToConfirm, ParticipationRequestStatus.CONFIRMED); + } else { + long freeSeats = eventDto.getParticipantLimit() - confirmedRequestCount; + requestsToConfirm = updateRequestDto.getRequestIds().stream() + .limit(freeSeats) + .toList(); + requestsToReject = updateRequestDto.getRequestIds().stream() + .skip(freeSeats) + .toList(); + requestRepository.updateStatusByIds(requestsToConfirm, ParticipationRequestStatus.CONFIRMED); + // если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки необходимо отклонить + requestRepository.setStatusToRejectForAllPending(eventId); + } + + } else if (updateRequestDto.getStatus() == ParticipationRequestStatus.REJECTED) { + requestsToReject = updateRequestDto.getRequestIds(); + requestRepository.updateStatusByIds(requestsToReject, ParticipationRequestStatus.REJECTED); + } else { + throw new ConflictException("Only CONFIRMED and REJECTED statuses are allowed", "Forbidden action"); + } + + EventRequestStatusUpdateResultDto resultDto = new EventRequestStatusUpdateResultDto(); + List confirmedRequests = requestRepository.findAllById(requestsToConfirm).stream() + .map(RequestMapper::toDto) + .toList(); + resultDto.setConfirmedRequests(confirmedRequests); + List rejectedRequests = requestRepository.findAllById(requestsToReject).stream() + .map(RequestMapper::toDto) + .toList(); + resultDto.setRejectedRequests(rejectedRequests); + return resultDto; + }); + } + + @Transactional(readOnly = true) + public Map getConfirmedRequestsByEventIds(Collection eventIds) { + if (eventIds == null || eventIds.isEmpty()) return Map.of(); + return requestRepository.getConfirmedRequestsByEventIds(eventIds).stream() + .collect(Collectors.toMap( + r -> (Long) r[0], + r -> (Long) r[1] + )); + } + +} \ No newline at end of file diff --git a/core/request-service/src/main/resources/application.yaml b/core/request-service/src/main/resources/application.yaml new file mode 100644 index 0000000..ea537b6 --- /dev/null +++ b/core/request-service/src/main/resources/application.yaml @@ -0,0 +1,25 @@ +spring: + application: + name: request-service + config: + import: "configserver:" + cloud: + config: + discovery: + enabled: true + serviceId: config-server + fail-fast: true + retry: + useRandomPolicy: true + max-interval: 10000 + max-attempts: 100 + +eureka: + client: + registerWithEureka: true + serviceUrl: + defaultZone: http://localhost:8761/eureka/ + instance: + instance-id: ${spring.application.name}${random.int} + preferIpAddress: false + hostname: localhost diff --git a/core/request-service/src/main/resources/schema.sql b/core/request-service/src/main/resources/schema.sql new file mode 100644 index 0000000..cddb36d --- /dev/null +++ b/core/request-service/src/main/resources/schema.sql @@ -0,0 +1 @@ +CREATE SCHEMA IF NOT EXISTS request_service; diff --git a/core/user-service/Dockerfile b/core/user-service/Dockerfile new file mode 100644 index 0000000..16eef7a --- /dev/null +++ b/core/user-service/Dockerfile @@ -0,0 +1,4 @@ +FROM amazoncorretto:21-alpine +LABEL authors="Слава" +COPY target/*.jar app.jar +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/core/user-service/pom.xml b/core/user-service/pom.xml new file mode 100644 index 0000000..00de4bc --- /dev/null +++ b/core/user-service/pom.xml @@ -0,0 +1,124 @@ + + + 4.0.0 + + + ru.practicum + core + 0.0.1-SNAPSHOT + + + user-service + + + + + + + ru.practicum + core-common + 0.0.1-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.projectlombok + lombok + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + javax.validation + validation-api + 2.0.1.Final + + + + + + org.postgresql + postgresql + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + + org.springframework.cloud + spring-cloud-starter-config + + + + org.springframework.retry + spring-retry + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + + + + diff --git a/core/user-service/src/main/java/ru/practicum/UserServiceApplication.java b/core/user-service/src/main/java/ru/practicum/UserServiceApplication.java new file mode 100644 index 0000000..3560f9f --- /dev/null +++ b/core/user-service/src/main/java/ru/practicum/UserServiceApplication.java @@ -0,0 +1,11 @@ +package ru.practicum; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class UserServiceApplication { + public static void main(String[] args) { + SpringApplication.run(UserServiceApplication.class, args); + } +} diff --git a/core/user-service/src/main/java/ru/practicum/user/controller/UserController.java b/core/user-service/src/main/java/ru/practicum/user/controller/UserController.java new file mode 100644 index 0000000..067f59b --- /dev/null +++ b/core/user-service/src/main/java/ru/practicum/user/controller/UserController.java @@ -0,0 +1,63 @@ +package ru.practicum.user.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import ru.practicum.api.user.UserApi; +import ru.practicum.dto.user.NewUserRequestDto; +import ru.practicum.dto.user.UserDto; +import ru.practicum.dto.user.UserShortDto; +import ru.practicum.user.service.UserService; + +import java.util.Collection; +import java.util.List; + +@RestController +@RequiredArgsConstructor +@Validated +public class UserController implements UserApi { + + private final UserService userService; + + // MODIFY OPS + + @Override + public UserDto createUser(NewUserRequestDto newUserRequestDto) { + return userService.create(newUserRequestDto); + } + + @Override + public void deleteUser(Long userId) { + userService.delete(userId); + } + + // GET + HEAD + + @Override + public UserDto getUser(Long userId) { + return userService.get(userId); + } + + @Override + public UserShortDto getUserShort(Long userId) { + return userService.getShort(userId); + } + + // GET COLLECTION + + @Override + public Collection getUsers(List ids, Integer from, Integer size) { + return userService.findByIdListWithOffsetAndLimit(ids, from, size); + } + + @Override + public Collection getUserShortDtoListByIds(Collection ids) { + return userService.findUserShortDtoListByIds(ids); + } + + @Override + public Collection getUserDtoListByIds(Collection ids) { + return userService.findUserDtoListByIds(ids); + } + +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/practicum/user/model/User.java b/core/user-service/src/main/java/ru/practicum/user/dal/User.java similarity index 67% rename from core/main-service/src/main/java/ru/practicum/user/model/User.java rename to core/user-service/src/main/java/ru/practicum/user/dal/User.java index 02bd49d..9b6bbb4 100644 --- a/core/main-service/src/main/java/ru/practicum/user/model/User.java +++ b/core/user-service/src/main/java/ru/practicum/user/dal/User.java @@ -1,19 +1,22 @@ -package ru.practicum.user.model; +package ru.practicum.user.dal; import jakarta.persistence.*; import lombok.*; @Getter @Setter +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString +@Entity @Builder @NoArgsConstructor @AllArgsConstructor -@Entity -@Table(name = "users") +@Table(name = "users", indexes = {@Index(name = "idx_users_email", columnList = "email")}) public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @EqualsAndHashCode.Include @Column(name = "id") private Long id; @@ -23,4 +26,4 @@ public class User { @Column(name = "name", length = 250, nullable = false) private String name; -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/user/repository/UserRepository.java b/core/user-service/src/main/java/ru/practicum/user/dal/UserRepository.java similarity index 69% rename from core/main-service/src/main/java/ru/practicum/user/repository/UserRepository.java rename to core/user-service/src/main/java/ru/practicum/user/dal/UserRepository.java index 9ccc466..f764043 100644 --- a/core/main-service/src/main/java/ru/practicum/user/repository/UserRepository.java +++ b/core/user-service/src/main/java/ru/practicum/user/dal/UserRepository.java @@ -1,10 +1,9 @@ -package ru.practicum.user.repository; +package ru.practicum.user.dal; import org.springframework.data.jpa.repository.JpaRepository; -import ru.practicum.user.model.User; public interface UserRepository extends JpaRepository { boolean existsByEmail(String email); -} \ No newline at end of file +} diff --git a/core/main-service/src/main/java/ru/practicum/user/mapper/UserMapper.java b/core/user-service/src/main/java/ru/practicum/user/service/UserMapper.java similarity index 69% rename from core/main-service/src/main/java/ru/practicum/user/mapper/UserMapper.java rename to core/user-service/src/main/java/ru/practicum/user/service/UserMapper.java index 116a50d..fc0a4ad 100644 --- a/core/main-service/src/main/java/ru/practicum/user/mapper/UserMapper.java +++ b/core/user-service/src/main/java/ru/practicum/user/service/UserMapper.java @@ -1,9 +1,9 @@ -package ru.practicum.user.mapper; +package ru.practicum.user.service; -import ru.practicum.user.dto.NewUserRequestDto; -import ru.practicum.user.dto.UserDto; -import ru.practicum.user.dto.UserShortDto; -import ru.practicum.user.model.User; +import ru.practicum.dto.user.NewUserRequestDto; +import ru.practicum.dto.user.UserDto; +import ru.practicum.dto.user.UserShortDto; +import ru.practicum.user.dal.User; public class UserMapper { @@ -15,7 +15,7 @@ public static UserDto toDto(User user) { .build(); } - public static User toEntity(NewUserRequestDto dto) { + public static User toNewEntity(NewUserRequestDto dto) { return User.builder() .email(dto.getEmail()) .name(dto.getName()) diff --git a/core/main-service/src/main/java/ru/practicum/user/service/UserService.java b/core/user-service/src/main/java/ru/practicum/user/service/UserService.java similarity index 57% rename from core/main-service/src/main/java/ru/practicum/user/service/UserService.java rename to core/user-service/src/main/java/ru/practicum/user/service/UserService.java index b108d49..dadcb7a 100644 --- a/core/main-service/src/main/java/ru/practicum/user/service/UserService.java +++ b/core/user-service/src/main/java/ru/practicum/user/service/UserService.java @@ -5,18 +5,20 @@ import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import ru.practicum.dto.user.NewUserRequestDto; +import ru.practicum.dto.user.UserDto; +import ru.practicum.dto.user.UserShortDto; import ru.practicum.exception.ConflictException; import ru.practicum.exception.NotFoundException; -import ru.practicum.user.dto.NewUserRequestDto; -import ru.practicum.user.dto.UserDto; -import ru.practicum.user.mapper.UserMapper; -import ru.practicum.user.model.User; -import ru.practicum.user.repository.UserRepository; +import ru.practicum.user.dal.User; +import ru.practicum.user.dal.UserRepository; +import java.util.Collection; import java.util.List; @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class UserService { private final UserRepository userRepository; @@ -29,7 +31,7 @@ public UserDto create(NewUserRequestDto newUserRequestDto) { throw new ConflictException("User with email " + newUserRequestDto.getEmail() + " already exists", "Integrity constraint has been violated"); } - User newUser = UserMapper.toEntity(newUserRequestDto); + User newUser = UserMapper.toNewEntity(newUserRequestDto); userRepository.save(newUser); return UserMapper.toDto(newUser); } @@ -41,6 +43,20 @@ public void delete(Long userId) { userRepository.delete(userToDelete); } + // GET + HEAD + + public UserDto get(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Not found User " + userId)); + return UserMapper.toDto(user); + } + + public UserShortDto getShort(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Not found User " + userId)); + return UserMapper.toUserShortDto(user); + } + // GET COLLECTION public List findByIdListWithOffsetAndLimit(List idList, Integer from, Integer size) { @@ -58,4 +74,18 @@ public List findByIdListWithOffsetAndLimit(List idList, Integer f } } -} \ No newline at end of file + public Collection findUserShortDtoListByIds(Collection idList) { + if (idList == null || idList.isEmpty()) return List.of(); + return userRepository.findAllById(idList).stream() + .map(UserMapper::toUserShortDto) + .toList(); + } + + public Collection findUserDtoListByIds(Collection idList) { + if (idList == null || idList.isEmpty()) return List.of(); + return userRepository.findAllById(idList).stream() + .map(UserMapper::toDto) + .toList(); + } + +} diff --git a/core/main-service/src/main/resources/application.yaml b/core/user-service/src/main/resources/application.yaml similarity index 91% rename from core/main-service/src/main/resources/application.yaml rename to core/user-service/src/main/resources/application.yaml index d2b045b..da056e5 100644 --- a/core/main-service/src/main/resources/application.yaml +++ b/core/user-service/src/main/resources/application.yaml @@ -1,6 +1,6 @@ spring: application: - name: main-service + name: user-service config: import: "configserver:" cloud: @@ -22,4 +22,4 @@ eureka: instance: instance-id: ${spring.application.name}${random.int} preferIpAddress: false - hostname: localhost \ No newline at end of file + hostname: localhost diff --git a/core/user-service/src/main/resources/schema.sql b/core/user-service/src/main/resources/schema.sql new file mode 100644 index 0000000..ca98228 --- /dev/null +++ b/core/user-service/src/main/resources/schema.sql @@ -0,0 +1 @@ +CREATE SCHEMA IF NOT EXISTS user_service; diff --git a/docker-compose.yml b/docker-compose.yml index 2437e74..0199e0b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,29 +1,128 @@ services: - main-server: - build: server - image: main-server - container_name: main-server + discovery-server: + build: infra/discovery-server + image: discovery-server + container_name: discovery-server + restart: on-failure + ports: + - "8761:8761" + environment: + - eureka.instance.hostname=discovery-server + + config-server: + build: infra/config-server + image: config-server + container_name: config-server + restart: on-failure + ports: + - "8181:8181" + depends_on: + - discovery-server + environment: + - eureka.client.serviceUrl.defaultZone=http://discovery-server:8761/eureka/ + - eureka.instance.hostname=config-server + - server.port=8181 + + gateway-server: + build: infra/gateway-server + image: gateway-server + container_name: gateway-server restart: on-failure ports: - "8080:8080" + depends_on: + - discovery-server + - config-server + environment: + - eureka.client.serviceUrl.defaultZone=http://discovery-server:8761/eureka/ + - eureka.instance.hostname=gateway-server + - server.port=8080 + + user-service: + build: core/user-service + image: user-service + container_name: user-service + restart: on-failure + ports: + - "8082:8082" + depends_on: + - db-main + - discovery-server + - config-server + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://db-main:5432/main_db?currentSchema=user_service + - eureka.client.serviceUrl.defaultZone=http://discovery-server:8761/eureka/ + - eureka.instance.hostname=user-service + - server.port=8082 + + event-service: + build: core/event-service + image: event-service + container_name: event-service + restart: on-failure + ports: + - "8083:8083" + depends_on: + - db-main + - discovery-server + - config-server + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://db-main:5432/main_db?currentSchema=event_service + - eureka.client.serviceUrl.defaultZone=http://discovery-server:8761/eureka/ + - eureka.instance.hostname=event-service + - server.port=8083 + + request-service: + build: core/request-service + image: request-service + container_name: request-service + restart: on-failure + ports: + - "8084:8084" + depends_on: + - db-main + - discovery-server + - config-server + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://db-main:5432/main_db?currentSchema=request_service + - eureka.client.serviceUrl.defaultZone=http://discovery-server:8761/eureka/ + - eureka.instance.hostname=request-service + - server.port=8084 + + comment-service: + build: core/comment-service + image: comment-service + container_name: comment-service + restart: on-failure + ports: + - "8085:8085" depends_on: - db-main + - discovery-server + - config-server environment: - - SPRING_DATASOURCE_URL=jdbc:postgresql://db-main:5432/main_db - - EXPLORE_WITH_ME.STAT_SERVER.URL=http://stats-server:9090 + - SPRING_DATASOURCE_URL=jdbc:postgresql://db-main:5432/main_db?currentSchema=comment_service + - eureka.client.serviceUrl.defaultZone=http://discovery-server:8761/eureka/ + - eureka.instance.hostname=comment-service + - server.port=8085 stats-server: - build: stat/stat-server - image: stat-server - container_name: stat-server + build: stats/stats-server + image: stats-server + container_name: stats-server restart: on-failure ports: - - "9090:9090" + - "8090:8090" depends_on: - db-stat + - discovery-server + - config-server environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://db-stat:5432/stat_db + - eureka.client.serviceUrl.defaultZone=http://discovery-server:8761/eureka/ + - eureka.instance.hostname=stats-server + - server.port=8090 db-main: image: postgres:alpine diff --git a/infra/config-server/Dockerfile b/infra/config-server/Dockerfile new file mode 100644 index 0000000..16eef7a --- /dev/null +++ b/infra/config-server/Dockerfile @@ -0,0 +1,4 @@ +FROM amazoncorretto:21-alpine +LABEL authors="Слава" +COPY target/*.jar app.jar +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/infra/config-server/src/main/resources/application.yaml b/infra/config-server/src/main/resources/application.yaml index b93c364..8eae556 100644 --- a/infra/config-server/src/main/resources/application.yaml +++ b/infra/config-server/src/main/resources/application.yaml @@ -40,4 +40,4 @@ eureka: instance: instance-id: ${spring.application.name}${random.int} preferIpAddress: false - hostname: localhost \ No newline at end of file + hostname: localhost diff --git a/infra/config-server/src/main/resources/config/comment-service/application.yaml b/infra/config-server/src/main/resources/config/comment-service/application.yaml new file mode 100644 index 0000000..f79d92a --- /dev/null +++ b/infra/config-server/src/main/resources/config/comment-service/application.yaml @@ -0,0 +1,58 @@ +info: + app: + name: comment-service + description: Explore With Me + version: 1.0-SNAPSHOT + company: + name: Somekind Software + +logging: + level: + org.springframework.web: INFO + org.springframework.validation: INFO + +management: + endpoints.web.exposure.include: health,info,metrics + endpoint.health.show-details: always + metrics.export.prometheus.enabled: true + info.env.enabled: true + +explore-with-me: + stat-server.discovery.name: stats-server + datetime.format: yyyy-MM-dd HH:mm:ss + main.datetime.format: yyyy-MM-dd HH:mm:ss + stat.datetime.format: yyyy-MM-dd HH:mm:ss + +server: + port: 0 + shutdown: graceful + +spring: + lifecycle: + timeout-per-shutdown-phase: 30s + jpa: + show-sql: true + properties.hibernate.format_sql: false + hibernate.ddl-auto: create + sql: + init.mode: always + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://localhost:5433/main_db?currentSchema=comment_service + username: postgres + password: 12345 + cloud: + openfeign.circuitbreaker.enabled: true + +resilience4j.circuitbreaker: + configs: + default: + slidingWindowSize: 10 + failureRateThreshold: 50 + waitDurationInOpenState: 10000 + permittedNumberOfCallsInHalfOpenState: 3 + slidingWindowType: COUNT_BASED + minimumNumberOfCalls: 5 + automaticTransitionFromOpenToHalfOpenEnabled: true + ignoreExceptions: + - feign.FeignException.FeignClientException diff --git a/infra/config-server/src/main/resources/config/event-service/application.yaml b/infra/config-server/src/main/resources/config/event-service/application.yaml new file mode 100644 index 0000000..df63fa5 --- /dev/null +++ b/infra/config-server/src/main/resources/config/event-service/application.yaml @@ -0,0 +1,58 @@ +info: + app: + name: event-service + description: Explore With Me + version: 1.0-SNAPSHOT + company: + name: Somekind Software + +logging: + level: + org.springframework.web: INFO + org.springframework.validation: INFO + +management: + endpoints.web.exposure.include: health,info,metrics + endpoint.health.show-details: always + metrics.export.prometheus.enabled: true + info.env.enabled: true + +explore-with-me: + stat-server.discovery.name: stats-server + datetime.format: yyyy-MM-dd HH:mm:ss + main.datetime.format: yyyy-MM-dd HH:mm:ss + stat.datetime.format: yyyy-MM-dd HH:mm:ss + +server: + port: 0 + shutdown: graceful + +spring: + lifecycle: + timeout-per-shutdown-phase: 30s + jpa: + show-sql: true + properties.hibernate.format_sql: false + hibernate.ddl-auto: create + sql: + init.mode: always + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://localhost:5433/main_db?currentSchema=event_service + username: postgres + password: 12345 + cloud: + openfeign.circuitbreaker.enabled: true + +resilience4j.circuitbreaker: + configs: + default: + slidingWindowSize: 10 + failureRateThreshold: 50 + waitDurationInOpenState: 10000 + permittedNumberOfCallsInHalfOpenState: 3 + slidingWindowType: COUNT_BASED + minimumNumberOfCalls: 5 + automaticTransitionFromOpenToHalfOpenEnabled: true + ignoreExceptions: + - feign.FeignException.FeignClientException diff --git a/infra/config-server/src/main/resources/config/gateway-server/application.yaml b/infra/config-server/src/main/resources/config/gateway-server/application.yaml index 83b5998..674fb10 100644 --- a/infra/config-server/src/main/resources/config/gateway-server/application.yaml +++ b/infra/config-server/src/main/resources/config/gateway-server/application.yaml @@ -21,7 +21,47 @@ spring: webflux: routes: - - id: main-service - uri: lb://main-service + - id: user_service + uri: lb://user-service predicates: - - Path=/** + - Path= + /admin/users, + /admin/users/*, + /admin/users/*/short, + /admin/users/all/* + + - id: comment_service + uri: lb://comment-service + predicates: + - Path= + /comments/**, + /events/*/comments/**, + /users/*/events/*/comments, + /users/*/comments/**, + /admin/comments/**, + /admin/users/*/comments + + - id: request_service + uri: lb://request-service + predicates: + - Path= + /requests/**, + /users/*/requests, + /users/*/requests/*/cancel, + /users/*/events/*/requests + + - id: event-service + uri: lb://event-service + predicates: + - Path= + /admin/events/**, + /users/*/events, + /users/*/events/*, + /events, + /events/*, + /events/*/dto/*, + /events/dto/list/comment, + /categories/**, + /admin/categories/**, + /compilations/**, + /admin/compilations/** diff --git a/infra/config-server/src/main/resources/config/request-service/application.yaml b/infra/config-server/src/main/resources/config/request-service/application.yaml new file mode 100644 index 0000000..ea7616a --- /dev/null +++ b/infra/config-server/src/main/resources/config/request-service/application.yaml @@ -0,0 +1,58 @@ +info: + app: + name: request-service + description: Explore With Me + version: 1.0-SNAPSHOT + company: + name: Somekind Software + +logging: + level: + org.springframework.web: INFO + org.springframework.validation: INFO + +management: + endpoints.web.exposure.include: health,info,metrics + endpoint.health.show-details: always + metrics.export.prometheus.enabled: true + info.env.enabled: true + +explore-with-me: + stat-server.discovery.name: stats-server + datetime.format: yyyy-MM-dd HH:mm:ss + main.datetime.format: yyyy-MM-dd HH:mm:ss + stat.datetime.format: yyyy-MM-dd HH:mm:ss + +server: + port: 0 + shutdown: graceful + +spring: + lifecycle: + timeout-per-shutdown-phase: 30s + jpa: + show-sql: true + properties.hibernate.format_sql: false + hibernate.ddl-auto: create + sql: + init.mode: always + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://localhost:5433/main_db?currentSchema=request_service + username: postgres + password: 12345 + cloud: + openfeign.circuitbreaker.enabled: true + +resilience4j.circuitbreaker: + configs: + default: + slidingWindowSize: 10 + failureRateThreshold: 50 + waitDurationInOpenState: 10000 + permittedNumberOfCallsInHalfOpenState: 3 + slidingWindowType: COUNT_BASED + minimumNumberOfCalls: 5 + automaticTransitionFromOpenToHalfOpenEnabled: true + ignoreExceptions: + - feign.FeignException.FeignClientException diff --git a/infra/config-server/src/main/resources/config/main-service/application.yaml b/infra/config-server/src/main/resources/config/user-service/application.yaml similarity index 89% rename from infra/config-server/src/main/resources/config/main-service/application.yaml rename to infra/config-server/src/main/resources/config/user-service/application.yaml index 51e63d7..8197886 100644 --- a/infra/config-server/src/main/resources/config/main-service/application.yaml +++ b/infra/config-server/src/main/resources/config/user-service/application.yaml @@ -33,11 +33,11 @@ spring: jpa: show-sql: true properties.hibernate.format_sql: false - hibernate.ddl-auto: none + hibernate.ddl-auto: create sql: init.mode: always datasource: driver-class-name: org.postgresql.Driver - url: jdbc:postgresql://localhost:5433/main_db + url: jdbc:postgresql://localhost:5433/main_db?currentSchema=user_service username: postgres password: 12345 diff --git a/infra/discovery-server/Dockerfile b/infra/discovery-server/Dockerfile new file mode 100644 index 0000000..16eef7a --- /dev/null +++ b/infra/discovery-server/Dockerfile @@ -0,0 +1,4 @@ +FROM amazoncorretto:21-alpine +LABEL authors="Слава" +COPY target/*.jar app.jar +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/infra/discovery-server/src/main/resources/application.yaml b/infra/discovery-server/src/main/resources/application.yaml index cb91954..213e9e0 100644 --- a/infra/discovery-server/src/main/resources/application.yaml +++ b/infra/discovery-server/src/main/resources/application.yaml @@ -1,7 +1,7 @@ info: app: name: discovery-server - description: My Area Guide + description: Explore With Me version: 1.0-SNAPSHOT company: name: Somekind Software diff --git a/infra/gateway-server/Dockerfile b/infra/gateway-server/Dockerfile new file mode 100644 index 0000000..16eef7a --- /dev/null +++ b/infra/gateway-server/Dockerfile @@ -0,0 +1,4 @@ +FROM amazoncorretto:21-alpine +LABEL authors="Слава" +COPY target/*.jar app.jar +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/pom.xml b/pom.xml index 5c23a44..10191e4 100644 --- a/pom.xml +++ b/pom.xml @@ -11,17 +11,18 @@ Explore With Me - - core - infra - stats - - ru.practicum + ru.practicum explore-with-me 0.0.1-SNAPSHOT pom + + stats + core + infra + + UTF-8 21 @@ -55,6 +56,12 @@ ${stats-client.version} + + ru.practicum + core-common + ${version} + + From c10a38461926a6a262bafb283a162058f0ce8682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BB=D0=B0=D0=B2=D0=B0?= Date: Sun, 16 Nov 2025 13:53:37 +0400 Subject: [PATCH 2/6] pom fix --- core/request-service/pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/request-service/pom.xml b/core/request-service/pom.xml index 417a2b5..40c7b6d 100644 --- a/core/request-service/pom.xml +++ b/core/request-service/pom.xml @@ -10,7 +10,7 @@ 0.0.1-SNAPSHOT - comment-service + request-service @@ -19,7 +19,6 @@ ru.practicum core-common - 0.0.1-SNAPSHOT From b06ee01e9caa83742da7609945541518cdaa8975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BB=D0=B0=D0=B2=D0=B0?= Date: Sun, 16 Nov 2025 13:59:30 +0400 Subject: [PATCH 3/6] pom fix --- core/comment-service/pom.xml | 92 +++++++++---------- .../java/ru/practicum/client/EventClient.java | 2 +- .../practicum/client/EventClientHelper.java | 2 +- 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/core/comment-service/pom.xml b/core/comment-service/pom.xml index fd2f012..b42ffae 100644 --- a/core/comment-service/pom.xml +++ b/core/comment-service/pom.xml @@ -9,18 +9,17 @@ core 0.0.1-SNAPSHOT - ru.practicum + comment-service - 0.0.1-SNAPSHOT - - - - + + ru.practicum + core-common + @@ -35,10 +34,10 @@ lombok - - - - + + org.springframework.boot + spring-boot-starter-actuator + @@ -60,62 +59,63 @@ - - - - - - - - - - + + org.postgresql + postgresql + - - - - + + org.springframework.boot + spring-boot-starter-data-jpa + - + org.springframework.cloud - spring-cloud-starter-openfeign + spring-cloud-starter-netflix-eureka-client org.springframework.cloud - spring-cloud-starter-circuitbreaker-resilience4j + spring-cloud-starter-config - - - - - - - - - + + org.springframework.retry + spring-retry + - - - - + - + + org.springframework.cloud + spring-cloud-starter-openfeign + - - - - - + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-resilience4j + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + org.apache.maven.plugins maven-compiler-plugin diff --git a/core/comment-service/src/main/java/ru/practicum/client/EventClient.java b/core/comment-service/src/main/java/ru/practicum/client/EventClient.java index a808401..50a0caf 100644 --- a/core/comment-service/src/main/java/ru/practicum/client/EventClient.java +++ b/core/comment-service/src/main/java/ru/practicum/client/EventClient.java @@ -5,4 +5,4 @@ @FeignClient(name = "event-service") public interface EventClient extends EventAllApi { -} +} \ No newline at end of file diff --git a/core/comment-service/src/main/java/ru/practicum/client/EventClientHelper.java b/core/comment-service/src/main/java/ru/practicum/client/EventClientHelper.java index 633a138..2c7eb86 100644 --- a/core/comment-service/src/main/java/ru/practicum/client/EventClientHelper.java +++ b/core/comment-service/src/main/java/ru/practicum/client/EventClientHelper.java @@ -10,4 +10,4 @@ public EventClientHelper(EventAllApi eventApiClient) { super(eventApiClient); } -} +} \ No newline at end of file From f217257515ca9073584aa59ad42fc0e7dda3c596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BB=D0=B0=D0=B2=D0=B0?= Date: Tue, 18 Nov 2025 00:01:18 +0400 Subject: [PATCH 4/6] Comment service fix + readme --- README.md | 108 ++++++++++++++++++ .../comment/service/CommentAdminService.java | 2 +- .../service/CommentAdminServiceImpl.java | 8 +- 3 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee92cce --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +# Explore With Me + +Explore With Me is a web application for people who want to be in touch with +current events in their local areas. + +People can view the list of events, make participation requests, get approvals or rejections, +leave comments. + +Event makers can start new events and manage participants lists + +Administrators can manage events, users, categories and make compilations of events. +Also, they have statistics microservice to control important metrics. + +# Infrastructure modules (folder `/infra`) + +## Discovery Service + +The module `discovery-server` implements Service Registry - the central part of +Service Discovery pattern. + +Stack: +- String Boot +- Spring Cloud Eureka server + +## Configuration Service + +The module `config-server` implements an External Configuration pattern as a central storage +of configuration files for all microservices except discovery. + +Stack: +- String Boot +- Spring Cloud Config server + +## Gateway Service + +The module `gateway-server` implements an API Gateway pattern. It works as a single entry point +for all microservices. + +Stack: +- String Boot +- Spring Cloud Gateway server + +# Application core modules (folder `/core`) + +The Microservice-based application contains 4 integrated services. They have separate databases +(implemented as different schemas in this release). The interaction is realized through Feign-clients with circuit breaker. + +Stack: +- Java +- String Boot +- Spring MVC (REST) +- Spring Data JPA (Hibernate) +- Spring Cloud Discovery and Configuration +- OpenFeign client + Resilience4j +- PostgreSQL + +## Common library + +The module `core-common` contains common classes and interfaces used by all core modules such as: +- API description interfaces +- Feign client interaction helpers +- DTO +- Exceptions and exception handlers +- Validation custom annotations + +## User Management Service + +The module `user-service` provides Admin interface to manage users. It doesn't access other services +but provides them information about users. + +Public API Specification: [API-user-service-specification.json](API-user-service-specification.json) + +Interaction API endpoints: +- GET /admin/users/{userId}/short (returns UserShortDto to common interaction) +- GET /admin/users/all/short (returns list of UserShortDto) +- GET /admin/users/all/full (returns list of UserDto) + +## Event Management Service + +The module `event-service` provides Admin, Private and Public interface to operate events - the central +entity in application. The module interacts with request-service (to get information about requests number) +and user-service (to get extended user information) + +Public API Specification: [API-event-service-specification.json](API-event-service-specification.json) + +Interaction API endpoints: +- GET /events/{id}/dto/interaction (returns shortened EventInteractionDto to common interaction) +- GET /events/{id}/dto/comment (returns EventCommentDto for comment-service) +- POST /events/dto/list/comment (returns list of EventCommentDto for comment-service) + +## Participation Requests Management Service + +The module `request-service` provides Private interface to operate participation requests. It interacts with +event-service and user-service (to get extended info and check existence) + +Public API Specification: [API-request-service-specification.json](API-request-service-specification.json) + +Interaction API endpoints: +- POST /requests/confirmed (returns map of confirmed requests by event ID list) + +## Event Comments Management Service + +The module `comment-service` provides Admin, Private and Public interface to operate comments to events. +It interacts with event-service and user-service (to get extended info and check existence) + +Public API Specification: [API-comment-service-specification.json](API-comment-service-specification.json) + + diff --git a/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java index bfa01e5..2744230 100644 --- a/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java +++ b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminService.java @@ -6,7 +6,7 @@ public interface CommentAdminService { - String delete(Long comId); + boolean delete(Long comId); List search(String text, int from, int size); diff --git a/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java index 0b7e42f..b6a2ba3 100644 --- a/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java +++ b/core/comment-service/src/main/java/ru/practicum/comment/service/CommentAdminServiceImpl.java @@ -34,10 +34,12 @@ public class CommentAdminServiceImpl implements CommentAdminService { @Override @Transactional - public String delete(Long comId) { - if (!commentRepository.existsById(comId)) throw new NotFoundException("Not found Comment " + comId); + public boolean delete(Long comId) { + if (!commentRepository.existsById(comId)) { + throw new NotFoundException("Not found Comment " + comId); + } commentRepository.deleteById(comId); - return "deleted comment " + comId; + return true; } @Override From 82a6f6b549f268b59112ce7ceeabe2b904a02884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BB=D0=B0=D0=B2=D0=B0?= Date: Tue, 18 Nov 2025 00:05:34 +0400 Subject: [PATCH 5/6] CommentAdminController fix --- .../ru/practicum/comment/controller/CommentAdminController.java | 2 +- .../src/main/java/ru/practicum/api/comment/CommentAdminApi.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java b/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java index c6f0aa7..f39aa50 100644 --- a/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java +++ b/core/comment-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java @@ -27,7 +27,7 @@ public Collection get(Long userId, int from, int size) { } @Override - public String delete(Long comId) { + public boolean delete(Long comId) { return commentAdminService.delete(comId); } diff --git a/core/core-common/src/main/java/ru/practicum/api/comment/CommentAdminApi.java b/core/core-common/src/main/java/ru/practicum/api/comment/CommentAdminApi.java index 35eb626..9738b88 100644 --- a/core/core-common/src/main/java/ru/practicum/api/comment/CommentAdminApi.java +++ b/core/core-common/src/main/java/ru/practicum/api/comment/CommentAdminApi.java @@ -28,7 +28,7 @@ Collection get( @DeleteMapping("/admin/comments/{comId}") @ResponseStatus(HttpStatus.NO_CONTENT) - String delete( + boolean delete( @PathVariable @Positive Long comId ); From a7f8eede319b3d64b5e28456ccbb5107fbe4e987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BB=D0=B0=D0=B2=D0=B0?= Date: Tue, 18 Nov 2025 00:13:35 +0400 Subject: [PATCH 6/6] add specification.json --- ... => API-comment-service-specification.json | 0 ...on => API-event-service-specification.json | 704 +----------------- API-request-service-specification.json | 599 +++++++++++++++ ...son => API-stats-server-specification.json | 0 API-user-service-specification.json | 387 ++++++++++ 5 files changed, 1011 insertions(+), 679 deletions(-) rename ewm-feature-comments-spec.json => API-comment-service-specification.json (100%) rename ewm-main-service-spec.json => API-event-service-specification.json (76%) create mode 100644 API-request-service-specification.json rename ewm-stats-service-spec.json => API-stats-server-specification.json (100%) create mode 100644 API-user-service-specification.json diff --git a/ewm-feature-comments-spec.json b/API-comment-service-specification.json similarity index 100% rename from ewm-feature-comments-spec.json rename to API-comment-service-specification.json diff --git a/ewm-main-service-spec.json b/API-event-service-specification.json similarity index 76% rename from ewm-main-service-spec.json rename to API-event-service-specification.json index f28d141..3de2ee2 100644 --- a/ewm-main-service-spec.json +++ b/API-event-service-specification.json @@ -2,7 +2,7 @@ "openapi": "3.0.1", "info": { "description": "Documentation \"Explore With Me\" API v1.0", - "title": "\"Explore With Me\" API сервер", + "title": "Explore With Me - EVENT-SERVICE API", "version": "1.0" }, "servers": [ @@ -36,14 +36,6 @@ "description": "Публичный API для работы с событиями", "name": "Public: События" }, - { - "description": "Закрытый API для работы с запросами текущего пользователя на участие в событиях", - "name": "Private: Запросы на участие" - }, - { - "description": "API для работы с пользователями", - "name": "Admin: Пользователи" - }, { "description": "API для работы с подборками событий", "name": "Admin: Подборки событий" @@ -612,189 +604,6 @@ ] } }, - "/admin/users": { - "get": { - "description": "Возвращает информацию обо всех пользователях (учитываются параметры ограничения выборки), либо о конкретных (учитываются указанные идентификаторы)\n\nВ случае, если по заданным фильтрам не найдено ни одного пользователя, возвращает пустой список", - "operationId": "getUsers", - "parameters": [ - { - "description": "id пользователей", - "in": "query", - "name": "ids", - "required": false, - "schema": { - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - } - }, - { - "description": "количество элементов, которые нужно пропустить для формирования текущего набора", - "in": "query", - "name": "from", - "required": false, - "schema": { - "minimum": 0, - "type": "integer", - "format": "int32", - "default": 0 - } - }, - { - "description": "количество элементов в наборе", - "in": "query", - "name": "size", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 10 - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserDto" - } - } - } - }, - "description": "Пользователи найдены" - }, - "400": { - "content": { - "application/json": { - "example": { - "status": "BAD_REQUEST", - "reason": "Incorrectly made request.", - "message": "Failed to convert value of type java.lang.String to required type int; nested exception is java.lang.NumberFormatException: For input string: ad", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Запрос составлен некорректно" - } - }, - "summary": "Получение информации о пользователях", - "tags": [ - "Admin: Пользователи" - ] - }, - "post": { - "operationId": "registerUser", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NewUserRequest" - } - } - }, - "description": "Данные добавляемого пользователя", - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserDto" - } - } - }, - "description": "Пользователь зарегистрирован" - }, - "400": { - "content": { - "application/json": { - "example": { - "status": "BAD_REQUEST", - "reason": "Incorrectly made request.", - "message": "Field: name. Error: must not be blank. Value: null", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Запрос составлен некорректно" - }, - "409": { - "content": { - "application/json": { - "example": { - "status": "CONFLICT", - "reason": "Integrity constraint has been violated.", - "message": "could not execute statement; SQL [n/a]; constraint [uq_email]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Нарушение целостности данных" - } - }, - "summary": "Добавление нового пользователя", - "tags": [ - "Admin: Пользователи" - ] - } - }, - "/admin/users/{userId}": { - "delete": { - "operationId": "delete", - "parameters": [ - { - "description": "id пользователя", - "in": "path", - "name": "userId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "204": { - "description": "Пользователь удален" - }, - "404": { - "content": { - "application/json": { - "example": { - "status": "NOT_FOUND", - "reason": "The required object was not found.", - "message": "User with id=555 was not found", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Пользователь не найден или недоступен" - } - }, - "summary": "Удаление пользователя", - "tags": [ - "Admin: Пользователи" - ] - } - }, "/categories": { "get": { "description": "В случае, если по заданным фильтрам не найдено ни одной категории, возвращает пустой список", @@ -1601,414 +1410,32 @@ "Private: События" ] } - }, - "/users/{userId}/events/{eventId}/requests": { - "get": { - "description": "В случае, если по заданным фильтрам не найдено ни одной заявки, возвращает пустой список", - "operationId": "getEventParticipants", - "parameters": [ - { - "description": "id текущего пользователя", - "in": "path", - "name": "userId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" + } + }, + "components": { + "schemas": { + "ApiError": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "description": "Список стектрейсов или описания ошибок", + "example": [], + "items": { + "type": "string", + "description": "Список стектрейсов или описания ошибок", + "example": "[]" } }, - { - "description": "id события", - "in": "path", - "name": "eventId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ParticipationRequestDto" - } - } - } - }, - "description": "Найдены запросы на участие" + "message": { + "type": "string", + "description": "Сообщение об ошибке", + "example": "Only pending or canceled events can be changed" }, - "400": { - "content": { - "application/json": { - "example": { - "status": "BAD_REQUEST", - "reason": "Incorrectly made request.", - "message": "Failed to convert value of type java.lang.String to required type int; nested exception is java.lang.NumberFormatException: For input string: ad", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Запрос составлен некорректно" - } - }, - "summary": "Получение информации о запросах на участие в событии текущего пользователя", - "tags": [ - "Private: События" - ] - }, - "patch": { - "description": "Обратите внимание:\n- если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется\n- нельзя подтвердить заявку, если уже достигнут лимит по заявкам на данное событие (Ожидается код ошибки 409)\n- статус можно изменить только у заявок, находящихся в состоянии ожидания (Ожидается код ошибки 409)\n- если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки необходимо отклонить", - "operationId": "changeRequestStatus", - "parameters": [ - { - "description": "id текущего пользователя", - "in": "path", - "name": "userId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "id события текущего пользователя", - "in": "path", - "name": "eventId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventRequestStatusUpdateRequest" - } - } - }, - "description": "Новый статус для заявок на участие в событии текущего пользователя", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventRequestStatusUpdateResult" - } - } - }, - "description": "Статус заявок изменён" - }, - "400": { - "content": { - "application/json": { - "example": { - "status": "BAD_REQUEST", - "reason": "Incorrectly made request.", - "message": "Request must have status PENDING", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Запрос составлен некорректно" - }, - "404": { - "content": { - "application/json": { - "example": { - "status": "NOT_FOUND", - "reason": "The required object was not found.", - "message": "Event with id=321 was not found", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Событие не найдено или недоступно" - }, - "409": { - "content": { - "application/json": { - "example": { - "status": "CONFLICT", - "reason": "For the requested operation the conditions are not met.", - "message": "The participant limit has been reached", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Достигнут лимит одобренных заявок" - } - }, - "summary": "Изменение статуса (подтверждена, отменена) заявок на участие в событии текущего пользователя", - "tags": [ - "Private: События" - ] - } - }, - "/users/{userId}/requests": { - "get": { - "description": "В случае, если по заданным фильтрам не найдено ни одной заявки, возвращает пустой список", - "operationId": "getUserRequests", - "parameters": [ - { - "description": "id текущего пользователя", - "in": "path", - "name": "userId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ParticipationRequestDto" - } - } - } - }, - "description": "Найдены запросы на участие" - }, - "400": { - "content": { - "application/json": { - "example": { - "status": "BAD_REQUEST", - "reason": "Incorrectly made request.", - "message": "Failed to convert value of type java.lang.String to required type long; nested exception is java.lang.NumberFormatException: For input string: ad", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Запрос составлен некорректно" - }, - "404": { - "content": { - "application/json": { - "example": { - "status": "NOT_FOUND", - "reason": "The required object was not found.", - "message": "User with id=11 was not found", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Пользователь не найден" - } - }, - "summary": "Получение информации о заявках текущего пользователя на участие в чужих событиях", - "tags": [ - "Private: Запросы на участие" - ] - }, - "post": { - "description": "Обратите внимание:\n- нельзя добавить повторный запрос (Ожидается код ошибки 409)\n- инициатор события не может добавить запрос на участие в своём событии (Ожидается код ошибки 409)\n- нельзя участвовать в неопубликованном событии (Ожидается код ошибки 409)\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку (Ожидается код ошибки 409)\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного", - "operationId": "addParticipationRequest", - "parameters": [ - { - "description": "id текущего пользователя", - "in": "path", - "name": "userId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "id события", - "in": "query", - "name": "eventId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ParticipationRequestDto" - } - } - }, - "description": "Заявка создана" - }, - "400": { - "content": { - "application/json": { - "example": { - "status": "BAD_REQUEST", - "reason": "Incorrectly made request.", - "message": "Failed to convert value of type java.lang.String to required type long; nested exception is java.lang.NumberFormatException: For input string: ad", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Запрос составлен некорректно" - }, - "404": { - "content": { - "application/json": { - "example": { - "status": "NOT_FOUND", - "reason": "The required object was not found.", - "message": "Event with id=13 was not found", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Событие не найдено или недоступно" - }, - "409": { - "content": { - "application/json": { - "example": { - "status": "CONFLICT", - "reason": "Integrity constraint has been violated.", - "message": "could not execute statement; SQL [n/a]; constraint [uq_request]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Нарушение целостности данных" - } - }, - "summary": "Добавление запроса от текущего пользователя на участие в событии", - "tags": [ - "Private: Запросы на участие" - ] - } - }, - "/users/{userId}/requests/{requestId}/cancel": { - "patch": { - "operationId": "cancelRequest", - "parameters": [ - { - "description": "id текущего пользователя", - "in": "path", - "name": "userId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "description": "id запроса на участие", - "in": "path", - "name": "requestId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ParticipationRequestDto" - } - } - }, - "description": "Заявка отменена" - }, - "404": { - "content": { - "application/json": { - "example": { - "status": "NOT_FOUND", - "reason": "The required object was not found.", - "message": "Request with id=2727 was not found", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Запрос не найден или недоступен" - } - }, - "summary": "Отмена своего запроса на участие в событии", - "tags": [ - "Private: Запросы на участие" - ] - } - } - }, - "components": { - "schemas": { - "ApiError": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "description": "Список стектрейсов или описания ошибок", - "example": [], - "items": { - "type": "string", - "description": "Список стектрейсов или описания ошибок", - "example": "[]" - } - }, - "message": { - "type": "string", - "description": "Сообщение об ошибке", - "example": "Only pending or canceled events can be changed" - }, - "reason": { - "type": "string", - "description": "Общее описание причины ошибки", - "example": "For the requested operation the conditions are not met." + "reason": { + "type": "string", + "description": "Общее описание причины ошибки", + "example": "For the requested operation the conditions are not met." }, "status": { "type": "string", @@ -2286,53 +1713,6 @@ } } }, - "EventRequestStatusUpdateRequest": { - "type": "object", - "properties": { - "requestIds": { - "type": "array", - "description": "Идентификаторы запросов на участие в событии текущего пользователя", - "example": [ - 1, - 2, - 3 - ], - "items": { - "type": "integer", - "description": "Идентификаторы запросов на участие в событии текущего пользователя", - "format": "int64" - } - }, - "status": { - "type": "string", - "description": "Новый статус запроса на участие в событии текущего пользователя", - "example": "CONFIRMED", - "enum": [ - "CONFIRMED", - "REJECTED" - ] - } - }, - "description": "Изменение статуса запроса на участие в событии текущего пользователя" - }, - "EventRequestStatusUpdateResult": { - "type": "object", - "properties": { - "confirmedRequests": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ParticipationRequestDto" - } - }, - "rejectedRequests": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ParticipationRequestDto" - } - } - }, - "description": "Результат подтверждения/отклонения заявок на участие в событии" - }, "EventShortDto": { "required": [ "annotation", @@ -2590,40 +1970,6 @@ }, "description": "Данные нового пользователя" }, - "ParticipationRequestDto": { - "type": "object", - "properties": { - "created": { - "type": "string", - "description": "Дата и время создания заявки", - "example": "2022-09-06T21:10:05.432" - }, - "event": { - "type": "integer", - "description": "Идентификатор события", - "format": "int64", - "example": 1 - }, - "id": { - "type": "integer", - "description": "Идентификатор заявки", - "format": "int64", - "example": 3 - }, - "requester": { - "type": "integer", - "description": "Идентификатор пользователя, отправившего заявку", - "format": "int64", - "example": 2 - }, - "status": { - "type": "string", - "description": "Статус заявки", - "example": "PENDING" - } - }, - "description": "Заявка на участие в событии" - }, "UpdateCompilationRequest": { "type": "object", "properties": { @@ -2833,4 +2179,4 @@ } } } -} +} \ No newline at end of file diff --git a/API-request-service-specification.json b/API-request-service-specification.json new file mode 100644 index 0000000..ba9d56a --- /dev/null +++ b/API-request-service-specification.json @@ -0,0 +1,599 @@ +{ + "openapi": "3.0.1", + "info": { + "description": "Documentation \"Explore With Me\" API v1.0", + "title": "Explore With Me - REQUEST SERVICE API", + "version": "1.0" + }, + "servers": [ + { + "description": "Generated server url", + "url": "http://localhost:8080" + } + ], + "tags": [ + { + "description": "Закрытый API для работы с событиями", + "name": "Private: События" + }, + { + "description": "Закрытый API для работы с запросами на участие в событиях", + "name": "Private: Запросы на участие" + } + ], + "paths": { + "/users/{userId}/events/{eventId}/requests": { + "get": { + "description": "В случае, если по заданным фильтрам не найдено ни одной заявки, возвращает пустой список", + "operationId": "getEventParticipants", + "parameters": [ + { + "description": "id текущего пользователя", + "in": "path", + "name": "userId", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "description": "id события", + "in": "path", + "name": "eventId", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ParticipationRequestDto" + } + } + } + }, + "description": "Найдены запросы на участие" + }, + "400": { + "content": { + "application/json": { + "example": { + "status": "BAD_REQUEST", + "reason": "Incorrectly made request.", + "message": "Failed to convert value of type java.lang.String to required type int; nested exception is java.lang.NumberFormatException: For input string: ad", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Запрос составлен некорректно" + } + }, + "summary": "Получение информации о запросах на участие в событии текущего пользователя", + "tags": [ + "Private: События" + ] + }, + "patch": { + "description": "Обратите внимание:\n- если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется\n- нельзя подтвердить заявку, если уже достигнут лимит по заявкам на данное событие (Ожидается код ошибки 409)\n- статус можно изменить только у заявок, находящихся в состоянии ожидания (Ожидается код ошибки 409)\n- если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки необходимо отклонить", + "operationId": "changeRequestStatus", + "parameters": [ + { + "description": "id текущего пользователя", + "in": "path", + "name": "userId", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "description": "id события текущего пользователя", + "in": "path", + "name": "eventId", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventRequestStatusUpdateRequest" + } + } + }, + "description": "Новый статус для заявок на участие в событии текущего пользователя", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventRequestStatusUpdateResult" + } + } + }, + "description": "Статус заявок изменён" + }, + "400": { + "content": { + "application/json": { + "example": { + "status": "BAD_REQUEST", + "reason": "Incorrectly made request.", + "message": "Request must have status PENDING", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Запрос составлен некорректно" + }, + "404": { + "content": { + "application/json": { + "example": { + "status": "NOT_FOUND", + "reason": "The required object was not found.", + "message": "Event with id=321 was not found", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Событие не найдено или недоступно" + }, + "409": { + "content": { + "application/json": { + "example": { + "status": "CONFLICT", + "reason": "For the requested operation the conditions are not met.", + "message": "The participant limit has been reached", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Достигнут лимит одобренных заявок" + } + }, + "summary": "Изменение статуса (подтверждена, отменена) заявок на участие в событии текущего пользователя", + "tags": [ + "Private: События" + ] + } + }, + "/users/{userId}/requests": { + "get": { + "description": "В случае, если по заданным фильтрам не найдено ни одной заявки, возвращает пустой список", + "operationId": "getUserRequests", + "parameters": [ + { + "description": "id текущего пользователя", + "in": "path", + "name": "userId", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ParticipationRequestDto" + } + } + } + }, + "description": "Найдены запросы на участие" + }, + "400": { + "content": { + "application/json": { + "example": { + "status": "BAD_REQUEST", + "reason": "Incorrectly made request.", + "message": "Failed to convert value of type java.lang.String to required type long; nested exception is java.lang.NumberFormatException: For input string: ad", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Запрос составлен некорректно" + }, + "404": { + "content": { + "application/json": { + "example": { + "status": "NOT_FOUND", + "reason": "The required object was not found.", + "message": "User with id=11 was not found", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Пользователь не найден" + } + }, + "summary": "Получение информации о заявках текущего пользователя на участие в чужих событиях", + "tags": [ + "Private: Запросы на участие" + ] + }, + "post": { + "description": "Обратите внимание:\n- нельзя добавить повторный запрос (Ожидается код ошибки 409)\n- инициатор события не может добавить запрос на участие в своём событии (Ожидается код ошибки 409)\n- нельзя участвовать в неопубликованном событии (Ожидается код ошибки 409)\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку (Ожидается код ошибки 409)\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного", + "operationId": "addParticipationRequest", + "parameters": [ + { + "description": "id текущего пользователя", + "in": "path", + "name": "userId", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "description": "id события", + "in": "query", + "name": "eventId", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ParticipationRequestDto" + } + } + }, + "description": "Заявка создана" + }, + "400": { + "content": { + "application/json": { + "example": { + "status": "BAD_REQUEST", + "reason": "Incorrectly made request.", + "message": "Failed to convert value of type java.lang.String to required type long; nested exception is java.lang.NumberFormatException: For input string: ad", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Запрос составлен некорректно" + }, + "404": { + "content": { + "application/json": { + "example": { + "status": "NOT_FOUND", + "reason": "The required object was not found.", + "message": "Event with id=13 was not found", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Событие не найдено или недоступно" + }, + "409": { + "content": { + "application/json": { + "example": { + "status": "CONFLICT", + "reason": "Integrity constraint has been violated.", + "message": "could not execute statement; SQL [n/a]; constraint [uq_request]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Нарушение целостности данных" + } + }, + "summary": "Добавление запроса от текущего пользователя на участие в событии", + "tags": [ + "Private: Запросы на участие" + ] + } + }, + "/users/{userId}/requests/{requestId}/cancel": { + "patch": { + "operationId": "cancelRequest", + "parameters": [ + { + "description": "id текущего пользователя", + "in": "path", + "name": "userId", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "description": "id запроса на участие", + "in": "path", + "name": "requestId", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ParticipationRequestDto" + } + } + }, + "description": "Заявка отменена" + }, + "404": { + "content": { + "application/json": { + "example": { + "status": "NOT_FOUND", + "reason": "The required object was not found.", + "message": "Request with id=2727 was not found", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Запрос не найден или недоступен" + } + }, + "summary": "Отмена своего запроса на участие в событии", + "tags": [ + "Private: Запросы на участие" + ] + } + } + }, + "components": { + "schemas": { + "ApiError": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "description": "Список стектрейсов или описания ошибок", + "example": [], + "items": { + "type": "string", + "description": "Список стектрейсов или описания ошибок", + "example": "[]" + } + }, + "message": { + "type": "string", + "description": "Сообщение об ошибке", + "example": "Only pending or canceled events can be changed" + }, + "reason": { + "type": "string", + "description": "Общее описание причины ошибки", + "example": "For the requested operation the conditions are not met." + }, + "status": { + "type": "string", + "description": "Код статуса HTTP-ответа", + "example": "FORBIDDEN", + "enum": [ + "100 CONTINUE", + "101 SWITCHING_PROTOCOLS", + "102 PROCESSING", + "103 CHECKPOINT", + "200 OK", + "201 CREATED", + "202 ACCEPTED", + "203 NON_AUTHORITATIVE_INFORMATION", + "204 NO_CONTENT", + "205 RESET_CONTENT", + "206 PARTIAL_CONTENT", + "207 MULTI_STATUS", + "208 ALREADY_REPORTED", + "226 IM_USED", + "300 MULTIPLE_CHOICES", + "301 MOVED_PERMANENTLY", + "302 FOUND", + "302 MOVED_TEMPORARILY", + "303 SEE_OTHER", + "304 NOT_MODIFIED", + "305 USE_PROXY", + "307 TEMPORARY_REDIRECT", + "308 PERMANENT_REDIRECT", + "400 BAD_REQUEST", + "401 UNAUTHORIZED", + "402 PAYMENT_REQUIRED", + "403 FORBIDDEN", + "404 NOT_FOUND", + "405 METHOD_NOT_ALLOWED", + "406 NOT_ACCEPTABLE", + "407 PROXY_AUTHENTICATION_REQUIRED", + "408 REQUEST_TIMEOUT", + "409 CONFLICT", + "410 GONE", + "411 LENGTH_REQUIRED", + "412 PRECONDITION_FAILED", + "413 PAYLOAD_TOO_LARGE", + "413 REQUEST_ENTITY_TOO_LARGE", + "414 URI_TOO_LONG", + "414 REQUEST_URI_TOO_LONG", + "415 UNSUPPORTED_MEDIA_TYPE", + "416 REQUESTED_RANGE_NOT_SATISFIABLE", + "417 EXPECTATION_FAILED", + "418 I_AM_A_TEAPOT", + "419 INSUFFICIENT_SPACE_ON_RESOURCE", + "420 METHOD_FAILURE", + "421 DESTINATION_LOCKED", + "422 UNPROCESSABLE_ENTITY", + "423 LOCKED", + "424 FAILED_DEPENDENCY", + "425 TOO_EARLY", + "426 UPGRADE_REQUIRED", + "428 PRECONDITION_REQUIRED", + "429 TOO_MANY_REQUESTS", + "431 REQUEST_HEADER_FIELDS_TOO_LARGE", + "451 UNAVAILABLE_FOR_LEGAL_REASONS", + "500 INTERNAL_SERVER_ERROR", + "501 NOT_IMPLEMENTED", + "502 BAD_GATEWAY", + "503 SERVICE_UNAVAILABLE", + "504 GATEWAY_TIMEOUT", + "505 HTTP_VERSION_NOT_SUPPORTED", + "506 VARIANT_ALSO_NEGOTIATES", + "507 INSUFFICIENT_STORAGE", + "508 LOOP_DETECTED", + "509 BANDWIDTH_LIMIT_EXCEEDED", + "510 NOT_EXTENDED", + "511 NETWORK_AUTHENTICATION_REQUIRED" + ] + }, + "timestamp": { + "type": "string", + "description": "Дата и время когда произошла ошибка (в формате \"yyyy-MM-dd HH:mm:ss\")", + "example": "2022-06-09 06:27:23" + } + }, + "description": "Сведения об ошибке" + }, + "EventRequestStatusUpdateRequest": { + "type": "object", + "properties": { + "requestIds": { + "type": "array", + "description": "Идентификаторы запросов на участие в событии текущего пользователя", + "example": [ + 1, + 2, + 3 + ], + "items": { + "type": "integer", + "description": "Идентификаторы запросов на участие в событии текущего пользователя", + "format": "int64" + } + }, + "status": { + "type": "string", + "description": "Новый статус запроса на участие в событии текущего пользователя", + "example": "CONFIRMED", + "enum": [ + "CONFIRMED", + "REJECTED" + ] + } + }, + "description": "Изменение статуса запроса на участие в событии текущего пользователя" + }, + "EventRequestStatusUpdateResult": { + "type": "object", + "properties": { + "confirmedRequests": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ParticipationRequestDto" + } + }, + "rejectedRequests": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ParticipationRequestDto" + } + } + }, + "description": "Результат подтверждения/отклонения заявок на участие в событии" + }, + "ParticipationRequestDto": { + "type": "object", + "properties": { + "created": { + "type": "string", + "description": "Дата и время создания заявки", + "example": "2022-09-06T21:10:05.432" + }, + "event": { + "type": "integer", + "description": "Идентификатор события", + "format": "int64", + "example": 1 + }, + "id": { + "type": "integer", + "description": "Идентификатор заявки", + "format": "int64", + "example": 3 + }, + "requester": { + "type": "integer", + "description": "Идентификатор пользователя, отправившего заявку", + "format": "int64", + "example": 2 + }, + "status": { + "type": "string", + "description": "Статус заявки", + "example": "PENDING" + } + }, + "description": "Заявка на участие в событии" + } + } + } +} \ No newline at end of file diff --git a/ewm-stats-service-spec.json b/API-stats-server-specification.json similarity index 100% rename from ewm-stats-service-spec.json rename to API-stats-server-specification.json diff --git a/API-user-service-specification.json b/API-user-service-specification.json new file mode 100644 index 0000000..acbd611 --- /dev/null +++ b/API-user-service-specification.json @@ -0,0 +1,387 @@ +{ + "openapi": "3.0.1", + "info": { + "description": "Documentation \"Explore With Me\" API v1.0", + "title": "Explore With Me- USER SERVICE API", + "version": "1.0" + }, + "servers": [ + { + "description": "Generated server url", + "url": "http://localhost:8080" + } + ], + "tags": [ + { + "description": "API для работы с пользователями", + "name": "Admin: Пользователи" + } + ], + "paths": { + "/admin/users": { + "get": { + "description": "Возвращает информацию обо всех пользователях (учитываются параметры ограничения выборки), либо о конкретных (учитываются указанные идентификаторы)\n\nВ случае, если по заданным фильтрам не найдено ни одного пользователя, возвращает пустой список", + "operationId": "getUsers", + "parameters": [ + { + "description": "id пользователей", + "in": "query", + "name": "ids", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + { + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "in": "query", + "name": "from", + "required": false, + "schema": { + "minimum": 0, + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "description": "количество элементов в наборе", + "in": "query", + "name": "size", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserDto" + } + } + } + }, + "description": "Пользователи найдены" + }, + "400": { + "content": { + "application/json": { + "example": { + "status": "BAD_REQUEST", + "reason": "Incorrectly made request.", + "message": "Failed to convert value of type java.lang.String to required type int; nested exception is java.lang.NumberFormatException: For input string: ad", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Запрос составлен некорректно" + } + }, + "summary": "Получение информации о пользователях", + "tags": [ + "Admin: Пользователи" + ] + }, + "post": { + "operationId": "registerUser", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewUserRequest" + } + } + }, + "description": "Данные добавляемого пользователя", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + } + }, + "description": "Пользователь зарегистрирован" + }, + "400": { + "content": { + "application/json": { + "example": { + "status": "BAD_REQUEST", + "reason": "Incorrectly made request.", + "message": "Field: name. Error: must not be blank. Value: null", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Запрос составлен некорректно" + }, + "409": { + "content": { + "application/json": { + "example": { + "status": "CONFLICT", + "reason": "Integrity constraint has been violated.", + "message": "could not execute statement; SQL [n/a]; constraint [uq_email]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Нарушение целостности данных" + } + }, + "summary": "Добавление нового пользователя", + "tags": [ + "Admin: Пользователи" + ] + } + }, + "/admin/users/{userId}": { + "delete": { + "operationId": "delete", + "parameters": [ + { + "description": "id пользователя", + "in": "path", + "name": "userId", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "204": { + "description": "Пользователь удален" + }, + "404": { + "content": { + "application/json": { + "example": { + "status": "NOT_FOUND", + "reason": "The required object was not found.", + "message": "User with id=555 was not found", + "timestamp": "2022-09-07 09:10:50" + }, + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + }, + "description": "Пользователь не найден или недоступен" + } + }, + "summary": "Удаление пользователя", + "tags": [ + "Admin: Пользователи" + ] + } + } + }, + "components": { + "schemas": { + "ApiError": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "description": "Список стектрейсов или описания ошибок", + "example": [], + "items": { + "type": "string", + "description": "Список стектрейсов или описания ошибок", + "example": "[]" + } + }, + "message": { + "type": "string", + "description": "Сообщение об ошибке", + "example": "Only pending or canceled events can be changed" + }, + "reason": { + "type": "string", + "description": "Общее описание причины ошибки", + "example": "For the requested operation the conditions are not met." + }, + "status": { + "type": "string", + "description": "Код статуса HTTP-ответа", + "example": "FORBIDDEN", + "enum": [ + "100 CONTINUE", + "101 SWITCHING_PROTOCOLS", + "102 PROCESSING", + "103 CHECKPOINT", + "200 OK", + "201 CREATED", + "202 ACCEPTED", + "203 NON_AUTHORITATIVE_INFORMATION", + "204 NO_CONTENT", + "205 RESET_CONTENT", + "206 PARTIAL_CONTENT", + "207 MULTI_STATUS", + "208 ALREADY_REPORTED", + "226 IM_USED", + "300 MULTIPLE_CHOICES", + "301 MOVED_PERMANENTLY", + "302 FOUND", + "302 MOVED_TEMPORARILY", + "303 SEE_OTHER", + "304 NOT_MODIFIED", + "305 USE_PROXY", + "307 TEMPORARY_REDIRECT", + "308 PERMANENT_REDIRECT", + "400 BAD_REQUEST", + "401 UNAUTHORIZED", + "402 PAYMENT_REQUIRED", + "403 FORBIDDEN", + "404 NOT_FOUND", + "405 METHOD_NOT_ALLOWED", + "406 NOT_ACCEPTABLE", + "407 PROXY_AUTHENTICATION_REQUIRED", + "408 REQUEST_TIMEOUT", + "409 CONFLICT", + "410 GONE", + "411 LENGTH_REQUIRED", + "412 PRECONDITION_FAILED", + "413 PAYLOAD_TOO_LARGE", + "413 REQUEST_ENTITY_TOO_LARGE", + "414 URI_TOO_LONG", + "414 REQUEST_URI_TOO_LONG", + "415 UNSUPPORTED_MEDIA_TYPE", + "416 REQUESTED_RANGE_NOT_SATISFIABLE", + "417 EXPECTATION_FAILED", + "418 I_AM_A_TEAPOT", + "419 INSUFFICIENT_SPACE_ON_RESOURCE", + "420 METHOD_FAILURE", + "421 DESTINATION_LOCKED", + "422 UNPROCESSABLE_ENTITY", + "423 LOCKED", + "424 FAILED_DEPENDENCY", + "425 TOO_EARLY", + "426 UPGRADE_REQUIRED", + "428 PRECONDITION_REQUIRED", + "429 TOO_MANY_REQUESTS", + "431 REQUEST_HEADER_FIELDS_TOO_LARGE", + "451 UNAVAILABLE_FOR_LEGAL_REASONS", + "500 INTERNAL_SERVER_ERROR", + "501 NOT_IMPLEMENTED", + "502 BAD_GATEWAY", + "503 SERVICE_UNAVAILABLE", + "504 GATEWAY_TIMEOUT", + "505 HTTP_VERSION_NOT_SUPPORTED", + "506 VARIANT_ALSO_NEGOTIATES", + "507 INSUFFICIENT_STORAGE", + "508 LOOP_DETECTED", + "509 BANDWIDTH_LIMIT_EXCEEDED", + "510 NOT_EXTENDED", + "511 NETWORK_AUTHENTICATION_REQUIRED" + ] + }, + "timestamp": { + "type": "string", + "description": "Дата и время когда произошла ошибка (в формате \"yyyy-MM-dd HH:mm:ss\")", + "example": "2022-06-09 06:27:23" + } + }, + "description": "Сведения об ошибке" + }, + "NewUserRequest": { + "required": [ + "email", + "name" + ], + "type": "object", + "properties": { + "email": { + "maxLength": 254, + "minLength": 6, + "type": "string", + "description": "Почтовый адрес", + "example": "ivan.petrov@practicummail.ru" + }, + "name": { + "maxLength": 250, + "minLength": 2, + "type": "string", + "description": "Имя", + "example": "Иван Петров" + } + }, + "description": "Данные нового пользователя" + }, + "UserDto": { + "required": [ + "email", + "name" + ], + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "Почтовый адрес", + "example": "petrov.i@practicummail.ru" + }, + "id": { + "type": "integer", + "description": "Идентификатор", + "format": "int64", + "readOnly": true, + "example": 1 + }, + "name": { + "type": "string", + "description": "Имя", + "example": "Петров Иван" + } + }, + "description": "Пользователь" + }, + "UserShortDto": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Идентификатор", + "format": "int64", + "example": 3 + }, + "name": { + "type": "string", + "description": "Имя", + "example": "Фёдоров Матвей" + } + }, + "description": "Пользователь (краткая информация)" + } + } + } +} \ No newline at end of file