From d8be0c9ddc3242c87540660208c15864e427d217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Wed, 4 Feb 2026 19:03:08 +0300 Subject: [PATCH 01/10] feat: splitting the monolith into microservices (request-service, user-service and comment-service) and add interaction-api --- core/comment-service/pom.xml | 152 +++++++++++++ .../yandex/practicum/CommentServiceApp.java | 13 ++ .../controller/AdminCommentController.java | 63 ++++++ .../controller/PrivateCommentController.java | 57 +++++ .../controller/PublicCommentController.java | 28 +++ .../practicum/mapper/CommentMapper.java | 43 ++++ .../ru/yandex/practicum/model/Comment.java | 44 ++++ .../repository/CommentRepository.java | 72 +++++++ .../practicum/service/CommentService.java | 36 ++++ .../practicum/service/CommentServiceImpl.java | 201 ++++++++++++++++++ .../src/main/resources/application.yaml | 29 +++ .../src/main/resources/schema.sql | 17 ++ core/interaction-api/pom.xml | 63 ++++++ .../yandex/practicum/client/EventClient.java | 17 ++ .../practicum/client/EventClientFallback.java | 16 ++ .../practicum/client/RequestClient.java | 17 ++ .../client/RequestClientFallback.java | 16 ++ .../yandex/practicum/client/UserClient.java | 17 ++ .../practicum/client/UserClientFallback.java | 16 ++ .../constants/DateTimeConstants.java | 0 .../practicum/dto/category}/CategoryDto.java | 2 +- .../dto/category}/NewCategoryDto.java | 2 +- .../dto/comment/CommentAdminDto.java | 16 ++ .../practicum/dto/comment/CommentDto.java | 26 +++ .../practicum/dto/comment}/NewCommentDto.java | 2 +- .../dto/comment}/UpdateCommentDto.java | 2 +- .../dto/compilation}/CompilationDto.java | 2 +- .../dto/compilation}/NewCompilationDto.java | 2 +- .../UpdateCompilationRequest.java | 2 +- .../practicum/dto/event}/EventFullDto.java | 15 +- .../practicum/dto/event}/EventShortDto.java | 4 +- .../practicum/dto/event}/EventStatistics.java | 2 +- .../EventWithCountConfirmedRequests.java | 2 +- .../practicum/dto/event}/LocationDto.java | 2 +- .../practicum/dto/event}/NewEventDto.java | 4 +- .../dto/event}/SearchEventAdminRequest.java | 6 +- .../dto/event}/SearchEventPublicRequest.java | 6 +- .../dto/event}/UpdateEventAdminRequest.java | 4 +- .../dto/event}/UpdateEventUserRequest.java | 4 +- .../EventRequestStatusUpdateRequest.java | 4 +- .../EventRequestStatusUpdateResult.java | 2 +- .../dto/request}/ParticipationRequestDto.java | 9 +- .../practicum/dto/user}/NewUserRequest.java | 2 +- .../yandex/practicum/dto/user}/UserDto.java | 2 +- .../practicum/dto/user}/UserShortDto.java | 2 +- .../yandex/practicum/enums/CommentStatus.java | 7 + .../yandex/practicum/enums}/EventState.java | 2 +- .../practicum/enums}/RequestStatus.java | 2 +- .../ru/yandex/practicum/enums}/SortState.java | 2 +- .../practicum/enums}/StateActionAdmin.java | 2 +- .../practicum/enums}/StateActionUser.java | 2 +- .../exception/BadRequestException.java | 0 .../exception/ConflictException.java | 0 .../exception/ForbiddenException.java | 0 .../exception/NotFoundException.java | 0 .../exception/ValidationException.java | 0 .../controller/AdminCommentController.java | 25 --- .../controller/PrivateCommentController.java | 65 ------ .../controller/PublicCommentController.java | 31 --- .../practicum/comment/dto/CommentDto.java | 21 -- .../comment/mapper/CommentMapper.java | 26 --- .../practicum/comment/model/Comment.java | 38 ---- .../comment/repository/CommentRepository.java | 47 ---- .../comment/service/CommentService.java | 22 -- .../comment/service/CommentServiceImpl.java | 106 --------- .../practicum/event/mapper/EventMapper.java | 1 - .../practicum/event/service/EventService.java | 1 - .../event/service/EventServiceImpl.java | 1 - .../request/mapper/RequestMapper.java | 29 --- .../request/repository/RequestRepository.java | 56 ----- .../practicum/user/mapper/UserMapper.java | 19 -- .../ru/yandex/practicum/user/model/User.java | 26 --- .../practicum/user/service/UserService.java | 20 -- .../src/main/resources/schema.sql | 42 ++-- core/pom.xml | 4 + core/request-service/pom.xml | 153 +++++++++++++ .../yandex/practicum/RequestServiceApp.java | 15 ++ .../controller/InternalRequestController.java | 22 ++ .../controller/PrivateRequestController.java | 6 +- .../practicum/mapper/RequestMapper.java | 18 ++ .../ru/yandex/practicum}/model/Request.java | 34 +-- .../repository/RequestRepository.java | 25 +++ .../practicum}/service/RequestService.java | 12 +- .../service/RequestServiceImpl.java | 175 ++++++++------- .../src/main/resources/application.yaml | 26 +++ .../src/main/resources/schema.sql | 16 ++ core/user-service/pom.xml | 153 +++++++++++++ .../ru/yandex/practicum/UserServiceApp.java | 13 ++ .../controller/AdminUserController.java | 8 +- .../controller/InternalUserController.java | 24 +++ .../yandex/practicum/mapper/UserMapper.java | 26 +++ .../java/ru/yandex/practicum/model/User.java | 25 +++ .../practicum}/repository/UserRepository.java | 5 +- .../yandex/practicum/service/UserService.java | 18 ++ .../practicum}/service/UserServiceImpl.java | 35 +-- .../src/main/resources/application.yaml | 26 +++ .../src/main/resources/schema.sql | 11 + 97 files changed, 1763 insertions(+), 723 deletions(-) create mode 100644 core/comment-service/pom.xml create mode 100644 core/comment-service/src/main/java/ru/yandex/practicum/CommentServiceApp.java create mode 100644 core/comment-service/src/main/java/ru/yandex/practicum/controller/AdminCommentController.java create mode 100644 core/comment-service/src/main/java/ru/yandex/practicum/controller/PrivateCommentController.java create mode 100644 core/comment-service/src/main/java/ru/yandex/practicum/controller/PublicCommentController.java create mode 100644 core/comment-service/src/main/java/ru/yandex/practicum/mapper/CommentMapper.java create mode 100644 core/comment-service/src/main/java/ru/yandex/practicum/model/Comment.java create mode 100644 core/comment-service/src/main/java/ru/yandex/practicum/repository/CommentRepository.java create mode 100644 core/comment-service/src/main/java/ru/yandex/practicum/service/CommentService.java create mode 100644 core/comment-service/src/main/java/ru/yandex/practicum/service/CommentServiceImpl.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/interaction-api/pom.xml create mode 100644 core/interaction-api/src/main/java/ru/yandex/practicum/client/EventClient.java create mode 100644 core/interaction-api/src/main/java/ru/yandex/practicum/client/EventClientFallback.java create mode 100644 core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java create mode 100644 core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClientFallback.java create mode 100644 core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClient.java create mode 100644 core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClientFallback.java rename core/{main-service => interaction-api}/src/main/java/ru/yandex/practicum/constants/DateTimeConstants.java (100%) rename core/{main-service/src/main/java/ru/yandex/practicum/category/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/category}/CategoryDto.java (90%) rename core/{main-service/src/main/java/ru/yandex/practicum/category/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/category}/NewCategoryDto.java (90%) create mode 100644 core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentAdminDto.java create mode 100644 core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentDto.java rename core/{main-service/src/main/java/ru/yandex/practicum/comment/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/comment}/NewCommentDto.java (90%) rename core/{main-service/src/main/java/ru/yandex/practicum/comment/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/comment}/UpdateCommentDto.java (90%) rename core/{main-service/src/main/java/ru/yandex/practicum/compilation/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/compilation}/CompilationDto.java (82%) rename core/{main-service/src/main/java/ru/yandex/practicum/compilation/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/compilation}/NewCompilationDto.java (91%) rename core/{main-service/src/main/java/ru/yandex/practicum/compilation/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/compilation}/UpdateCompilationRequest.java (87%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/event}/EventFullDto.java (70%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/event}/EventShortDto.java (89%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/event}/EventStatistics.java (77%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/event}/EventWithCountConfirmedRequests.java (91%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/event}/LocationDto.java (87%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/event}/NewEventDto.java (96%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/event}/SearchEventAdminRequest.java (73%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/event}/SearchEventPublicRequest.java (82%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/event}/UpdateEventAdminRequest.java (94%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/event}/UpdateEventUserRequest.java (94%) rename core/{main-service/src/main/java/ru/yandex/practicum/request/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/request}/EventRequestStatusUpdateRequest.java (58%) rename core/{main-service/src/main/java/ru/yandex/practicum/request/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/request}/EventRequestStatusUpdateResult.java (82%) rename core/{main-service/src/main/java/ru/yandex/practicum/request/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/request}/ParticipationRequestDto.java (61%) rename core/{main-service/src/main/java/ru/yandex/practicum/user/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/user}/NewUserRequest.java (93%) rename core/{main-service/src/main/java/ru/yandex/practicum/user/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/user}/UserDto.java (70%) rename core/{main-service/src/main/java/ru/yandex/practicum/user/dto => interaction-api/src/main/java/ru/yandex/practicum/dto/user}/UserShortDto.java (65%) create mode 100644 core/interaction-api/src/main/java/ru/yandex/practicum/enums/CommentStatus.java rename core/{main-service/src/main/java/ru/yandex/practicum/event/model => interaction-api/src/main/java/ru/yandex/practicum/enums}/EventState.java (62%) rename core/{main-service/src/main/java/ru/yandex/practicum/request/model => interaction-api/src/main/java/ru/yandex/practicum/enums}/RequestStatus.java (66%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/model => interaction-api/src/main/java/ru/yandex/practicum/enums}/SortState.java (56%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/model => interaction-api/src/main/java/ru/yandex/practicum/enums}/StateActionAdmin.java (63%) rename core/{main-service/src/main/java/ru/yandex/practicum/event/model => interaction-api/src/main/java/ru/yandex/practicum/enums}/StateActionUser.java (63%) rename core/{main-service => interaction-api}/src/main/java/ru/yandex/practicum/exception/BadRequestException.java (100%) rename core/{main-service => interaction-api}/src/main/java/ru/yandex/practicum/exception/ConflictException.java (100%) rename core/{main-service => interaction-api}/src/main/java/ru/yandex/practicum/exception/ForbiddenException.java (100%) rename core/{main-service => interaction-api}/src/main/java/ru/yandex/practicum/exception/NotFoundException.java (100%) rename core/{main-service => interaction-api}/src/main/java/ru/yandex/practicum/exception/ValidationException.java (100%) delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/comment/controller/AdminCommentController.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/comment/controller/PrivateCommentController.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/comment/controller/PublicCommentController.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/comment/dto/CommentDto.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/comment/mapper/CommentMapper.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/comment/model/Comment.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/comment/repository/CommentRepository.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/comment/service/CommentService.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/comment/service/CommentServiceImpl.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/request/mapper/RequestMapper.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/request/repository/RequestRepository.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/user/mapper/UserMapper.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/user/model/User.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/user/service/UserService.java create mode 100644 core/request-service/pom.xml create mode 100644 core/request-service/src/main/java/ru/yandex/practicum/RequestServiceApp.java create mode 100644 core/request-service/src/main/java/ru/yandex/practicum/controller/InternalRequestController.java rename core/{main-service/src/main/java/ru/yandex/practicum/request => request-service/src/main/java/ru/yandex/practicum}/controller/PrivateRequestController.java (90%) create mode 100644 core/request-service/src/main/java/ru/yandex/practicum/mapper/RequestMapper.java rename core/{main-service/src/main/java/ru/yandex/practicum/request => request-service/src/main/java/ru/yandex/practicum}/model/Request.java (51%) create mode 100644 core/request-service/src/main/java/ru/yandex/practicum/repository/RequestRepository.java rename core/{main-service/src/main/java/ru/yandex/practicum/request => request-service/src/main/java/ru/yandex/practicum}/service/RequestService.java (55%) rename core/{main-service/src/main/java/ru/yandex/practicum/request => request-service/src/main/java/ru/yandex/practicum}/service/RequestServiceImpl.java (51%) 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/pom.xml create mode 100644 core/user-service/src/main/java/ru/yandex/practicum/UserServiceApp.java rename core/{main-service/src/main/java/ru/yandex/practicum/user => user-service/src/main/java/ru/yandex/practicum}/controller/AdminUserController.java (89%) create mode 100644 core/user-service/src/main/java/ru/yandex/practicum/controller/InternalUserController.java create mode 100644 core/user-service/src/main/java/ru/yandex/practicum/mapper/UserMapper.java create mode 100644 core/user-service/src/main/java/ru/yandex/practicum/model/User.java rename core/{main-service/src/main/java/ru/yandex/practicum/user => user-service/src/main/java/ru/yandex/practicum}/repository/UserRepository.java (76%) create mode 100644 core/user-service/src/main/java/ru/yandex/practicum/service/UserService.java rename core/{main-service/src/main/java/ru/yandex/practicum/user => user-service/src/main/java/ru/yandex/practicum}/service/UserServiceImpl.java (73%) create mode 100644 core/user-service/src/main/resources/application.yaml create mode 100644 core/user-service/src/main/resources/schema.sql diff --git a/core/comment-service/pom.xml b/core/comment-service/pom.xml new file mode 100644 index 0000000..2c8f54e --- /dev/null +++ b/core/comment-service/pom.xml @@ -0,0 +1,152 @@ + + + 4.0.0 + + ru.yandex.practicum + core + 0.0.1-SNAPSHOT + + + comment-service + + + 21 + 21 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-aop + + + + ru.yandex.practicum + interaction-api + ${project.version} + + + + ru.yandex.practicum + stats-client + ${project.version} + + + + ru.yandex.practicum + stats-dto + ${project.version} + + + + org.projectlombok + lombok + provided + + + + org.postgresql + postgresql + + + + org.springframework.cloud + spring-cloud-starter-config + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.retry + spring-retry + 2.0.5 + + + + io.github.openfeign + feign-okhttp + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + com.h2database + h2 + runtime + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 21 + 21 + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + + \ No newline at end of file diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/CommentServiceApp.java b/core/comment-service/src/main/java/ru/yandex/practicum/CommentServiceApp.java new file mode 100644 index 0000000..6b423d4 --- /dev/null +++ b/core/comment-service/src/main/java/ru/yandex/practicum/CommentServiceApp.java @@ -0,0 +1,13 @@ +package ru.yandex.practicum; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +@SpringBootApplication +@EnableFeignClients +public class CommentServiceApp { + public static void main(String[] args) { + SpringApplication.run(CommentServiceApp.class, args); + } +} diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/controller/AdminCommentController.java b/core/comment-service/src/main/java/ru/yandex/practicum/controller/AdminCommentController.java new file mode 100644 index 0000000..e5a6711 --- /dev/null +++ b/core/comment-service/src/main/java/ru/yandex/practicum/controller/AdminCommentController.java @@ -0,0 +1,63 @@ +package ru.yandex.practicum.controller; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.dto.comment.CommentAdminDto; +import ru.yandex.practicum.dto.comment.CommentDto; +import ru.yandex.practicum.enums.CommentStatus; +import ru.yandex.practicum.service.CommentService; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Validated +@RestController +@RequestMapping("/admin/comments") +@RequiredArgsConstructor +public class AdminCommentController { + private final CommentService commentService; + + @GetMapping + public List adminSearch( + @RequestParam(required = false) CommentStatus status, + @RequestParam(required = false) Long eventId, + @RequestParam(required = false) Long userId, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime start, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime end, + @RequestParam(defaultValue = "0") @Min(0) int from, + @RequestParam(defaultValue = "10") @Min(1) int size) { + + log.info("Admin search: status={}, eventId={}, userId={}, start={}, end={}, from={}, size={}", + status, eventId, userId, start, end, from, size); + + return commentService.adminSearch(status, eventId, userId, start, end, from, size); + } + + @PatchMapping("/{commentId}") + public CommentDto updateCommentByAdmin(@PathVariable Long commentId, + @RequestBody @Valid CommentAdminDto commentAdminDto) { + log.info("Admin update comment: id={}, data={}", commentId, commentAdminDto); + return commentService.updateCommentByAdmin(commentId, commentAdminDto); + } + + @DeleteMapping("/{commentId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteCommentByAdmin(@PathVariable Long commentId) { + log.info("Admin delete comment: id={}", commentId); + commentService.deleteCommentByAdmin(commentId); + } + + @GetMapping("/events/{eventId}") + public List getCommentsByEventAndStatus(@PathVariable Long eventId, + @RequestParam CommentStatus status) { + log.info("Get comments by event and status: eventId={}, status={}", eventId, status); + return commentService.getCommentByStatus(eventId, status); + } +} diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/controller/PrivateCommentController.java b/core/comment-service/src/main/java/ru/yandex/practicum/controller/PrivateCommentController.java new file mode 100644 index 0000000..6a25ede --- /dev/null +++ b/core/comment-service/src/main/java/ru/yandex/practicum/controller/PrivateCommentController.java @@ -0,0 +1,57 @@ +package ru.yandex.practicum.controller; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +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.yandex.practicum.dto.comment.CommentDto; +import ru.yandex.practicum.dto.comment.NewCommentDto; +import ru.yandex.practicum.dto.comment.UpdateCommentDto; +import ru.yandex.practicum.service.CommentService; + +import java.util.List; + +@Slf4j +@Validated +@RestController +@RequestMapping("/users/{userId}/comments") +@RequiredArgsConstructor +public class PrivateCommentController { + private final CommentService commentService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public CommentDto createComment(@PathVariable Long userId, + @RequestParam Long eventId, + @RequestBody @Valid NewCommentDto newCommentDto) { + log.info("Create comment: userId={}, eventId={}", userId, eventId); + return commentService.createComment(userId, eventId, newCommentDto); + } + + @PatchMapping("/{commentId}") + public CommentDto updateCommentByUser(@PathVariable Long userId, + @PathVariable Long commentId, + @RequestBody @Valid UpdateCommentDto updateCommentDto) { + log.info("Update comment: userId={}, commentId={}", userId, commentId); + return commentService.updateComment(userId, commentId, updateCommentDto); + } + + @DeleteMapping("/{commentId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteCommentByUser(@PathVariable Long userId, + @PathVariable Long commentId) { + log.info("Delete comment: userId={}, commentId={}", userId, commentId); + commentService.deleteComment(userId, commentId); + } + + @GetMapping + public List getUserComments(@PathVariable Long userId, + @RequestParam(defaultValue = "0") @Min(0) int from, + @RequestParam(defaultValue = "10") @Min(1) int size) { + log.info("Get user comments: userId={}, from={}, size={}", userId, from, size); + return commentService.getUserComments(userId, from, size); + } +} diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/controller/PublicCommentController.java b/core/comment-service/src/main/java/ru/yandex/practicum/controller/PublicCommentController.java new file mode 100644 index 0000000..e63b769 --- /dev/null +++ b/core/comment-service/src/main/java/ru/yandex/practicum/controller/PublicCommentController.java @@ -0,0 +1,28 @@ +package ru.yandex.practicum.controller; + +import jakarta.validation.constraints.Min; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.dto.comment.CommentDto; +import ru.yandex.practicum.service.CommentService; + +import java.util.List; + +@Slf4j +@Validated +@RestController +@RequestMapping("/comments") +@RequiredArgsConstructor +public class PublicCommentController { + private final CommentService commentService; + + @GetMapping("/events/{eventId}") + public List getPublishedCommentsByEvent(@PathVariable Long eventId, + @RequestParam(defaultValue = "0") @Min(0) int from, + @RequestParam(defaultValue = "10") @Min(1) int size) { + log.info("Get published comments: eventId={}, from={}, size={}", eventId, from, size); + return commentService.getPublishedCommentsByEvent(eventId, from, size); + } +} diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/mapper/CommentMapper.java b/core/comment-service/src/main/java/ru/yandex/practicum/mapper/CommentMapper.java new file mode 100644 index 0000000..cc5ae28 --- /dev/null +++ b/core/comment-service/src/main/java/ru/yandex/practicum/mapper/CommentMapper.java @@ -0,0 +1,43 @@ +package ru.yandex.practicum.mapper; + +import org.mapstruct.*; +import ru.yandex.practicum.dto.comment.CommentAdminDto; +import ru.yandex.practicum.dto.comment.CommentDto; +import ru.yandex.practicum.dto.comment.NewCommentDto; +import ru.yandex.practicum.dto.comment.UpdateCommentDto; +import ru.yandex.practicum.model.Comment; + +@Mapper(componentModel = "spring") +public interface CommentMapper { + + @Mapping(target = "id", ignore = true) + @Mapping(target = "createdDate", ignore = true) + @Mapping(target = "updatedDate", ignore = true) + @Mapping(target = "userId", source = "userId") + @Mapping(target = "eventId", source = "eventId") + @Mapping(target = "status", expression = "java(CommentStatus.PENDING)") + Comment toComment(NewCommentDto newCommentDto, Long userId, Long eventId); + + @Mapping(target = "userId", source = "userId") + @Mapping(target = "eventId", source = "eventId") + CommentDto toDto(Comment comment); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + @Mapping(target = "id", ignore = true) + @Mapping(target = "userId", ignore = true) + @Mapping(target = "eventId", ignore = true) + @Mapping(target = "createdDate", ignore = true) + @Mapping(target = "updatedDate", ignore = true) + @Mapping(target = "status", ignore = true) + void updateCommentFromDto(UpdateCommentDto dto, @MappingTarget Comment comment); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + @Mapping(target = "id", ignore = true) + @Mapping(target = "createdDate", ignore = true) + @Mapping(target = "updatedDate", ignore = true) + @Mapping(target = "userId", ignore = true) + @Mapping(target = "eventId", ignore = true) + @Mapping(target = "text", source = "text") + @Mapping(target = "status", source = "status") + void updateFromAdminDto(CommentAdminDto commentAdminDto, @MappingTarget Comment comment); +} diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/model/Comment.java b/core/comment-service/src/main/java/ru/yandex/practicum/model/Comment.java new file mode 100644 index 0000000..a50b6b9 --- /dev/null +++ b/core/comment-service/src/main/java/ru/yandex/practicum/model/Comment.java @@ -0,0 +1,44 @@ +package ru.yandex.practicum.model; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import ru.yandex.practicum.enums.CommentStatus; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "comments") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +@ToString +public class Comment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 5000) + private String text; + + @Column(name = "user_id", nullable = false) + private Long userId; + + @Column(name = "event_id", nullable = false) + private Long eventId; + + @Column(name = "created_date", nullable = false) + @CreationTimestamp + private LocalDateTime createdDate; + + @Column(name = "updated_date") + @CreationTimestamp + private LocalDateTime updatedDate; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private CommentStatus status; +} diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/repository/CommentRepository.java b/core/comment-service/src/main/java/ru/yandex/practicum/repository/CommentRepository.java new file mode 100644 index 0000000..d48ce3b --- /dev/null +++ b/core/comment-service/src/main/java/ru/yandex/practicum/repository/CommentRepository.java @@ -0,0 +1,72 @@ +package ru.yandex.practicum.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 org.springframework.data.repository.query.Param; +import ru.yandex.practicum.enums.CommentStatus; +import ru.yandex.practicum.model.Comment; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +public interface CommentRepository extends JpaRepository { + + // Поиск опубликованных комментов события + @Query(""" + SELECT c FROM Comment c + WHERE c.eventId = :eventId AND c.status = ru.yandex.practicum.enums.CommentStatus.PUBLISHED + ORDER BY c.createdDate DESC + """) + List findPublishedByEvent(@Param("eventId") Long eventId, Pageable pageable); + + // Поиск комментария по его ID и ID автора + Optional findByIdAndUserId(Long id, Long userId); + + // Расширенный поиск комментариев по фильтрам (без фильтрации по датам) для модерации + @Query(""" + SELECT c FROM Comment c + WHERE (:status IS NULL OR c.status = :status) + AND (:eventId IS NULL OR c.eventId = :eventId) + AND (:userId IS NULL OR c.userId = :userId) + ORDER BY c.createdDate DESC + """) + Page searchWithoutDates(@Param("status") CommentStatus status, + @Param("eventId") Long eventId, + @Param("userId") Long userId, + Pageable pageable); + + // Расширенный поиск комментариев по фильтрам (с фильтрацией по датам) для модерации + @Query(""" + SELECT c FROM Comment c + WHERE (:status IS NULL OR c.status = :status) + AND (:eventId IS NULL OR c.eventId = :eventId) + AND (:userId IS NULL OR c.userId = :userId) + AND (c.createdDate >= :start) + AND (c.createdDate <= :end) + ORDER BY c.createdDate DESC + """) + Page searchWithDates(@Param("status") CommentStatus status, + @Param("eventId") Long eventId, + @Param("userId") Long userId, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end, + Pageable pageable); + + // Поиск комментариев к событию по статусу + @Query(""" + SELECT c FROM Comment c + WHERE c.eventId = :eventId AND c.status = :status + ORDER BY c.createdDate DESC + """) + List findByEventIdAndStatus(@Param("eventId") Long eventId, + @Param("status") CommentStatus status); + + // Поиск всех комментариев к событию + List findByEventId(Long eventId, Pageable pageable); + + // Поиск всех комментариев пользователя + List findByUserId(Long userId, Pageable pageable); +} diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentService.java b/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentService.java new file mode 100644 index 0000000..d2a2c9a --- /dev/null +++ b/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentService.java @@ -0,0 +1,36 @@ +package ru.yandex.practicum.service; + +import ru.yandex.practicum.dto.comment.CommentAdminDto; +import ru.yandex.practicum.dto.comment.CommentDto; +import ru.yandex.practicum.dto.comment.NewCommentDto; +import ru.yandex.practicum.dto.comment.UpdateCommentDto; +import ru.yandex.practicum.enums.CommentStatus; + +import java.time.LocalDateTime; +import java.util.List; + +public interface CommentService { + CommentDto createComment(Long userId, Long eventId, NewCommentDto newCommentDto); + + CommentDto updateComment(Long userId, Long commentId, UpdateCommentDto updateCommentDto); + + void deleteComment(Long userId, Long commentId); + + CommentDto updateCommentByAdmin(Long commentId, CommentAdminDto commentAdminDto); + + void deleteCommentByAdmin(Long commentId); + + List getCommentByStatus(Long eventId, CommentStatus status); + + List getPublishedCommentsByEvent(Long eventId, int from, int size); + + List adminSearch(CommentStatus status, + Long eventId, + Long userId, + LocalDateTime start, + LocalDateTime end, + int from, + int size); + + public List getUserComments(Long userId, int from, int size); +} diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentServiceImpl.java b/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentServiceImpl.java new file mode 100644 index 0000000..f0d5f37 --- /dev/null +++ b/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentServiceImpl.java @@ -0,0 +1,201 @@ +package ru.yandex.practicum.service; + +import feign.FeignException; +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.yandex.practicum.client.EventClient; +import ru.yandex.practicum.client.UserClient; +import ru.yandex.practicum.dto.comment.*; +import ru.yandex.practicum.dto.event.EventFullDto; +import ru.yandex.practicum.enums.CommentStatus; +import ru.yandex.practicum.enums.EventState; +import ru.yandex.practicum.exception.*; +import ru.yandex.practicum.mapper.CommentMapper; +import ru.yandex.practicum.model.Comment; +import ru.yandex.practicum.repository.CommentRepository; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class CommentServiceImpl implements CommentService { + private final CommentRepository commentRepository; + private final EventClient eventClient; + private final UserClient userClient; + private final CommentMapper commentMapper; + + @Override + @Transactional + public CommentDto createComment(Long userId, Long eventId, NewCommentDto newCommentDto) { + checkUserExists(userId); + EventFullDto event = eventClient.getEvent(eventId); + + if (event.state() != EventState.PUBLISHED) { + throw new ConflictException("Нельзя комментировать неопубликованное событие"); + } + + Comment comment = commentMapper.toComment(newCommentDto, userId, eventId); + Comment saved = commentRepository.save(comment); + + log.info("Создан комментарий: id={}, userId={}, eventId={}", saved.getId(), userId, eventId); + return commentMapper.toDto(saved); + } + + @Override + @Transactional + public CommentDto updateComment(Long userId, Long commentId, UpdateCommentDto updateCommentDto) { + Comment comment = getCommentOrThrow(commentId, userId); + + if (comment.getStatus() != CommentStatus.PENDING) { + throw new ConflictException("Можно редактировать только комментарии в статусе PENDING"); + } + + commentMapper.updateCommentFromDto(updateCommentDto, comment); + comment.setUpdatedDate(LocalDateTime.now()); + + log.info("Комментарий обновлен пользователем: commentId={}, userId={}", commentId, userId); + return commentMapper.toDto(commentRepository.save(comment)); + } + + @Override + @Transactional + public void deleteComment(Long userId, Long commentId) { + Comment comment = getCommentOrThrow(commentId, userId); + + comment.setStatus(CommentStatus.REJECTED); + comment.setUpdatedDate(LocalDateTime.now()); + commentRepository.save(comment); + + log.info("Комментарий помечен как удаленный пользователем: commentId={}, userId={}", commentId, userId); + } + + @Override + @Transactional + public CommentDto updateCommentByAdmin(Long commentId, CommentAdminDto commentAdminDto) { + Comment comment = commentRepository.findById(commentId) + .orElseThrow(() -> new NotFoundException("Комментарий с id=" + commentId + " не найден")); + + if (commentAdminDto.status() == CommentStatus.PENDING) { + throw new ConflictException("Целевой статус модерации не может быть PENDING"); + } + + commentMapper.updateFromAdminDto(commentAdminDto, comment); + comment.setUpdatedDate(LocalDateTime.now()); + + log.info("Комментарий обновлен администратором: commentId={}", commentId); + return commentMapper.toDto(commentRepository.save(comment)); + } + + @Override + @Transactional + public void deleteCommentByAdmin(Long commentId) { + if (!commentRepository.existsById(commentId)) { + throw new NotFoundException("Комментарий с id=" + commentId + " не найден"); + } + + commentRepository.deleteById(commentId); + log.info("Комментарий удален администратором: commentId={}", commentId); + } + + @Override + @Transactional(readOnly = true) + public List getCommentByStatus(Long eventId, CommentStatus status) { + checkEventExists(eventId); + + log.info("Получение комментариев по статусу: eventId={}, status={}", eventId, status); + return commentRepository.findByEventIdAndStatus(eventId, status) + .stream() + .map(commentMapper::toDto) + .toList(); + } + + @Override + @Transactional(readOnly = true) + public List getPublishedCommentsByEvent(Long eventId, int from, int size) { + checkEventExists(eventId); + + Pageable pageable = PageRequest.of(from / size, size); + log.info("Получение опубликованных комментариев: eventId={}, from={}, size={}", eventId, from, size); + + return commentRepository.findPublishedByEvent(eventId, pageable) + .stream() + .map(commentMapper::toDto) + .toList(); + } + + @Override + @Transactional(readOnly = true) + public List adminSearch(CommentStatus status, + Long eventId, + Long userId, + LocalDateTime start, + LocalDateTime end, + int from, int size) { + + Pageable pageable = PageRequest.of(from / size, size); + Page result; + + if (start == null && end == null) { + result = commentRepository.searchWithoutDates(status, eventId, userId, pageable); + } else if (start == null) { + result = commentRepository.searchWithDates(status, eventId, userId, LocalDateTime.MIN, end, pageable); + } else if (end == null) { + result = commentRepository.searchWithDates(status, eventId, userId, start, LocalDateTime.MAX, pageable); + } else { + result = commentRepository.searchWithDates(status, eventId, userId, start, end, pageable); + } + + log.info("Поиск администратором: status={}, eventId={}, userId={}, from={}, size={}", + status, eventId, userId, from, size); + + return result.getContent() + .stream() + .map(commentMapper::toDto) + .toList(); + } + + @Override + @Transactional(readOnly = true) + public List getUserComments(Long userId, int from, int size) { + checkUserExists(userId); + + Pageable pageable = PageRequest.of(from / size, size); + log.info("Получение комментариев пользователя: userId={}, from={}, size={}", userId, from, size); + + return commentRepository.findByUserId(userId, pageable) + .stream() + .map(commentMapper::toDto) + .toList(); + } + + private Comment getCommentOrThrow(Long commentId, Long userId) { + return commentRepository.findByIdAndUserId(commentId, userId) + .orElseThrow(() -> new NotFoundException( + "Комментарий с id=" + commentId + " от пользователя с id=" + userId + " не найден" + )); + } + + private void checkUserExists(Long userId) { + try { + userClient.getUser(userId); + } catch (FeignException.NotFound e) { + throw new NotFoundException("Пользователь с id=" + userId + " не найден"); + } + } + + private void checkEventExists(Long eventId) { + try { + eventClient.getEvent(eventId); + } catch (FeignException.NotFound e) { + throw new NotFoundException("Событие с id=" + eventId + " не найден"); + } + } +} 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..3e20474 --- /dev/null +++ b/core/comment-service/src/main/resources/application.yaml @@ -0,0 +1,29 @@ +spring: + application: + name: comment-service + config: + import: "configserver" + cloud: + loadbalancer: + retry: + enabled: true + config: + fail-fast: true + discovery: + enabled: true + serviceId: config-server + retry: + useRandomPolicy: true + max-interval: 6000 + +server: + port: 0 + +eureka: + instance: + instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}} + client: + healthcheck: + enabled: true + serviceUrl: + defaultZone: http://localhost:8761/eureka \ No newline at end of file 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..7cc6630 --- /dev/null +++ b/core/comment-service/src/main/resources/schema.sql @@ -0,0 +1,17 @@ +DROP TABLE IF EXISTS comments CASCADE; + +--Создание таблицы комментариев +create TABLE IF NOT EXISTS comments ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + text TEXT NOT NULL, + user_id BIGINT NOT NULL, + event_id BIGINT NOT NULL, + created_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + updated_date TIMESTAMP WITHOUT TIME ZONE, + status VARCHAR(20) NOT NULL +); + +--Индекс для поиска комментариев к событию +CREATE INDEX IF NOT EXISTS idx_comments_event_id ON comments(event_id); +--Индекс для поиска комментариев пользователя +CREATE INDEX IF NOT EXISTS idx_comments_user_id ON comments(user_id); \ No newline at end of file diff --git a/core/interaction-api/pom.xml b/core/interaction-api/pom.xml new file mode 100644 index 0000000..fd235dd --- /dev/null +++ b/core/interaction-api/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + ru.yandex.practicum + core + 0.0.1-SNAPSHOT + + + interaction-api + + + 21 + 21 + UTF-8 + + + + + org.projectlombok + lombok + true + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.data + spring-data-commons + + + + jakarta.validation + jakarta.validation-api + + + + io.github.openfeign + feign-okhttp + + + + org.springframework + spring-context + + + + jakarta.persistence + jakarta.persistence-api + + + + com.fasterxml.jackson.core + jackson-databind + + + + \ No newline at end of file diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/EventClient.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/EventClient.java new file mode 100644 index 0000000..ebfd010 --- /dev/null +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/EventClient.java @@ -0,0 +1,17 @@ +package ru.yandex.practicum.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import ru.yandex.practicum.dto.event.EventFullDto; + +@FeignClient( + name = "event-service", + path = "/internal/events", + fallback = EventClientFallback.class +) +public interface EventClient { + + @GetMapping("/{eventId}") + EventFullDto getEvent(@PathVariable Long eventId); +} diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/EventClientFallback.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/EventClientFallback.java new file mode 100644 index 0000000..0987a0d --- /dev/null +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/EventClientFallback.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.client; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.dto.event.EventFullDto; + +@Component +@Slf4j +public class EventClientFallback implements EventClient { + + @Override + public EventFullDto getEvent(Long eventId) { + log.warn("Сервис событий недоступен"); + return null; + } +} diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java new file mode 100644 index 0000000..1308f68 --- /dev/null +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java @@ -0,0 +1,17 @@ +package ru.yandex.practicum.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import ru.yandex.practicum.enums.RequestStatus; + +@FeignClient( + name = "request-service", + path = "/internal/requests", + fallback = RequestClientFallback.class +) +public interface RequestClient { + + @GetMapping("/event/{eventId}/count/{status}") + Long countByStatus(@PathVariable Long eventId, @PathVariable RequestStatus status); +} diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClientFallback.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClientFallback.java new file mode 100644 index 0000000..a0829e1 --- /dev/null +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClientFallback.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.client; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.enums.RequestStatus; + +@Component +@Slf4j +public class RequestClientFallback implements RequestClient { + + @Override + public Long countByStatus(Long eventId, RequestStatus status) { + log.warn("Сервис запросов недоступен, eventId={}, status={}", eventId, status); + return 0L; + } +} diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClient.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClient.java new file mode 100644 index 0000000..3447a1f --- /dev/null +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClient.java @@ -0,0 +1,17 @@ +package ru.yandex.practicum.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import ru.yandex.practicum.dto.user.UserShortDto; + +@FeignClient( + name = "user-service", + path = "/internal/users", + fallback = UserClientFallback.class +) +public interface UserClient { + + @GetMapping("/{userId}") + UserShortDto getUser(@PathVariable("userId") Long userId); +} diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClientFallback.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClientFallback.java new file mode 100644 index 0000000..f87a7cf --- /dev/null +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClientFallback.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.client; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.yandex.practicum.dto.user.UserShortDto; + +@Component +@Slf4j +public class UserClientFallback implements UserClient { + + @Override + public UserShortDto getUser(Long userId) { + log.warn("Сервис пользователей недоступен"); + return null; + } +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/constants/DateTimeConstants.java b/core/interaction-api/src/main/java/ru/yandex/practicum/constants/DateTimeConstants.java similarity index 100% rename from core/main-service/src/main/java/ru/yandex/practicum/constants/DateTimeConstants.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/constants/DateTimeConstants.java diff --git a/core/main-service/src/main/java/ru/yandex/practicum/category/dto/CategoryDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/CategoryDto.java similarity index 90% rename from core/main-service/src/main/java/ru/yandex/practicum/category/dto/CategoryDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/CategoryDto.java index fdf9a17..3dd67ae 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/category/dto/CategoryDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/CategoryDto.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.category.dto; +package ru.yandex.practicum.dto.category; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/category/dto/NewCategoryDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/NewCategoryDto.java similarity index 90% rename from core/main-service/src/main/java/ru/yandex/practicum/category/dto/NewCategoryDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/NewCategoryDto.java index 971c5cf..412a7f3 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/category/dto/NewCategoryDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/NewCategoryDto.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.category.dto; +package ru.yandex.practicum.dto.category; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentAdminDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentAdminDto.java new file mode 100644 index 0000000..05a81d5 --- /dev/null +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentAdminDto.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.dto.comment; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import ru.yandex.practicum.enums.CommentStatus; + +public record CommentAdminDto( + @NotBlank(message = "Комментарий не может быть пустым") + @Size(min = 1, max = 5000, message = "Комментарий должен содержать от {min} до {max} символов") + String text, + + @NotNull + CommentStatus status +) { +} diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentDto.java new file mode 100644 index 0000000..d348fbe --- /dev/null +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentDto.java @@ -0,0 +1,26 @@ +package ru.yandex.practicum.dto.comment; + +import com.fasterxml.jackson.annotation.JsonFormat; +import ru.yandex.practicum.constants.DateTimeConstants; +import ru.yandex.practicum.enums.CommentStatus; + +import java.time.LocalDateTime; + +public record CommentDto( + Long id, + + String text, + + Long userId, + + Long eventId, + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) + LocalDateTime createdDate, + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) + LocalDateTime updatedDate, + + CommentStatus status +) { +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/comment/dto/NewCommentDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/NewCommentDto.java similarity index 90% rename from core/main-service/src/main/java/ru/yandex/practicum/comment/dto/NewCommentDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/NewCommentDto.java index 805e2d3..935c6e4 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/comment/dto/NewCommentDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/NewCommentDto.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.comment.dto; +package ru.yandex.practicum.dto.comment; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/comment/dto/UpdateCommentDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/UpdateCommentDto.java similarity index 90% rename from core/main-service/src/main/java/ru/yandex/practicum/comment/dto/UpdateCommentDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/UpdateCommentDto.java index da8f1db..9fa5f5d 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/comment/dto/UpdateCommentDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/UpdateCommentDto.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.comment.dto; +package ru.yandex.practicum.dto.comment; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/compilation/dto/CompilationDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/CompilationDto.java similarity index 82% rename from core/main-service/src/main/java/ru/yandex/practicum/compilation/dto/CompilationDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/CompilationDto.java index a978e5a..2cad8b9 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/compilation/dto/CompilationDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/CompilationDto.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.compilation.dto; +package ru.yandex.practicum.dto.compilation; import ru.yandex.practicum.event.dto.EventShortDto; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/compilation/dto/NewCompilationDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/NewCompilationDto.java similarity index 91% rename from core/main-service/src/main/java/ru/yandex/practicum/compilation/dto/NewCompilationDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/NewCompilationDto.java index 00b509d..3c0b120 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/compilation/dto/NewCompilationDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/NewCompilationDto.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.compilation.dto; +package ru.yandex.practicum.dto.compilation; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/compilation/dto/UpdateCompilationRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/UpdateCompilationRequest.java similarity index 87% rename from core/main-service/src/main/java/ru/yandex/practicum/compilation/dto/UpdateCompilationRequest.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/UpdateCompilationRequest.java index 5aeeef1..c087564 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/compilation/dto/UpdateCompilationRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/UpdateCompilationRequest.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.compilation.dto; +package ru.yandex.practicum.dto.compilation; import jakarta.validation.constraints.Size; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/EventFullDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventFullDto.java similarity index 70% rename from core/main-service/src/main/java/ru/yandex/practicum/event/dto/EventFullDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventFullDto.java index a7331a5..3d4e01e 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/EventFullDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventFullDto.java @@ -1,13 +1,12 @@ -package ru.yandex.practicum.event.dto; +package ru.yandex.practicum.dto.event; import com.fasterxml.jackson.annotation.JsonFormat; -import ru.yandex.practicum.category.dto.CategoryDto; -import ru.yandex.practicum.event.model.EventState; -import ru.yandex.practicum.user.dto.UserShortDto; +import ru.yandex.practicum.constants.DateTimeConstants; +import ru.yandex.practicum.dto.category.CategoryDto; +import ru.yandex.practicum.dto.user.UserShortDto; import java.time.LocalDateTime; -import static ru.yandex.practicum.constants.DateTimeConstants.DATE_TIME_PATTERN; public record EventFullDto( Long id, @@ -18,12 +17,12 @@ public record EventFullDto( Long confirmedRequests, - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_TIME_PATTERN) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime createdOn, String description, - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_TIME_PATTERN) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime eventDate, UserShortDto initiator, @@ -34,7 +33,7 @@ public record EventFullDto( Integer participantLimit, - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_TIME_PATTERN) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime publishedOn, Boolean requestModeration, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/EventShortDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventShortDto.java similarity index 89% rename from core/main-service/src/main/java/ru/yandex/practicum/event/dto/EventShortDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventShortDto.java index c70ff6f..c5c54dc 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/EventShortDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventShortDto.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.dto; +package ru.yandex.practicum.dto.event; import com.fasterxml.jackson.annotation.JsonFormat; import ru.yandex.practicum.category.dto.CategoryDto; @@ -17,7 +17,7 @@ public record EventShortDto( Long confirmedRequests, - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_TIME_PATTERN) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime eventDate, UserShortDto initiator, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/EventStatistics.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventStatistics.java similarity index 77% rename from core/main-service/src/main/java/ru/yandex/practicum/event/dto/EventStatistics.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventStatistics.java index 6ee33b0..e79e735 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/EventStatistics.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventStatistics.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.dto; +package ru.yandex.practicum.dto.event; import java.util.Map; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/EventWithCountConfirmedRequests.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventWithCountConfirmedRequests.java similarity index 91% rename from core/main-service/src/main/java/ru/yandex/practicum/event/dto/EventWithCountConfirmedRequests.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventWithCountConfirmedRequests.java index 1480e23..d71db70 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/EventWithCountConfirmedRequests.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventWithCountConfirmedRequests.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.dto; +package ru.yandex.practicum.dto.event; import lombok.Getter; import lombok.Setter; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/LocationDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/LocationDto.java similarity index 87% rename from core/main-service/src/main/java/ru/yandex/practicum/event/dto/LocationDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/LocationDto.java index e6cfcd1..a5fedb0 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/LocationDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/LocationDto.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.dto; +package ru.yandex.practicum.dto.event; import jakarta.validation.constraints.NotNull; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/NewEventDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/NewEventDto.java similarity index 96% rename from core/main-service/src/main/java/ru/yandex/practicum/event/dto/NewEventDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/NewEventDto.java index c7563b1..f5ee1d0 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/NewEventDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/NewEventDto.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.dto; +package ru.yandex.practicum.dto.event; import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.*; @@ -21,7 +21,7 @@ public record NewEventDto( @NotNull(message = "Дата события не может быть null") @Future(message = "Дата события должна быть в будущем") - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_TIME_PATTERN) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime eventDate, @NotNull(message = "Место проведения события не может быть null") diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/SearchEventAdminRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventAdminRequest.java similarity index 73% rename from core/main-service/src/main/java/ru/yandex/practicum/event/dto/SearchEventAdminRequest.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventAdminRequest.java index 8bc26e4..01931b7 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/SearchEventAdminRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventAdminRequest.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.dto; +package ru.yandex.practicum.dto.event; import org.springframework.format.annotation.DateTimeFormat; import ru.yandex.practicum.event.model.EventState; @@ -15,10 +15,10 @@ public record SearchEventAdminRequest( List categories, - @DateTimeFormat(pattern = DATE_TIME_PATTERN) + @DateTimeFormat(pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime rangeStart, - @DateTimeFormat(pattern = DATE_TIME_PATTERN) + @DateTimeFormat(pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime rangeEnd, Integer from, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/SearchEventPublicRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventPublicRequest.java similarity index 82% rename from core/main-service/src/main/java/ru/yandex/practicum/event/dto/SearchEventPublicRequest.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventPublicRequest.java index 7602285..254c090 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/SearchEventPublicRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventPublicRequest.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.dto; +package ru.yandex.practicum.dto.event; import org.springframework.format.annotation.DateTimeFormat; import ru.yandex.practicum.event.model.SortState; @@ -15,10 +15,10 @@ public record SearchEventPublicRequest( Boolean paid, - @DateTimeFormat(pattern = DATE_TIME_PATTERN) + @DateTimeFormat(pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime rangeStart, - @DateTimeFormat(pattern = DATE_TIME_PATTERN) + @DateTimeFormat(pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime rangeEnd, Boolean onlyAvailable, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/UpdateEventAdminRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventAdminRequest.java similarity index 94% rename from core/main-service/src/main/java/ru/yandex/practicum/event/dto/UpdateEventAdminRequest.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventAdminRequest.java index ec136dc..b089c75 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/UpdateEventAdminRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventAdminRequest.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.dto; +package ru.yandex.practicum.dto.event; import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.Future; @@ -20,7 +20,7 @@ public record UpdateEventAdminRequest( String description, @Future(message = "Дата события должна быть в будущем") - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_TIME_PATTERN) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime eventDate, LocationDto location, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/UpdateEventUserRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventUserRequest.java similarity index 94% rename from core/main-service/src/main/java/ru/yandex/practicum/event/dto/UpdateEventUserRequest.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventUserRequest.java index a2045fd..fdf8e87 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/dto/UpdateEventUserRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventUserRequest.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.dto; +package ru.yandex.practicum.dto.event; import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.Future; @@ -20,7 +20,7 @@ public record UpdateEventUserRequest( String description, @Future(message = "Дата события должна быть в будущем") - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_TIME_PATTERN) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime eventDate, LocationDto location, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/request/dto/EventRequestStatusUpdateRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateRequest.java similarity index 58% rename from core/main-service/src/main/java/ru/yandex/practicum/request/dto/EventRequestStatusUpdateRequest.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateRequest.java index d8a18d5..5aa1e1a 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/request/dto/EventRequestStatusUpdateRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateRequest.java @@ -1,6 +1,6 @@ -package ru.yandex.practicum.request.dto; +package ru.yandex.practicum.dto.request; -import ru.yandex.practicum.request.model.RequestStatus; +import ru.yandex.practicum.enums.RequestStatus; import java.util.List; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/request/dto/EventRequestStatusUpdateResult.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateResult.java similarity index 82% rename from core/main-service/src/main/java/ru/yandex/practicum/request/dto/EventRequestStatusUpdateResult.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateResult.java index 81b52b4..ebd63cf 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/request/dto/EventRequestStatusUpdateResult.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateResult.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.request.dto; +package ru.yandex.practicum.dto.request; import java.util.List; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/request/dto/ParticipationRequestDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/ParticipationRequestDto.java similarity index 61% rename from core/main-service/src/main/java/ru/yandex/practicum/request/dto/ParticipationRequestDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/ParticipationRequestDto.java index b9b4264..c00e4d8 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/request/dto/ParticipationRequestDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/ParticipationRequestDto.java @@ -1,16 +1,15 @@ -package ru.yandex.practicum.request.dto; +package ru.yandex.practicum.dto.request; import com.fasterxml.jackson.annotation.JsonFormat; -import ru.yandex.practicum.request.model.RequestStatus; +import ru.yandex.practicum.constants.DateTimeConstants; +import ru.yandex.practicum.enums.RequestStatus; import java.time.LocalDateTime; -import static ru.yandex.practicum.constants.DateTimeConstants.DATE_TIME_PATTERN; - public record ParticipationRequestDto( Long id, - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_TIME_PATTERN) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime created, Long event, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/user/dto/NewUserRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/NewUserRequest.java similarity index 93% rename from core/main-service/src/main/java/ru/yandex/practicum/user/dto/NewUserRequest.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/NewUserRequest.java index 2cd824c..15e1d50 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/user/dto/NewUserRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/NewUserRequest.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.user.dto; +package ru.yandex.practicum.dto.user; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/user/dto/UserDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserDto.java similarity index 70% rename from core/main-service/src/main/java/ru/yandex/practicum/user/dto/UserDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserDto.java index 778ac5e..e400999 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/user/dto/UserDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserDto.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.user.dto; +package ru.yandex.practicum.dto.user; public record UserDto( Long id, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/user/dto/UserShortDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserShortDto.java similarity index 65% rename from core/main-service/src/main/java/ru/yandex/practicum/user/dto/UserShortDto.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserShortDto.java index 525a4d7..865455c 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/user/dto/UserShortDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserShortDto.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.user.dto; +package ru.yandex.practicum.dto.user; public record UserShortDto( Long id, diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/enums/CommentStatus.java b/core/interaction-api/src/main/java/ru/yandex/practicum/enums/CommentStatus.java new file mode 100644 index 0000000..7ea38bd --- /dev/null +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/enums/CommentStatus.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.enums; + +public enum CommentStatus { + PENDING, + PUBLISHED, + REJECTED +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/model/EventState.java b/core/interaction-api/src/main/java/ru/yandex/practicum/enums/EventState.java similarity index 62% rename from core/main-service/src/main/java/ru/yandex/practicum/event/model/EventState.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/enums/EventState.java index ddcae09..01d53b8 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/model/EventState.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/enums/EventState.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.model; +package ru.yandex.practicum.enums; public enum EventState { PENDING, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/request/model/RequestStatus.java b/core/interaction-api/src/main/java/ru/yandex/practicum/enums/RequestStatus.java similarity index 66% rename from core/main-service/src/main/java/ru/yandex/practicum/request/model/RequestStatus.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/enums/RequestStatus.java index d25345a..a9e26da 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/request/model/RequestStatus.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/enums/RequestStatus.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.request.model; +package ru.yandex.practicum.enums; public enum RequestStatus { PENDING, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/model/SortState.java b/core/interaction-api/src/main/java/ru/yandex/practicum/enums/SortState.java similarity index 56% rename from core/main-service/src/main/java/ru/yandex/practicum/event/model/SortState.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/enums/SortState.java index 8a35d2d..1e028df 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/model/SortState.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/enums/SortState.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.model; +package ru.yandex.practicum.enums; public enum SortState { EVENT_DATE, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/model/StateActionAdmin.java b/core/interaction-api/src/main/java/ru/yandex/practicum/enums/StateActionAdmin.java similarity index 63% rename from core/main-service/src/main/java/ru/yandex/practicum/event/model/StateActionAdmin.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/enums/StateActionAdmin.java index 27a800c..3bf500f 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/model/StateActionAdmin.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/enums/StateActionAdmin.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.model; +package ru.yandex.practicum.enums; public enum StateActionAdmin { PUBLISH_EVENT, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/model/StateActionUser.java b/core/interaction-api/src/main/java/ru/yandex/practicum/enums/StateActionUser.java similarity index 63% rename from core/main-service/src/main/java/ru/yandex/practicum/event/model/StateActionUser.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/enums/StateActionUser.java index 0cc9766..ee790e0 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/model/StateActionUser.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/enums/StateActionUser.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.model; +package ru.yandex.practicum.enums; public enum StateActionUser { SEND_TO_REVIEW, diff --git a/core/main-service/src/main/java/ru/yandex/practicum/exception/BadRequestException.java b/core/interaction-api/src/main/java/ru/yandex/practicum/exception/BadRequestException.java similarity index 100% rename from core/main-service/src/main/java/ru/yandex/practicum/exception/BadRequestException.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/exception/BadRequestException.java diff --git a/core/main-service/src/main/java/ru/yandex/practicum/exception/ConflictException.java b/core/interaction-api/src/main/java/ru/yandex/practicum/exception/ConflictException.java similarity index 100% rename from core/main-service/src/main/java/ru/yandex/practicum/exception/ConflictException.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/exception/ConflictException.java diff --git a/core/main-service/src/main/java/ru/yandex/practicum/exception/ForbiddenException.java b/core/interaction-api/src/main/java/ru/yandex/practicum/exception/ForbiddenException.java similarity index 100% rename from core/main-service/src/main/java/ru/yandex/practicum/exception/ForbiddenException.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/exception/ForbiddenException.java diff --git a/core/main-service/src/main/java/ru/yandex/practicum/exception/NotFoundException.java b/core/interaction-api/src/main/java/ru/yandex/practicum/exception/NotFoundException.java similarity index 100% rename from core/main-service/src/main/java/ru/yandex/practicum/exception/NotFoundException.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/exception/NotFoundException.java diff --git a/core/main-service/src/main/java/ru/yandex/practicum/exception/ValidationException.java b/core/interaction-api/src/main/java/ru/yandex/practicum/exception/ValidationException.java similarity index 100% rename from core/main-service/src/main/java/ru/yandex/practicum/exception/ValidationException.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/exception/ValidationException.java diff --git a/core/main-service/src/main/java/ru/yandex/practicum/comment/controller/AdminCommentController.java b/core/main-service/src/main/java/ru/yandex/practicum/comment/controller/AdminCommentController.java deleted file mode 100644 index cd5a919..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/comment/controller/AdminCommentController.java +++ /dev/null @@ -1,25 +0,0 @@ -package ru.yandex.practicum.comment.controller; - -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.yandex.practicum.comment.service.CommentService; - -@Slf4j -@Validated -@RestController -@RequestMapping("/admin/comments") -@RequiredArgsConstructor -public class AdminCommentController { - private final CommentService commentService; - - @DeleteMapping("/{commentId}") - @ResponseStatus(HttpStatus.NO_CONTENT) - public void deleteCommentAdmin(@PathVariable @Positive Long commentId) { - log.debug("Controller: deleteCommentAdmin commentId={}", commentId); - commentService.deleteCommentAdmin(commentId); - } -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/comment/controller/PrivateCommentController.java b/core/main-service/src/main/java/ru/yandex/practicum/comment/controller/PrivateCommentController.java deleted file mode 100644 index 8e68f7a..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/comment/controller/PrivateCommentController.java +++ /dev/null @@ -1,65 +0,0 @@ -package ru.yandex.practicum.comment.controller; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.Positive; -import jakarta.validation.constraints.PositiveOrZero; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.PageRequest; -import org.springframework.http.HttpStatus; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.comment.dto.CommentDto; -import ru.yandex.practicum.comment.dto.NewCommentDto; -import ru.yandex.practicum.comment.dto.UpdateCommentDto; -import ru.yandex.practicum.comment.service.CommentService; - -import java.util.List; - -@Slf4j -@Validated -@RestController -@RequestMapping("/users/{userId}/comments") -@RequiredArgsConstructor -public class PrivateCommentController { - private final CommentService commentService; - - @PostMapping("/{eventId}") - @ResponseStatus(HttpStatus.CREATED) - public CommentDto createComment(@PathVariable @Positive Long userId, - @PathVariable @Positive Long eventId, - @RequestBody @Valid NewCommentDto newCommentDto - ) { - log.debug("Controller: createComment userId={}, eventId={}, data={}", userId, eventId, newCommentDto); - return commentService.createComment(userId, eventId, newCommentDto); - } - - @PatchMapping("/{eventId}/{commentId}") - public CommentDto updateComment(@PathVariable @Positive Long userId, - @PathVariable @Positive Long eventId, - @PathVariable @Positive Long commentId, - @RequestBody @Valid UpdateCommentDto updateCommentDto - ) { - log.debug("Controller: updateComment userId={}, eventId={}, commentId={}, data={}", userId, eventId, commentId, - updateCommentDto); - return commentService.updateComment(userId, eventId, commentId, updateCommentDto); - } - - @DeleteMapping("/{commentId}") - @ResponseStatus(HttpStatus.NO_CONTENT) - public void deleteComment(@PathVariable @Positive Long userId, - @PathVariable @Positive Long commentId - ) { - log.debug("Controller: deleteComment userId={}, commentId={}", userId, commentId); - commentService.deleteComment(userId, commentId); - } - - @GetMapping - public List getCommentsForUser(@PathVariable @Positive Long userId, - @RequestParam(defaultValue = "0") @PositiveOrZero Integer from, - @RequestParam(defaultValue = "10") @Positive Integer size - ) { - log.debug("Controller: getCommentsForUser userId={}, from={}, size={}", userId, from, size); - return commentService.getCommentsForUser(userId, PageRequest.of(from / size, size)); - } -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/comment/controller/PublicCommentController.java b/core/main-service/src/main/java/ru/yandex/practicum/comment/controller/PublicCommentController.java deleted file mode 100644 index 9561fba..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/comment/controller/PublicCommentController.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.yandex.practicum.comment.controller; - -import jakarta.validation.constraints.Positive; -import jakarta.validation.constraints.PositiveOrZero; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.PageRequest; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.comment.dto.CommentDto; -import ru.yandex.practicum.comment.service.CommentService; - -import java.util.List; - -@Slf4j -@Validated -@RestController -@RequestMapping("/comments") -@RequiredArgsConstructor -public class PublicCommentController { - private final CommentService commentService; - - @GetMapping("/event/{eventId}") - public List getCommentsForEvent(@PathVariable @Positive Long eventId, - @RequestParam(defaultValue = "0") @PositiveOrZero Integer from, - @RequestParam(defaultValue = "10") @Positive Integer size - ) { - log.debug("Controller: getCommentsForEvent eventId={}, from={}, size={}", eventId, from, size); - return commentService.getCommentsForEvent(eventId, PageRequest.of(from / size, size)); - } -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/comment/dto/CommentDto.java b/core/main-service/src/main/java/ru/yandex/practicum/comment/dto/CommentDto.java deleted file mode 100644 index 4e8b862..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/comment/dto/CommentDto.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.yandex.practicum.comment.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; - -import java.time.LocalDateTime; - -import static ru.yandex.practicum.constants.DateTimeConstants.DATE_TIME_PATTERN; - -public record CommentDto( - Long id, - - String text, - - Long userId, - - Long eventId, - - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_TIME_PATTERN) - LocalDateTime createdDate -) { -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/comment/mapper/CommentMapper.java b/core/main-service/src/main/java/ru/yandex/practicum/comment/mapper/CommentMapper.java deleted file mode 100644 index 56a943a..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/comment/mapper/CommentMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.yandex.practicum.comment.mapper; - -import org.mapstruct.*; -import ru.yandex.practicum.comment.dto.CommentDto; -import ru.yandex.practicum.comment.dto.NewCommentDto; -import ru.yandex.practicum.comment.dto.UpdateCommentDto; -import ru.yandex.practicum.comment.model.Comment; -import ru.yandex.practicum.event.model.Event; -import ru.yandex.practicum.user.model.User; - -@Mapper(componentModel = "spring") -public interface CommentMapper { - @Mapping(target = "id", ignore = true) - @Mapping(target = "createdDate", ignore = true) - Comment toComment(NewCommentDto newCommentDto, User user, Event event); - - @Mapping(target = "userId", source = "user.id") - @Mapping(target = "eventId", source = "event.id") - CommentDto toDto(Comment comment); - - @Mapping(target = "id", ignore = true) - @Mapping(target = "user", ignore = true) - @Mapping(target = "event", ignore = true) - @Mapping(target = "createdDate", ignore = true) - void updateCommentFromDto(UpdateCommentDto dto, @MappingTarget Comment comment); -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/comment/model/Comment.java b/core/main-service/src/main/java/ru/yandex/practicum/comment/model/Comment.java deleted file mode 100644 index e3ee4d7..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/comment/model/Comment.java +++ /dev/null @@ -1,38 +0,0 @@ -package ru.yandex.practicum.comment.model; - -import jakarta.persistence.*; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import org.hibernate.annotations.CreationTimestamp; -import ru.yandex.practicum.event.model.Event; -import ru.yandex.practicum.user.model.User; - -import java.time.LocalDateTime; - -@Entity -@Table(name = "comments") -@Getter -@Setter -@EqualsAndHashCode(onlyExplicitlyIncluded = true) -public class Comment { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @EqualsAndHashCode.Include - private Long id; - - @Column(nullable = false, length = 5000) - private String text; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "event_id", nullable = false) - private Event event; - - @Column(name = "created_date", nullable = false) - @CreationTimestamp - private LocalDateTime createdDate; -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/comment/repository/CommentRepository.java b/core/main-service/src/main/java/ru/yandex/practicum/comment/repository/CommentRepository.java deleted file mode 100644 index 8b8cadd..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/comment/repository/CommentRepository.java +++ /dev/null @@ -1,47 +0,0 @@ -package ru.yandex.practicum.comment.repository; - -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import ru.yandex.practicum.comment.model.Comment; - -import java.util.List; -import java.util.Optional; - -public interface CommentRepository extends JpaRepository { - - @Query(""" - SELECT c FROM Comment c - LEFT JOIN FETCH c.user - LEFT JOIN FETCH c.event - WHERE c.id = :id AND c.user.id = :userId AND c.event.id = :eventId""" - ) - Optional findByIdAndUserIdAndEventId(@Param("id") Long id, @Param("userId") Long userId, @Param("eventId") Long eventId); - - @Query(""" - SELECT c FROM Comment c - LEFT JOIN FETCH c.user - LEFT JOIN FETCH c.event - WHERE c.id = :id AND c.user.id = :userId""" - ) - Optional findByIdAndUserId(@Param("id") Long id, @Param("userId") Long userId); - - @Query(""" - SELECT c FROM Comment c - LEFT JOIN FETCH c.user - LEFT JOIN FETCH c.event - WHERE c.event.id = :eventId - ORDER BY c.createdDate DESC""" - ) - List findByEventId(@Param("eventId") Long eventId, Pageable pageable); - - @Query(""" - SELECT c FROM Comment c - LEFT JOIN FETCH c.user - LEFT JOIN FETCH c.event - WHERE c.user.id = :userId - ORDER BY c.createdDate DESC""" - ) - List findByUserId(@Param("userId") Long userId, Pageable pageable); -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/comment/service/CommentService.java b/core/main-service/src/main/java/ru/yandex/practicum/comment/service/CommentService.java deleted file mode 100644 index 10468b1..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/comment/service/CommentService.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.yandex.practicum.comment.service; - -import org.springframework.data.domain.Pageable; -import ru.yandex.practicum.comment.dto.CommentDto; -import ru.yandex.practicum.comment.dto.NewCommentDto; -import ru.yandex.practicum.comment.dto.UpdateCommentDto; - -import java.util.List; - -public interface CommentService { - CommentDto createComment(Long userId, Long eventId, NewCommentDto newCommentDto); - - CommentDto updateComment(Long userId, Long eventId, Long commentId, UpdateCommentDto updateCommentDto); - - void deleteComment(Long userId, Long commentId); - - void deleteCommentAdmin(Long commentId); - - List getCommentsForEvent(Long eventId, Pageable pageable); - - List getCommentsForUser(Long userId, Pageable pageable); -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/comment/service/CommentServiceImpl.java b/core/main-service/src/main/java/ru/yandex/practicum/comment/service/CommentServiceImpl.java deleted file mode 100644 index 7fa5eff..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/comment/service/CommentServiceImpl.java +++ /dev/null @@ -1,106 +0,0 @@ -package ru.yandex.practicum.comment.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.yandex.practicum.comment.dto.CommentDto; -import ru.yandex.practicum.comment.dto.NewCommentDto; -import ru.yandex.practicum.comment.dto.UpdateCommentDto; -import ru.yandex.practicum.comment.mapper.CommentMapper; -import ru.yandex.practicum.comment.model.Comment; -import ru.yandex.practicum.comment.repository.CommentRepository; -import ru.yandex.practicum.event.model.Event; -import ru.yandex.practicum.event.model.EventState; -import ru.yandex.practicum.event.service.EventService; -import ru.yandex.practicum.exception.ConflictException; -import ru.yandex.practicum.exception.NotFoundException; -import ru.yandex.practicum.user.model.User; -import ru.yandex.practicum.user.service.UserService; - -import java.util.List; - -@Slf4j -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class CommentServiceImpl implements CommentService { - private final CommentRepository commentRepository; - private final EventService eventService; - private final UserService userService; - private final CommentMapper commentMapper; - - @Override - @Transactional - public CommentDto createComment(Long userId, Long eventId, NewCommentDto newCommentDto) { - User user = userService.getUserByIdOrThrow(userId); - Event event = eventService.getEventOrThrow(eventId); - - if (EventState.PUBLISHED != event.getState()) - throw new ConflictException("Нельзя комментировать неопубликованное событие"); - - Comment comment = commentMapper.toComment(newCommentDto, user, event); - - log.info("Создан новый комментарий: authorId={}, eventId={}, newCommentDto={}", userId, eventId, newCommentDto); - return commentMapper.toDto(commentRepository.save(comment)); - } - - @Override - @Transactional - public CommentDto updateComment(Long userId, Long eventId, Long commentId, UpdateCommentDto updateCommentDto) { - Comment comment = commentRepository.findByIdAndUserIdAndEventId(commentId, userId, eventId) - .orElseThrow(() -> new NotFoundException("Комментарий с id=" + commentId - + " от пользователя с id=" + userId - + " к событию с id=" + eventId + " не найден")); - - commentMapper.updateCommentFromDto(updateCommentDto, comment); - - log.info("Комментарий обновлен: commentId={}, userId={}, eventId={}, updateCommentDto={}", - commentId, userId, eventId, updateCommentDto); - return commentMapper.toDto(commentRepository.save(comment)); - } - - @Override - @Transactional - public void deleteComment(Long userId, Long commentId) { - commentRepository.findByIdAndUserId(commentId, userId) - .orElseThrow(() -> new NotFoundException("Комментарий с id=" + commentId - + " от пользователя с id=" + userId + " не найден")); - - log.info("Комментарий удален: commentId={}, userId={}", commentId, userId); - commentRepository.deleteById(commentId); - } - - @Override - @Transactional - public void deleteCommentAdmin(Long commentId) { - commentRepository.findById(commentId) - .orElseThrow(() -> new NotFoundException("Комментарий с id=" + commentId + " не найден")); - - log.info("Комментарий удален администратором: commentId={}", commentId); - commentRepository.deleteById(commentId); - } - - @Override - public List getCommentsForEvent(Long eventId, Pageable pageable) { - eventService.getEventOrThrow(eventId); - - log.info("Получен список комментариев для события: eventId={}", eventId); - return commentRepository.findByEventId(eventId, pageable) - .stream() - .map(commentMapper::toDto) - .toList(); - } - - @Override - public List getCommentsForUser(Long userId, Pageable pageable) { - userService.getUserByIdOrThrow(userId); - - log.info("Получен список комментариев для пользователя: userId={}", userId); - return commentRepository.findByUserId(userId, pageable) - .stream() - .map(commentMapper::toDto) - .toList(); - } -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/mapper/EventMapper.java b/core/main-service/src/main/java/ru/yandex/practicum/event/mapper/EventMapper.java index d3d046e..a450c47 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/mapper/EventMapper.java +++ b/core/main-service/src/main/java/ru/yandex/practicum/event/mapper/EventMapper.java @@ -2,7 +2,6 @@ import org.mapstruct.*; import ru.yandex.practicum.category.model.Category; -import ru.yandex.practicum.event.dto.*; import ru.yandex.practicum.event.model.Event; import ru.yandex.practicum.event.model.EventState; import ru.yandex.practicum.user.model.User; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/service/EventService.java b/core/main-service/src/main/java/ru/yandex/practicum/event/service/EventService.java index ba9cc3a..035819b 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/service/EventService.java +++ b/core/main-service/src/main/java/ru/yandex/practicum/event/service/EventService.java @@ -1,7 +1,6 @@ package ru.yandex.practicum.event.service; import org.springframework.data.domain.Pageable; -import ru.yandex.practicum.event.dto.*; import ru.yandex.practicum.event.model.Event; import java.util.List; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/service/EventServiceImpl.java b/core/main-service/src/main/java/ru/yandex/practicum/event/service/EventServiceImpl.java index 565f009..e557794 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/service/EventServiceImpl.java +++ b/core/main-service/src/main/java/ru/yandex/practicum/event/service/EventServiceImpl.java @@ -14,7 +14,6 @@ import ru.yandex.practicum.category.mapper.CategoryMapper; import ru.yandex.practicum.category.model.Category; import ru.yandex.practicum.category.service.CategoryService; -import ru.yandex.practicum.event.dto.*; import ru.yandex.practicum.event.mapper.EventMapper; import ru.yandex.practicum.event.model.*; import ru.yandex.practicum.event.repository.EventRepository; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/request/mapper/RequestMapper.java b/core/main-service/src/main/java/ru/yandex/practicum/request/mapper/RequestMapper.java deleted file mode 100644 index 68ae2fe..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/request/mapper/RequestMapper.java +++ /dev/null @@ -1,29 +0,0 @@ -package ru.yandex.practicum.request.mapper; - -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import ru.yandex.practicum.event.model.Event; -import ru.yandex.practicum.request.dto.EventRequestStatusUpdateResult; -import ru.yandex.practicum.request.dto.ParticipationRequestDto; -import ru.yandex.practicum.request.model.Request; -import ru.yandex.practicum.request.model.RequestStatus; -import ru.yandex.practicum.user.model.User; - -import java.util.List; - -@Mapper(componentModel = "spring") -public interface RequestMapper { - - @Mapping(target = "id", ignore = true) - @Mapping(target = "created", ignore = true) - Request toEntity(Event event, User requester, RequestStatus status); - - @Mapping(source = "event.id", target = "event") - @Mapping(source = "requester.id", target = "requester") - ParticipationRequestDto toDto(Request request); - - EventRequestStatusUpdateResult toEventRequestStatusUpdateResultDto( - List confirmedRequests, - List rejectedRequests - ); -} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/yandex/practicum/request/repository/RequestRepository.java b/core/main-service/src/main/java/ru/yandex/practicum/request/repository/RequestRepository.java deleted file mode 100644 index b450636..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/request/repository/RequestRepository.java +++ /dev/null @@ -1,56 +0,0 @@ -package ru.yandex.practicum.request.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import ru.yandex.practicum.event.dto.EventWithCountConfirmedRequests; -import ru.yandex.practicum.request.dto.ParticipationRequestDto; -import ru.yandex.practicum.request.model.Request; -import ru.yandex.practicum.request.model.RequestStatus; - -import java.util.List; - -public interface RequestRepository extends JpaRepository { - - @Query(""" - SELECT NEW ru.yandex.practicum.event.dto.EventWithCountConfirmedRequests(r.event.id, COUNT(r)) - FROM Request r - WHERE r.event.id IN :eventIds AND r.status = ru.yandex.practicum.request.model.RequestStatus.CONFIRMED - GROUP BY r.event.id""" - ) - List findCountConfirmedRequestsByEventIds(@Param("eventIds") List eventIds); - - @Query(""" - SELECT r FROM Request r - LEFT JOIN FETCH r.event - LEFT JOIN FETCH r.requester - WHERE r.event.id = :eventId""" - ) - List findAllParticipationRequestByEventId(@Param("eventId") Long eventId); - - @Query(""" - SELECT r FROM Request r - LEFT JOIN FETCH r.event - LEFT JOIN FETCH r.requester - WHERE r.id IN :requestIds""" - ) - List findAllRequestById(@Param("requestIds") List requestIds); - - @Query(""" - SELECT NEW ru.yandex.practicum.request.dto.ParticipationRequestDto( - r.id, - r.created, - r.event.id, - r.requester.id, - r.status) - FROM Request r - WHERE r.requester.id = :userId""" - ) - List findAllRequestsByUserId(@Param("userId") Long userId); - - boolean existsByEventIdAndRequesterId(Long eventId, Long userId); - - int countByEventIdAndStatus(Long id, RequestStatus requestStatus); - - List findAllByEventIdAndStatus(Long eventId, RequestStatus requestStatus); -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/user/mapper/UserMapper.java b/core/main-service/src/main/java/ru/yandex/practicum/user/mapper/UserMapper.java deleted file mode 100644 index 3143b3e..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/user/mapper/UserMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.yandex.practicum.user.mapper; - -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import ru.yandex.practicum.user.dto.NewUserRequest; -import ru.yandex.practicum.user.dto.UserDto; -import ru.yandex.practicum.user.dto.UserShortDto; -import ru.yandex.practicum.user.model.User; - -@Mapper(componentModel = "spring") -public interface UserMapper { - - @Mapping(target = "id", ignore = true) - User toEntity(NewUserRequest dto); - - UserDto toDto(User user); - - UserShortDto toShortDto(User user); -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/user/model/User.java b/core/main-service/src/main/java/ru/yandex/practicum/user/model/User.java deleted file mode 100644 index 50270d1..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/user/model/User.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.yandex.practicum.user.model; - -import jakarta.persistence.*; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -@Entity -@Table(name = "users") -@Getter -@Setter -@ToString -@EqualsAndHashCode(onlyExplicitlyIncluded = true) -public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @EqualsAndHashCode.Include - private Long id; - - @Column(nullable = false) - private String name; - - @Column(nullable = false, unique = true) - private String email; -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/user/service/UserService.java b/core/main-service/src/main/java/ru/yandex/practicum/user/service/UserService.java deleted file mode 100644 index ad636a1..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/user/service/UserService.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.yandex.practicum.user.service; - -import org.springframework.data.domain.Pageable; -import ru.yandex.practicum.user.dto.NewUserRequest; -import ru.yandex.practicum.user.dto.UserDto; -import ru.yandex.practicum.user.model.User; - -import java.util.List; - -public interface UserService { - UserDto createUser(NewUserRequest userRequest); - - List getUsers(List ids, Pageable pageable); - - void deleteUser(Long userId); - - User getUserById(Long userId); - - User getUserByIdOrThrow(Long userId); -} diff --git a/core/main-service/src/main/resources/schema.sql b/core/main-service/src/main/resources/schema.sql index 59365c2..6cd84d9 100644 --- a/core/main-service/src/main/resources/schema.sql +++ b/core/main-service/src/main/resources/schema.sql @@ -3,11 +3,11 @@ create TABLE IF NOT EXISTS categories ( name VARCHAR(50) NOT NULL UNIQUE ); -create TABLE IF NOT EXISTS users ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) NOT NULL UNIQUE -); +--create TABLE IF NOT EXISTS users ( +-- id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, +-- name VARCHAR(255) NOT NULL, +-- email VARCHAR(255) NOT NULL UNIQUE +--); create TABLE IF NOT EXISTS events ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, @@ -27,13 +27,13 @@ create TABLE IF NOT EXISTS events ( lon FLOAT ); -create TABLE IF NOT EXISTS requests ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - event_id BIGINT REFERENCES events(id), - requester_id BIGINT REFERENCES users(id), - created TIMESTAMP WITHOUT TIME ZONE, - status VARCHAR(255) -); +--create TABLE IF NOT EXISTS requests ( +-- id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, +-- event_id BIGINT REFERENCES events(id), +-- requester_id BIGINT REFERENCES users(id), +-- created TIMESTAMP WITHOUT TIME ZONE, +-- status VARCHAR(255) +--); create TABLE IF NOT EXISTS compilations ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, @@ -47,13 +47,13 @@ create TABLE IF NOT EXISTS compilation_events ( CONSTRAINT pk_compilation_events PRIMARY KEY (compilation_id, event_id) ); -create TABLE IF NOT EXISTS comments ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - text TEXT NOT NULL, - user_id BIGINT NOT NULL REFERENCES users (id), - event_id BIGINT NOT NULL REFERENCES events (id), - created_date TIMESTAMP WITHOUT TIME ZONE NOT NULL -); +--create TABLE IF NOT EXISTS comments ( +-- id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, +-- text TEXT NOT NULL, +-- user_id BIGINT NOT NULL REFERENCES users (id), +-- event_id BIGINT NOT NULL REFERENCES events (id), +-- created_date TIMESTAMP WITHOUT TIME ZONE NOT NULL +--); -CREATE INDEX IF NOT EXISTS idx_comments_event_id ON comments(event_id); -CREATE INDEX IF NOT EXISTS idx_comments_user_id ON comments(user_id); \ No newline at end of file +--CREATE INDEX IF NOT EXISTS idx_comments_event_id ON comments(event_id); +--CREATE INDEX IF NOT EXISTS idx_comments_user_id ON comments(user_id); \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 18aa957..0cdaad3 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -15,6 +15,10 @@ main-service + interaction-api + request-service + user-service + comment-service diff --git a/core/request-service/pom.xml b/core/request-service/pom.xml new file mode 100644 index 0000000..5e27657 --- /dev/null +++ b/core/request-service/pom.xml @@ -0,0 +1,153 @@ + + + 4.0.0 + + ru.yandex.practicum + core + 0.0.1-SNAPSHOT + + + request-service + + + 21 + 21 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-aop + + + + ru.yandex.practicum + interaction-api + ${project.version} + + + + ru.yandex.practicum + stats-client + ${project.version} + + + + ru.yandex.practicum + stats-dto + ${project.version} + + + + org.projectlombok + lombok + provided + + + + org.postgresql + postgresql + + + + org.springframework.cloud + spring-cloud-starter-config + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.retry + spring-retry + 2.0.5 + + + + io.github.openfeign + feign-okhttp + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + com.h2database + h2 + runtime + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 21 + 21 + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + + + \ No newline at end of file diff --git a/core/request-service/src/main/java/ru/yandex/practicum/RequestServiceApp.java b/core/request-service/src/main/java/ru/yandex/practicum/RequestServiceApp.java new file mode 100644 index 0000000..9ac7c7e --- /dev/null +++ b/core/request-service/src/main/java/ru/yandex/practicum/RequestServiceApp.java @@ -0,0 +1,15 @@ +package ru.yandex.practicum; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.openfeign.EnableFeignClients; + +@SpringBootApplication +@EnableFeignClients +@EnableDiscoveryClient +public class RequestServiceApp { + public static void main(String[] args) { + SpringApplication.run(RequestServiceApp.class, args); + } +} diff --git a/core/request-service/src/main/java/ru/yandex/practicum/controller/InternalRequestController.java b/core/request-service/src/main/java/ru/yandex/practicum/controller/InternalRequestController.java new file mode 100644 index 0000000..c9bbb5f --- /dev/null +++ b/core/request-service/src/main/java/ru/yandex/practicum/controller/InternalRequestController.java @@ -0,0 +1,22 @@ +package ru.yandex.practicum.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.yandex.practicum.enums.RequestStatus; +import ru.yandex.practicum.repository.RequestRepository; + +@RestController +@RequestMapping("/internal/requests") +@RequiredArgsConstructor +public class InternalRequestController { + + private final RequestRepository requestRepository; + + @GetMapping("/event/{eventId}/count/{status}") + public Long countByStatus(@PathVariable Long eventId, @PathVariable RequestStatus status) { + return requestRepository.countByEventIdAndStatus(eventId, status); + } +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/request/controller/PrivateRequestController.java b/core/request-service/src/main/java/ru/yandex/practicum/controller/PrivateRequestController.java similarity index 90% rename from core/main-service/src/main/java/ru/yandex/practicum/request/controller/PrivateRequestController.java rename to core/request-service/src/main/java/ru/yandex/practicum/controller/PrivateRequestController.java index 4dede69..0531cf9 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/request/controller/PrivateRequestController.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/controller/PrivateRequestController.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.request.controller; +package ru.yandex.practicum.controller; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; @@ -6,8 +6,8 @@ import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.request.dto.ParticipationRequestDto; -import ru.yandex.practicum.request.service.RequestService; +import ru.yandex.practicum.dto.request.ParticipationRequestDto; +import ru.yandex.practicum.service.RequestService; import java.util.List; diff --git a/core/request-service/src/main/java/ru/yandex/practicum/mapper/RequestMapper.java b/core/request-service/src/main/java/ru/yandex/practicum/mapper/RequestMapper.java new file mode 100644 index 0000000..93ac361 --- /dev/null +++ b/core/request-service/src/main/java/ru/yandex/practicum/mapper/RequestMapper.java @@ -0,0 +1,18 @@ +package ru.yandex.practicum.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import ru.yandex.practicum.dto.request.ParticipationRequestDto; +import ru.yandex.practicum.model.Request; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface RequestMapper { + + @Mapping(source = "eventId", target = "event") + @Mapping(source = "requesterId", target = "requester") + ParticipationRequestDto toDto(Request request); + + List toDtoList(List requests); +} \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/yandex/practicum/request/model/Request.java b/core/request-service/src/main/java/ru/yandex/practicum/model/Request.java similarity index 51% rename from core/main-service/src/main/java/ru/yandex/practicum/request/model/Request.java rename to core/request-service/src/main/java/ru/yandex/practicum/model/Request.java index 1c69cf6..425081c 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/request/model/Request.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/model/Request.java @@ -1,38 +1,42 @@ -package ru.yandex.practicum.request.model; +package ru.yandex.practicum.model; import jakarta.persistence.*; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import org.hibernate.annotations.CreationTimestamp; -import ru.yandex.practicum.event.model.Event; -import ru.yandex.practicum.user.model.User; +import ru.yandex.practicum.enums.RequestStatus; import java.time.LocalDateTime; @Entity -@Table(name = "requests") +@Table(name = "requests", + uniqueConstraints = {@UniqueConstraint( + columnNames = {"requester_id", "event_id"}, + name = "uq_requester_event")} +) @Getter @Setter -@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@AllArgsConstructor +@NoArgsConstructor +@Builder +@ToString public class Request { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @EqualsAndHashCode.Include private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "event_id") - private Event event; + @Column(name = "event_id", nullable = false) + private Long eventId; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "requester_id") - private User requester; + @Column(name = "requester_id", nullable = false) + private Long requesterId; @CreationTimestamp + @Column(nullable = false, updatable = false) private LocalDateTime created; @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 60) private RequestStatus status; public void confirmed() { diff --git a/core/request-service/src/main/java/ru/yandex/practicum/repository/RequestRepository.java b/core/request-service/src/main/java/ru/yandex/practicum/repository/RequestRepository.java new file mode 100644 index 0000000..aa1b964 --- /dev/null +++ b/core/request-service/src/main/java/ru/yandex/practicum/repository/RequestRepository.java @@ -0,0 +1,25 @@ +package ru.yandex.practicum.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.yandex.practicum.enums.RequestStatus; +import ru.yandex.practicum.model.Request; + +import java.util.List; +import java.util.Optional; + +public interface RequestRepository extends JpaRepository { + + boolean existsByRequesterIdAndEventId(Long requesterId, Long eventId); + + List findByRequesterId(Long requesterId); + + List findByEventId(Long eventId); + + Optional findByIdAndRequesterId(Long id, Long requesterId); + + long countByEventIdAndStatus(Long eventId, RequestStatus status); + + List findByEventIdAndStatus(Long eventId, RequestStatus status); + + List findByIdIn(List ids); +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/request/service/RequestService.java b/core/request-service/src/main/java/ru/yandex/practicum/service/RequestService.java similarity index 55% rename from core/main-service/src/main/java/ru/yandex/practicum/request/service/RequestService.java rename to core/request-service/src/main/java/ru/yandex/practicum/service/RequestService.java index 7c0e8c7..0b12f05 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/request/service/RequestService.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/service/RequestService.java @@ -1,15 +1,17 @@ -package ru.yandex.practicum.request.service; +package ru.yandex.practicum.service; -import ru.yandex.practicum.request.dto.EventRequestStatusUpdateRequest; -import ru.yandex.practicum.request.dto.EventRequestStatusUpdateResult; -import ru.yandex.practicum.request.dto.ParticipationRequestDto; + +import ru.yandex.practicum.dto.request.EventRequestStatusUpdateRequest; +import ru.yandex.practicum.dto.request.EventRequestStatusUpdateResult; +import ru.yandex.practicum.dto.request.ParticipationRequestDto; import java.util.List; public interface RequestService { List getRequestsByEvent(Long userId, Long eventId); - EventRequestStatusUpdateResult updateRequestStatus(Long userId, Long eventId, EventRequestStatusUpdateRequest request); + EventRequestStatusUpdateResult updateRequestStatus(Long userId, + Long eventId, EventRequestStatusUpdateRequest request); List getRequestsByUserId(Long userId); diff --git a/core/main-service/src/main/java/ru/yandex/practicum/request/service/RequestServiceImpl.java b/core/request-service/src/main/java/ru/yandex/practicum/service/RequestServiceImpl.java similarity index 51% rename from core/main-service/src/main/java/ru/yandex/practicum/request/service/RequestServiceImpl.java rename to core/request-service/src/main/java/ru/yandex/practicum/service/RequestServiceImpl.java index 2f7470c..d59a086 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/request/service/RequestServiceImpl.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/service/RequestServiceImpl.java @@ -1,24 +1,24 @@ -package ru.yandex.practicum.request.service; +package ru.yandex.practicum.service; +import feign.FeignException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.yandex.practicum.event.model.Event; -import ru.yandex.practicum.event.model.EventState; -import ru.yandex.practicum.event.service.EventService; +import ru.yandex.practicum.client.EventClient; +import ru.yandex.practicum.client.UserClient; +import ru.yandex.practicum.dto.event.EventFullDto; +import ru.yandex.practicum.dto.request.EventRequestStatusUpdateRequest; +import ru.yandex.practicum.dto.request.EventRequestStatusUpdateResult; +import ru.yandex.practicum.dto.request.ParticipationRequestDto; +import ru.yandex.practicum.enums.EventState; +import ru.yandex.practicum.enums.RequestStatus; import ru.yandex.practicum.exception.ConflictException; import ru.yandex.practicum.exception.NotFoundException; import ru.yandex.practicum.exception.ValidationException; -import ru.yandex.practicum.request.dto.EventRequestStatusUpdateRequest; -import ru.yandex.practicum.request.dto.EventRequestStatusUpdateResult; -import ru.yandex.practicum.request.dto.ParticipationRequestDto; -import ru.yandex.practicum.request.mapper.RequestMapper; -import ru.yandex.practicum.request.model.Request; -import ru.yandex.practicum.request.model.RequestStatus; -import ru.yandex.practicum.request.repository.RequestRepository; -import ru.yandex.practicum.user.model.User; -import ru.yandex.practicum.user.service.UserService; +import ru.yandex.practicum.mapper.RequestMapper; +import ru.yandex.practicum.model.Request; +import ru.yandex.practicum.repository.RequestRepository; import java.util.ArrayList; import java.util.List; @@ -28,33 +28,42 @@ @Transactional(readOnly = true) @RequiredArgsConstructor public class RequestServiceImpl implements RequestService { - private final EventService eventService; private final RequestRepository requestRepository; private final RequestMapper requestMapper; - private final UserService userService; + private final UserClient userClient; + private final EventClient eventClient; @Override @Transactional public ParticipationRequestDto addRequest(Long userId, Long eventId) { - User user = userService.getUserByIdOrThrow(userId); - Event event = eventService.getEventOrThrow(eventId); + checkUserExists(userId); + EventFullDto event = eventClient.getEvent(eventId); - if (requestRepository.existsByEventIdAndRequesterId(eventId, userId)) + if (requestRepository.existsByRequesterIdAndEventId(userId, eventId)) { // 1. Исправлен порядок параметров throw new ConflictException("Нельзя добавить повторный запрос"); + } - if (event.getInitiator().getId().equals(userId)) + if (event.initiator().id().equals(userId)) { throw new ConflictException("Инициатор события не может добавить запрос на участие в своём событии"); + } - if (event.getState() != EventState.PUBLISHED) + if (event.state() != EventState.PUBLISHED) { throw new ConflictException("Нельзя участвовать в неопубликованном событии"); + } - if (!hasSlots(event)) { + if (event.participantLimit() != 0 && + requestRepository.countByEventIdAndStatus(eventId, RequestStatus.CONFIRMED) + >= event.participantLimit()) { throw new ConflictException("Достигнут лимит по количеству участников события с id=" + eventId); } - Request request = requestMapper.toEntity(event, user, RequestStatus.PENDING); + Request request = Request.builder() + .requesterId(userId) + .eventId(eventId) + .status(RequestStatus.PENDING) + .build(); - if (!event.isRequestModeration() || event.getParticipantLimit() == 0) { + if (!event.requestModeration() || event.participantLimit() == 0) { log.debug("Модерация заявок на участие в событии с id={} не требуется", eventId); request.confirmed(); } @@ -67,20 +76,20 @@ public ParticipationRequestDto addRequest(Long userId, Long eventId) { @Override public List getRequestsByUserId(Long userId) { - userService.getUserByIdOrThrow(userId); + checkUserExists(userId); log.info("Получение информации о заявках на участие пользователя с id={}", userId); - return requestRepository.findAllRequestsByUserId(userId); + List requests = requestRepository.findByRequesterId(userId); + return requestMapper.toDtoList(requests); } @Override + @Transactional public ParticipationRequestDto cancelRequest(Long userId, Long requestId) { - userService.getUserByIdOrThrow(userId); - Request request = getRequestOrThrow(requestId); + checkUserExists(userId); - if (!request.getRequester().getId().equals(userId)) { - throw new ConflictException("Запрос с id=" + requestId + " не принадлежит пользователю с id=" + userId); - } + Request request = requestRepository.findByIdAndRequesterId(requestId, userId) + .orElseThrow(() -> new NotFoundException("Запрос с id=" + requestId + " не найден")); request.canceled(); request = requestRepository.save(request); @@ -88,100 +97,116 @@ public ParticipationRequestDto cancelRequest(Long userId, Long requestId) { return requestMapper.toDto(request); } + @Override public List getRequestsByEvent(Long userId, Long eventId) { - Event event = eventService.getEventOrThrow(eventId); + checkUserExists(userId); + EventFullDto event = eventClient.getEvent(eventId); - if (!event.getInitiator().getId().equals(userId)) + if (!event.initiator().id().equals(userId)) { throw new ValidationException("Пользователь с id=" + userId + " не является создателем события"); + } log.info("Получение информации о запросах на участие в событии с id={}", eventId); - return requestRepository.findAllParticipationRequestByEventId(eventId).stream() - .map(requestMapper::toDto) - .toList(); + List requests = requestRepository.findByEventId(eventId); + return requestMapper.toDtoList(requests); } @Override @Transactional - public EventRequestStatusUpdateResult updateRequestStatus(Long userId, Long eventId, EventRequestStatusUpdateRequest request) { - Event event = eventService.getEventOrThrow(eventId); + public EventRequestStatusUpdateResult updateRequestStatus(Long userId, Long eventId, + EventRequestStatusUpdateRequest requestDto) { + checkUserExists(userId); + EventFullDto event = eventClient.getEvent(eventId); - if (!event.getInitiator().getId().equals(userId)) + if (!event.initiator().id().equals(userId)) { throw new ValidationException("Пользователь с id=" + userId + " не является создателем события"); + } - if (!event.isRequestModeration() || event.getParticipantLimit() == 0) + if (!event.requestModeration() || event.participantLimit() == 0) { throw new ValidationException("Для данного события подтверждение заявок не требуется"); + } - RequestStatus newStatus = request.status(); - if (newStatus == RequestStatus.PENDING) + RequestStatus newStatus = requestDto.status(); + if (newStatus == RequestStatus.PENDING) { throw new ValidationException("Устанавливать можно только статусы CONFIRMED или REJECTED"); + } - List requestsForUpdate = requestRepository.findAllRequestById(request.requestIds()); + // 2. Оптимизированный запрос для фиксированного количества + List requestsForUpdate = requestRepository.findByIdIn(requestDto.requestIds()); + long currentConfirmedCount = requestRepository.countByEventIdAndStatus(eventId, RequestStatus.CONFIRMED); + List pendingRequests = currentConfirmedCount + requestsForUpdate.size() >= event.participantLimit() + ? requestRepository.findByEventIdAndStatus(eventId, RequestStatus.PENDING) + : List.of(); - validateAllRequestsExist(request.requestIds(), requestsForUpdate); + validateAllRequestsExist(requestDto.requestIds(), requestsForUpdate); validateRequestsState(requestsForUpdate, eventId); - int currentConfirmedCount = requestRepository.countByEventIdAndStatus(eventId, RequestStatus.CONFIRMED); - int availableSlots = event.getParticipantLimit() - currentConfirmedCount; + long availableSlots = event.participantLimit() - currentConfirmedCount; - List confirmedRequests = new ArrayList<>(); - List rejectedRequests = new ArrayList<>(); + List confirmedRequests = new ArrayList<>(); + List rejectedRequests = new ArrayList<>(); if (newStatus == RequestStatus.CONFIRMED) { - if (availableSlots <= 0) throw new ConflictException("Свободных мест больше нет"); + if (availableSlots <= 0) { + throw new ConflictException("Свободных мест больше нет"); + } int confirmedCount = 0; for (Request req : requestsForUpdate) { if (confirmedCount < availableSlots) { req.confirmed(); - confirmedRequests.add(requestMapper.toDto(req)); + confirmedRequests.add(req); confirmedCount++; } else { req.rejected(); - rejectedRequests.add(requestMapper.toDto(req)); + rejectedRequests.add(req); } } - if (currentConfirmedCount + confirmedCount >= event.getParticipantLimit()) { - List pendingRequests = requestRepository - .findAllByEventIdAndStatus(eventId, RequestStatus.PENDING); - + // Автоматическое отклонение остальных + if (!pendingRequests.isEmpty()) { for (Request pendingReq : pendingRequests) { pendingReq.rejected(); - rejectedRequests.add(requestMapper.toDto(pendingReq)); + rejectedRequests.add(pendingReq); } - if (!pendingRequests.isEmpty()) { - requestRepository.saveAll(pendingRequests); - log.info("Автоматически отклонено {} заявок из-за исчерпания лимита на событие с id={}", - pendingRequests.size(), eventId); - } + // Batch сохранение всех в одном вызове + List allRequestsToSave = new ArrayList<>(); + allRequestsToSave.addAll(requestsForUpdate); + allRequestsToSave.addAll(pendingRequests); + requestRepository.saveAll(allRequestsToSave); + + log.info("Автоматически отклонено {} заявок из-за исчерпания лимита на событие с id={}", + pendingRequests.size(), eventId); + } else { + requestRepository.saveAll(requestsForUpdate); } } else { for (Request req : requestsForUpdate) { req.rejected(); - rejectedRequests.add(requestMapper.toDto(req)); + rejectedRequests.add(req); } + requestRepository.saveAll(requestsForUpdate); } - requestRepository.saveAll(requestsForUpdate); - log.info("Обновление статусов заявок на участие в событии с id={}: подтверждено={}, отклонено={}", eventId, confirmedRequests.size(), rejectedRequests.size()); - return requestMapper.toEventRequestStatusUpdateResultDto(confirmedRequests, rejectedRequests); + return new EventRequestStatusUpdateResult( + requestMapper.toDtoList(confirmedRequests), + requestMapper.toDtoList(rejectedRequests) + ); } - private Request getRequestOrThrow(Long requestId) { - return requestRepository.findById(requestId) - .orElseThrow(() -> new NotFoundException("Запрос с id=" + requestId + " не найден")); - } - - private boolean hasSlots(Event event) { - Integer limit = event.getParticipantLimit(); - if (limit == null || limit == 0) return true; - long confirmed = requestRepository.countByEventIdAndStatus(event.getId(), RequestStatus.CONFIRMED); - return confirmed < limit; + private void checkUserExists(Long userId) { + try { + userClient.getUser(userId); + } catch (FeignException.NotFound e) { + throw new NotFoundException("Пользователь с id=" + userId + " не найден"); + } catch (FeignException e) { + log.warn("Сервис пользователей недоступен, операция может быть выполнена некорректно"); + } } private void validateAllRequestsExist(List requestedIds, List foundRequests) { @@ -204,7 +229,7 @@ private void validateRequestsState(List requests, Long eventId) { throw new ConflictException("Можно изменять только запросы в статусе PENDING"); } - if (!req.getEvent().getId().equals(eventId)) { + if (!req.getEventId().equals(eventId)) { throw new ConflictException("Запрос с id=" + req.getId() + " не относится к событию с id=" + eventId); } } 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..4e1269f --- /dev/null +++ b/core/request-service/src/main/resources/application.yaml @@ -0,0 +1,26 @@ +spring: + application: + name: request-service + config: + import: "configserver" + cloud: + config: + fail-fast: true + discovery: + enabled: true + serviceId: config-server + retry: + useRandomPolicy: true + max-interval: 6000 + +server: + port: 0 + +eureka: + instance: + instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}} + client: + healthcheck: + enabled: true + serviceUrl: + defaultZone: http://localhost:8761/eureka \ No newline at end of file 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..47dcb6f --- /dev/null +++ b/core/request-service/src/main/resources/schema.sql @@ -0,0 +1,16 @@ +DROP TABLE IF EXISTS requests CASCADE; + +-- Создание таблицы запросов +create TABLE IF NOT EXISTS requests ( + id BIGINT GENERATED BY ALWAYS AS IDENTITY PRIMARY KEY, + event_id BIGINT NOT NULL, + requester_id BIGINT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + status VARCHAR(60) NOT NULL + CONSTRAINT uq_requester_event UNIQUE (requester_id, event_id), +); + +-- Индекс для быстрого поиска заявок на событие +CREATE INDEX IF NOT EXISTS idx_requests_event_id ON requests(event_id); +-- Индекс для быстрого поиска заявок пользователя +CREATE INDEX IF NOT EXISTS idx_requests_requester_id ON requests(requester_id); \ No newline at end of file diff --git a/core/user-service/pom.xml b/core/user-service/pom.xml new file mode 100644 index 0000000..d43bfff --- /dev/null +++ b/core/user-service/pom.xml @@ -0,0 +1,153 @@ + + + 4.0.0 + + ru.yandex.practicum + core + 0.0.1-SNAPSHOT + + + user-service + + + 21 + 21 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-aop + + + + ru.yandex.practicum + interaction-api + ${project.version} + + + + ru.yandex.practicum + stats-client + ${project.version} + + + + ru.yandex.practicum + stats-dto + ${project.version} + + + + org.projectlombok + lombok + provided + + + + org.postgresql + postgresql + + + + org.springframework.cloud + spring-cloud-starter-config + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.retry + spring-retry + 2.0.5 + + + + io.github.openfeign + feign-okhttp + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + com.h2database + h2 + runtime + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 21 + 21 + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + + + \ No newline at end of file diff --git a/core/user-service/src/main/java/ru/yandex/practicum/UserServiceApp.java b/core/user-service/src/main/java/ru/yandex/practicum/UserServiceApp.java new file mode 100644 index 0000000..cfb2b63 --- /dev/null +++ b/core/user-service/src/main/java/ru/yandex/practicum/UserServiceApp.java @@ -0,0 +1,13 @@ +package ru.yandex.practicum; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +@SpringBootApplication +@EnableFeignClients +public class UserServiceApp { + public static void main(String[] args) { + SpringApplication.run(UserServiceApp.class, args); + } +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/user/controller/AdminUserController.java b/core/user-service/src/main/java/ru/yandex/practicum/controller/AdminUserController.java similarity index 89% rename from core/main-service/src/main/java/ru/yandex/practicum/user/controller/AdminUserController.java rename to core/user-service/src/main/java/ru/yandex/practicum/controller/AdminUserController.java index 995a796..487f929 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/user/controller/AdminUserController.java +++ b/core/user-service/src/main/java/ru/yandex/practicum/controller/AdminUserController.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.user.controller; +package ru.yandex.practicum.controller; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; @@ -10,9 +10,9 @@ import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.user.dto.NewUserRequest; -import ru.yandex.practicum.user.dto.UserDto; -import ru.yandex.practicum.user.service.UserService; +import ru.yandex.practicum.dto.user.NewUserRequest; +import ru.yandex.practicum.dto.user.UserDto; +import ru.yandex.practicum.service.UserService; import java.util.List; diff --git a/core/user-service/src/main/java/ru/yandex/practicum/controller/InternalUserController.java b/core/user-service/src/main/java/ru/yandex/practicum/controller/InternalUserController.java new file mode 100644 index 0000000..3b522c3 --- /dev/null +++ b/core/user-service/src/main/java/ru/yandex/practicum/controller/InternalUserController.java @@ -0,0 +1,24 @@ +package ru.yandex.practicum.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.yandex.practicum.dto.user.UserShortDto; +import ru.yandex.practicum.service.UserService; + +@Slf4j +@RestController +@RequestMapping("/internal/users") +@RequiredArgsConstructor +public class InternalUserController { + private final UserService userService; + + @GetMapping("/{userId}") + public UserShortDto getUser(@PathVariable("userId") Long userId) { + log.debug("Internal: getUser userId={}", userId); + return userService.getUserShortInfo(userId); + } +} diff --git a/core/user-service/src/main/java/ru/yandex/practicum/mapper/UserMapper.java b/core/user-service/src/main/java/ru/yandex/practicum/mapper/UserMapper.java new file mode 100644 index 0000000..2755c2a --- /dev/null +++ b/core/user-service/src/main/java/ru/yandex/practicum/mapper/UserMapper.java @@ -0,0 +1,26 @@ +package ru.yandex.practicum.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import ru.yandex.practicum.dto.user.NewUserRequest; +import ru.yandex.practicum.dto.user.UserDto; +import ru.yandex.practicum.dto.user.UserShortDto; +import ru.yandex.practicum.model.User; + +import java.util.List; + + +@Mapper(componentModel = "spring") +public interface UserMapper { + + @Mapping(target = "id", ignore = true) + User toEntity(NewUserRequest dto); + + UserDto toDto(User user); + + UserShortDto toShortDto(User user); + + List toDtoList(List users); + + List toShortDtoList(List users); +} diff --git a/core/user-service/src/main/java/ru/yandex/practicum/model/User.java b/core/user-service/src/main/java/ru/yandex/practicum/model/User.java new file mode 100644 index 0000000..7df438b --- /dev/null +++ b/core/user-service/src/main/java/ru/yandex/practicum/model/User.java @@ -0,0 +1,25 @@ +package ru.yandex.practicum.model; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "users") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@ToString +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 255) + private String name; + + @Column(nullable = false, length = 255, unique = true) + private String email; +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/user/repository/UserRepository.java b/core/user-service/src/main/java/ru/yandex/practicum/repository/UserRepository.java similarity index 76% rename from core/main-service/src/main/java/ru/yandex/practicum/user/repository/UserRepository.java rename to core/user-service/src/main/java/ru/yandex/practicum/repository/UserRepository.java index 45a807b..038f62e 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/user/repository/UserRepository.java +++ b/core/user-service/src/main/java/ru/yandex/practicum/repository/UserRepository.java @@ -1,9 +1,10 @@ -package ru.yandex.practicum.user.repository; +package ru.yandex.practicum.repository; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import ru.yandex.practicum.user.model.User; +import org.springframework.data.repository.query.Param; +import ru.yandex.practicum.model.User; import java.util.List; diff --git a/core/user-service/src/main/java/ru/yandex/practicum/service/UserService.java b/core/user-service/src/main/java/ru/yandex/practicum/service/UserService.java new file mode 100644 index 0000000..cf0ae15 --- /dev/null +++ b/core/user-service/src/main/java/ru/yandex/practicum/service/UserService.java @@ -0,0 +1,18 @@ +package ru.yandex.practicum.service; + +import org.springframework.data.domain.Pageable; +import ru.yandex.practicum.dto.user.NewUserRequest; +import ru.yandex.practicum.dto.user.UserDto; +import ru.yandex.practicum.dto.user.UserShortDto; + +import java.util.List; + +public interface UserService { + UserDto createUser(NewUserRequest userRequest); + + List getUsers(List ids, Pageable pageable); + + void deleteUser(Long userId); + + UserShortDto getUserShortInfo(Long userId); +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/user/service/UserServiceImpl.java b/core/user-service/src/main/java/ru/yandex/practicum/service/UserServiceImpl.java similarity index 73% rename from core/main-service/src/main/java/ru/yandex/practicum/user/service/UserServiceImpl.java rename to core/user-service/src/main/java/ru/yandex/practicum/service/UserServiceImpl.java index d8984d2..8a9acc4 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/user/service/UserServiceImpl.java +++ b/core/user-service/src/main/java/ru/yandex/practicum/service/UserServiceImpl.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.user.service; +package ru.yandex.practicum.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -7,15 +7,18 @@ import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import ru.yandex.practicum.dto.user.NewUserRequest; +import ru.yandex.practicum.dto.user.UserDto; +import ru.yandex.practicum.dto.user.UserShortDto; import ru.yandex.practicum.exception.ConflictException; import ru.yandex.practicum.exception.NotFoundException; -import ru.yandex.practicum.user.dto.NewUserRequest; -import ru.yandex.practicum.user.dto.UserDto; -import ru.yandex.practicum.user.mapper.UserMapper; -import ru.yandex.practicum.user.model.User; -import ru.yandex.practicum.user.repository.UserRepository; +import ru.yandex.practicum.mapper.UserMapper; +import ru.yandex.practicum.model.User; +import ru.yandex.practicum.repository.UserRepository; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static org.springframework.data.domain.Sort.Direction.ASC; @@ -45,31 +48,31 @@ public List getUsers(List ids, Pageable pageable) { Pageable sortedPageable = PageRequest.of( pageable.getPageNumber(), pageable.getPageSize(), - Sort.by(ASC, "id") + Sort.by(Sort.Direction.ASC, "id") ); + List users = (ids == null || ids.isEmpty()) ? userRepository.findAllList(sortedPageable) : userRepository.findByIdIn(ids, sortedPageable); - return users.stream() - .map(userMapper::toDto) - .toList(); + + return userMapper.toDtoList(users); } @Override @Transactional public void deleteUser(Long userId) { - getUserByIdOrThrow(userId); - log.info("Удален пользователь с id {}", userId); + getUserEntityById(userId); // Проверка существования userRepository.deleteById(userId); + log.info("Удален пользователь с id {}", userId); } @Override - public User getUserById(Long userId) { - return getUserByIdOrThrow(userId); + public UserShortDto getUserShortInfo(Long userId) { + User user = getUserEntityById(userId); + return userMapper.toShortDto(user); } - @Override - public User getUserByIdOrThrow(Long userId) { + private User getUserEntityById(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new NotFoundException("Пользователь с id=" + userId + " не найден")); } diff --git a/core/user-service/src/main/resources/application.yaml b/core/user-service/src/main/resources/application.yaml new file mode 100644 index 0000000..35a08ad --- /dev/null +++ b/core/user-service/src/main/resources/application.yaml @@ -0,0 +1,26 @@ +spring: + application: + name: user-service + config: + import: "configserver" + cloud: + config: + fail-fast: true + discovery: + enabled: true + serviceId: config-server + retry: + useRandomPolicy: true + max-interval: 6000 + +server: + port: 0 + +eureka: + instance: + instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}} + client: + healthcheck: + enabled: true + serviceUrl: + defaultZone: http://localhost:8761/eureka \ No newline at end of file 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..6f23d25 --- /dev/null +++ b/core/user-service/src/main/resources/schema.sql @@ -0,0 +1,11 @@ +DROP TABLE IF EXISTS users CASCADE; + +--Создание таблицы пользователей +CREATE TABLE IF NOT EXISTS users ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE +); + +--Индекс для поиска пользователя по email +CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); \ No newline at end of file From e5853593bc6be731effd51a293122fb56034ef3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Fri, 6 Feb 2026 19:21:40 +0300 Subject: [PATCH 02/10] feat: extract event, category, compilation to event-service --- core/comment-service/pom.xml | 5 + .../controller/AdminCommentController.java | 3 +- .../controller/PrivateCommentController.java | 4 +- .../practicum/handler}/ErrorHandler.java | 30 +- .../practicum/mapper/CommentMapper.java | 5 +- .../practicum/service/CommentService.java | 5 +- .../practicum/service/CommentServiceImpl.java | 2 +- .../src/main/resources/application.yaml | 2 +- core/{main-service => event-service}/pom.xml | 89 +- .../ru/yandex/practicum/EventServiceApp.java} | 8 +- .../controller/AdminCategoryController.java | 13 +- .../AdminCompilationController.java | 8 +- .../controller/AdminEventController.java | 8 +- .../controller/InternalEventController.java | 39 + .../controller/PrivateEventController.java | 21 +- .../controller/PublicCategoryController.java | 6 +- .../PublicCompilationController.java | 6 +- .../controller/PublicEventController.java | 8 +- .../practicum/handler/ErrorHandler.java | 149 + .../practicum}/mapper/CategoryMapper.java | 11 +- .../practicum/mapper/CompilationMapper.java | 20 + .../yandex/practicum}/mapper/EventMapper.java | 44 +- .../practicum}/mapper/LocationMapper.java | 6 +- .../ru/yandex/practicum}/model/Category.java | 13 +- .../yandex/practicum}/model/Compilation.java | 16 +- .../ru/yandex/practicum}/model/Event.java | 33 +- .../practicum}/model/EventLocation.java | 8 +- .../repository/CategoryRepository.java | 8 +- .../repository/CompilationRepository.java | 24 + .../repository/EventRepository.java | 18 +- .../repository/SearchEventSpecifications.java | 14 +- .../practicum/service/CategoryService.java | 19 + .../service/CategoryServiceImpl.java | 48 +- .../service/CompilationService.java | 6 +- .../service/CompilationServiceImpl.java | 19 +- .../practicum}/service/EventService.java | 5 +- .../practicum}/service/EventServiceImpl.java | 174 +- .../src/main/resources/application.yaml | 26 + .../src/main/resources/schema.sql | 52 + .../practicum/client/RequestClient.java | 20 +- .../client/RequestClientFallback.java | 25 + .../practicum/dto/category/CategoryDto.java | 1 + .../dto/category/NewCategoryDto.java | 1 + .../dto/category/UpdateCategoryDto.java | 12 + .../dto/comment/CommentAdminDto.java | 1 + .../practicum/dto/comment/CommentDto.java | 1 + .../practicum/dto/comment/NewCommentDto.java | 1 + .../dto/comment/UpdateCommentDto.java | 1 + .../dto/compilation/CompilationDto.java | 6 +- .../dto/compilation/NewCompilationDto.java | 1 + .../compilation/UpdateCompilationRequest.java | 1 + .../practicum/dto/event/EventFullDto.java | 50 +- .../practicum/dto/event/EventShortDto.java | 35 +- .../practicum/dto/event/EventStatistics.java | 4 +- .../EventWithCountConfirmedRequests.java | 2 + .../practicum/dto/event/LocationDto.java | 1 + .../practicum/dto/event/NewEventDto.java | 4 +- .../dto/event/SearchEventAdminRequest.java | 6 +- .../dto/event/SearchEventPublicRequest.java | 6 +- .../dto/event/UpdateEventAdminRequest.java | 6 +- .../dto/event/UpdateEventUserRequest.java | 6 +- .../EventRequestStatusUpdateRequest.java | 2 + .../EventRequestStatusUpdateResult.java | 2 + .../dto/request/ParticipationRequestDto.java | 8 +- .../practicum/dto/user/NewUserRequest.java | 1 + .../ru/yandex/practicum/dto/user/UserDto.java | 3 + .../practicum/dto/user/UserShortDto.java | 2 + .../practicum/exception/ErrorResponse.java | 5 + core/main-service/Dockerfile | 5 - .../category/service/CategoryService.java | 22 - .../compilation/mapper/CompilationMapper.java | 24 - .../repository/CompilationRepository.java | 37 - .../src/main/resources/application.yaml | 21 - .../src/main/resources/schema.sql | 59 - core/pom.xml | 2 +- .../controller/InternalRequestController.java | 15 +- .../PrivateEventRequestController.java | 42 + .../practicum/handler/ErrorHandler.java | 149 + .../practicum/mapper/RequestMapper.java | 3 - .../repository/RequestRepository.java | 7 + .../practicum/service/RequestService.java | 8 +- .../practicum/service/RequestServiceImpl.java | 69 +- .../src/main/resources/application.yaml | 5 +- .../src/main/resources/schema.sql | 6 +- .../practicum/handler/ErrorHandler.java | 149 + .../yandex/practicum/mapper/UserMapper.java | 4 +- .../practicum/repository/UserRepository.java | 1 - .../yandex/practicum/service/UserService.java | 4 +- .../practicum/service/UserServiceImpl.java | 11 +- .../src/main/resources/application.yaml | 2 +- docker-compose.yml | 2 +- .../src/main/resources/application.yaml | 5 +- .../comment-service/comment-service.yaml | 26 + .../config/event-service/event-service.yaml | 26 + .../config/gateway-server/gateway-server.yaml | 26 +- .../request-server/request-service.yaml | 26 + .../user-service.yaml} | 0 postman/ewm-main-service.json | 14671 ++++++++++++++++ postman/ewm-stat-service.json | 1072 ++ postman/feature.json | 829 - .../practicum/CreateEndpointHitDto.java | 1 + .../ru/yandex/practicum/StatsRequest.java | 4 + .../ru/yandex/practicum/ViewStatsDto.java | 3 + .../practicum/exception/ErrorHandler.java | 6 +- .../practicum/exception/ErrorResponse.java | 5 + .../yandex/practicum/model/EndpointHit.java | 4 + .../practicum/repository/StatsRepository.java | 13 +- .../src/main/resources/schema.sql | 2 + 108 files changed, 17148 insertions(+), 1407 deletions(-) rename core/{main-service/src/main/java/ru/yandex/practicum/exception => comment-service/src/main/java/ru/yandex/practicum/handler}/ErrorHandler.java (81%) rename core/{main-service => event-service}/pom.xml (60%) rename core/{main-service/src/main/java/ru/yandex/practicum/MainApplication.java => event-service/src/main/java/ru/yandex/practicum/EventServiceApp.java} (60%) rename core/{main-service/src/main/java/ru/yandex/practicum/category => event-service/src/main/java/ru/yandex/practicum}/controller/AdminCategoryController.java (74%) rename core/{main-service/src/main/java/ru/yandex/practicum/compilation => event-service/src/main/java/ru/yandex/practicum}/controller/AdminCompilationController.java (82%) rename core/{main-service/src/main/java/ru/yandex/practicum/event => event-service/src/main/java/ru/yandex/practicum}/controller/AdminEventController.java (83%) create mode 100644 core/event-service/src/main/java/ru/yandex/practicum/controller/InternalEventController.java rename core/{main-service/src/main/java/ru/yandex/practicum/event => event-service/src/main/java/ru/yandex/practicum}/controller/PrivateEventController.java (81%) rename core/{main-service/src/main/java/ru/yandex/practicum/category => event-service/src/main/java/ru/yandex/practicum}/controller/PublicCategoryController.java (88%) rename core/{main-service/src/main/java/ru/yandex/practicum/compilation => event-service/src/main/java/ru/yandex/practicum}/controller/PublicCompilationController.java (88%) rename core/{main-service/src/main/java/ru/yandex/practicum/event => event-service/src/main/java/ru/yandex/practicum}/controller/PublicEventController.java (85%) create mode 100644 core/event-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java rename core/{main-service/src/main/java/ru/yandex/practicum/category => event-service/src/main/java/ru/yandex/practicum}/mapper/CategoryMapper.java (50%) create mode 100644 core/event-service/src/main/java/ru/yandex/practicum/mapper/CompilationMapper.java rename core/{main-service/src/main/java/ru/yandex/practicum/event => event-service/src/main/java/ru/yandex/practicum}/mapper/EventMapper.java (59%) rename core/{main-service/src/main/java/ru/yandex/practicum/event => event-service/src/main/java/ru/yandex/practicum}/mapper/LocationMapper.java (57%) rename core/{main-service/src/main/java/ru/yandex/practicum/category => event-service/src/main/java/ru/yandex/practicum}/model/Category.java (57%) rename core/{main-service/src/main/java/ru/yandex/practicum/compilation => event-service/src/main/java/ru/yandex/practicum}/model/Compilation.java (66%) rename core/{main-service/src/main/java/ru/yandex/practicum/event => event-service/src/main/java/ru/yandex/practicum}/model/Event.java (76%) rename core/{main-service/src/main/java/ru/yandex/practicum/event => event-service/src/main/java/ru/yandex/practicum}/model/EventLocation.java (76%) rename core/{main-service/src/main/java/ru/yandex/practicum/category => event-service/src/main/java/ru/yandex/practicum}/repository/CategoryRepository.java (66%) create mode 100644 core/event-service/src/main/java/ru/yandex/practicum/repository/CompilationRepository.java rename core/{main-service/src/main/java/ru/yandex/practicum/event => event-service/src/main/java/ru/yandex/practicum}/repository/EventRepository.java (64%) rename core/{main-service/src/main/java/ru/yandex/practicum/event => event-service/src/main/java/ru/yandex/practicum}/repository/SearchEventSpecifications.java (91%) create mode 100644 core/event-service/src/main/java/ru/yandex/practicum/service/CategoryService.java rename core/{main-service/src/main/java/ru/yandex/practicum/category => event-service/src/main/java/ru/yandex/practicum}/service/CategoryServiceImpl.java (61%) rename core/{main-service/src/main/java/ru/yandex/practicum/compilation => event-service/src/main/java/ru/yandex/practicum}/service/CompilationService.java (64%) rename core/{main-service/src/main/java/ru/yandex/practicum/compilation => event-service/src/main/java/ru/yandex/practicum}/service/CompilationServiceImpl.java (88%) rename core/{main-service/src/main/java/ru/yandex/practicum/event => event-service/src/main/java/ru/yandex/practicum}/service/EventService.java (87%) rename core/{main-service/src/main/java/ru/yandex/practicum/event => event-service/src/main/java/ru/yandex/practicum}/service/EventServiceImpl.java (68%) create mode 100644 core/event-service/src/main/resources/application.yaml create mode 100644 core/event-service/src/main/resources/schema.sql create mode 100644 core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/UpdateCategoryDto.java rename core/{main-service => interaction-api}/src/main/java/ru/yandex/practicum/exception/ErrorResponse.java (97%) delete mode 100644 core/main-service/Dockerfile delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/category/service/CategoryService.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/compilation/mapper/CompilationMapper.java delete mode 100644 core/main-service/src/main/java/ru/yandex/practicum/compilation/repository/CompilationRepository.java delete mode 100644 core/main-service/src/main/resources/application.yaml delete mode 100644 core/main-service/src/main/resources/schema.sql create mode 100644 core/request-service/src/main/java/ru/yandex/practicum/controller/PrivateEventRequestController.java create mode 100644 core/request-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java create mode 100644 core/user-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java create mode 100644 infra/config-server/src/main/resources/config/comment-service/comment-service.yaml create mode 100644 infra/config-server/src/main/resources/config/event-service/event-service.yaml create mode 100644 infra/config-server/src/main/resources/config/request-server/request-service.yaml rename infra/config-server/src/main/resources/config/{main-service/main-service.yaml => user-service/user-service.yaml} (100%) create mode 100644 postman/ewm-main-service.json create mode 100644 postman/ewm-stat-service.json delete mode 100644 postman/feature.json diff --git a/core/comment-service/pom.xml b/core/comment-service/pom.xml index 2c8f54e..d017437 100644 --- a/core/comment-service/pom.xml +++ b/core/comment-service/pom.xml @@ -87,6 +87,11 @@ spring-cloud-starter-openfeign + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + org.springframework.retry spring-retry diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/controller/AdminCommentController.java b/core/comment-service/src/main/java/ru/yandex/practicum/controller/AdminCommentController.java index e5a6711..ad6995c 100644 --- a/core/comment-service/src/main/java/ru/yandex/practicum/controller/AdminCommentController.java +++ b/core/comment-service/src/main/java/ru/yandex/practicum/controller/AdminCommentController.java @@ -8,8 +8,7 @@ import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.dto.comment.CommentAdminDto; -import ru.yandex.practicum.dto.comment.CommentDto; +import ru.yandex.practicum.dto.comment.*; import ru.yandex.practicum.enums.CommentStatus; import ru.yandex.practicum.service.CommentService; diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/controller/PrivateCommentController.java b/core/comment-service/src/main/java/ru/yandex/practicum/controller/PrivateCommentController.java index 6a25ede..80f4dbe 100644 --- a/core/comment-service/src/main/java/ru/yandex/practicum/controller/PrivateCommentController.java +++ b/core/comment-service/src/main/java/ru/yandex/practicum/controller/PrivateCommentController.java @@ -7,9 +7,7 @@ import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.dto.comment.CommentDto; -import ru.yandex.practicum.dto.comment.NewCommentDto; -import ru.yandex.practicum.dto.comment.UpdateCommentDto; +import ru.yandex.practicum.dto.comment.*; import ru.yandex.practicum.service.CommentService; import java.util.List; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/exception/ErrorHandler.java b/core/comment-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java similarity index 81% rename from core/main-service/src/main/java/ru/yandex/practicum/exception/ErrorHandler.java rename to core/comment-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java index 3dfd3d2..19f22c2 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/exception/ErrorHandler.java +++ b/core/comment-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.exception; +package ru.yandex.practicum.handler; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.ConstraintViolationException; @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.HandlerMethodValidationException; import org.springframework.web.servlet.NoHandlerFoundException; +import ru.yandex.practicum.exception.*; import java.util.stream.Collectors; @@ -53,7 +54,8 @@ public ErrorResponse handleValidationException(ValidationException exception, Ht @ExceptionHandler(ConstraintViolationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse handleConstraintViolation(ConstraintViolationException ex, HttpServletRequest request) { + public ErrorResponse handleConstraintViolation( + ConstraintViolationException ex, HttpServletRequest request) { String violations = ex.getConstraintViolations().stream() .map(violation -> violation.getPropertyPath() + ": " + violation.getMessage()) .collect(Collectors.joining("; ")); @@ -64,7 +66,8 @@ public ErrorResponse handleConstraintViolation(ConstraintViolationException ex, @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpServletRequest request) { + public ErrorResponse handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, HttpServletRequest request) { String validationErrors = ex.getBindingResult().getFieldErrors().stream() .map(error -> error.getField() + ": " + error.getDefaultMessage()) .collect(Collectors.joining("; ")); @@ -76,7 +79,8 @@ public ErrorResponse handleMethodArgumentNotValid(MethodArgumentNotValidExceptio @ExceptionHandler(HandlerMethodValidationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse handleHandlerMethodValidation(HandlerMethodValidationException ex, HttpServletRequest request) { + public ErrorResponse handleHandlerMethodValidation( + HandlerMethodValidationException ex, HttpServletRequest request) { String validationErrors = ex.getAllErrors().stream() .map(MessageSourceResolvable::getDefaultMessage) .collect(Collectors.joining("; ")); @@ -88,25 +92,29 @@ public ErrorResponse handleHandlerMethodValidation(HandlerMethodValidationExcept @ExceptionHandler(ServletRequestBindingException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse handleServletRequestBindingException(ServletRequestBindingException ex, HttpServletRequest request) { + public ErrorResponse handleServletRequestBindingException( + ServletRequestBindingException ex, HttpServletRequest request) { return buildResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), null, request); } @ExceptionHandler(HttpRequestMethodNotSupportedException.class) @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) - public ErrorResponse handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpServletRequest request) { + public ErrorResponse handleHttpRequestMethodNotSupported( + HttpRequestMethodNotSupportedException ex, HttpServletRequest request) { return buildResponse(HttpStatus.METHOD_NOT_ALLOWED, ex.getMessage(), null, request); } @ExceptionHandler(HttpMediaTypeNotSupportedException.class) @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) - public ErrorResponse handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpServletRequest request) { + public ErrorResponse handleHttpMediaTypeNotSupported( + HttpMediaTypeNotSupportedException ex, HttpServletRequest request) { return buildResponse(HttpStatus.UNSUPPORTED_MEDIA_TYPE, ex.getMessage(), null, request); } @ExceptionHandler(NoHandlerFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) - public ErrorResponse handleNoHandlerFoundException(NoHandlerFoundException ex, HttpServletRequest request) { + public ErrorResponse handleNoHandlerFoundException( + NoHandlerFoundException ex, HttpServletRequest request) { return buildResponse(HttpStatus.NOT_FOUND, ex.getMessage(), null, request); } @@ -116,11 +124,13 @@ public ErrorResponse handleUnexpected(Exception exception, HttpServletRequest re return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception, request); } - private ErrorResponse buildResponse(HttpStatus httpStatus, Exception exception, HttpServletRequest request) { + private ErrorResponse buildResponse( + HttpStatus httpStatus,Exception exception, HttpServletRequest request) { return buildResponse(httpStatus, exception.getMessage(), exception, request); } - private ErrorResponse buildResponse(HttpStatus httpStatus, String message, Exception exception, HttpServletRequest request) { + private ErrorResponse buildResponse( + HttpStatus httpStatus, String message, Exception exception, HttpServletRequest request) { String path = request.getRequestURI(); String httpMethod = request.getMethod(); int statusCode = httpStatus.value(); diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/mapper/CommentMapper.java b/core/comment-service/src/main/java/ru/yandex/practicum/mapper/CommentMapper.java index cc5ae28..2c8f779 100644 --- a/core/comment-service/src/main/java/ru/yandex/practicum/mapper/CommentMapper.java +++ b/core/comment-service/src/main/java/ru/yandex/practicum/mapper/CommentMapper.java @@ -1,10 +1,7 @@ package ru.yandex.practicum.mapper; import org.mapstruct.*; -import ru.yandex.practicum.dto.comment.CommentAdminDto; -import ru.yandex.practicum.dto.comment.CommentDto; -import ru.yandex.practicum.dto.comment.NewCommentDto; -import ru.yandex.practicum.dto.comment.UpdateCommentDto; +import ru.yandex.practicum.dto.comment.*; import ru.yandex.practicum.model.Comment; @Mapper(componentModel = "spring") diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentService.java b/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentService.java index d2a2c9a..706f90c 100644 --- a/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentService.java +++ b/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentService.java @@ -1,9 +1,6 @@ package ru.yandex.practicum.service; -import ru.yandex.practicum.dto.comment.CommentAdminDto; -import ru.yandex.practicum.dto.comment.CommentDto; -import ru.yandex.practicum.dto.comment.NewCommentDto; -import ru.yandex.practicum.dto.comment.UpdateCommentDto; +import ru.yandex.practicum.dto.comment.*; import ru.yandex.practicum.enums.CommentStatus; import java.time.LocalDateTime; diff --git a/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentServiceImpl.java b/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentServiceImpl.java index f0d5f37..1e94992 100644 --- a/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentServiceImpl.java +++ b/core/comment-service/src/main/java/ru/yandex/practicum/service/CommentServiceImpl.java @@ -38,7 +38,7 @@ public CommentDto createComment(Long userId, Long eventId, NewCommentDto newComm checkUserExists(userId); EventFullDto event = eventClient.getEvent(eventId); - if (event.state() != EventState.PUBLISHED) { + if (event.getState() != EventState.PUBLISHED) { throw new ConflictException("Нельзя комментировать неопубликованное событие"); } diff --git a/core/comment-service/src/main/resources/application.yaml b/core/comment-service/src/main/resources/application.yaml index 3e20474..cfae722 100644 --- a/core/comment-service/src/main/resources/application.yaml +++ b/core/comment-service/src/main/resources/application.yaml @@ -2,7 +2,7 @@ spring: application: name: comment-service config: - import: "configserver" + import: "configserver:" cloud: loadbalancer: retry: diff --git a/core/main-service/pom.xml b/core/event-service/pom.xml similarity index 60% rename from core/main-service/pom.xml rename to core/event-service/pom.xml index 1452bad..fc467b1 100644 --- a/core/main-service/pom.xml +++ b/core/event-service/pom.xml @@ -3,14 +3,13 @@ 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.yandex.practicum core 0.0.1-SNAPSHOT - main-service + event-service 21 @@ -24,6 +23,11 @@ spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-validation + + org.springframework.boot spring-boot-starter-actuator @@ -31,24 +35,41 @@ org.springframework.boot - spring-boot-starter-validation + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-aop + + + + ru.yandex.practicum + interaction-api + ${project.version} ru.yandex.practicum stats-client - 0.0.1-SNAPSHOT + ${project.version} ru.yandex.practicum stats-dto - 0.0.1-SNAPSHOT + ${project.version} - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client + org.projectlombok + lombok + provided + + + + org.postgresql + postgresql @@ -58,29 +79,28 @@ org.springframework.cloud - spring-cloud-starter-openfeign + spring-cloud-starter-netflix-eureka-client - org.postgresql - postgresql + org.springframework.cloud + spring-cloud-starter-openfeign - com.h2database - h2 - runtime + org.springframework.cloud + spring-cloud-starter-loadbalancer - org.projectlombok - lombok - provided + org.springframework.retry + spring-retry + 2.0.5 - org.springframework.boot - spring-boot-starter-data-jpa + io.github.openfeign + feign-okhttp @@ -88,6 +108,12 @@ mapstruct ${mapstruct.version} + + + com.h2database + h2 + runtime + @@ -95,12 +121,37 @@ org.springframework.boot spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + org.apache.maven.plugins maven-compiler-plugin + 3.11.0 + + 21 + 21 + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + - \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/yandex/practicum/MainApplication.java b/core/event-service/src/main/java/ru/yandex/practicum/EventServiceApp.java similarity index 60% rename from core/main-service/src/main/java/ru/yandex/practicum/MainApplication.java rename to core/event-service/src/main/java/ru/yandex/practicum/EventServiceApp.java index 17b4ca6..c88c2ba 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/MainApplication.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/EventServiceApp.java @@ -2,14 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.cloud.openfeign.EnableFeignClients; -@ConfigurationPropertiesScan -@EnableFeignClients @SpringBootApplication -public class MainApplication { +@EnableFeignClients +public class EventServiceApp { public static void main(String[] args) { - SpringApplication.run(MainApplication.class, args); + SpringApplication.run(EventServiceApp.class, args); } } diff --git a/core/main-service/src/main/java/ru/yandex/practicum/category/controller/AdminCategoryController.java b/core/event-service/src/main/java/ru/yandex/practicum/controller/AdminCategoryController.java similarity index 74% rename from core/main-service/src/main/java/ru/yandex/practicum/category/controller/AdminCategoryController.java rename to core/event-service/src/main/java/ru/yandex/practicum/controller/AdminCategoryController.java index d09cc16..99082b1 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/category/controller/AdminCategoryController.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/controller/AdminCategoryController.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.category.controller; +package ru.yandex.practicum.controller; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; @@ -7,9 +7,8 @@ import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.category.dto.CategoryDto; -import ru.yandex.practicum.category.dto.NewCategoryDto; -import ru.yandex.practicum.category.service.CategoryService; +import ru.yandex.practicum.dto.category.*; +import ru.yandex.practicum.service.CategoryService; @Slf4j @Validated @@ -35,8 +34,8 @@ public void deleteCategory(@PathVariable @Positive Long categoryId) { @PatchMapping("/{categoryId}") public CategoryDto updateCategory(@PathVariable @Positive Long categoryId, - @RequestBody @Valid NewCategoryDto newCategoryDto) { - log.debug("Controller: updateCategory categoryId={}, data={}", categoryId, newCategoryDto); - return categoryService.updateCategory(categoryId, newCategoryDto); + @RequestBody @Valid UpdateCategoryDto updateCategoryDto) { + log.debug("Controller: updateCategory categoryId={}, data={}", categoryId, updateCategoryDto); + return categoryService.updateCategory(categoryId, updateCategoryDto); } } diff --git a/core/main-service/src/main/java/ru/yandex/practicum/compilation/controller/AdminCompilationController.java b/core/event-service/src/main/java/ru/yandex/practicum/controller/AdminCompilationController.java similarity index 82% rename from core/main-service/src/main/java/ru/yandex/practicum/compilation/controller/AdminCompilationController.java rename to core/event-service/src/main/java/ru/yandex/practicum/controller/AdminCompilationController.java index 4a50145..c2c8e84 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/compilation/controller/AdminCompilationController.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/controller/AdminCompilationController.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.compilation.controller; +package ru.yandex.practicum.controller; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; @@ -7,10 +7,8 @@ import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.compilation.dto.CompilationDto; -import ru.yandex.practicum.compilation.dto.NewCompilationDto; -import ru.yandex.practicum.compilation.dto.UpdateCompilationRequest; -import ru.yandex.practicum.compilation.service.CompilationService; +import ru.yandex.practicum.dto.compilation.*; +import ru.yandex.practicum.service.CompilationService; @Slf4j @Validated diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/controller/AdminEventController.java b/core/event-service/src/main/java/ru/yandex/practicum/controller/AdminEventController.java similarity index 83% rename from core/main-service/src/main/java/ru/yandex/practicum/event/controller/AdminEventController.java rename to core/event-service/src/main/java/ru/yandex/practicum/controller/AdminEventController.java index 92f3945..2078aa9 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/controller/AdminEventController.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/controller/AdminEventController.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.controller; +package ru.yandex.practicum.controller; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; @@ -7,10 +7,8 @@ import org.springframework.data.domain.PageRequest; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.event.dto.EventFullDto; -import ru.yandex.practicum.event.dto.SearchEventAdminRequest; -import ru.yandex.practicum.event.dto.UpdateEventAdminRequest; -import ru.yandex.practicum.event.service.EventService; +import ru.yandex.practicum.dto.event.*; +import ru.yandex.practicum.service.EventService; import java.util.List; diff --git a/core/event-service/src/main/java/ru/yandex/practicum/controller/InternalEventController.java b/core/event-service/src/main/java/ru/yandex/practicum/controller/InternalEventController.java new file mode 100644 index 0000000..00ff0a3 --- /dev/null +++ b/core/event-service/src/main/java/ru/yandex/practicum/controller/InternalEventController.java @@ -0,0 +1,39 @@ +package ru.yandex.practicum.controller; + +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.yandex.practicum.client.UserClient; +import ru.yandex.practicum.dto.event.EventFullDto; +import ru.yandex.practicum.exception.NotFoundException; +import ru.yandex.practicum.mapper.EventMapper; +import ru.yandex.practicum.model.Event; +import ru.yandex.practicum.repository.EventRepository; + +@Slf4j +@RestController +@RequestMapping("/internal/events") +@RequiredArgsConstructor +public class InternalEventController { + + private final EventRepository eventRepository; + private final EventMapper eventMapper; + private final UserClient userClient; + + @GetMapping("/{eventId}") + public EventFullDto getEvent(@PathVariable @Positive Long eventId) { + log.debug("Controller: getEvent eventId={}", eventId); + + Event event = eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Event not found with id: " + eventId)); + + EventFullDto dto = eventMapper.toFullDto(event); + dto.setInitiator(userClient.getUser(event.getInitiatorId())); + + return dto; + } +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/controller/PrivateEventController.java b/core/event-service/src/main/java/ru/yandex/practicum/controller/PrivateEventController.java similarity index 81% rename from core/main-service/src/main/java/ru/yandex/practicum/event/controller/PrivateEventController.java rename to core/event-service/src/main/java/ru/yandex/practicum/controller/PrivateEventController.java index f089a95..f6331dc 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/controller/PrivateEventController.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/controller/PrivateEventController.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.controller; +package ru.yandex.practicum.controller; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; @@ -11,15 +11,10 @@ import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.event.dto.EventFullDto; -import ru.yandex.practicum.event.dto.EventShortDto; -import ru.yandex.practicum.event.dto.NewEventDto; -import ru.yandex.practicum.event.dto.UpdateEventUserRequest; -import ru.yandex.practicum.event.service.EventService; -import ru.yandex.practicum.request.dto.EventRequestStatusUpdateRequest; -import ru.yandex.practicum.request.dto.EventRequestStatusUpdateResult; -import ru.yandex.practicum.request.dto.ParticipationRequestDto; -import ru.yandex.practicum.request.service.RequestService; +import ru.yandex.practicum.client.RequestClient; +import ru.yandex.practicum.dto.event.*; +import ru.yandex.practicum.dto.request.*; +import ru.yandex.practicum.service.EventService; import java.util.List; @@ -30,7 +25,7 @@ @RequiredArgsConstructor public class PrivateEventController { private final EventService eventService; - private final RequestService requestService; + private final RequestClient requestClient; @GetMapping public List getEvents(@PathVariable("userId") @Positive Long userId, @@ -74,7 +69,7 @@ public List getRequestsByEvent(@PathVariable("userId") @PathVariable("eventId") @Positive Long eventId ) { log.debug("Controller: getRequestsByEvent userId={}, eventId={}", userId, eventId); - return requestService.getRequestsByEvent(userId, eventId); + return requestClient.getRequestsByEvent(userId, eventId); } @PatchMapping("/{eventId}/requests") @@ -83,6 +78,6 @@ public EventRequestStatusUpdateResult updateRequestStatus(@PathVariable("userId" @RequestBody EventRequestStatusUpdateRequest request ) { log.debug("Controller: updateRequestStatus userId={}, eventId={}, data={}", userId, eventId, request); - return requestService.updateRequestStatus(userId, eventId, request); + return requestClient.updateRequestStatus(userId, eventId, request); } } diff --git a/core/main-service/src/main/java/ru/yandex/practicum/category/controller/PublicCategoryController.java b/core/event-service/src/main/java/ru/yandex/practicum/controller/PublicCategoryController.java similarity index 88% rename from core/main-service/src/main/java/ru/yandex/practicum/category/controller/PublicCategoryController.java rename to core/event-service/src/main/java/ru/yandex/practicum/controller/PublicCategoryController.java index 39a6fc9..9422bb5 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/category/controller/PublicCategoryController.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/controller/PublicCategoryController.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.category.controller; +package ru.yandex.practicum.controller; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.PositiveOrZero; @@ -8,8 +8,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.category.dto.CategoryDto; -import ru.yandex.practicum.category.service.CategoryService; +import ru.yandex.practicum.dto.category.CategoryDto; +import ru.yandex.practicum.service.CategoryService; import java.util.List; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/compilation/controller/PublicCompilationController.java b/core/event-service/src/main/java/ru/yandex/practicum/controller/PublicCompilationController.java similarity index 88% rename from core/main-service/src/main/java/ru/yandex/practicum/compilation/controller/PublicCompilationController.java rename to core/event-service/src/main/java/ru/yandex/practicum/controller/PublicCompilationController.java index c902fae..73b9bcc 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/compilation/controller/PublicCompilationController.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/controller/PublicCompilationController.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.compilation.controller; +package ru.yandex.practicum.controller; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.PositiveOrZero; @@ -8,8 +8,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.compilation.dto.CompilationDto; -import ru.yandex.practicum.compilation.service.CompilationService; +import ru.yandex.practicum.dto.compilation.CompilationDto; +import ru.yandex.practicum.service.CompilationService; import java.util.List; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/controller/PublicEventController.java b/core/event-service/src/main/java/ru/yandex/practicum/controller/PublicEventController.java similarity index 85% rename from core/main-service/src/main/java/ru/yandex/practicum/event/controller/PublicEventController.java rename to core/event-service/src/main/java/ru/yandex/practicum/controller/PublicEventController.java index dcbec45..cfe01a4 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/controller/PublicEventController.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/controller/PublicEventController.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.controller; +package ru.yandex.practicum.controller; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; @@ -8,10 +8,8 @@ import org.springframework.data.domain.PageRequest; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.event.dto.EventFullDto; -import ru.yandex.practicum.event.dto.EventShortDto; -import ru.yandex.practicum.event.dto.SearchEventPublicRequest; -import ru.yandex.practicum.event.service.EventService; +import ru.yandex.practicum.dto.event.*; +import ru.yandex.practicum.service.EventService; import java.util.List; diff --git a/core/event-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java b/core/event-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java new file mode 100644 index 0000000..6c1ca58 --- /dev/null +++ b/core/event-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java @@ -0,0 +1,149 @@ +package ru.yandex.practicum.handler; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.MessageSourceResolvable; +import org.springframework.http.HttpStatus; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.HandlerMethodValidationException; +import org.springframework.web.servlet.NoHandlerFoundException; +import ru.yandex.practicum.exception.*; + +import java.util.stream.Collectors; + +@Slf4j +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNotFoundException(NotFoundException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.NOT_FOUND, exception, request); + } + + @ExceptionHandler(BadRequestException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleBadRequestException(BadRequestException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.BAD_REQUEST, exception, request); + } + + @ExceptionHandler(ForbiddenException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public ErrorResponse handleForbiddenException(ForbiddenException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.FORBIDDEN, exception, request); + } + + @ExceptionHandler(ConflictException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleConflictException(ConflictException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.CONFLICT, exception, request); + } + + @ExceptionHandler(ValidationException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleValidationException(ValidationException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.CONFLICT, exception, request); + } + + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleConstraintViolation( + ConstraintViolationException ex, HttpServletRequest request) { + String violations = ex.getConstraintViolations().stream() + .map(violation -> violation.getPropertyPath() + ": " + violation.getMessage()) + .collect(Collectors.joining("; ")); + + String message = violations.isEmpty() ? "Constraint violation" : violations; + return buildResponse(HttpStatus.BAD_REQUEST, message, null, request); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, HttpServletRequest request) { + String validationErrors = ex.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining("; ")); + + String message = validationErrors.isEmpty() ? "Validation failed" : validationErrors; + + return buildResponse(HttpStatus.BAD_REQUEST, message, null, request); + } + + @ExceptionHandler(HandlerMethodValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleHandlerMethodValidation( + HandlerMethodValidationException ex, HttpServletRequest request) { + String validationErrors = ex.getAllErrors().stream() + .map(MessageSourceResolvable::getDefaultMessage) + .collect(Collectors.joining("; ")); + + String message = validationErrors.isEmpty() ? "Validation failed" : validationErrors; + + return buildResponse(HttpStatus.BAD_REQUEST, message, null, request); + } + + @ExceptionHandler(ServletRequestBindingException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleServletRequestBindingException( + ServletRequestBindingException ex, HttpServletRequest request) { + return buildResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), null, request); + } + + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + public ErrorResponse handleHttpRequestMethodNotSupported( + HttpRequestMethodNotSupportedException ex, HttpServletRequest request) { + return buildResponse(HttpStatus.METHOD_NOT_ALLOWED, ex.getMessage(), null, request); + } + + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) + public ErrorResponse handleHttpMediaTypeNotSupported( + HttpMediaTypeNotSupportedException ex, HttpServletRequest request) { + return buildResponse(HttpStatus.UNSUPPORTED_MEDIA_TYPE, ex.getMessage(), null, request); + } + + @ExceptionHandler(NoHandlerFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNoHandlerFoundException( + NoHandlerFoundException ex, HttpServletRequest request) { + return buildResponse(HttpStatus.NOT_FOUND, ex.getMessage(), null, request); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleUnexpected(Exception exception, HttpServletRequest request) { + return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception, request); + } + + private ErrorResponse buildResponse( + HttpStatus httpStatus, Exception exception, HttpServletRequest request) { + return buildResponse(httpStatus, exception.getMessage(), exception, request); + } + + private ErrorResponse buildResponse( + HttpStatus httpStatus, String message, Exception exception, HttpServletRequest request) { + String path = request.getRequestURI(); + String httpMethod = request.getMethod(); + int statusCode = httpStatus.value(); + String error = httpStatus.getReasonPhrase(); + + String logMessage = String.format("Path: %s %s, '%d' %s — %s", httpMethod, path, statusCode, error, message); + + if (statusCode >= 500) { + log.error(logMessage, exception); + } else { + log.warn(logMessage); + } + + return new ErrorResponse(path, httpMethod, statusCode, error, message); + } +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/category/mapper/CategoryMapper.java b/core/event-service/src/main/java/ru/yandex/practicum/mapper/CategoryMapper.java similarity index 50% rename from core/main-service/src/main/java/ru/yandex/practicum/category/mapper/CategoryMapper.java rename to core/event-service/src/main/java/ru/yandex/practicum/mapper/CategoryMapper.java index c62518c..9c48596 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/category/mapper/CategoryMapper.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/mapper/CategoryMapper.java @@ -1,11 +1,10 @@ -package ru.yandex.practicum.category.mapper; +package ru.yandex.practicum.mapper; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; -import ru.yandex.practicum.category.dto.CategoryDto; -import ru.yandex.practicum.category.dto.NewCategoryDto; -import ru.yandex.practicum.category.model.Category; +import ru.yandex.practicum.dto.category.*; +import ru.yandex.practicum.model.Category; @Mapper(componentModel = "spring") public interface CategoryMapper { @@ -16,7 +15,5 @@ public interface CategoryMapper { CategoryDto toDto(Category category); @Mapping(target = "id", ignore = true) - void updateCategoryFromDto(NewCategoryDto dto, @MappingTarget Category category); - - Category toEntityFromDto(CategoryDto dto); + void updateCategoryFromDto(UpdateCategoryDto updateCategoryDto, @MappingTarget Category category); } diff --git a/core/event-service/src/main/java/ru/yandex/practicum/mapper/CompilationMapper.java b/core/event-service/src/main/java/ru/yandex/practicum/mapper/CompilationMapper.java new file mode 100644 index 0000000..5781c84 --- /dev/null +++ b/core/event-service/src/main/java/ru/yandex/practicum/mapper/CompilationMapper.java @@ -0,0 +1,20 @@ +package ru.yandex.practicum.mapper; + +import org.mapstruct.*; +import ru.yandex.practicum.dto.compilation.*; +import ru.yandex.practicum.model.Compilation; + +@Mapper(componentModel = "spring") +public interface CompilationMapper { + + @Mapping(target = "id", ignore = true) + @Mapping(target = "events", ignore = true) + Compilation toEntity(NewCompilationDto newCompilationDto); + + CompilationDto toDto(Compilation compilation); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "events", ignore = true) + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void updateCompilationFromRequest(UpdateCompilationRequest request, @MappingTarget Compilation compilation); +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/mapper/EventMapper.java b/core/event-service/src/main/java/ru/yandex/practicum/mapper/EventMapper.java similarity index 59% rename from core/main-service/src/main/java/ru/yandex/practicum/event/mapper/EventMapper.java rename to core/event-service/src/main/java/ru/yandex/practicum/mapper/EventMapper.java index a450c47..18dcf68 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/mapper/EventMapper.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/mapper/EventMapper.java @@ -1,44 +1,50 @@ -package ru.yandex.practicum.event.mapper; +package ru.yandex.practicum.mapper; import org.mapstruct.*; -import ru.yandex.practicum.category.model.Category; -import ru.yandex.practicum.event.model.Event; -import ru.yandex.practicum.event.model.EventState; -import ru.yandex.practicum.user.model.User; +import ru.yandex.practicum.dto.event.*; +import ru.yandex.practicum.model.Category; +import ru.yandex.practicum.model.Event; -@Mapper(componentModel = "spring", uses = {LocationMapper.class}) +@Mapper(componentModel = "spring", uses = {LocationMapper.class, CategoryMapper.class}) public interface EventMapper { @Mapping(target = "id", ignore = true) - @Mapping(target = "publishedOn", ignore = true) - @Mapping(target = "createdOn", ignore = true) @Mapping(target = "category", source = "category") - Event fromNewEvent(NewEventDto dto, User initiator, Category category, EventState state); - - EventShortDto toEventShortDto(Event event, Integer confirmedRequests, Long views); - - @Named("toEventShortWithoutStats") + @Mapping(target = "initiatorId", source = "initiatorId") + @Mapping(target = "state", constant = "PENDING") + @Mapping(target = "createdOn", ignore = true) + @Mapping(target = "publishedOn", ignore = true) @Mapping(target = "confirmedRequests", ignore = true) @Mapping(target = "views", ignore = true) - EventShortDto toEventShortWithoutStats(Event event); + Event toEntity(NewEventDto dto, Long initiatorId, Category category); - EventFullDto toEventFullDto(Event event, Integer confirmedRequests, Long views); + @Mapping(target = "initiator", ignore = true) + @Mapping(target = "category", source = "category") + EventShortDto toShortDto(Event event); - @Mapping(target = "id", ignore = true) @Mapping(target = "initiator", ignore = true) + @Mapping(target = "category", source = "category") + EventFullDto toFullDto(Event event); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "initiatorId", ignore = true) @Mapping(target = "createdOn", ignore = true) @Mapping(target = "publishedOn", ignore = true) @Mapping(target = "state", ignore = true) - @Mapping(target = "category", source = "category") + @Mapping(target = "category", ignore = true) + @Mapping(target = "views", ignore = true) + @Mapping(target = "confirmedRequests", ignore = true) @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) void updateEventFromUserRequest(UpdateEventUserRequest request, @MappingTarget Event event, Category category); @Mapping(target = "id", ignore = true) - @Mapping(target = "initiator", ignore = true) + @Mapping(target = "category", ignore = true) + @Mapping(target = "initiatorId", ignore = true) @Mapping(target = "createdOn", ignore = true) @Mapping(target = "publishedOn", ignore = true) @Mapping(target = "state", ignore = true) - @Mapping(target = "category", source = "category") + @Mapping(target = "views", ignore = true) + @Mapping(target = "confirmedRequests", ignore = true) @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) void updateEventFromAdminRequest(UpdateEventAdminRequest request, @MappingTarget Event event, Category category); } diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/mapper/LocationMapper.java b/core/event-service/src/main/java/ru/yandex/practicum/mapper/LocationMapper.java similarity index 57% rename from core/main-service/src/main/java/ru/yandex/practicum/event/mapper/LocationMapper.java rename to core/event-service/src/main/java/ru/yandex/practicum/mapper/LocationMapper.java index d0297fd..617d8c0 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/mapper/LocationMapper.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/mapper/LocationMapper.java @@ -1,8 +1,8 @@ -package ru.yandex.practicum.event.mapper; +package ru.yandex.practicum.mapper; import org.mapstruct.Mapper; -import ru.yandex.practicum.event.dto.LocationDto; -import ru.yandex.practicum.event.model.EventLocation; +import ru.yandex.practicum.dto.event.LocationDto; +import ru.yandex.practicum.model.EventLocation; @Mapper(componentModel = "spring") public interface LocationMapper { diff --git a/core/main-service/src/main/java/ru/yandex/practicum/category/model/Category.java b/core/event-service/src/main/java/ru/yandex/practicum/model/Category.java similarity index 57% rename from core/main-service/src/main/java/ru/yandex/practicum/category/model/Category.java rename to core/event-service/src/main/java/ru/yandex/practicum/model/Category.java index 0d13352..d57a849 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/category/model/Category.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/model/Category.java @@ -1,21 +1,20 @@ -package ru.yandex.practicum.category.model; +package ru.yandex.practicum.model; import jakarta.persistence.*; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; +import lombok.*; @Entity @Table(name = "categories") @Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder @ToString -@EqualsAndHashCode(onlyExplicitlyIncluded = true) public class Category { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @EqualsAndHashCode.Include private Long id; @Column(name = "name", unique = true, nullable = false, length = 50) diff --git a/core/main-service/src/main/java/ru/yandex/practicum/compilation/model/Compilation.java b/core/event-service/src/main/java/ru/yandex/practicum/model/Compilation.java similarity index 66% rename from core/main-service/src/main/java/ru/yandex/practicum/compilation/model/Compilation.java rename to core/event-service/src/main/java/ru/yandex/practicum/model/Compilation.java index 1e50a3f..8b80ece 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/compilation/model/Compilation.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/model/Compilation.java @@ -1,10 +1,7 @@ -package ru.yandex.practicum.compilation.model; +package ru.yandex.practicum.model; import jakarta.persistence.*; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import ru.yandex.practicum.event.model.Event; +import lombok.*; import java.util.Set; @@ -12,14 +9,17 @@ @Table(name = "compilations") @Getter @Setter -@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@AllArgsConstructor +@NoArgsConstructor +@Builder +@ToString public class Compilation { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @EqualsAndHashCode.Include private Long id; - @Column(nullable = false, length = 50) + @Column(nullable = false, unique = true, length = 50) private String title; @Column(nullable = false) diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/model/Event.java b/core/event-service/src/main/java/ru/yandex/practicum/model/Event.java similarity index 76% rename from core/main-service/src/main/java/ru/yandex/practicum/event/model/Event.java rename to core/event-service/src/main/java/ru/yandex/practicum/model/Event.java index 0ab14ac..48c9eb5 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/model/Event.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/model/Event.java @@ -1,12 +1,9 @@ -package ru.yandex.practicum.event.model; +package ru.yandex.practicum.model; import jakarta.persistence.*; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import org.hibernate.annotations.CreationTimestamp; -import ru.yandex.practicum.category.model.Category; -import ru.yandex.practicum.user.model.User; +import ru.yandex.practicum.enums.EventState; import java.time.LocalDateTime; @@ -14,11 +11,14 @@ @Table(name = "events") @Getter @Setter -@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@AllArgsConstructor +@NoArgsConstructor +@Builder +@ToString public class Event { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @EqualsAndHashCode.Include private Long id; @Column(nullable = false, length = 120) @@ -34,9 +34,8 @@ public class Event { @JoinColumn(name = "category_id") private Category category; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "initiator_id") - private User initiator; + @Column(name = "initiator_id") + private Long initiatorId; @Column(name = "created_on", nullable = false) @CreationTimestamp @@ -56,18 +55,26 @@ public class Event { private EventLocation location; @Column(nullable = false) - private boolean paid; + private Boolean paid = false; @Column(name = "participant_limit", nullable = false) private Integer participantLimit; @Column(name = "request_moderation", nullable = false) - private boolean requestModeration; + private Boolean requestModeration; @Column(nullable = false, length = 20) @Enumerated(EnumType.STRING) private EventState state; + @Column(name = "confirmed_requests", nullable = false) + @Builder.Default + private Integer confirmedRequests = 0; + + @Column(name = "views", nullable = false) + @Builder.Default + private Long views = 0L; + public void pending() { this.state = EventState.PENDING; } diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/model/EventLocation.java b/core/event-service/src/main/java/ru/yandex/practicum/model/EventLocation.java similarity index 76% rename from core/main-service/src/main/java/ru/yandex/practicum/event/model/EventLocation.java rename to core/event-service/src/main/java/ru/yandex/practicum/model/EventLocation.java index 670d836..10df78b 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/model/EventLocation.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/model/EventLocation.java @@ -1,13 +1,15 @@ -package ru.yandex.practicum.event.model; +package ru.yandex.practicum.model; import jakarta.persistence.Embeddable; import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; +import lombok.*; @Embeddable @Getter @Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder public class EventLocation { @NotNull(message = "Широта не может быть null") private Float lat; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/category/repository/CategoryRepository.java b/core/event-service/src/main/java/ru/yandex/practicum/repository/CategoryRepository.java similarity index 66% rename from core/main-service/src/main/java/ru/yandex/practicum/category/repository/CategoryRepository.java rename to core/event-service/src/main/java/ru/yandex/practicum/repository/CategoryRepository.java index 6744789..059404e 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/category/repository/CategoryRepository.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/repository/CategoryRepository.java @@ -1,10 +1,10 @@ -package ru.yandex.practicum.category.repository; +package ru.yandex.practicum.repository; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import ru.yandex.practicum.category.model.Category; +import ru.yandex.practicum.model.Category; import java.util.List; import java.util.Optional; @@ -17,4 +17,8 @@ public interface CategoryRepository extends JpaRepository { @Query("SELECT c FROM Category c") List findAllList(Pageable pageable); + + @Query("SELECT CASE WHEN COUNT(c) > 0 THEN true ELSE false END" + + " FROM Category c WHERE LOWER(TRIM(c.name)) = LOWER(TRIM(:name))") + boolean existsByNameIgnoreCaseAndTrim(@Param("name") String name); } diff --git a/core/event-service/src/main/java/ru/yandex/practicum/repository/CompilationRepository.java b/core/event-service/src/main/java/ru/yandex/practicum/repository/CompilationRepository.java new file mode 100644 index 0000000..7ad701c --- /dev/null +++ b/core/event-service/src/main/java/ru/yandex/practicum/repository/CompilationRepository.java @@ -0,0 +1,24 @@ +package ru.yandex.practicum.repository; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import ru.yandex.practicum.model.Compilation; + +import java.util.List; +import java.util.Optional; + +public interface CompilationRepository extends JpaRepository { + + @Query("SELECT DISTINCT c FROM Compilation c LEFT JOIN FETCH c.events WHERE c.id = :compId") + Optional findByIdWithEvents(@Param("compId") Long compId); + + boolean existsByTitle(String title); + + @Query("SELECT c.id FROM Compilation c WHERE (:pinned IS NULL OR c.pinned = :pinned)") + List findIdsByPinned(@Param("pinned") Boolean pinned, Pageable pageable); + + @Query("SELECT DISTINCT c FROM Compilation c LEFT JOIN FETCH c.events WHERE c.id IN :ids") + List findAllByIdInWithEvents(@Param("ids") List ids); +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/repository/EventRepository.java b/core/event-service/src/main/java/ru/yandex/practicum/repository/EventRepository.java similarity index 64% rename from core/main-service/src/main/java/ru/yandex/practicum/event/repository/EventRepository.java rename to core/event-service/src/main/java/ru/yandex/practicum/repository/EventRepository.java index 7038c64..1c98c36 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/repository/EventRepository.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/repository/EventRepository.java @@ -1,11 +1,11 @@ -package ru.yandex.practicum.event.repository; +package ru.yandex.practicum.repository; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import ru.yandex.practicum.event.model.Event; +import ru.yandex.practicum.model.Event; import java.util.List; import java.util.Optional; @@ -16,20 +16,10 @@ public interface EventRepository extends JpaRepository, JpaSpecific Event findFirstByOrderByCreatedOnAsc(); - @Query(""" - SELECT e FROM Event e - LEFT JOIN FETCH e.category - LEFT JOIN FETCH e.initiator - WHERE e.id IN :eventIds""" - ) + @Query("SELECT e FROM Event e LEFT JOIN FETCH e.category WHERE e.id IN :eventIds") List findAllByEventIds(@Param("eventIds") List eventIds); - @Query(""" - SELECT e FROM Event e - LEFT JOIN FETCH e.category - LEFT JOIN FETCH e.initiator - WHERE e.id = :eventId""" - ) + @Query("SELECT e FROM Event e LEFT JOIN FETCH e.category WHERE e.id = :eventId") Optional findByIdNew(@Param("eventId") Long eventId); boolean existsByCategoryId(@Param("categoryId") Long categoryId); diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/repository/SearchEventSpecifications.java b/core/event-service/src/main/java/ru/yandex/practicum/repository/SearchEventSpecifications.java similarity index 91% rename from core/main-service/src/main/java/ru/yandex/practicum/event/repository/SearchEventSpecifications.java rename to core/event-service/src/main/java/ru/yandex/practicum/repository/SearchEventSpecifications.java index ef62273..139a2d2 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/repository/SearchEventSpecifications.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/repository/SearchEventSpecifications.java @@ -1,12 +1,12 @@ -package ru.yandex.practicum.event.repository; +package ru.yandex.practicum.repository; +import feign.Request; import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.Subquery; import org.springframework.data.jpa.domain.Specification; -import ru.yandex.practicum.event.model.Event; -import ru.yandex.practicum.event.model.EventState; -import ru.yandex.practicum.request.model.Request; -import ru.yandex.practicum.request.model.RequestStatus; +import ru.yandex.practicum.enums.EventState; +import ru.yandex.practicum.enums.RequestStatus; +import ru.yandex.practicum.model.Event; import java.time.LocalDateTime; import java.util.List; @@ -20,7 +20,7 @@ public static Specification addWhereNull() { public static Specification addWhereUsers(List userIds) { return (root, query, criteriaBuilder) -> { if (userIds == null || userIds.isEmpty()) return criteriaBuilder.conjunction(); - return root.get("initiator").get("id").in(userIds); + return root.get("initiatorId").in(userIds); }; } @@ -79,7 +79,7 @@ public static Specification addWhereAvailableSlots() { subquery.select(criteriaBuilder.count(requestRoot)); subquery.where( criteriaBuilder.and( - criteriaBuilder.equal(requestRoot.get("event"), root), + criteriaBuilder.equal(requestRoot.get("eventId"), root.get("id")), criteriaBuilder.equal(requestRoot.get("status"), RequestStatus.CONFIRMED) ) ); diff --git a/core/event-service/src/main/java/ru/yandex/practicum/service/CategoryService.java b/core/event-service/src/main/java/ru/yandex/practicum/service/CategoryService.java new file mode 100644 index 0000000..7ce6787 --- /dev/null +++ b/core/event-service/src/main/java/ru/yandex/practicum/service/CategoryService.java @@ -0,0 +1,19 @@ +package ru.yandex.practicum.service; + +import org.springframework.data.domain.Pageable; +import ru.yandex.practicum.dto.category.*; + +import java.util.List; + +public interface CategoryService { + + CategoryDto createCategory(NewCategoryDto newCategoryDto); + + void deleteCategory(Long categoryId); + + CategoryDto updateCategory(Long categoryId, UpdateCategoryDto updateCategoryDto); + + List getCategories(Pageable pageable); + + CategoryDto getCategoryById(Long categoryId); +} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/category/service/CategoryServiceImpl.java b/core/event-service/src/main/java/ru/yandex/practicum/service/CategoryServiceImpl.java similarity index 61% rename from core/main-service/src/main/java/ru/yandex/practicum/category/service/CategoryServiceImpl.java rename to core/event-service/src/main/java/ru/yandex/practicum/service/CategoryServiceImpl.java index 4aca28d..2fa5ae2 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/category/service/CategoryServiceImpl.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/service/CategoryServiceImpl.java @@ -1,21 +1,18 @@ -package ru.yandex.practicum.category.service; +package ru.yandex.practicum.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.yandex.practicum.category.dto.CategoryDto; -import ru.yandex.practicum.category.dto.NewCategoryDto; -import ru.yandex.practicum.category.mapper.CategoryMapper; -import ru.yandex.practicum.category.model.Category; -import ru.yandex.practicum.category.repository.CategoryRepository; -import ru.yandex.practicum.event.repository.EventRepository; -import ru.yandex.practicum.exception.ConflictException; -import ru.yandex.practicum.exception.NotFoundException; +import ru.yandex.practicum.dto.category.*; +import ru.yandex.practicum.exception.*; +import ru.yandex.practicum.mapper.CategoryMapper; +import ru.yandex.practicum.model.Category; +import ru.yandex.practicum.repository.CategoryRepository; +import ru.yandex.practicum.repository.EventRepository; import java.util.List; -import java.util.Optional; @Slf4j @Service @@ -29,10 +26,11 @@ public class CategoryServiceImpl implements CategoryService { @Override @Transactional public CategoryDto createCategory(NewCategoryDto newCategoryDto) { - checkCategoryNameUnique(newCategoryDto.name(), null); + if (categoryRepository.existsByNameIgnoreCaseAndTrim(newCategoryDto.name())) { + throw new ConflictException("Категория с таким названием уже существует"); + } Category category = categoryMapper.toEntity(newCategoryDto); - Category savedCategory = categoryRepository.save(category); log.info("Создана категория: {}", savedCategory); @@ -54,15 +52,17 @@ public void deleteCategory(Long categoryId) { @Override @Transactional - public CategoryDto updateCategory(Long categoryId, NewCategoryDto newCategoryDto) { + public CategoryDto updateCategory(Long categoryId, UpdateCategoryDto updateCategoryDto) { Category category = getCategoryByIdOrThrow(categoryId); - checkCategoryNameUnique(newCategoryDto.name(), categoryId); + if (categoryRepository.findByNameAndIdNot(updateCategoryDto.name(), categoryId).isPresent()) { + throw new ConflictException("Категория с таким названием уже существует"); + } - categoryMapper.updateCategoryFromDto(newCategoryDto, category); + categoryMapper.updateCategoryFromDto(updateCategoryDto, category); Category updatedCategory = categoryRepository.save(category); - log.info("Обновлена категория: {}", updatedCategory); + log.info("Обновлена категория {}", updatedCategory); return categoryMapper.toDto(updatedCategory); } @@ -78,7 +78,7 @@ public List getCategories(Pageable pageable) { .map(categoryMapper::toDto) .toList(); - log.info("Найдено {} категорий", result.size()); + log.debug("Найдено {} категорий", result.size()); return result; } @@ -88,19 +88,9 @@ public CategoryDto getCategoryById(Long categoryId) { return categoryMapper.toDto(category); } - @Override - public Category getCategoryByIdOrThrow(Long categoryId) { + + private Category getCategoryByIdOrThrow(Long categoryId) { return categoryRepository.findById(categoryId) .orElseThrow(() -> new NotFoundException("Категория с id " + categoryId + " не найдена")); } - - private void checkCategoryNameUnique(String name, Long excludedId) { - Optional existingCategory = (excludedId == null) - ? categoryRepository.findByName(name) - : categoryRepository.findByNameAndIdNot(name, excludedId); - - existingCategory.ifPresent(category -> { - throw new ConflictException("Категория с названием '" + name + "' уже существует"); - }); - } } diff --git a/core/main-service/src/main/java/ru/yandex/practicum/compilation/service/CompilationService.java b/core/event-service/src/main/java/ru/yandex/practicum/service/CompilationService.java similarity index 64% rename from core/main-service/src/main/java/ru/yandex/practicum/compilation/service/CompilationService.java rename to core/event-service/src/main/java/ru/yandex/practicum/service/CompilationService.java index 103ff8a..0fadc05 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/compilation/service/CompilationService.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/service/CompilationService.java @@ -1,9 +1,7 @@ -package ru.yandex.practicum.compilation.service; +package ru.yandex.practicum.service; import org.springframework.data.domain.Pageable; -import ru.yandex.practicum.compilation.dto.CompilationDto; -import ru.yandex.practicum.compilation.dto.NewCompilationDto; -import ru.yandex.practicum.compilation.dto.UpdateCompilationRequest; +import ru.yandex.practicum.dto.compilation.*; import java.util.List; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/compilation/service/CompilationServiceImpl.java b/core/event-service/src/main/java/ru/yandex/practicum/service/CompilationServiceImpl.java similarity index 88% rename from core/main-service/src/main/java/ru/yandex/practicum/compilation/service/CompilationServiceImpl.java rename to core/event-service/src/main/java/ru/yandex/practicum/service/CompilationServiceImpl.java index 4364de1..443e2c8 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/compilation/service/CompilationServiceImpl.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/service/CompilationServiceImpl.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.compilation.service; +package ru.yandex.practicum.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -7,16 +7,13 @@ import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.yandex.practicum.compilation.dto.CompilationDto; -import ru.yandex.practicum.compilation.dto.NewCompilationDto; -import ru.yandex.practicum.compilation.dto.UpdateCompilationRequest; -import ru.yandex.practicum.compilation.mapper.CompilationMapper; -import ru.yandex.practicum.compilation.model.Compilation; -import ru.yandex.practicum.compilation.repository.CompilationRepository; -import ru.yandex.practicum.event.model.Event; -import ru.yandex.practicum.event.repository.EventRepository; -import ru.yandex.practicum.exception.ConflictException; -import ru.yandex.practicum.exception.NotFoundException; +import ru.yandex.practicum.dto.compilation.*; +import ru.yandex.practicum.exception.*; +import ru.yandex.practicum.mapper.CompilationMapper; +import ru.yandex.practicum.model.Compilation; +import ru.yandex.practicum.model.Event; +import ru.yandex.practicum.repository.CompilationRepository; +import ru.yandex.practicum.repository.EventRepository; import java.util.*; import java.util.function.Function; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/service/EventService.java b/core/event-service/src/main/java/ru/yandex/practicum/service/EventService.java similarity index 87% rename from core/main-service/src/main/java/ru/yandex/practicum/event/service/EventService.java rename to core/event-service/src/main/java/ru/yandex/practicum/service/EventService.java index 035819b..934e08e 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/service/EventService.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/service/EventService.java @@ -1,7 +1,8 @@ -package ru.yandex.practicum.event.service; +package ru.yandex.practicum.service; import org.springframework.data.domain.Pageable; -import ru.yandex.practicum.event.model.Event; +import ru.yandex.practicum.dto.event.*; +import ru.yandex.practicum.model.Event; import java.util.List; diff --git a/core/main-service/src/main/java/ru/yandex/practicum/event/service/EventServiceImpl.java b/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java similarity index 68% rename from core/main-service/src/main/java/ru/yandex/practicum/event/service/EventServiceImpl.java rename to core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java index e557794..4f13130 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/event/service/EventServiceImpl.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java @@ -1,4 +1,4 @@ -package ru.yandex.practicum.event.service; +package ru.yandex.practicum.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -10,21 +10,16 @@ import ru.yandex.practicum.CreateEndpointHitDto; import ru.yandex.practicum.StatsClientApp; import ru.yandex.practicum.ViewStatsDto; -import ru.yandex.practicum.category.dto.CategoryDto; -import ru.yandex.practicum.category.mapper.CategoryMapper; -import ru.yandex.practicum.category.model.Category; -import ru.yandex.practicum.category.service.CategoryService; -import ru.yandex.practicum.event.mapper.EventMapper; -import ru.yandex.practicum.event.model.*; -import ru.yandex.practicum.event.repository.EventRepository; -import ru.yandex.practicum.event.repository.SearchEventSpecifications; -import ru.yandex.practicum.exception.BadRequestException; -import ru.yandex.practicum.exception.ConflictException; -import ru.yandex.practicum.exception.NotFoundException; -import ru.yandex.practicum.exception.ValidationException; -import ru.yandex.practicum.request.repository.RequestRepository; -import ru.yandex.practicum.user.model.User; -import ru.yandex.practicum.user.service.UserService; +import ru.yandex.practicum.client.RequestClient; +import ru.yandex.practicum.client.UserClient; +import ru.yandex.practicum.dto.event.*; +import ru.yandex.practicum.dto.user.UserShortDto; +import ru.yandex.practicum.enums.*; +import ru.yandex.practicum.exception.*; +import ru.yandex.practicum.mapper.EventMapper; +import ru.yandex.practicum.model.Category; +import ru.yandex.practicum.model.Event; +import ru.yandex.practicum.repository.*; import java.time.LocalDateTime; import java.util.Comparator; @@ -39,32 +34,37 @@ public class EventServiceImpl implements EventService { private final EventRepository eventRepository; private final EventMapper eventMapper; - private final UserService userService; - private final RequestRepository requestRepository; + private final UserClient userClient; + private final RequestClient requestClient; private final StatsClientApp statsClient; - private final CategoryService categoryService; - private final CategoryMapper categoryMapper; + private final CategoryRepository categoryRepository; @Override @Transactional public EventFullDto createEvent(Long userId, NewEventDto newEventDto) { validateDateEvent(newEventDto.eventDate(), 2); - User user = userService.getUserById(userId); + UserShortDto userDto = userClient.getUser(userId); - CategoryDto categoryDto = categoryService.getCategoryById(newEventDto.category()); - Category category = categoryMapper.toEntityFromDto(categoryDto); + Category category = categoryRepository.findById(newEventDto.category()) + .orElseThrow(() -> new NotFoundException("Категория с id=" + newEventDto.category() + " не найдена")); - Event event = eventMapper.fromNewEvent(newEventDto, user, category, EventState.PENDING); + Event event = eventMapper.toEntity(newEventDto, userId, category); event = eventRepository.save(event); log.info("Создано событие с id={}, title={}, initiatorId={}", event.getId(), event.getTitle(), userId); - return eventMapper.toEventFullDto(event, 0, 0L); + + EventFullDto dto = eventMapper.toFullDto(event); + dto.setInitiator(userDto); + dto.setConfirmedRequests(0L); + dto.setViews(0L); + return dto; } @Override public List getEvents(Long userId, Pageable pageable) { - userService.getUserById(userId); + userClient.getUser(userId); + List events = eventRepository.findAllByInitiatorIdOrderByCreatedOnDesc(userId, pageable); if (events.isEmpty()) return List.of(); @@ -76,20 +76,26 @@ public List getEvents(Long userId, Pageable pageable) { EventStatistics stats = getEventStatistics(eventIds); return events.stream() - .map(event -> eventMapper.toEventShortDto( - event, - stats.confirmedRequests().getOrDefault(event.getId(), 0), - stats.views().getOrDefault(event.getId(), 0L) - )) + .map(event -> { + EventShortDto dto = eventMapper.toShortDto(event); + dto.setConfirmedRequests((long) stats.confirmedRequests() + .getOrDefault(event.getId(), 0L)); + dto.setViews(stats.views().getOrDefault(event.getId(), 0L)); + + UserShortDto initiatorDto = userClient.getUser(event.getInitiatorId()); + dto.setInitiator(initiatorDto); + return dto; + }) .toList(); } @Override public EventFullDto getEvent(Long userId, Long eventId, String ip) { - userService.getUserByIdOrThrow(userId); + userClient.getUser(userId); + Event event = getEventOrThrow(eventId); - if (!event.getInitiator().getId().equals(userId)) { + if (!event.getInitiatorId().equals(userId)) { throw new NotFoundException("Событие c id " + eventId + " не найдено у пользователя с id " + userId); } @@ -101,10 +107,11 @@ public EventFullDto getEvent(Long userId, Long eventId, String ip) { @Override @Transactional public EventFullDto updateEvent(Long userId, Long eventId, UpdateEventUserRequest request) { - userService.getUserByIdOrThrow(userId); + userClient.getUser(userId); + Event event = getEventOrThrow(eventId); - if (!event.getInitiator().getId().equals(userId)) + if (!event.getInitiatorId().equals(userId)) throw new ValidationException("Пользователь не является инициатором события и не может его редактировать"); if (event.getState() == EventState.PUBLISHED) @@ -114,7 +121,8 @@ public EventFullDto updateEvent(Long userId, Long eventId, UpdateEventUserReques Category category = null; if (request.category() != null) { - category = categoryService.getCategoryByIdOrThrow(request.category()); + category = categoryRepository.findById(request.category()) + .orElseThrow(() -> new NotFoundException("Категория с id=" + request.category() + " не найдена")); } eventMapper.updateEventFromUserRequest(request, event, category); @@ -153,8 +161,10 @@ public List getEventsAdmin(SearchEventAdminRequest request, Pageab if (request.rangeStart() == null && request.rangeEnd() == null) specification = specification.and(SearchEventSpecifications.addWhereStartsBefore(LocalDateTime.now())); - Page eventIdsPage = eventRepository.findAll(specification, pageable).map(Event::getId); - List eventIds = eventIdsPage.getContent(); + Page eventsPage = eventRepository.findAll(specification, pageable); + List eventIds = eventsPage.getContent().stream() + .map(Event::getId) + .toList(); if (eventIds.isEmpty()) return List.of(); List events = eventRepository.findAllByEventIds(eventIds); @@ -168,11 +178,16 @@ public List getEventsAdmin(SearchEventAdminRequest request, Pageab EventStatistics stats = getEventStatistics(searchEventIds); return events.stream() - .map(event -> eventMapper.toEventFullDto( - event, - stats.confirmedRequests().getOrDefault(event.getId(), 0), - stats.views().getOrDefault(event.getId(), 0L) - )) + .map(event -> { + EventFullDto dto = eventMapper.toFullDto(event); + dto.setConfirmedRequests((long) stats.confirmedRequests() + .getOrDefault(event.getId(), 0L)); + dto.setViews(stats.views().getOrDefault(event.getId(), 0L)); + + UserShortDto initiatorDto = userClient.getUser(event.getInitiatorId()); + dto.setInitiator(initiatorDto); + return dto; + }) .toList(); } @@ -188,7 +203,8 @@ public EventFullDto updateEventAdmin(Long eventId, UpdateEventAdminRequest reque if (stateActionAdmin == StateActionAdmin.PUBLISH_EVENT) { if (currentState != EventState.PENDING) - throw new ConflictException("Событие можно опубликовать только если оно в состоянии ожидания публикации"); + throw new ConflictException( + "Событие можно опубликовать только если оно в состоянии ожидания публикации"); validateDateEvent(event.getEventDate(), 1); event.publish(); } @@ -201,19 +217,23 @@ public EventFullDto updateEventAdmin(Long eventId, UpdateEventAdminRequest reque Category category = null; if (request.category() != null) { - category = categoryService.getCategoryByIdOrThrow(request.category()); + category = categoryRepository.findById(request.category()) + .orElseThrow(() -> new NotFoundException("Категория с id=" + request.category() + " не найдена")); } eventMapper.updateEventFromAdminRequest(request, event, category); Event updatedEvent = eventRepository.save(event); log.info("Обновлено событие с id={}, title={}, initiatorId={}", - updatedEvent.getId(), updatedEvent.getTitle(), updatedEvent.getInitiator().getId()); + updatedEvent.getId(), updatedEvent.getTitle(), updatedEvent.getInitiatorId()); + return buildFullDto(updatedEvent); } @Override - public List getEventsPublic(SearchEventPublicRequest request, Pageable pageable, String ip) { + public List getEventsPublic( + SearchEventPublicRequest request, Pageable pageable, String ip) { + validateRangeStartAndEnd(request.rangeStart(), request.rangeEnd()); Specification specification = SearchEventSpecifications.addWhereNull(); @@ -243,22 +263,28 @@ public List getEventsPublic(SearchEventPublicRequest request, Pag EventStatistics stats = getEventStatistics(eventIds); List result = events.stream() - .map(event -> eventMapper.toEventShortDto( - event, - stats.confirmedRequests().getOrDefault(event.getId(), 0), - stats.views().getOrDefault(event.getId(), 0L) - )) + .map(event -> { + EventShortDto dto = eventMapper.toShortDto(event); + dto.setConfirmedRequests((long) stats.confirmedRequests() + .getOrDefault(event.getId(), 0L)); + + dto.setViews(stats.views().getOrDefault(event.getId(), 0L)); + + UserShortDto initiatorDto = userClient.getUser(event.getInitiatorId()); + dto.setInitiator(initiatorDto); + return dto; + }) .toList(); saveHit("/events", ip); if (SortState.VIEWS.equals(request.sort())) { return result.stream() - .sorted(Comparator.comparing(EventShortDto::views).reversed()) + .sorted(Comparator.comparing(EventShortDto::getViews).reversed()) .toList(); } else if (SortState.EVENT_DATE.equals(request.sort())) { return result.stream() - .sorted(Comparator.comparing(EventShortDto::eventDate)) + .sorted(Comparator.comparing(EventShortDto::getEventDate)) .toList(); } @@ -277,14 +303,17 @@ public EventFullDto getEventByIdPublic(Long eventId, String ip) { } private EventFullDto buildFullDto(Event event) { - Map confirmedRequests = getConfirmedRequests(List.of(event.getId())); + Map confirmedRequests = getConfirmedRequests(List.of(event.getId())); Map views = getViewsForEvents(List.of(event.getId())); - return eventMapper.toEventFullDto( - event, - confirmedRequests.getOrDefault(event.getId(), 0), - views.getOrDefault(event.getId(), 0L) - ); + EventFullDto dto = eventMapper.toFullDto(event); + dto.setConfirmedRequests((long) confirmedRequests.getOrDefault(event.getId(), 0L)); + dto.setViews(views.getOrDefault(event.getId(), 0L)); + + UserShortDto initiatorDto = userClient.getUser(event.getInitiatorId()); + dto.setInitiator(initiatorDto); + + return dto; } private void validateRangeStartAndEnd(LocalDateTime rangeStart, LocalDateTime rangeEnd) { @@ -293,25 +322,28 @@ private void validateRangeStartAndEnd(LocalDateTime rangeStart, LocalDateTime ra } private void saveHit(String path, String ip) { - CreateEndpointHitDto endpointHitDto = new CreateEndpointHitDto("main-service", path, ip, LocalDateTime.now()); + CreateEndpointHitDto endpointHitDto = new CreateEndpointHitDto( + "main-service", path, ip, LocalDateTime.now()); statsClient.saveHit(endpointHitDto); } private void validateDateEvent(LocalDateTime eventDate, long minHoursBeforeStartEvent) { if (eventDate != null && eventDate.isBefore(LocalDateTime.now().plusHours(minHoursBeforeStartEvent))) - throw new ValidationException("Дата начала события не может быть ранее чем через " + minHoursBeforeStartEvent + " часа(ов)"); + throw new ValidationException( + "Дата начала события не может быть ранее чем через " + minHoursBeforeStartEvent + " часа(ов)"); } - private Map getConfirmedRequests(List eventIds) { + private Map getConfirmedRequests(List eventIds) { // ← Integer → Long if (eventIds.isEmpty()) return Map.of(); - List events = requestRepository.findCountConfirmedRequestsByEventIds(eventIds); - Map confirmedRequests = eventIds.stream() - .collect(Collectors.toMap(id -> id, id -> 0)); + Map confirmedRequests = requestClient.getConfirmedRequestsCount(eventIds); - events.forEach(dto -> confirmedRequests.put(dto.getEventId(), dto.getCountConfirmedRequests())); + Map result = eventIds.stream() + .collect(Collectors.toMap(id -> id, id -> 0L)); + + result.putAll(confirmedRequests); - return confirmedRequests; + return result; } private Map getViewsForEvents(List eventIds) { @@ -320,8 +352,8 @@ private Map getViewsForEvents(List eventIds) { List uris = eventIds.stream() .map(id -> "/events/" + id) .toList(); - - LocalDateTime start = eventRepository.findFirstByOrderByCreatedOnAsc().getCreatedOn(); + Event firstEvent = eventRepository.findFirstByOrderByCreatedOnAsc(); + LocalDateTime start = firstEvent != null ? firstEvent.getCreatedOn() : LocalDateTime.now().minusYears(1); LocalDateTime end = LocalDateTime.now(); List stats = statsClient.getStats(start, end, uris, true); @@ -353,7 +385,7 @@ private EventStatistics getEventStatistics(List eventIds) { return new EventStatistics(Map.of(), Map.of()); } - Map confirmedRequests = getConfirmedRequests(eventIds); + Map confirmedRequests = getConfirmedRequests(eventIds); // ← теперь Long Map views = getViewsForEvents(eventIds); return new EventStatistics(confirmedRequests, views); 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..ca8a5dc --- /dev/null +++ b/core/event-service/src/main/resources/application.yaml @@ -0,0 +1,26 @@ +spring: + application: + name: event-service + config: + import: "configserver:" + cloud: + config: + fail-fast: true + discovery: + enabled: true + serviceId: config-server + retry: + useRandomPolicy: true + max-interval: 6000 + +server: + port: 0 + +eureka: + instance: + instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}} + client: + healthcheck: + enabled: true + serviceUrl: + defaultZone: http://localhost:8761/eureka \ No newline at end of file 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..2963e87 --- /dev/null +++ b/core/event-service/src/main/resources/schema.sql @@ -0,0 +1,52 @@ +DROP TABLE IF EXISTS compilation_events CASCADE; +DROP TABLE IF EXISTS compilations CASCADE; +DROP TABLE IF EXISTS events CASCADE; +DROP TABLE IF EXISTS categories CASCADE; + +-- Создание таблицы категорий +CREATE TABLE IF NOT EXISTS categories ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name VARCHAR(50) NOT NULL UNIQUE +); + +-- Создание таблицы событий +create TABLE IF NOT EXISTS events ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + title VARCHAR(120) NOT NULL, + annotation VARCHAR(2000) NOT NULL, + description VARCHAR(7000) NOT NULL, + event_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + created_on TIMESTAMP WITHOUT TIME ZONE NOT NULL, + published_on TIMESTAMP WITHOUT TIME ZONE, + initiator_id BIGINT, + category_id BIGINT REFERENCES categories(id), + state VARCHAR(20) NOT NULL, + lat FLOAT, + lon FLOAT, + paid BOOLEAN NOT NULL, + participant_limit INTEGER NOT NULL, + request_moderation BOOLEAN NOT NULL, + confirmed_requests INT NOT NULL, + views BIGINT NOT NULL +); + +-- Создание таблицы подборок +create TABLE IF NOT EXISTS compilations ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + title VARCHAR(50) NOT NULL UNIQUE, + pinned BOOLEAN NOT NULL DEFAULT FALSE +); + +-- Создание таблицы связи подборок и событий +create TABLE IF NOT EXISTS compilation_events ( + compilation_id BIGINT NOT NULL REFERENCES compilations (id) ON DELETE CASCADE, + event_id BIGINT NOT NULL REFERENCES events (id) ON DELETE CASCADE, + CONSTRAINT pk_compilation_events PRIMARY KEY (compilation_id, event_id) +); + +-- Индекс для быстрого поиска событий по категории +CREATE INDEX IF NOT EXISTS idx_events_category ON events(category_id); +-- Индекс для быстрого поиска всех событий пользователя +CREATE INDEX IF NOT EXISTS idx_events_initiator ON events(initiator_id); +-- Составной индекс по статусу и дате +CREATE INDEX IF NOT EXISTS idx_events_state_date ON events(state, event_date); \ No newline at end of file diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java index 1308f68..2212f5b 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java @@ -1,10 +1,13 @@ package ru.yandex.practicum.client; import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.dto.request.*; import ru.yandex.practicum.enums.RequestStatus; +import java.util.List; +import java.util.Map; + @FeignClient( name = "request-service", path = "/internal/requests", @@ -14,4 +17,17 @@ public interface RequestClient { @GetMapping("/event/{eventId}/count/{status}") Long countByStatus(@PathVariable Long eventId, @PathVariable RequestStatus status); + + @PostMapping("/events/count") + Map getConfirmedRequestsCount(@RequestBody List eventIds); + + @GetMapping("/users/{userId}/events/{eventId}/requests") + List getRequestsByEvent(@PathVariable Long userId, @PathVariable Long eventId); + + @PatchMapping("/users/{userId}/events/{eventId}/requests") + EventRequestStatusUpdateResult updateRequestStatus( + @PathVariable Long userId, + @PathVariable Long eventId, + @RequestBody EventRequestStatusUpdateRequest request + ); } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClientFallback.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClientFallback.java index a0829e1..bc890fc 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClientFallback.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClientFallback.java @@ -2,8 +2,13 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import ru.yandex.practicum.dto.request.*; import ru.yandex.practicum.enums.RequestStatus; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + @Component @Slf4j public class RequestClientFallback implements RequestClient { @@ -13,4 +18,24 @@ public Long countByStatus(Long eventId, RequestStatus status) { log.warn("Сервис запросов недоступен, eventId={}, status={}", eventId, status); return 0L; } + + @Override + public Map getConfirmedRequestsCount(List eventIds) { + log.warn("Сервис запросов недоступен, eventIds={}", eventIds); + return eventIds.stream() + .collect(Collectors.toMap(id -> id, id -> 0L)); + } + + @Override + public List getRequestsByEvent(Long userId, Long eventId) { + log.warn("Сервис запросов недоступен, userId={}, eventId={}", userId, eventId); + return List.of(); + } + + @Override + public EventRequestStatusUpdateResult updateRequestStatus( + Long userId, Long eventId, EventRequestStatusUpdateRequest request) { + log.warn("Сервис запросов недоступен, userId={}, eventId={}, request={}", userId, eventId, request); + return new EventRequestStatusUpdateResult(List.of(), List.of()); + } } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/CategoryDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/CategoryDto.java index 3dd67ae..37cfc42 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/CategoryDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/CategoryDto.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.Size; public record CategoryDto( + Long id, @NotBlank(message = "Название категории не может быть пустым") diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/NewCategoryDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/NewCategoryDto.java index 412a7f3..f3ca14c 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/NewCategoryDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/NewCategoryDto.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.Size; public record NewCategoryDto( + @NotBlank(message = "Название категории не может быть пустым") @Size(min = 1, max = 50, message = "Название категории должно содержать от {min} до {max} символов") String name diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/UpdateCategoryDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/UpdateCategoryDto.java new file mode 100644 index 0000000..8ef193c --- /dev/null +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/category/UpdateCategoryDto.java @@ -0,0 +1,12 @@ +package ru.yandex.practicum.dto.category; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record UpdateCategoryDto( + + @NotBlank(message = "Название категории не может быть пустым") + @Size(min = 1, max = 50, message = "Название категории должно содержать от {min} до {max} символов") + String name +) { +} diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentAdminDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentAdminDto.java index 05a81d5..de91f47 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentAdminDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentAdminDto.java @@ -6,6 +6,7 @@ import ru.yandex.practicum.enums.CommentStatus; public record CommentAdminDto( + @NotBlank(message = "Комментарий не может быть пустым") @Size(min = 1, max = 5000, message = "Комментарий должен содержать от {min} до {max} символов") String text, diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentDto.java index d348fbe..f66c333 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/CommentDto.java @@ -7,6 +7,7 @@ import java.time.LocalDateTime; public record CommentDto( + Long id, String text, diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/NewCommentDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/NewCommentDto.java index 935c6e4..e61b424 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/NewCommentDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/NewCommentDto.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.Size; public record NewCommentDto( + @NotBlank(message = "Комментарий не может быть пустым") @Size(min = 1, max = 5000, message = "Комментарий должен содержать от {min} до {max} символов") String text diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/UpdateCommentDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/UpdateCommentDto.java index 9fa5f5d..9f7a777 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/UpdateCommentDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/comment/UpdateCommentDto.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.Size; public record UpdateCommentDto( + @NotBlank(message = "Комментарий не может быть пустым") @Size(min = 1, max = 5000, message = "Комментарий должен содержать от {min} до {max} символов") String text diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/CompilationDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/CompilationDto.java index 2cad8b9..fa1797c 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/CompilationDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/CompilationDto.java @@ -1,13 +1,17 @@ package ru.yandex.practicum.dto.compilation; -import ru.yandex.practicum.event.dto.EventShortDto; +import ru.yandex.practicum.dto.event.EventShortDto; import java.util.Set; public record CompilationDto( + Long id, + Set events, + boolean pinned, + String title ) { } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/NewCompilationDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/NewCompilationDto.java index 3c0b120..5169fc8 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/NewCompilationDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/NewCompilationDto.java @@ -6,6 +6,7 @@ import java.util.Set; public record NewCompilationDto( + Set events, boolean pinned, diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/UpdateCompilationRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/UpdateCompilationRequest.java index c087564..9e30877 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/UpdateCompilationRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/compilation/UpdateCompilationRequest.java @@ -5,6 +5,7 @@ import java.util.Set; public record UpdateCompilationRequest( + Set events, Boolean pinned, diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventFullDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventFullDto.java index 3d4e01e..a785993 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventFullDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventFullDto.java @@ -1,47 +1,55 @@ package ru.yandex.practicum.dto.event; import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.*; +import lombok.experimental.FieldDefaults; import ru.yandex.practicum.constants.DateTimeConstants; import ru.yandex.practicum.dto.category.CategoryDto; import ru.yandex.practicum.dto.user.UserShortDto; +import ru.yandex.practicum.enums.EventState; import java.time.LocalDateTime; +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) +public class EventFullDto { -public record EventFullDto( - Long id, + Long id; - String annotation, + String annotation; - CategoryDto category, + CategoryDto category; - Long confirmedRequests, + Long confirmedRequests; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) - LocalDateTime createdOn, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) + LocalDateTime createdOn; - String description, + String description; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) - LocalDateTime eventDate, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) + LocalDateTime eventDate; - UserShortDto initiator, + UserShortDto initiator; - LocationDto location, + LocationDto location; - Boolean paid, + Boolean paid; - Integer participantLimit, + Integer participantLimit; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) - LocalDateTime publishedOn, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) + LocalDateTime publishedOn; - Boolean requestModeration, + Boolean requestModeration; - EventState state, + EventState state; - String title, + String title; - Long views -) { + Long views; } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventShortDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventShortDto.java index c5c54dc..935db89 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventShortDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventShortDto.java @@ -1,31 +1,38 @@ package ru.yandex.practicum.dto.event; import com.fasterxml.jackson.annotation.JsonFormat; -import ru.yandex.practicum.category.dto.CategoryDto; -import ru.yandex.practicum.user.dto.UserShortDto; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.yandex.practicum.constants.DateTimeConstants; +import ru.yandex.practicum.dto.category.CategoryDto; +import ru.yandex.practicum.dto.user.UserShortDto; import java.time.LocalDateTime; -import static ru.yandex.practicum.constants.DateTimeConstants.DATE_TIME_PATTERN; +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@FieldDefaults(level = AccessLevel.PRIVATE) +public class EventShortDto { -public record EventShortDto( - Long id, + Long id; - String annotation, + String annotation; - CategoryDto category, + CategoryDto category; - Long confirmedRequests, + Long confirmedRequests; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) - LocalDateTime eventDate, + LocalDateTime eventDate; - UserShortDto initiator, + UserShortDto initiator; - boolean paid, + Boolean paid; - String title, + String title; - Long views -) { + Long views; } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventStatistics.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventStatistics.java index e79e735..3f7201e 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventStatistics.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventStatistics.java @@ -3,7 +3,9 @@ import java.util.Map; public record EventStatistics( - Map confirmedRequests, + + Map confirmedRequests, + Map views ) { } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventWithCountConfirmedRequests.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventWithCountConfirmedRequests.java index d71db70..bf15456 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventWithCountConfirmedRequests.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/EventWithCountConfirmedRequests.java @@ -6,7 +6,9 @@ @Getter @Setter public class EventWithCountConfirmedRequests { + private Long eventId; + private int countConfirmedRequests; public EventWithCountConfirmedRequests(Long eventId, Long countConfirmedRequestsLong) { diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/LocationDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/LocationDto.java index a5fedb0..feeec2a 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/LocationDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/LocationDto.java @@ -3,6 +3,7 @@ import jakarta.validation.constraints.NotNull; public record LocationDto( + @NotNull(message = "Широта не может быть null") Float lat, diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/NewEventDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/NewEventDto.java index f5ee1d0..c14e412 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/NewEventDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/NewEventDto.java @@ -2,12 +2,12 @@ import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.*; +import ru.yandex.practicum.constants.DateTimeConstants; import java.time.LocalDateTime; -import static ru.yandex.practicum.constants.DateTimeConstants.DATE_TIME_PATTERN; - public record NewEventDto( + @NotBlank(message = "Краткое описание не может быть пустым") @Size(min = 20, max = 2000, message = "Аннотация должна содержать от {min} до {max} символов") String annotation, diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventAdminRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventAdminRequest.java index 01931b7..4166487 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventAdminRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventAdminRequest.java @@ -1,14 +1,14 @@ package ru.yandex.practicum.dto.event; import org.springframework.format.annotation.DateTimeFormat; -import ru.yandex.practicum.event.model.EventState; +import ru.yandex.practicum.constants.DateTimeConstants; +import ru.yandex.practicum.enums.EventState; import java.time.LocalDateTime; import java.util.List; -import static ru.yandex.practicum.constants.DateTimeConstants.DATE_TIME_PATTERN; - public record SearchEventAdminRequest( + List users, List states, diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventPublicRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventPublicRequest.java index 254c090..5b064ab 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventPublicRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/SearchEventPublicRequest.java @@ -1,14 +1,14 @@ package ru.yandex.practicum.dto.event; import org.springframework.format.annotation.DateTimeFormat; -import ru.yandex.practicum.event.model.SortState; +import ru.yandex.practicum.constants.DateTimeConstants; +import ru.yandex.practicum.enums.SortState; import java.time.LocalDateTime; import java.util.List; -import static ru.yandex.practicum.constants.DateTimeConstants.DATE_TIME_PATTERN; - public record SearchEventPublicRequest( + String text, List categories, diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventAdminRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventAdminRequest.java index b089c75..2eba84a 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventAdminRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventAdminRequest.java @@ -4,13 +4,13 @@ import jakarta.validation.constraints.Future; import jakarta.validation.constraints.PositiveOrZero; import jakarta.validation.constraints.Size; -import ru.yandex.practicum.event.model.StateActionAdmin; +import ru.yandex.practicum.constants.DateTimeConstants; +import ru.yandex.practicum.enums.StateActionAdmin; import java.time.LocalDateTime; -import static ru.yandex.practicum.constants.DateTimeConstants.DATE_TIME_PATTERN; - public record UpdateEventAdminRequest( + @Size(min = 20, max = 2000, message = "Недопустимое количество символов") String annotation, diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventUserRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventUserRequest.java index fdf8e87..442e763 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventUserRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/event/UpdateEventUserRequest.java @@ -4,13 +4,13 @@ import jakarta.validation.constraints.Future; import jakarta.validation.constraints.PositiveOrZero; import jakarta.validation.constraints.Size; -import ru.yandex.practicum.event.model.StateActionUser; +import ru.yandex.practicum.constants.DateTimeConstants; +import ru.yandex.practicum.enums.StateActionUser; import java.time.LocalDateTime; -import static ru.yandex.practicum.constants.DateTimeConstants.DATE_TIME_PATTERN; - public record UpdateEventUserRequest( + @Size(min = 20, max = 2000, message = "Недопустимое количество символов") String annotation, diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateRequest.java index 5aa1e1a..d1a8bbc 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateRequest.java @@ -5,7 +5,9 @@ import java.util.List; public record EventRequestStatusUpdateRequest( + List requestIds, + RequestStatus status ) { } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateResult.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateResult.java index ebd63cf..3809b90 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateResult.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/EventRequestStatusUpdateResult.java @@ -3,7 +3,9 @@ import java.util.List; public record EventRequestStatusUpdateResult( + List confirmedRequests, + List rejectedRequests ) { } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/ParticipationRequestDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/ParticipationRequestDto.java index c00e4d8..04de703 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/ParticipationRequestDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/request/ParticipationRequestDto.java @@ -1,20 +1,24 @@ package ru.yandex.practicum.dto.request; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; import ru.yandex.practicum.constants.DateTimeConstants; import ru.yandex.practicum.enums.RequestStatus; import java.time.LocalDateTime; public record ParticipationRequestDto( + Long id, @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeConstants.DATE_TIME_PATTERN) LocalDateTime created, - Long event, + @JsonProperty("event") + Long eventId, - Long requester, + @JsonProperty("requester") + Long requesterId, RequestStatus status ) { diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/NewUserRequest.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/NewUserRequest.java index 15e1d50..dc9ab42 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/NewUserRequest.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/NewUserRequest.java @@ -5,6 +5,7 @@ import jakarta.validation.constraints.Size; public record NewUserRequest( + @NotBlank(message = "Имя пользователя не может быть пустым") @Size(min = 2, max = 250) String name, diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserDto.java index e400999..0a667f6 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserDto.java @@ -1,8 +1,11 @@ package ru.yandex.practicum.dto.user; public record UserDto( + Long id, + String name, + String email ) { } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserShortDto.java b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserShortDto.java index 865455c..f11b7b7 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserShortDto.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/dto/user/UserShortDto.java @@ -1,7 +1,9 @@ package ru.yandex.practicum.dto.user; public record UserShortDto( + Long id, + String name ) { } diff --git a/core/main-service/src/main/java/ru/yandex/practicum/exception/ErrorResponse.java b/core/interaction-api/src/main/java/ru/yandex/practicum/exception/ErrorResponse.java similarity index 97% rename from core/main-service/src/main/java/ru/yandex/practicum/exception/ErrorResponse.java rename to core/interaction-api/src/main/java/ru/yandex/practicum/exception/ErrorResponse.java index e110861..8147162 100644 --- a/core/main-service/src/main/java/ru/yandex/practicum/exception/ErrorResponse.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/exception/ErrorResponse.java @@ -1,10 +1,15 @@ package ru.yandex.practicum.exception; public record ErrorResponse( + String path, + String httpMethod, + int statusCode, + String error, + String message ) { } diff --git a/core/main-service/Dockerfile b/core/main-service/Dockerfile deleted file mode 100644 index 0ff1817..0000000 --- a/core/main-service/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM eclipse-temurin:21-jre-jammy -VOLUME /tmp -ARG JAR_FILE=target/*.jar -COPY ${JAR_FILE} app.jar -ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] \ No newline at end of file diff --git a/core/main-service/src/main/java/ru/yandex/practicum/category/service/CategoryService.java b/core/main-service/src/main/java/ru/yandex/practicum/category/service/CategoryService.java deleted file mode 100644 index 5e398ca..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/category/service/CategoryService.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.yandex.practicum.category.service; - -import org.springframework.data.domain.Pageable; -import ru.yandex.practicum.category.dto.CategoryDto; -import ru.yandex.practicum.category.dto.NewCategoryDto; -import ru.yandex.practicum.category.model.Category; - -import java.util.List; - -public interface CategoryService { - CategoryDto createCategory(NewCategoryDto newCategoryDto); - - void deleteCategory(Long categoryId); - - CategoryDto updateCategory(Long categoryId, NewCategoryDto newCategoryDto); - - List getCategories(Pageable pageable); - - CategoryDto getCategoryById(Long categoryId); - - Category getCategoryByIdOrThrow(Long categoryId); -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/compilation/mapper/CompilationMapper.java b/core/main-service/src/main/java/ru/yandex/practicum/compilation/mapper/CompilationMapper.java deleted file mode 100644 index 64b6e42..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/compilation/mapper/CompilationMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.yandex.practicum.compilation.mapper; - -import org.mapstruct.*; -import ru.yandex.practicum.compilation.dto.CompilationDto; -import ru.yandex.practicum.compilation.dto.NewCompilationDto; -import ru.yandex.practicum.compilation.dto.UpdateCompilationRequest; -import ru.yandex.practicum.compilation.model.Compilation; -import ru.yandex.practicum.event.mapper.EventMapper; - -@Mapper(componentModel = "spring", uses = EventMapper.class) -public interface CompilationMapper { - - @Mapping(target = "id", ignore = true) - @Mapping(target = "events", ignore = true) - Compilation toEntity(NewCompilationDto request); - - @Mapping(target = "events", qualifiedByName = "toEventShortWithoutStats") - CompilationDto toDto(Compilation compilation); - - @Mapping(target = "id", ignore = true) - @Mapping(target = "events", ignore = true) - @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) - void updateCompilationFromRequest(UpdateCompilationRequest request, @MappingTarget Compilation compilation); -} diff --git a/core/main-service/src/main/java/ru/yandex/practicum/compilation/repository/CompilationRepository.java b/core/main-service/src/main/java/ru/yandex/practicum/compilation/repository/CompilationRepository.java deleted file mode 100644 index ef90d2c..0000000 --- a/core/main-service/src/main/java/ru/yandex/practicum/compilation/repository/CompilationRepository.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.yandex.practicum.compilation.repository; - -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import ru.yandex.practicum.compilation.model.Compilation; - -import java.util.List; -import java.util.Optional; - -public interface CompilationRepository extends JpaRepository { - - @Query(""" - SELECT c FROM Compilation c - LEFT JOIN FETCH c.events e - LEFT JOIN FETCH e.category - LEFT JOIN FETCH e.initiator - WHERE c.id = :id""") - Optional findByIdWithEvents(@Param("id") Long id); - - boolean existsByTitle(String title); - - @Query(""" - SELECT c.id FROM Compilation c - WHERE (:pinned IS NULL OR c.pinned = :pinned) - ORDER BY c.id ASC""") - List findIdsByPinned(@Param("pinned") Boolean pinned, Pageable pageable); - - @Query(""" - SELECT DISTINCT c FROM Compilation c - LEFT JOIN FETCH c.events e - LEFT JOIN FETCH e.category - LEFT JOIN FETCH e.initiator - WHERE c.id IN :ids""") - List findAllByIdInWithEvents(@Param("ids") List ids); -} diff --git a/core/main-service/src/main/resources/application.yaml b/core/main-service/src/main/resources/application.yaml deleted file mode 100644 index 9574515..0000000 --- a/core/main-service/src/main/resources/application.yaml +++ /dev/null @@ -1,21 +0,0 @@ -spring: - application: - name: main-service - config: - import: "configserver:" - cloud: - config: - discovery: - enabled: true - service-id: config-server - fail-fast: true - -server: - port: 0 - -eureka: - client: - serviceUrl: - defaultZone: http://localhost:8761/eureka/ - instance: - prefer-ip-address: true \ 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 6cd84d9..0000000 --- a/core/main-service/src/main/resources/schema.sql +++ /dev/null @@ -1,59 +0,0 @@ -create TABLE IF NOT EXISTS categories ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - name VARCHAR(50) NOT NULL UNIQUE -); - ---create TABLE IF NOT EXISTS users ( --- id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, --- name VARCHAR(255) NOT NULL, --- email VARCHAR(255) NOT NULL UNIQUE ---); - -create TABLE IF NOT EXISTS events ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - title VARCHAR(120) NOT NULL, - annotation VARCHAR(2000) NOT NULL, - description VARCHAR(7000) NOT NULL, - category_id BIGINT REFERENCES categories(id), - initiator_id BIGINT REFERENCES users (id), - created_on TIMESTAMP WITHOUT TIME ZONE NOT NULL, - event_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, - published_on TIMESTAMP WITHOUT TIME ZONE, - paid BOOLEAN NOT NULL, - participant_limit INTEGER NOT NULL, - request_moderation BOOLEAN NOT NULL, - state VARCHAR(20) NOT NULL, - lat FLOAT, - lon FLOAT -); - ---create TABLE IF NOT EXISTS requests ( --- id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, --- event_id BIGINT REFERENCES events(id), --- requester_id BIGINT REFERENCES users(id), --- created TIMESTAMP WITHOUT TIME ZONE, --- status VARCHAR(255) ---); - -create TABLE IF NOT EXISTS compilations ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - title VARCHAR(50) NOT NULL, - pinned BOOLEAN NOT NULL -); - -create TABLE IF NOT EXISTS compilation_events ( - compilation_id BIGINT NOT NULL REFERENCES compilations (id), - event_id BIGINT NOT NULL REFERENCES events (id), - CONSTRAINT pk_compilation_events PRIMARY KEY (compilation_id, event_id) -); - ---create TABLE IF NOT EXISTS comments ( --- id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, --- text TEXT NOT NULL, --- user_id BIGINT NOT NULL REFERENCES users (id), --- event_id BIGINT NOT NULL REFERENCES events (id), --- created_date TIMESTAMP WITHOUT TIME ZONE NOT NULL ---); - ---CREATE INDEX IF NOT EXISTS idx_comments_event_id ON comments(event_id); ---CREATE INDEX IF NOT EXISTS idx_comments_user_id ON comments(user_id); \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 0cdaad3..2b14877 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -14,11 +14,11 @@ pom - main-service interaction-api request-service user-service comment-service + event-service diff --git a/core/request-service/src/main/java/ru/yandex/practicum/controller/InternalRequestController.java b/core/request-service/src/main/java/ru/yandex/practicum/controller/InternalRequestController.java index c9bbb5f..78ae449 100644 --- a/core/request-service/src/main/java/ru/yandex/practicum/controller/InternalRequestController.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/controller/InternalRequestController.java @@ -1,12 +1,13 @@ package ru.yandex.practicum.controller; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import ru.yandex.practicum.enums.RequestStatus; import ru.yandex.practicum.repository.RequestRepository; +import ru.yandex.practicum.service.RequestService; + +import java.util.List; +import java.util.Map; @RestController @RequestMapping("/internal/requests") @@ -14,9 +15,15 @@ public class InternalRequestController { private final RequestRepository requestRepository; + private final RequestService requestService; @GetMapping("/event/{eventId}/count/{status}") public Long countByStatus(@PathVariable Long eventId, @PathVariable RequestStatus status) { return requestRepository.countByEventIdAndStatus(eventId, status); } + + @PostMapping("/events/count") + public Map getConfirmedRequestsCount(@RequestBody List eventIds) { + return requestService.getConfirmedRequestsForEvents(eventIds); + } } diff --git a/core/request-service/src/main/java/ru/yandex/practicum/controller/PrivateEventRequestController.java b/core/request-service/src/main/java/ru/yandex/practicum/controller/PrivateEventRequestController.java new file mode 100644 index 0000000..f4c1405 --- /dev/null +++ b/core/request-service/src/main/java/ru/yandex/practicum/controller/PrivateEventRequestController.java @@ -0,0 +1,42 @@ +package ru.yandex.practicum.controller; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.dto.request.*; +import ru.yandex.practicum.service.RequestService; + +import java.util.List; + +@Slf4j +@Validated +@RestController +@RequestMapping("/users/{userId}/events/{eventId}/requests") +@RequiredArgsConstructor +public class PrivateEventRequestController { + private final RequestService requestService; + + @PatchMapping + public EventRequestStatusUpdateResult updateRequestStatus( + @PathVariable @Positive Long userId, + @PathVariable @Positive Long eventId, + @RequestBody @Valid EventRequestStatusUpdateRequest request) { + + log.debug("Controller: updateRequestStatus userId={}, eventId={}, request={}", + userId, eventId, request); + + return requestService.updateRequestStatus(userId, eventId, request); + } + + @GetMapping + public List getEventRequests( + @PathVariable @Positive Long userId, + @PathVariable @Positive Long eventId) { + + log.debug("Controller: getEventRequests userId={}, eventId={}", userId, eventId); + return requestService.getRequestsByEvent(userId, eventId); + } +} diff --git a/core/request-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java b/core/request-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java new file mode 100644 index 0000000..6c1ca58 --- /dev/null +++ b/core/request-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java @@ -0,0 +1,149 @@ +package ru.yandex.practicum.handler; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.MessageSourceResolvable; +import org.springframework.http.HttpStatus; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.HandlerMethodValidationException; +import org.springframework.web.servlet.NoHandlerFoundException; +import ru.yandex.practicum.exception.*; + +import java.util.stream.Collectors; + +@Slf4j +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNotFoundException(NotFoundException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.NOT_FOUND, exception, request); + } + + @ExceptionHandler(BadRequestException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleBadRequestException(BadRequestException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.BAD_REQUEST, exception, request); + } + + @ExceptionHandler(ForbiddenException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public ErrorResponse handleForbiddenException(ForbiddenException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.FORBIDDEN, exception, request); + } + + @ExceptionHandler(ConflictException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleConflictException(ConflictException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.CONFLICT, exception, request); + } + + @ExceptionHandler(ValidationException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleValidationException(ValidationException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.CONFLICT, exception, request); + } + + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleConstraintViolation( + ConstraintViolationException ex, HttpServletRequest request) { + String violations = ex.getConstraintViolations().stream() + .map(violation -> violation.getPropertyPath() + ": " + violation.getMessage()) + .collect(Collectors.joining("; ")); + + String message = violations.isEmpty() ? "Constraint violation" : violations; + return buildResponse(HttpStatus.BAD_REQUEST, message, null, request); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, HttpServletRequest request) { + String validationErrors = ex.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining("; ")); + + String message = validationErrors.isEmpty() ? "Validation failed" : validationErrors; + + return buildResponse(HttpStatus.BAD_REQUEST, message, null, request); + } + + @ExceptionHandler(HandlerMethodValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleHandlerMethodValidation( + HandlerMethodValidationException ex, HttpServletRequest request) { + String validationErrors = ex.getAllErrors().stream() + .map(MessageSourceResolvable::getDefaultMessage) + .collect(Collectors.joining("; ")); + + String message = validationErrors.isEmpty() ? "Validation failed" : validationErrors; + + return buildResponse(HttpStatus.BAD_REQUEST, message, null, request); + } + + @ExceptionHandler(ServletRequestBindingException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleServletRequestBindingException( + ServletRequestBindingException ex, HttpServletRequest request) { + return buildResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), null, request); + } + + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + public ErrorResponse handleHttpRequestMethodNotSupported( + HttpRequestMethodNotSupportedException ex, HttpServletRequest request) { + return buildResponse(HttpStatus.METHOD_NOT_ALLOWED, ex.getMessage(), null, request); + } + + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) + public ErrorResponse handleHttpMediaTypeNotSupported( + HttpMediaTypeNotSupportedException ex, HttpServletRequest request) { + return buildResponse(HttpStatus.UNSUPPORTED_MEDIA_TYPE, ex.getMessage(), null, request); + } + + @ExceptionHandler(NoHandlerFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNoHandlerFoundException( + NoHandlerFoundException ex, HttpServletRequest request) { + return buildResponse(HttpStatus.NOT_FOUND, ex.getMessage(), null, request); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleUnexpected(Exception exception, HttpServletRequest request) { + return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception, request); + } + + private ErrorResponse buildResponse( + HttpStatus httpStatus, Exception exception, HttpServletRequest request) { + return buildResponse(httpStatus, exception.getMessage(), exception, request); + } + + private ErrorResponse buildResponse( + HttpStatus httpStatus, String message, Exception exception, HttpServletRequest request) { + String path = request.getRequestURI(); + String httpMethod = request.getMethod(); + int statusCode = httpStatus.value(); + String error = httpStatus.getReasonPhrase(); + + String logMessage = String.format("Path: %s %s, '%d' %s — %s", httpMethod, path, statusCode, error, message); + + if (statusCode >= 500) { + log.error(logMessage, exception); + } else { + log.warn(logMessage); + } + + return new ErrorResponse(path, httpMethod, statusCode, error, message); + } +} diff --git a/core/request-service/src/main/java/ru/yandex/practicum/mapper/RequestMapper.java b/core/request-service/src/main/java/ru/yandex/practicum/mapper/RequestMapper.java index 93ac361..9f442e1 100644 --- a/core/request-service/src/main/java/ru/yandex/practicum/mapper/RequestMapper.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/mapper/RequestMapper.java @@ -1,7 +1,6 @@ package ru.yandex.practicum.mapper; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; import ru.yandex.practicum.dto.request.ParticipationRequestDto; import ru.yandex.practicum.model.Request; @@ -10,8 +9,6 @@ @Mapper(componentModel = "spring") public interface RequestMapper { - @Mapping(source = "eventId", target = "event") - @Mapping(source = "requesterId", target = "requester") ParticipationRequestDto toDto(Request request); List toDtoList(List requests); diff --git a/core/request-service/src/main/java/ru/yandex/practicum/repository/RequestRepository.java b/core/request-service/src/main/java/ru/yandex/practicum/repository/RequestRepository.java index aa1b964..e44141e 100644 --- a/core/request-service/src/main/java/ru/yandex/practicum/repository/RequestRepository.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/repository/RequestRepository.java @@ -1,6 +1,8 @@ package ru.yandex.practicum.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import ru.yandex.practicum.enums.RequestStatus; import ru.yandex.practicum.model.Request; @@ -22,4 +24,9 @@ public interface RequestRepository extends JpaRepository { List findByEventIdAndStatus(Long eventId, RequestStatus status); List findByIdIn(List ids); + + @Query("SELECT r.eventId, COUNT(r) FROM Request r " + + "WHERE r.eventId IN :eventIds AND r.status = 'CONFIRMED' " + + "GROUP BY r.eventId") + List countConfirmedRequestsByEventIds(@Param("eventIds") List eventIds); } diff --git a/core/request-service/src/main/java/ru/yandex/practicum/service/RequestService.java b/core/request-service/src/main/java/ru/yandex/practicum/service/RequestService.java index 0b12f05..87fe7f8 100644 --- a/core/request-service/src/main/java/ru/yandex/practicum/service/RequestService.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/service/RequestService.java @@ -1,11 +1,9 @@ package ru.yandex.practicum.service; - -import ru.yandex.practicum.dto.request.EventRequestStatusUpdateRequest; -import ru.yandex.practicum.dto.request.EventRequestStatusUpdateResult; -import ru.yandex.practicum.dto.request.ParticipationRequestDto; +import ru.yandex.practicum.dto.request.*; import java.util.List; +import java.util.Map; public interface RequestService { List getRequestsByEvent(Long userId, Long eventId); @@ -18,4 +16,6 @@ EventRequestStatusUpdateResult updateRequestStatus(Long userId, ParticipationRequestDto addRequest(Long userId, Long eventId); ParticipationRequestDto cancelRequest(Long userId, Long requestId); + + Map getConfirmedRequestsForEvents(List eventIds); } diff --git a/core/request-service/src/main/java/ru/yandex/practicum/service/RequestServiceImpl.java b/core/request-service/src/main/java/ru/yandex/practicum/service/RequestServiceImpl.java index d59a086..6499c5a 100644 --- a/core/request-service/src/main/java/ru/yandex/practicum/service/RequestServiceImpl.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/service/RequestServiceImpl.java @@ -8,20 +8,16 @@ import ru.yandex.practicum.client.EventClient; import ru.yandex.practicum.client.UserClient; import ru.yandex.practicum.dto.event.EventFullDto; -import ru.yandex.practicum.dto.request.EventRequestStatusUpdateRequest; -import ru.yandex.practicum.dto.request.EventRequestStatusUpdateResult; -import ru.yandex.practicum.dto.request.ParticipationRequestDto; +import ru.yandex.practicum.dto.request.*; import ru.yandex.practicum.enums.EventState; import ru.yandex.practicum.enums.RequestStatus; -import ru.yandex.practicum.exception.ConflictException; -import ru.yandex.practicum.exception.NotFoundException; -import ru.yandex.practicum.exception.ValidationException; +import ru.yandex.practicum.exception.*; import ru.yandex.practicum.mapper.RequestMapper; import ru.yandex.practicum.model.Request; import ru.yandex.practicum.repository.RequestRepository; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; @Slf4j @Service @@ -39,21 +35,21 @@ public ParticipationRequestDto addRequest(Long userId, Long eventId) { checkUserExists(userId); EventFullDto event = eventClient.getEvent(eventId); - if (requestRepository.existsByRequesterIdAndEventId(userId, eventId)) { // 1. Исправлен порядок параметров + if (requestRepository.existsByRequesterIdAndEventId(userId, eventId)) { throw new ConflictException("Нельзя добавить повторный запрос"); } - if (event.initiator().id().equals(userId)) { + if (event.getInitiator().id().equals(userId)) { throw new ConflictException("Инициатор события не может добавить запрос на участие в своём событии"); } - if (event.state() != EventState.PUBLISHED) { + if (event.getState() != EventState.PUBLISHED) { throw new ConflictException("Нельзя участвовать в неопубликованном событии"); } - if (event.participantLimit() != 0 && + if (event.getParticipantLimit() != 0 && requestRepository.countByEventIdAndStatus(eventId, RequestStatus.CONFIRMED) - >= event.participantLimit()) { + >= event.getParticipantLimit()) { throw new ConflictException("Достигнут лимит по количеству участников события с id=" + eventId); } @@ -63,7 +59,7 @@ public ParticipationRequestDto addRequest(Long userId, Long eventId) { .status(RequestStatus.PENDING) .build(); - if (!event.requestModeration() || event.participantLimit() == 0) { + if (!event.getRequestModeration() || event.getParticipantLimit() == 0) { log.debug("Модерация заявок на участие в событии с id={} не требуется", eventId); request.confirmed(); } @@ -97,13 +93,12 @@ public ParticipationRequestDto cancelRequest(Long userId, Long requestId) { return requestMapper.toDto(request); } - @Override public List getRequestsByEvent(Long userId, Long eventId) { checkUserExists(userId); EventFullDto event = eventClient.getEvent(eventId); - if (!event.initiator().id().equals(userId)) { + if (!event.getInitiator().id().equals(userId)) { throw new ValidationException("Пользователь с id=" + userId + " не является создателем события"); } @@ -119,11 +114,11 @@ public EventRequestStatusUpdateResult updateRequestStatus(Long userId, Long even checkUserExists(userId); EventFullDto event = eventClient.getEvent(eventId); - if (!event.initiator().id().equals(userId)) { + if (!event.getInitiator().id().equals(userId)) { throw new ValidationException("Пользователь с id=" + userId + " не является создателем события"); } - if (!event.requestModeration() || event.participantLimit() == 0) { + if (!event.getRequestModeration() || event.getParticipantLimit() == 0) { throw new ValidationException("Для данного события подтверждение заявок не требуется"); } @@ -132,17 +127,16 @@ public EventRequestStatusUpdateResult updateRequestStatus(Long userId, Long even throw new ValidationException("Устанавливать можно только статусы CONFIRMED или REJECTED"); } - // 2. Оптимизированный запрос для фиксированного количества List requestsForUpdate = requestRepository.findByIdIn(requestDto.requestIds()); long currentConfirmedCount = requestRepository.countByEventIdAndStatus(eventId, RequestStatus.CONFIRMED); - List pendingRequests = currentConfirmedCount + requestsForUpdate.size() >= event.participantLimit() + List pendingRequests = currentConfirmedCount + requestsForUpdate.size() >= event.getParticipantLimit() ? requestRepository.findByEventIdAndStatus(eventId, RequestStatus.PENDING) : List.of(); validateAllRequestsExist(requestDto.requestIds(), requestsForUpdate); validateRequestsState(requestsForUpdate, eventId); - long availableSlots = event.participantLimit() - currentConfirmedCount; + long availableSlots = event.getParticipantLimit() - currentConfirmedCount; List confirmedRequests = new ArrayList<>(); List rejectedRequests = new ArrayList<>(); @@ -199,6 +193,39 @@ public EventRequestStatusUpdateResult updateRequestStatus(Long userId, Long even ); } + @Override + @Transactional(readOnly = true) + public Map getConfirmedRequestsForEvents(List eventIds) { + log.debug("Получение количества подтверждённых заявок для событий: {}", eventIds); + + if (eventIds == null || eventIds.isEmpty()) { + return Collections.emptyMap(); + } + + try { + List results = requestRepository.countConfirmedRequestsByEventIds(eventIds); + + Map resultMap = new HashMap<>(); + for (Long eventId : eventIds) { + resultMap.put(eventId, 0L); + } + + for (Object[] row : results) { + Long eventId = (Long) row[0]; + Long count = (Long) row[1]; + resultMap.put(eventId, count); + } + + log.debug("Результат: {}", resultMap); + return resultMap; + + } catch (Exception e) { + log.error("Ошибка при получении количества подтверждённых заявок", e); + return eventIds.stream() + .collect(Collectors.toMap(id -> id, id -> 0L)); + } + } + private void checkUserExists(Long userId) { try { userClient.getUser(userId); diff --git a/core/request-service/src/main/resources/application.yaml b/core/request-service/src/main/resources/application.yaml index 4e1269f..b5e47b4 100644 --- a/core/request-service/src/main/resources/application.yaml +++ b/core/request-service/src/main/resources/application.yaml @@ -2,7 +2,7 @@ spring: application: name: request-service config: - import: "configserver" + import: "configserver:" cloud: config: fail-fast: true @@ -16,6 +16,9 @@ spring: server: port: 0 +stats-server: + url: http://localhost:9090 + eureka: instance: instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}} diff --git a/core/request-service/src/main/resources/schema.sql b/core/request-service/src/main/resources/schema.sql index 47dcb6f..b856da4 100644 --- a/core/request-service/src/main/resources/schema.sql +++ b/core/request-service/src/main/resources/schema.sql @@ -2,12 +2,12 @@ DROP TABLE IF EXISTS requests CASCADE; -- Создание таблицы запросов create TABLE IF NOT EXISTS requests ( - id BIGINT GENERATED BY ALWAYS AS IDENTITY PRIMARY KEY, + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, event_id BIGINT NOT NULL, requester_id BIGINT NOT NULL, created TIMESTAMP WITHOUT TIME ZONE NOT NULL, - status VARCHAR(60) NOT NULL - CONSTRAINT uq_requester_event UNIQUE (requester_id, event_id), + status VARCHAR(60) NOT NULL, + CONSTRAINT uq_requester_event UNIQUE (requester_id, event_id) ); -- Индекс для быстрого поиска заявок на событие diff --git a/core/user-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java b/core/user-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java new file mode 100644 index 0000000..6c1ca58 --- /dev/null +++ b/core/user-service/src/main/java/ru/yandex/practicum/handler/ErrorHandler.java @@ -0,0 +1,149 @@ +package ru.yandex.practicum.handler; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.MessageSourceResolvable; +import org.springframework.http.HttpStatus; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.HandlerMethodValidationException; +import org.springframework.web.servlet.NoHandlerFoundException; +import ru.yandex.practicum.exception.*; + +import java.util.stream.Collectors; + +@Slf4j +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNotFoundException(NotFoundException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.NOT_FOUND, exception, request); + } + + @ExceptionHandler(BadRequestException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleBadRequestException(BadRequestException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.BAD_REQUEST, exception, request); + } + + @ExceptionHandler(ForbiddenException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public ErrorResponse handleForbiddenException(ForbiddenException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.FORBIDDEN, exception, request); + } + + @ExceptionHandler(ConflictException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleConflictException(ConflictException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.CONFLICT, exception, request); + } + + @ExceptionHandler(ValidationException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleValidationException(ValidationException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.CONFLICT, exception, request); + } + + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleConstraintViolation( + ConstraintViolationException ex, HttpServletRequest request) { + String violations = ex.getConstraintViolations().stream() + .map(violation -> violation.getPropertyPath() + ": " + violation.getMessage()) + .collect(Collectors.joining("; ")); + + String message = violations.isEmpty() ? "Constraint violation" : violations; + return buildResponse(HttpStatus.BAD_REQUEST, message, null, request); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, HttpServletRequest request) { + String validationErrors = ex.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining("; ")); + + String message = validationErrors.isEmpty() ? "Validation failed" : validationErrors; + + return buildResponse(HttpStatus.BAD_REQUEST, message, null, request); + } + + @ExceptionHandler(HandlerMethodValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleHandlerMethodValidation( + HandlerMethodValidationException ex, HttpServletRequest request) { + String validationErrors = ex.getAllErrors().stream() + .map(MessageSourceResolvable::getDefaultMessage) + .collect(Collectors.joining("; ")); + + String message = validationErrors.isEmpty() ? "Validation failed" : validationErrors; + + return buildResponse(HttpStatus.BAD_REQUEST, message, null, request); + } + + @ExceptionHandler(ServletRequestBindingException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleServletRequestBindingException( + ServletRequestBindingException ex, HttpServletRequest request) { + return buildResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), null, request); + } + + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + public ErrorResponse handleHttpRequestMethodNotSupported( + HttpRequestMethodNotSupportedException ex, HttpServletRequest request) { + return buildResponse(HttpStatus.METHOD_NOT_ALLOWED, ex.getMessage(), null, request); + } + + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) + public ErrorResponse handleHttpMediaTypeNotSupported( + HttpMediaTypeNotSupportedException ex, HttpServletRequest request) { + return buildResponse(HttpStatus.UNSUPPORTED_MEDIA_TYPE, ex.getMessage(), null, request); + } + + @ExceptionHandler(NoHandlerFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNoHandlerFoundException( + NoHandlerFoundException ex, HttpServletRequest request) { + return buildResponse(HttpStatus.NOT_FOUND, ex.getMessage(), null, request); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ErrorResponse handleUnexpected(Exception exception, HttpServletRequest request) { + return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception, request); + } + + private ErrorResponse buildResponse( + HttpStatus httpStatus, Exception exception, HttpServletRequest request) { + return buildResponse(httpStatus, exception.getMessage(), exception, request); + } + + private ErrorResponse buildResponse( + HttpStatus httpStatus, String message, Exception exception, HttpServletRequest request) { + String path = request.getRequestURI(); + String httpMethod = request.getMethod(); + int statusCode = httpStatus.value(); + String error = httpStatus.getReasonPhrase(); + + String logMessage = String.format("Path: %s %s, '%d' %s — %s", httpMethod, path, statusCode, error, message); + + if (statusCode >= 500) { + log.error(logMessage, exception); + } else { + log.warn(logMessage); + } + + return new ErrorResponse(path, httpMethod, statusCode, error, message); + } +} diff --git a/core/user-service/src/main/java/ru/yandex/practicum/mapper/UserMapper.java b/core/user-service/src/main/java/ru/yandex/practicum/mapper/UserMapper.java index 2755c2a..b0a21e0 100644 --- a/core/user-service/src/main/java/ru/yandex/practicum/mapper/UserMapper.java +++ b/core/user-service/src/main/java/ru/yandex/practicum/mapper/UserMapper.java @@ -2,9 +2,7 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import ru.yandex.practicum.dto.user.NewUserRequest; -import ru.yandex.practicum.dto.user.UserDto; -import ru.yandex.practicum.dto.user.UserShortDto; +import ru.yandex.practicum.dto.user.*; import ru.yandex.practicum.model.User; import java.util.List; diff --git a/core/user-service/src/main/java/ru/yandex/practicum/repository/UserRepository.java b/core/user-service/src/main/java/ru/yandex/practicum/repository/UserRepository.java index 038f62e..889c714 100644 --- a/core/user-service/src/main/java/ru/yandex/practicum/repository/UserRepository.java +++ b/core/user-service/src/main/java/ru/yandex/practicum/repository/UserRepository.java @@ -3,7 +3,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import ru.yandex.practicum.model.User; import java.util.List; diff --git a/core/user-service/src/main/java/ru/yandex/practicum/service/UserService.java b/core/user-service/src/main/java/ru/yandex/practicum/service/UserService.java index cf0ae15..81d6c92 100644 --- a/core/user-service/src/main/java/ru/yandex/practicum/service/UserService.java +++ b/core/user-service/src/main/java/ru/yandex/practicum/service/UserService.java @@ -1,9 +1,7 @@ package ru.yandex.practicum.service; import org.springframework.data.domain.Pageable; -import ru.yandex.practicum.dto.user.NewUserRequest; -import ru.yandex.practicum.dto.user.UserDto; -import ru.yandex.practicum.dto.user.UserShortDto; +import ru.yandex.practicum.dto.user.*; import java.util.List; diff --git a/core/user-service/src/main/java/ru/yandex/practicum/service/UserServiceImpl.java b/core/user-service/src/main/java/ru/yandex/practicum/service/UserServiceImpl.java index 8a9acc4..8189e25 100644 --- a/core/user-service/src/main/java/ru/yandex/practicum/service/UserServiceImpl.java +++ b/core/user-service/src/main/java/ru/yandex/practicum/service/UserServiceImpl.java @@ -7,20 +7,13 @@ import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.yandex.practicum.dto.user.NewUserRequest; -import ru.yandex.practicum.dto.user.UserDto; -import ru.yandex.practicum.dto.user.UserShortDto; -import ru.yandex.practicum.exception.ConflictException; -import ru.yandex.practicum.exception.NotFoundException; +import ru.yandex.practicum.dto.user.*; +import ru.yandex.practicum.exception.*; import ru.yandex.practicum.mapper.UserMapper; import ru.yandex.practicum.model.User; import ru.yandex.practicum.repository.UserRepository; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import static org.springframework.data.domain.Sort.Direction.ASC; @Slf4j @Service diff --git a/core/user-service/src/main/resources/application.yaml b/core/user-service/src/main/resources/application.yaml index 35a08ad..a3482ea 100644 --- a/core/user-service/src/main/resources/application.yaml +++ b/core/user-service/src/main/resources/application.yaml @@ -2,7 +2,7 @@ spring: application: name: user-service config: - import: "configserver" + import: "configserver:" cloud: config: fail-fast: true diff --git a/docker-compose.yml b/docker-compose.yml index 8252b6d..17c6317 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,7 +30,7 @@ services: POSTGRES_DB: ewm_main_db volumes: - - ./core/main-service/src/main/resources/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql + - ./core/event-service/src/main/resources/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql healthcheck: test: [ "CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB} -p 5432" ] interval: 10s diff --git a/infra/config-server/src/main/resources/application.yaml b/infra/config-server/src/main/resources/application.yaml index 84fdf6a..ab100f5 100644 --- a/infra/config-server/src/main/resources/application.yaml +++ b/infra/config-server/src/main/resources/application.yaml @@ -8,9 +8,12 @@ spring: server: native: searchLocations: - - classpath:config/main-service/ + - classpath:config/event-service/ - classpath:config/stats-server/ - classpath:config/gateway-server/ + - classpath:config/user-service/ + - classpath:config/request-service/ + - classpath:config/comment-service/ server: port: 0 diff --git a/infra/config-server/src/main/resources/config/comment-service/comment-service.yaml b/infra/config-server/src/main/resources/config/comment-service/comment-service.yaml new file mode 100644 index 0000000..ba86393 --- /dev/null +++ b/infra/config-server/src/main/resources/config/comment-service/comment-service.yaml @@ -0,0 +1,26 @@ +server: + port: 0 + +spring: + jpa: + show-sql: true + hibernate: + ddl-auto: none + properties: + hibernate: + format_sql: true + sql: + init: + mode: always + datasource: + url: jdbc:postgresql://localhost:5432/ewm_main_db + username: ewm_user + password: ewm_password + +stats-server: + url: http://localhost:9090 + +logging: + level: + root: INFO + ru.yandex.practicum.explorewithme.main: DEBUG \ No newline at end of file diff --git a/infra/config-server/src/main/resources/config/event-service/event-service.yaml b/infra/config-server/src/main/resources/config/event-service/event-service.yaml new file mode 100644 index 0000000..ba86393 --- /dev/null +++ b/infra/config-server/src/main/resources/config/event-service/event-service.yaml @@ -0,0 +1,26 @@ +server: + port: 0 + +spring: + jpa: + show-sql: true + hibernate: + ddl-auto: none + properties: + hibernate: + format_sql: true + sql: + init: + mode: always + datasource: + url: jdbc:postgresql://localhost:5432/ewm_main_db + username: ewm_user + password: ewm_password + +stats-server: + url: http://localhost:9090 + +logging: + level: + root: INFO + ru.yandex.practicum.explorewithme.main: DEBUG \ No newline at end of file diff --git a/infra/config-server/src/main/resources/config/gateway-server/gateway-server.yaml b/infra/config-server/src/main/resources/config/gateway-server/gateway-server.yaml index b084036..56457c9 100644 --- a/infra/config-server/src/main/resources/config/gateway-server/gateway-server.yaml +++ b/infra/config-server/src/main/resources/config/gateway-server/gateway-server.yaml @@ -5,10 +5,30 @@ spring: locator: enabled: false routes: - - id: main_service_route - uri: lb://main-service + - id: user-service + uri: lb://user-service predicates: - - Path=/** + - Path=/admin/users/** + + - id: request_service_route + uri: lb://request-service + predicates: + - Path=/internal/requests/**,/users/*/requests/**,/users/*/events/*/requests/** + + - id: event_service_route + uri: lb://event-service + predicates: + - Path=/admin/events/**,/events/**,/users/*/events/**,/admin/categories/**,/categories/**,/admin/compilations/**,/compilations/** + + - id: comment_service_route + uri: lb://comment-service + predicates: + - Path=/admin/comments/**,/users/*/comments/**,/users/*/events/*/comments/**,/events/*/comments/** + +# - id: stats-service +# uri: lb://stats-server +# predicates: +# - Path=/stats/** server: port: 8080 diff --git a/infra/config-server/src/main/resources/config/request-server/request-service.yaml b/infra/config-server/src/main/resources/config/request-server/request-service.yaml new file mode 100644 index 0000000..ba86393 --- /dev/null +++ b/infra/config-server/src/main/resources/config/request-server/request-service.yaml @@ -0,0 +1,26 @@ +server: + port: 0 + +spring: + jpa: + show-sql: true + hibernate: + ddl-auto: none + properties: + hibernate: + format_sql: true + sql: + init: + mode: always + datasource: + url: jdbc:postgresql://localhost:5432/ewm_main_db + username: ewm_user + password: ewm_password + +stats-server: + url: http://localhost:9090 + +logging: + level: + root: INFO + ru.yandex.practicum.explorewithme.main: DEBUG \ No newline at end of file diff --git a/infra/config-server/src/main/resources/config/main-service/main-service.yaml b/infra/config-server/src/main/resources/config/user-service/user-service.yaml similarity index 100% rename from infra/config-server/src/main/resources/config/main-service/main-service.yaml rename to infra/config-server/src/main/resources/config/user-service/user-service.yaml diff --git a/postman/ewm-main-service.json b/postman/ewm-main-service.json new file mode 100644 index 0000000..76e7776 --- /dev/null +++ b/postman/ewm-main-service.json @@ -0,0 +1,14671 @@ +{ + "info": { + "_postman_id": "b7d50c64-67b6-43c5-90ca-8e982296449a", + "name": "Test Explore With Me Plus - Main - Microservices", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "23073145", + "_collection_link": "https://universal-shadow-295426.postman.co/workspace/My-Workspace~4200f6aa-0504-44b1-8a1d-707d0dcbd5ce/collection/13708500-b7d50c64-67b6-43c5-90ca-8e982296449a?action=share&source=collection_link&creator=23073145" + }, + "item": [ + { + "name": "Validation", + "item": [ + { + "name": "Event", + "item": [ + { + "name": "Required query params", + "item": [ + { + "name": "Добавление запроса от текущего пользователя на участие в событии без обязательного query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события", + "disabled": true + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + } + ] + }, + { + "name": "Unrequired query params", + "item": [ + { + "name": "Получение событий, добавленных текущим пользователем без нескольких Query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество элементов в наборе", + "disabled": true + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение событий с возможностью фильтрации без нескольких Query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['text', 'categories', 'paid']);\r", + " pm.request.addQueryParams([`text=` + event.annotation, 'categories=' + category.id, 'paid=' + event.paid]);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "events" + ], + "query": [ + { + "key": "text", + "value": "0", + "description": "текст для поиска в содержимом аннотации и подробном описании события", + "disabled": true + }, + { + "key": "categories", + "value": "0", + "description": "список идентификаторов категорий в которых будет вестись поиск", + "disabled": true + }, + { + "key": "paid", + "value": "true", + "description": "поиск только платных/бесплатных событий", + "disabled": true + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие", + "disabled": true + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие", + "disabled": true + }, + { + "key": "onlyAvailable", + "value": "false", + "description": "только события у которых не исчерпан лимит запросов на участие", + "disabled": true + }, + { + "key": "sort", + "value": "EVENT_DATE", + "description": "Вариант сортировки: по дате события или по количеству просмотров", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе", + "disabled": true + } + ] + }, + "description": "Обратите внимание: \n- это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события\n- текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв\n- если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, которые произойдут позже текущей даты и времени\n- информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" + }, + "response": [] + } + ] + }, + { + "name": "Required params in body", + "item": [ + { + "name": "Добавление события без поля description", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " delete event[\"description\"];\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление события с пустым описанием", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event[\"description\"] = '';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление события со строкой из пробелов в качестве описания", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event[\"description\"] = ' ';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление события без поля annotation", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " delete event[\"annotation\"];\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление события с пустой аннотацией", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event[\"annotation\"] = '';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление события со строкой из пробелов в качестве аннотации", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event[\"annotation\"] = ' ';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление события с отрицательным лимитом участников", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event[\"participantLimit\"] = -6;\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Изменение события добавленного текущим пользователем. Изменение лимита участников на отрицательное значение", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.collectionVariables.set(\"response\", event);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " participantLimit : -156\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.have.status(400);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + } + ] + }, + { + "name": "Misc tests", + "item": [ + { + "name": "Отклонение публикации события", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let new_event, event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/admin/events/\" + event.id,\r", + " method : \"PATCH\",\r", + " header: { \"Content-Type\": \"application/json\" },\r", + " body: JSON.stringify({\r", + " stateAction: \"REJECT_EVENT\"\r", + " })\r", + " }, (error, response) => {\r", + "\r", + " });\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.collectionVariables.set(\"response\", event);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction: \"SEND_TO_REVIEW\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get(\"response\");\r", + "const target = pm.response.json();\r", + "\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация отменённого события должна соответствовать аннотации события до отмены');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Категория отменённого события должна соответствовать категории события до отмены');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость отменённого события должна соответствовать стоимости события до отмены');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения отменённого события должна соответствовать дате проведения события до отмены');\r", + " pm.expect(source.description).equal(target.description, 'Описание отменённого события должно соответствовать описанию события до отмены');\r", + " pm.expect(source.title).equal(target.title, 'Название отменённого события должно соответствовать названию события до отмены');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников отменённого события должен соответствовать лимиту участников события до отмены');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость отменённого события должна соответствовать стоимости события до отмены');\r", + "});\r", + "\r", + "pm.test(\"Событие должно иметь статус CANCELED при возвращении от администратора и статус PENDING после выполнения запроса\", function () {\r", + " pm.expect(target.state).equal(\"PENDING\");\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Попытка получения информации о событии по публичному эндпоинту без публикации", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.collectionVariables.set('response', event);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 404 и данные в формате json\", function () {\r", + " pm.response.to.be.notFound; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/events/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "events", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n- событие должно быть опубликовано\n- информация о событии должна включать в себя количество просмотров и количество подтвержденных запросов\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" + }, + "response": [] + }, + { + "name": "Поиск событий с проверкой параметров", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const user2 = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = rnd.getEvent(category.id)\r", + " event.requestModeration = true;\r", + " event.participantLimit = 2;\r", + " event = await api.addEvent(user.id, event);\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['users', 'categories']);\r", + " pm.request.addQueryParams([`users=` + user.id, 'categories=' + category.id]);\r", + " pm.collectionVariables.set('response', event);\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/admin/events?users=\" + user.id +\"&states=PUBLISHED&categories=\" + category.id + \"&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2097-09-06%2013%3A30%3A38&from=0&size=1000\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.collectionVariables.set('confirmedRequests', response.json()[0].confirmedRequests)\r", + " });\r", + " const requestToJoin = await api.publishParticipationRequest(event.id, user2.id);\r", + " const confirmedRequest = await api.acceptParticipationRequest(event.id, user.id, requestToJoin.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[0];\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать искомому событию');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Идентификатор категории должен соответствовать искомой категории');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость посещения события должна соответствовать искомому событию');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате искомого события');\r", + " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать искомому событию');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать искомому событию');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Число участников события должно соответствовать искомому событию');\r", + " pm.expect(pm.collectionVariables.get('confirmedRequests')).equal(0);\r", + " pm.expect(target.confirmedRequests).equal(1);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/events?users=0&states=PUBLISHED&categories=0&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2097-09-06%2013%3A30%3A38&from=0&size=1000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events" + ], + "query": [ + { + "key": "users", + "value": "0", + "description": "список id пользователей, чьи события нужно найти" + }, + { + "key": "states", + "value": "PUBLISHED", + "description": "список состояний в которых находятся искомые события" + }, + { + "key": "categories", + "value": "0", + "description": "список id категорий в которых будет вестись поиск" + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие" + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие" + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе" + } + ] + }, + "description": "Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия" + }, + "response": [] + }, + { + "name": "Получение событий с возможностью фильтрации и проверкой на валидацию", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " \r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/events?text=0&categories=0&paid=true&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2007-09-06%2013%3A30%3A38&onlyAvailable=false&sort=EVENT_DATE&from=0&size=1000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "events" + ], + "query": [ + { + "key": "text", + "value": "0", + "description": "текст для поиска в содержимом аннотации и подробном описании события" + }, + { + "key": "categories", + "value": "0", + "description": "список идентификаторов категорий в которых будет вестись поиск" + }, + { + "key": "paid", + "value": "true", + "description": "поиск только платных/бесплатных событий" + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие" + }, + { + "key": "rangeEnd", + "value": "2007-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие" + }, + { + "key": "onlyAvailable", + "value": "false", + "description": "только события у которых не исчерпан лимит запросов на участие" + }, + { + "key": "sort", + "value": "EVENT_DATE", + "description": "Вариант сортировки: по дате события или по количеству просмотров" + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе" + } + ] + }, + "description": "Обратите внимание: \n- это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события\n- текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв\n- если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, которые произойдут позже текущей даты и времени\n- информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" + }, + "response": [] + }, + { + "name": "Проверка работы поля views", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " await api.findEvent(event.id)\r", + " await api.findEvent(event.id)\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Значение поля views должно увеличится на 1 после выполнения GET запроса с уникального IP к событию\", function () {\r", + " pm.expect(target.views).equal(1);\r", + " \r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/events/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "events", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n- событие должно быть опубликовано\n- информация о событии должна включать в себя количество просмотров и количество подтвержденных запросов\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" + }, + "response": [] + }, + { + "name": "Добавление запроса на участие при participantLimit == 0", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " eventBody['participantLimit'] = 0\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.request.addQueryParams([`eventId=` + event.id]);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201); \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "var query = {};\r", + "pm.request.url.query.all().forEach((param) => { query[param.key] = param.value});\r", + "\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", + "});\r", + "\r", + "pm.test(\"При создании у запроса на участие должен быть статус CONFIRMED\", function () {\r", + " pm.expect(target.status).equal(\"CONFIRMED\");\r", + "});\r", + "\r", + "pm.test(\"Id ивента в запросе и в ответе должны совпадать\", function () {\r", + " pm.expect(target.event.toString()).equal(query['eventId'].toString());\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests?eventId=0", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + }, + { + "name": "Изменение даты события на уже наступившую", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " eventDate : \"2020-10-11 23:10:05\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.have.status(400);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение события добавленного текущим пользователем. Изменение даты не неподходящую", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.collectionVariables.set(\"response\", event);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " eventDate : \"2020-10-11 23:10:05\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.have.status(400);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Добавление события на неподходящую дату", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.eventDate = \"2020-12-31 15:10:05\";\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.have.status(400);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + } + ] + }, + { + "name": "String length restrictions", + "item": [ + { + "name": "Добавление нового события с description.length < 20", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.description = rnd.getWord(19);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление нового события с description.length < 20", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.description = rnd.getWord(19);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление нового события с annotation.length < 20", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.annotation = rnd.getWord(19);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление нового события с annotation.length > 2000", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.annotation = rnd.getWord(2001);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление нового события с description.length == 20 && annotation.length == 20 && title.length == 3", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.description = rnd.getWord(20);\r", + " event.annotation = rnd.getWord(20);\r", + " event.title = rnd.getWord(3);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление нового события с description.length == 7000 && annotation.length == 2000 && title.length == 120", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.description = rnd.getWord(7000);\r", + " event.annotation = rnd.getWord(2000);\r", + " event.title = rnd.getWord(120);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление нового события с title.length < 3", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.title = rnd.getWord(2);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление нового события с title.length > 120", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " event.title = rnd.getWord(121);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Изменение заголовка события с title.length < 3 (admin endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.title = rnd.getWord(2);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение заголовка события с title.length > 120 (admin endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.title = rnd.getWord(121);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение описания события с description.length < 20 (admin endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.description = rnd.getWord(19);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение описания события с description.length > 7000 (admin endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.description = rnd.getWord(7001);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение аннотации события с annotation.length < 20 (admin endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.annotation = rnd.getWord(19);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение аннотации события с annotation.length > 2000 (admin endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.annotation = rnd.getWord(2001);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение события с description.length == 20 && annotation.length == 20 && title.length == 3 (admin endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.annotation = rnd.getWord(20);\r", + " event2.description = rnd.getWord(20);\r", + " event2.title = rnd.getWord(3);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение события с description.length == 7000 && annotation.length == 2000 && title.length == 120 (admin endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.annotation = rnd.getWord(2000);\r", + " event2.description = rnd.getWord(7000);\r", + " event2.title = rnd.getWord(120);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение заголовка события с title.length < 3 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " title: rnd.getWord(2)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение заголовка события с title.length > 120 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " title: rnd.getWord(121)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение описания события с description.length < 20 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " description: rnd.getWord(19)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение описания события с description.length > 7000 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " description: rnd.getWord(7001)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение аннотации события с annotation.length < 20 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " annotation: rnd.getWord(19)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение аннотации события с annotation.length > 2000 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " annotation: rnd.getWord(2001)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение события с description.length == 20 && annotation.length == 20 && title.length == 3 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " description: rnd.getWord(20),\r", + " annotation: rnd.getWord(20),\r", + " title: rnd.getWord(3)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Изменение события с description.length == 7000 && annotation.length == 2000 && title.length == 120 (user endpoint)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " description: rnd.getWord(7000),\r", + " annotation: rnd.getWord(2000),\r", + " title: rnd.getWord(120)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + } + ] + }, + { + "name": "Default values check", + "item": [ + { + "name": "Добавление нового события без paid, participantLimit, requestModeration", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let usersArr = Array.from({length: 10}, () => rnd.getUser());\r", + " let categoriesArr = Array.from({length: 10}, () => rnd.getCategory());\r", + " let usersResponseArr = [], categoriesResponseArr = [], eventArr, eventResponseArr = [];\r", + " try {\r", + " for (const u of usersArr){\r", + " usersResponseArr.push(await api.addUser(u));\r", + " }\r", + " for (const c of categoriesArr){\r", + " categoriesResponseArr.push(await api.addCategory(c));\r", + " }\r", + " eventArr = Array.from(categoriesResponseArr, (x) => rnd.getEvent(x.id));\r", + " for (let i = 0; i < 10; i++){\r", + " delete eventArr[i].requestModeration;\r", + " delete eventArr[i].paid;\r", + " delete eventArr[i].participantLimit;\r", + " }\r", + " for (let i = 0; i < 10; i++){\r", + " eventResponseArr.push(await api.addEvent(usersResponseArr[i].id, eventArr[i]));\r", + " }\r", + " pm.collectionVariables.set('responseArr', eventResponseArr)\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201); \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('responseArr');\r", + "\r", + "\r", + "pm.test(\"У каждого созданного события paid должно принять значение по умолчанию(false)\", function () {\r", + " source.forEach(function(x){pm.expect(x.paid).to.be.equal(false)});\r", + "});\r", + "\r", + "pm.test(\"У каждого созданного события participantLimit должен принять значение по умолчанию(0)\", function () {\r", + " source.forEach(function(x){pm.expect(x.participantLimit).to.be.equal(0)});\r", + "});\r", + "\r", + "pm.test(\"У каждого созданного события requestModeration должно принять значение по умолчанию(true)\", function () {\r", + " source.forEach(function(x){pm.expect(x.requestModeration).to.be.equal(true)});\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Проверка на значения по-умолчанию from и size(event)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user, category, eventArr;\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " category = await api.addCategory(rnd.getCategory());\r", + " eventArr = Array.from({length:11}, () => rnd.getEvent(category.id));\r", + " for (let i = 0; i < 11; i++){\r", + " await api.addEvent(user.id, eventArr[i]);\r", + " }\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/admin/events?from=0\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", + " pm.expect(target[0].id).to.be.equal(source[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", + "});\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.be.equal(10);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events" + ], + "query": [ + { + "key": "users", + "value": "0", + "description": "список id пользователей, чьи события нужно найти", + "disabled": true + }, + { + "key": "states", + "value": "PUBLISHED", + "description": "список состояний в которых находятся искомые события", + "disabled": true + }, + { + "key": "categories", + "value": "0", + "description": "список id категорий в которых будет вестись поиск", + "disabled": true + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие", + "disabled": true + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе", + "disabled": true + } + ] + }, + "description": "Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия" + }, + "response": [] + }, + { + "name": "Получение событий, добавленных текущим пользователем. Проверка на значения по-умолчанию size и from", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let eventArr, user, category, eventResponseArr = [];\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " category = await api.addCategory(rnd.getCategory());\r", + " eventArr = Array.from({length:11}, () => rnd.getEvent(category.id));\r", + " for (let i = 0; i < 11; i++){\r", + " eventResponseArr.push(await api.addEvent(user.id, eventArr[i]));\r", + " }\r", + " pm.collectionVariables.set('responseArr', eventResponseArr)\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/users/\" + user.id + \"/events?from=0\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('responseArr');\r", + "newSourceArr = Array.from(source, (x) => x.id);\r", + "const responseWithFrom = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.equal(10);\r", + "});\r", + "\r", + "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", + " pm.expect(target[0].id).to.be.equal(responseWithFrom[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", + "});\r", + "\r", + "pm.test(\"Все найденные события должны быть в списке добавленных\", function () {\r", + " source.forEach(function(x){pm.expect(newSourceArr).to.include(x.id)});\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество элементов в наборе", + "disabled": true + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение событий с возможностью фильтрации. Проверка на значение по-умолчанию size", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user, category, eventArr, eventResponseArr = [], publishEventResponseArr = [];\r", + " try {\r", + " user = await api.addUser(rnd.getUser());\r", + " category = await api.addCategory(rnd.getCategory());\r", + " eventArr = Array.from({length:11}, () => rnd.getEvent(category.id));\r", + " for (let i = 0; i < 11; i++){\r", + " eventResponseArr.push(await api.addEvent(user.id, eventArr[i]));\r", + " }\r", + " for (let i = 0; i < 11; i++){\r", + " publishEventResponseArr.push(await api.publishEvent(eventResponseArr[i].id));\r", + " }\r", + " pm.collectionVariables.set('responseArr', eventResponseArr);\r", + " pm.collectionVariables.set('catid', category.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('responseArr');\r", + "newSourceArr = Array.from(source, (x) => x.id);\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.equal(10);\r", + "});\r", + "\r", + "pm.test(\"Все найденные события должны быть в списке добавленных\", function () {\r", + " source.forEach(function(x){pm.expect(newSourceArr).to.include(x.id)});\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/events?categories={{catid}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "events" + ], + "query": [ + { + "key": "text", + "value": "0", + "description": "текст для поиска в содержимом аннотации и подробном описании события", + "disabled": true + }, + { + "key": "categories", + "value": "{{catid}}", + "description": "список идентификаторов категорий в которых будет вестись поиск" + }, + { + "key": "paid", + "value": "true", + "description": "поиск только платных/бесплатных событий", + "disabled": true + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие", + "disabled": true + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие", + "disabled": true + }, + { + "key": "onlyAvailable", + "value": "false", + "description": "только события у которых не исчерпан лимит запросов на участие", + "disabled": true + }, + { + "key": "sort", + "value": "EVENT_DATE", + "description": "Вариант сортировки: по дате события или по количеству просмотров", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе", + "disabled": true + } + ] + }, + "description": "Обратите внимание: \n- это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события\n- текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв\n- если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, которые произойдут позже текущей даты и времени\n- информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" + }, + "response": [] + } + ] + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "Required query params", + "item": [] + }, + { + "name": "Unrequired query params", + "item": [ + { + "name": "Поиск пользователей без нескольких Query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/users?ids={{uid}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ], + "query": [ + { + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей" + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10", + "description": "количество элементов в наборе", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Поиск пользователей без параметра ids", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " let user1 = rnd.getUser();\r", + " user1 = await api.addUser(user1);\r", + "\r", + " let user2 = rnd.getUser()\r", + " user2 = await api.addUser(user2);\r", + " //pm.collectionVariables.set('fromId', user1.id);\r", + " pm.collectionVariables.set('source1', user1);\r", + " pm.collectionVariables.set('source2', user2);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source1 = pm.collectionVariables.get('source1');\r", + "const source2 = pm.collectionVariables.get('source2');\r", + "\r", + "pm.test(\"Пользователи должны содержать поля: id, name, email\", function () {\r", + " pm.expect(target[target.length-2]).to.have.property('id');\r", + " pm.expect(target[target.length-2]).to.have.property('name');\r", + " pm.expect(target[target.length-2]).to.have.property('email');\r", + " pm.expect(target[target.length-1]).to.have.property('id');\r", + " pm.expect(target[target.length-1]).to.have.property('name');\r", + " pm.expect(target[target.length-1]).to.have.property('email');\r", + "});\r", + "\r", + "pm.test(\"Данные последних двух пользователей должны совпадать с данными добавленных пользователей\", function () {\r", + " pm.expect(target[target.length-2].id).to.equal(source1.id);\r", + " pm.expect(target[target.length-2].name).to.equal(source1.name);\r", + " pm.expect(target[target.length-2].email).to.equal(source1.email);\r", + " pm.expect(target[target.length-1].id).to.equal(source2.id);\r", + " pm.expect(target[target.length-1].name).to.equal(source2.name);\r", + " pm.expect(target[target.length-1].email).to.equal(source2.email);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/users?from={{fromId}}&size=100000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ], + "query": [ + { + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей", + "disabled": true + }, + { + "key": "from", + "value": "{{fromId}}", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "100000", + "description": "количество элементов в наборе" + } + ] + } + }, + "response": [] + }, + { + "name": "Поиск событий без нескольких Query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['users', 'categories']);\r", + " pm.request.addQueryParams([`users=` + user.id, 'categories=' + category.id]);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events" + ], + "query": [ + { + "key": "users", + "value": "0", + "description": "список id пользователей, чьи события нужно найти", + "disabled": true + }, + { + "key": "states", + "value": "PUBLISHED", + "description": "список состояний в которых находятся искомые события", + "disabled": true + }, + { + "key": "categories", + "value": "0", + "description": "список id категорий в которых будет вестись поиск", + "disabled": true + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие", + "disabled": true + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе", + "disabled": true + } + ] + }, + "description": "Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия" + }, + "response": [] + } + ] + }, + { + "name": "Required params in body", + "item": [ + { + "name": "Добавление пользователя с электронной почтой, состоящей только из пробелов", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = \" \";\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с пустой электронной почтой", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = \"\";\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя без поля email", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " delete user.email;\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с пустым именем", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = \"\";\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с именем, состоящим только из пробелов", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = \" \";\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя без поля name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " delete user.name;\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Misc tests", + "item": [] + }, + { + "name": "String length restrictions", + "item": [ + { + "name": "Добавление пользователя с name.length < 2", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(1);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с name.length == 2", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(2);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с name.length > 250", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(251);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с name.length == 250", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(250);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.length < 6", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@a.r';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.length == 6", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(15) + '@a.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.localpart.length > 64", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(65) + '@a.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.localpart.length == 64", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(59) + '@a.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с domain.part.length > 63", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@' + rnd.getWord(64) + '.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с domain.part.length == 63", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@' + rnd.getWord(60) + '.ru';\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.length > 254", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(61);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Добавление пользователя с email.length == 254", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.email = rnd.getWord(1) + '@' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(63) + '.' + rnd.getWord(60);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Default values check", + "item": [ + { + "name": "Проверка на значения по-умолчанию from и size(user)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " for (let i = 0; i < 11; i++){\r", + " await api.addUser(rnd.getUser());\r", + " }\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/admin/users?from=0\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", + " pm.expect(target[0].id).to.be.equal(source[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", + "});\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.be.equal(10);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ], + "query": [ + { + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей", + "disabled": true + }, + { + "key": "ids", + "value": "-10833646", + "description": "id пользователей", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10", + "description": "количество элементов в наборе", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + } + ] + }, + { + "name": "Category", + "item": [ + { + "name": "Required query params", + "item": [] + }, + { + "name": "Unrequired params in body", + "item": [ + { + "name": "Получение категорий без нескольких Query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество категорий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10000", + "description": "количество категорий в наборе", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Required params in body", + "item": [ + { + "name": "Добавление категории с именем, состоящим из пробелов", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {name: ' '};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + } + }, + "response": [] + }, + { + "name": "Добавление категории с пустым полем name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {name: ''};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + } + }, + "response": [] + }, + { + "name": "Добавление категории без поля name", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Misc tests", + "item": [ + { + "name": "Изменение категории с неизменными данными", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category, categoryObj\r", + " try {\r", + " category = rnd.getCategory();\r", + " categoryObj = await api.addCategory(category);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(categoryObj.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "String length restrictions", + "item": [ + { + "name": "Добавление новой категории с name.length > 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {'name': rnd.getWord(51)};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Добавление новой категории с name.length == 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = {'name': rnd.getWord(50)};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Изменение имени категории с name.length > 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category\r", + " try {\r", + " category = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(category.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : rnd.getWord(51)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Изменение имени категории с name.length == 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category\r", + " try {\r", + " category = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(category.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : rnd.getWord(50)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Default values check", + "item": [ + { + "name": "Проверка на значения по-умолчанию from и size(category)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " for (let i = 0; i < 11; i++){\r", + " await api.addCategory(rnd.getCategory());\r", + " }\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/categories?from=0\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", + " pm.expect(target[0].id).to.be.equal(source[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", + "});\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.be.equal(10);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество категорий, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество категорий в наборе", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + } + ] + }, + { + "name": "Compilation", + "item": [ + { + "name": "Required query params", + "item": [] + }, + { + "name": "Unreqired params in body", + "item": [ + { + "name": "Получение подборок событий без нескольких Query params", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const compilation = await api.addCompilation(rnd.getCompilation());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "compilations" + ], + "query": [ + { + "key": "pinned", + "value": "true", + "description": "искать только закрепленные/не закрепленные подборки", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество элементов в наборе", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Добавление новой подборки без параметра pinned", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " compilation = rnd.getCompilation(event.id);\r", + " delete compilation['pinned'];\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Подборка должны содержать поля: id, title, pinned, events\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('pinned');\r", + "pm.expect(target).to.have.property('events');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(target.title).to.be.a(\"string\");\r", + " pm.expect(target.events).to.be.an(\"array\");\r", + " pm.expect(target.pinned).equal(false);\r", + "\r", + " pm.expect(source.events[0]).equal(target.events[0].id, 'Идентификаторы событий в подборке должен быть идентичен идентификаторам, указанным при создании подборки ');\r", + " pm.expect(source.title).equal(target.title, 'Название подборки должно соответствовать указанному при создании');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Required params in body", + "item": [ + { + "name": "Добавление подборки без поля title", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " compilation = {\r", + " \"pinned\":\"true\"};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Добавление подборки с пустым полем title", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " compilation = {\r", + " \"pinned\":\"true\",\r", + " \"title\": \"\"};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Добавление подборки с пустой строкой в качестве названия", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " compilation = {\r", + " \"pinned\":\"true\",\r", + " \"title\": \" \"};\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Misc tests", + "item": [ + { + "name": "Добавление подборки с проверкой связей многие-ко-многим", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " let compilation2;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " const event2 = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " compilation = rnd.getCompilation(event.id, event2.id);\r", + " compilation2 = rnd.getCompilation(event.id, event2.id);\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/admin/compilations\",\r", + " method : \"POST\",\r", + " header: { \"Content-Type\": \"application/json\" },\r", + " body: JSON.stringify({\r", + " events: compilation2.events,\r", + " title: compilation2.title\r", + " })\r", + " }, (error, response) => {\r", + "\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Подборка должны содержать поля: id, title, pinned, events\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('pinned');\r", + "pm.expect(target).to.have.property('events');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(target.title).to.be.a(\"string\");\r", + " pm.expect(target.events).to.be.an(\"array\");\r", + " if (target.events[0].id < target.events[1].id){\r", + " pm.expect(source.events[0]).equal(target.events[0].id, 'Идентификаторы событий в подборке должен быть идентичен идентификаторам, указанным при создании подборки ');\r", + " } else {\r", + " pm.expect(source.events[0]).equal(target.events[1].id, 'Идентификаторы событий в подборке должен быть идентичен идентификаторам, указанным при создании подборки ');\r", + " }\r", + " pm.expect(source.title).equal(target.title, 'Название подборки должно соответствовать указанному при создании');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Добавление подборки без событий", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " compilation = rnd.getCompilation();\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Подборка должны содержать поля: id, title, pinned, events\", function () {\r", + " pm.expect(target).to.have.property('id');\r", + " pm.expect(target).to.have.property('title');\r", + " pm.expect(target).to.have.property('pinned');\r", + " pm.expect(target).to.have.property('events');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(target.title).to.be.a(\"string\");\r", + " pm.expect(target.events).to.be.an(\"array\");\r", + "\r", + " pm.expect(source.title).equal(target.title, 'Название подборки должно соответствовать указанному при создании');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "String length restrictions", + "item": [ + { + "name": "Добавление подборки с title.length > 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({title: rnd.getWord(51)}),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Добавление подборки с title.length == 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({title: rnd.getWord(50)}),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Обновить названия подборки с title.length > 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const compilation = await api.addCompilation(rnd.getCompilation());\r", + " pm.collectionVariables.set('compid', compilation.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " title: rnd.getWord(51)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400 и данные в формате json\", function () {\r", + " pm.response.to.be.badRequest; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations/:compId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations", + ":compId" + ], + "variable": [ + { + "key": "compId", + "value": "{{compid}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Обновить названия подборки с title.length == 50", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const compilation = await api.addCompilation(rnd.getCompilation());\r", + " pm.collectionVariables.set('compid', compilation.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " title: rnd.getWord(50)\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.have.status(200);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations/:compId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations", + ":compId" + ], + "variable": [ + { + "key": "compId", + "value": "{{compid}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Default values check", + "item": [ + { + "name": "Добавление подборки без pinned", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let cArr = Array.from({length: 10}, () => rnd.getCompilation());\r", + " let responseArr = [];\r", + " try {\r", + " cArr.forEach(function(x){ delete x.pinned });\r", + " for (const c of cArr){\r", + " responseArr.push(await api.addCompilation(c));\r", + " }\r", + " pm.collectionVariables.set('responseArr', responseArr);\r", + " compilation = rnd.getCompilation();\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('responseArr');\r", + "\r", + "\r", + "pm.test(\"У каждой из созданных подборок pinned должно принять значение по умолчанию(false)\", function () {\r", + " source.forEach(function(x){pm.expect(x.pinned).to.be.equal(false)});\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Проверка на значения по-умолчанию from и size(compilation)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " for (let i = 0; i < 11; i++){\r", + " await api.addCompilation(rnd.getCompilation());\r", + " }\r", + " await pm.sendRequest({\r", + " url : \"http://localhost:8080/compilations?from=0\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {pm.collectionVariables.set('source', response.json())});\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('source');\r", + "\r", + "pm.test(\"Значение from по-умолчанию должно быть равным 0\", function () {\r", + " pm.expect(target[0].id).to.be.equal(source[0].id, 'Запросы с from=0 и без него должны начинаться с одного и того же события');\r", + "});\r", + "\r", + "pm.test(\"Значение size по-умолчанию должно быть равным 10\", function () {\r", + " pm.expect(target.length).to.be.equal(10);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "compilations" + ], + "query": [ + { + "key": "pinned", + "value": "true", + "description": "искать только закрепленные/не закрепленные подборки", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "1000", + "description": "количество элементов в наборе", + "disabled": true + } + ] + } + }, + "response": [] + } + ] + } + ] + } + ] + }, + { + "name": "409 Conflict", + "item": [ + { + "name": "Попытка изменения имени категории на уже существующее", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category1, category2\r", + " try {\r", + " category1 = await api.addCategory(rnd.getCategory());\r", + " category2 = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", category2.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : category1.name\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Добавление новой категории с занятым именем", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = rnd.getCategory();\r", + " await api.addCategory(category);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Добавление пользователя с занятым именем почты", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " user.name = rnd.getWord(10);\r", + " await api.addUser(user);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Удаление категории с привязанными событиями", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const user = await api.addUser(rnd.getUser());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set('catid', category.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + }, + "description": "Обратите внимание: с категорий не должно быть связано ни одного события." + }, + "response": [] + }, + { + "name": "Изменение имени категории на уже занятое", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category1, category2\r", + " try {\r", + " category1 = await api.addCategory(rnd.getCategory());\r", + " category2 = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(category1.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : category2.name\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Публикация уже опубликованного события", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction : \"PUBLISH_EVENT\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Публикация отмененного события", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.rejectEvent(event.id);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction : \"PUBLISH_EVENT\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Отмена опубликованного события", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction : \"REJECT_EVENT\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Добавление повторного запроса от пользователя на участие в событии", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.request.addQueryParams([`eventId=` + event.id]);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests?eventId=0", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + }, + { + "name": "Добавление запроса от инициатора события на участие в нём", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.request.addQueryParams([`eventId=` + event.id]);\r", + " pm.collectionVariables.set('uid', user.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests?eventId=0", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + }, + { + "name": "Добавление запроса на участие в неопубликованном событии", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.request.addQueryParams([`eventId=` + event.id]);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests?eventId=0", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + }, + { + "name": "Добавление запроса на участие в событии, у которого заполнен лимит участников", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody.participantLimit = 1;\r", + " eventBody.requestModeration = false;\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser1 = await api.addUser(rnd.getUser());\r", + " const submittedUser2 = await api.addUser(rnd.getUser());\r", + " await api.publishParticipationRequest(event.id, submittedUser1.id);\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.request.addQueryParams([`eventId=` + event.id]);\r", + " pm.collectionVariables.set('uid', submittedUser2.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests?eventId=0", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + }, + { + "name": "Изменение опубликованного события от имени пользователя", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.collectionVariables.set(\"response\", event);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " eventDate : \"2124-10-11 23:10:05\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Попытка принять заявку на участие в событии, когда лимит уже достигнут", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true;\r", + " eventBody['participantLimit'] = 1;\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser1 = await api.addUser(rnd.getUser());\r", + " const submittedUser2 = await api.addUser(rnd.getUser());\r", + " const requestToParticipate = await api.publishParticipationRequest(event.id, submittedUser1.id);\r", + " const requestToParticipate2 = await api.publishParticipationRequest(event.id, submittedUser2.id);\r", + " await api.acceptParticipationRequest(event.id, user.id, requestToParticipate.id);\r", + " pm.collectionVariables.set('uid', user.id);\r", + " pm.collectionVariables.set('eid', event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({requestIds: [requestToParticipate2.id],\r", + " status:\"CONFIRMED\"}),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId/requests", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId", + "requests" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется\n- нельзя подтвердить заявку, если уже достигнут лимит по заявкам на данное событие\n- статус можно изменить только у заявок, находящихся в состоянии ожидания\n- если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки необходимо отклонить" + }, + "response": [] + }, + { + "name": "Попытка отменить уже принятую заявку на участие в событии", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true;\r", + " eventBody['participantLimit'] = 1;\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " const requestToParticipate = await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " await api.acceptParticipationRequest(event.id, user.id, requestToParticipate.id);\r", + " pm.collectionVariables.set('uid', user.id);\r", + " pm.collectionVariables.set('eid', event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({requestIds: [requestToParticipate.id],\r", + " status:\"REJECTED\"}),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 409 и данные в формате json\", function () {\r", + " pm.response.to.have.status(409);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId/requests", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId", + "requests" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется\n- нельзя подтвердить заявку, если уже достигнут лимит по заявкам на данное событие\n- статус можно изменить только у заявок, находящихся в состоянии ожидания\n- если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки необходимо отклонить" + }, + "response": [] + } + ] + }, + { + "name": "Category", + "item": [ + { + "name": "Добавление новой категории", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let category;\r", + " try {\r", + " category = rnd.getCategory();\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(category),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + }, + "description": "Обратите внимание: имя категории должно быть уникальным" + }, + "response": [] + }, + { + "name": "Получение категорий", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", + "let founded;\r", + "target.forEach(function(element){\r", + " if (element.id == source.id) founded = element\r", + "});\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + " pm.expect(target[0]).to.have.property('id');\r", + " pm.expect(target[0]).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " if (founded) {\r", + " pm.expect(source.id).equal(founded.id, 'Идентификатор категории должен соответствовать идентификатору категории добавленной ранее');\r", + " pm.expect(source.name).equal(founded.name, 'Название категории должно соответствовать названию категории добавленной ранее');\r", + " } else {\r", + " pm.expect.fail(\"No matching category found in the response\");\r", + " }\r", + "});\r", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " pm.collectionVariables.set(\"response\", category)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories?from=0&size=1000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество категорий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество категорий в наборе" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение информации о категории по её идентификатору", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " pm.collectionVariables.set(\"response\", category)\r", + " pm.collectionVariables.set(\"catid\", category.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор категории должен соответствовать идентификатору в запросе');\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно соответствовать названию категории с указанным идентификатором');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}", + "description": "(Required) id категории" + } + ] + } + }, + "response": [] + }, + { + "name": "Удаление категории", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const findedCategory = await api.findCategory(category.id);\r", + " pm.collectionVariables.set(\"catid\", category.id)\r", + " pm.collectionVariables.set(\"response\", findedCategory)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});\r", + "\r", + "source = pm.collectionVariables.get('response');\r", + "catId = pm.collectionVariables.get('catid');\r", + "\r", + "pm.test(\"Категория должна быть найдена до удаления\", function () {\r", + " pm.expect(source.id).equal(catId, 'Идентификтор категории должен совпадать с удаляемым');\r", + "});\r", + "\r", + "pm.sendRequest({\r", + " url: pm.collectionVariables.get(\"baseUrl\") + \"/categories/\" + catId,\r", + " method: 'GET',\r", + " }, (error, response) => {\r", + " pm.test(\"Категория не должна быть найдена после удаления\", function () {\r", + " pm.expect(response.code).to.eql(404);\r", + " });\r", + " });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + }, + "description": "Обратите внимание: с категорий не должно быть связано ни одного события." + }, + "response": [] + }, + { + "name": "Изменение категории", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + " let category\r", + " try {\r", + " category = await api.addCategory(rnd.getCategory());\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + " pm.collectionVariables.set(\"catid\", Number(category.id))\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " name : rnd.getCategory().name\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Категория должна содержать поля: id, name\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Название категории должно совпадать с отправленным');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/categories/:catId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories", + ":catId" + ], + "variable": [ + { + "key": "catId", + "value": "{{catid}}" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "Поиск пользователей", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Пользователи должны содержать поля: id, name, email\", function () {\r", + " pm.expect(target[0]).to.have.property('id');\r", + " pm.expect(target[0]).to.have.property('name');\r", + " pm.expect(target[0]).to.have.property('email');\r", + "});\r", + "\r", + "pm.test(\"Должен быть найден только один пользователь по заданному фильтру\", function () {\r", + " pm.expect(target.length).to.eql(1);\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target[0].id).equal(pm.collectionVariables.get(\"uid\"));\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/users?ids={{uid}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ], + "query": [ + { + "key": "ids", + "value": "{{uid}}", + "description": "id пользователей" + }, + { + "key": "ids", + "value": "-10833646", + "description": "id пользователей", + "disabled": true + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора", + "disabled": true + }, + { + "key": "size", + "value": "10", + "description": "количество элементов в наборе", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Добавление нового пользователя", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let user;\r", + " try {\r", + " user = rnd.getUser();\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(user),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Пользователь должен содержать поля: id, name, email\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('name');\r", + "pm.expect(target).to.have.property('email');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(source.name).equal(target.name, 'Имя пользователя должно соответствовать отправленному в запросе');\r", + " pm.expect(source.email).equal(target.email, 'Почта пользователя должна соответствовать отправленной в запросе');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Удаление пользователя", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});\r", + "const source = pm.collectionVariables.get('response');\r", + "const userId = pm.collectionVariables.get('uid');\r", + "\r", + "pm.test(\"Пользователь должен быть найден до выполнения запроса\", function(){\r", + " pm.expect(source.length).to.eql(1);\r", + " pm.expect(source[0].id).to.eql(userId);\r", + "});\r", + "let body\r", + "const req = {\r", + " url: \"http://localhost:8080/admin/users?ids=\" + pm.collectionVariables.get(\"uid\"),\r", + " method: \"GET\",\r", + " body: body == null ? \"\" : JSON.stringify(body),\r", + " header: { \"Content-Type\": \"application/json\" },\r", + " };\r", + "pm.sendRequest(req, (error, response) => {\r", + " pm.test(\"Пользователь должен быть удалён после выполнения запроса\", function(){\r", + " pm.expect(response.json().length).to.eql(0);\r", + " });\r", + "})" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const foundedUser = await api.findUser(user.id);\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"response\", foundedUser)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/users/:userId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users", + ":userId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id пользователя" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Event", + "item": [ + { + "name": "Добавление нового события", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let event;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " event = rnd.getEvent(category.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(event),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201); \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(target.title).equal(source.title, 'Название события должно соответствовать названию события в запросе');\r", + " pm.expect(target.annotation).equal(source.annotation, 'Аннотация события должна соответствовать аннотации события в запросе');\r", + " pm.expect(target.paid.toString()).equal(source.paid.toString(), 'Стоимость события должна соответствовать стоимости события в запросе');\r", + " pm.expect(target.eventDate).equal(source.eventDate, 'Дата проведения события должна соответствовать дате проведения события в запросе');\r", + " pm.expect(target.description).equal(source.description, 'Описание события должно соответствовать описание события в запросе');\r", + " pm.expect(target.participantLimit.toString()).equal(source.participantLimit.toString(), 'Лимит участников события должно соответствовать лимиту участников события в запросе');\r", + " pm.expect(target.location.lat.toString()).equal(source.location.lat.toString(), 'Широта локации проведения события должна соответствовать широте локации проведения события в запросе');\r", + " pm.expect(target.location.lon.toString()).equal(source.location.lon.toString(), 'Долгота локации проведения события должна соответствовать долготе локации проведения события в запросе');\r", + " pm.expect(target.requestModeration.toString()).equal(source.requestModeration.toString(), 'Необходимость модерации события должна соответствовать необходимости модерации события в запросе');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента" + }, + "response": [] + }, + { + "name": "Добавление запроса от текущего пользователя на участие в событии", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " pm.request.removeQueryParams(['eventId']);\r", + " pm.request.addQueryParams([`eventId=` + event.id]);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201); \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "var query = {};\r", + "pm.request.url.query.all().forEach((param) => { query[param.key] = param.value});\r", + "\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", + "});\r", + "\r", + "pm.test(\"При создании у запроса на участие должен быть статус PENDING\", function () {\r", + " pm.expect(target.status).equal(\"PENDING\");\r", + "});\r", + "\r", + "pm.test(\"Id ивента в запросе и в ответе должны совпадать\", function () {\r", + " pm.expect(target.event.toString()).equal(query['eventId'].toString());\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests?eventId=0", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "query": [ + { + "key": "eventId", + "value": "0", + "description": "(Required) id события" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- нельзя добавить повторный запрос\n- инициатор события не может добавить запрос на участие в своём событии\n- нельзя участвовать в неопубликованном событии\n- если у события достигнут лимит запросов на участие - необходимо вернуть ошибку\n- если для события отключена пре-модерация запросов на участие, то запрос должен автоматически перейти в состояние подтвержденного" + }, + "response": [] + }, + { + "name": "Поиск событий", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['users', 'categories']);\r", + " pm.request.addQueryParams([`users=` + user.id, 'categories=' + category.id]);\r", + " pm.collectionVariables.set('response', event);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[0];\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать искомому событию');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Идентификатор категории должен соответствовать искомой категории');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость посещения события должна соответствовать искомому событию');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате искомого события');\r", + " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать искомому событию');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать искомому событию');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Число участников события должно соответствовать искомому событию');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/events?users=0&states=PUBLISHED&categories=0&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2097-09-06%2013%3A30%3A38&from=0&size=1000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events" + ], + "query": [ + { + "key": "users", + "value": "0", + "description": "список id пользователей, чьи события нужно найти" + }, + { + "key": "states", + "value": "PUBLISHED", + "description": "список состояний в которых находятся искомые события" + }, + { + "key": "categories", + "value": "0", + "description": "список id категорий в которых будет вестись поиск" + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие" + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие" + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе" + } + ] + }, + "description": "Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия" + }, + "response": [] + }, + { + "name": "Получение событий, добавленных текущим пользователем", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json()[0];\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate\", function () {\r", + " pm.expect(target).to.contain.keys('id', 'title', 'annotation', 'category', 'paid', 'eventDate');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/events?from=0&size=1000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events" + ], + "query": [ + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество элементов в наборе" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение событий с возможностью фильтрации", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.request.removeQueryParams(['text', 'categories', 'paid']);\r", + " pm.request.addQueryParams([`text=` + event.annotation, 'categories=' + category.id, 'paid=' + event.paid]);\r", + " pm.collectionVariables.set('response', event);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[0];\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать аннотации события с указанным идентификатором');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Категория события должна соответствовать категории события с указанным идентификатором');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость события должна соответствовать стоимости события с указанным идентификатором');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате проведения события с указанным идентификатором');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать названию события с указанным идентификатором');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/events?text=0&categories=0&paid=true&rangeStart=2022-01-06%2013%3A30%3A38&rangeEnd=2097-09-06%2013%3A30%3A38&onlyAvailable=false&sort=EVENT_DATE&from=0&size=1000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "events" + ], + "query": [ + { + "key": "text", + "value": "0", + "description": "текст для поиска в содержимом аннотации и подробном описании события" + }, + { + "key": "categories", + "value": "0", + "description": "список идентификаторов категорий в которых будет вестись поиск" + }, + { + "key": "paid", + "value": "true", + "description": "поиск только платных/бесплатных событий" + }, + { + "key": "rangeStart", + "value": "2022-01-06%2013%3A30%3A38", + "description": "дата и время не раньше которых должно произойти событие" + }, + { + "key": "rangeEnd", + "value": "2097-09-06%2013%3A30%3A38", + "description": "дата и время не позже которых должно произойти событие" + }, + { + "key": "onlyAvailable", + "value": "false", + "description": "только события у которых не исчерпан лимит запросов на участие" + }, + { + "key": "sort", + "value": "EVENT_DATE", + "description": "Вариант сортировки: по дате события или по количеству просмотров" + }, + { + "key": "from", + "value": "0", + "description": "количество событий, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество событий в наборе" + } + ] + }, + "description": "Обратите внимание: \n- это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события\n- текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв\n- если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, которые произойдут позже текущей даты и времени\n- информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" + }, + "response": [] + }, + { + "name": "Получение подробной информации об опубликованном событии по его идентификатору", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.collectionVariables.set('response', event);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, views, confirmedRequests, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('views');\r", + "pm.expect(target).to.have.property('confirmedRequests');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать аннотации события с указанным идентификатором');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Категория события должна соответствовать категории события с указанным идентификатором');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость события должна соответствовать стоимости события с указанным идентификатором');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать дате проведения события с указанным идентификатором');\r", + " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать описанию события с указанным идентификатором');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать названию события с указанным идентификатором');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников события должен соответствовать лимиту участников события с указанным идентификатором');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/events/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "events", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n- событие должно быть опубликовано\n- информация о событии должна включать в себя количество просмотров и количество подтвержденных запросов\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики" + }, + "response": [] + }, + { + "name": "Получение полной информации о событии добавленном текущим пользователем", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " pm.collectionVariables.set(\"uid\", user.id)\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение информации о заявках текущего пользователя на участие в чужих событиях", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " const requestToJoin = await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " pm.collectionVariables.set('response', requestToJoin);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[0];\r", + "\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " const sourceCreated = new Date(source.created);\r", + " const targetCreated = new Date(target.created);\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор запроса на участие в событии должен соответствовать идентификатору запроса, созданного ранее');\r", + " pm.expect(source.requester).equal(target.requester, 'Пользователя, запрашивающий доступ на участие в событии, должен соответствовать указанному пользователю');\r", + " pm.expect(source.event).equal(target.event, 'Событие, доступ к которому запрашивает пользователь, должно соответствовать событию, доступ к которому пользователь запрашивал доступ ранее');\r", + " pm.test('Время создания запроса на участие в событии должно соответствовать времени создания запроса, созданного ранее указанным пользователем', function () {\r", + " pm.expect(sourceCreated.getTime()).to.equal(targetCreated.getTime(), 'Времена не совпадают');\r", + "});\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение информации о запросах на участие в событии текущего пользователя", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " const requestToJoin = await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " pm.collectionVariables.set('response', requestToJoin);\r", + " pm.collectionVariables.set('uid', user.id);\r", + " pm.collectionVariables.set('eid', event.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[0];\r", + "\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " const sourceCreated = new Date(source.created);\r", + " const targetCreated = new Date(target.created);\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор запроса на участие в событии должен соответствовать идентификатору запроса на участие в событии указанного пользователя');\r", + " pm.expect(source.requester).equal(target.requester, 'Автор запроса на участие в событии должен соответствовать указанному пользователю');\r", + " pm.expect(source.event).equal(target.event, 'Событие в ответе должно соответствовать событию с запросом на участие от указанного пользователя');\r", + " pm.test('Время создания запроса на участие в событии должно соответствовать времени создания запроса на участие в событии указанного пользователя', function () {\r", + " pm.expect(sourceCreated.getTime()).to.equal(targetCreated.getTime(), 'Времена не совпадают');\r", + "});\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId/requests", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId", + "requests" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + } + }, + "response": [] + }, + { + "name": "Редактирование данных события и его статуса (отклонение/публикация).", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " let event2 = rnd.getEvent(category.id)\r", + " event2.stateAction = \"PUBLISH_EVENT\"\r", + " pm.collectionVariables.set('response', event2);\r", + " pm.collectionVariables.set(\"eid\", event.id)\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: event2,\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, publishedOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('publishedOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация события должна соответствовать искомому событию');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость события должна соответствовать искомому событию');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения события должна соответствовать искомому событию');\r", + " pm.expect(source.description).equal(target.description, 'Описание события должно соответствовать искомому событию');\r", + " pm.expect(source.title).equal(target.title, 'Название события должно соответствовать искомому событию');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников события должен соответствовать искомому событию');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + ":eventId" + ], + "variable": [ + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события" + } + ] + }, + "description": "Обратите внимание:\n - дата начала события должна быть не ранее чем за час от даты публикации.\n- событие должно быть в состоянии ожидания публикации" + }, + "response": [] + }, + { + "name": "Изменение события добавленного текущим пользователем", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " pm.collectionVariables.set(\"uid\", user.id);\r", + " pm.collectionVariables.set(\"eid\", event.id);\r", + " pm.collectionVariables.set(\"response\", event);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " stateAction: \"CANCEL_REVIEW\"\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get(\"response\");\r", + "const target = pm.response.json();\r", + "\r", + "\r", + "pm.test(\"Событие должно содержать поля: id, title, annotation, category, paid, eventDate, initiator, description, participantLimit, state, createdOn, location, requestModeration\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('annotation');\r", + "pm.expect(target).to.have.property('category');\r", + "pm.expect(target).to.have.property('paid');\r", + "pm.expect(target).to.have.property('eventDate');\r", + "pm.expect(target).to.have.property('initiator');\r", + "pm.expect(target).to.have.property('description');\r", + "pm.expect(target).to.have.property('participantLimit');\r", + "pm.expect(target).to.have.property('state');\r", + "pm.expect(target).to.have.property('createdOn');\r", + "pm.expect(target).to.have.property('location');\r", + "pm.expect(target).to.have.property('requestModeration');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.annotation).equal(target.annotation, 'Аннотация отменённого события должна соответствовать аннотации события до отмены');\r", + " pm.expect(source.category.id).equal(target.category.id, 'Категория отменённого события должна соответствовать категории события до отмены');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость отменённого события должна соответствовать стоимости события до отмены');\r", + " pm.expect(source.eventDate).equal(target.eventDate, 'Дата проведения отменённого события должна соответствовать дате проведения события до отмены');\r", + " pm.expect(source.description).equal(target.description, 'Описание отменённого события должно соответствовать описанию события до отмены');\r", + " pm.expect(source.title).equal(target.title, 'Название отменённого события должно соответствовать названию события до отмены');\r", + " pm.expect(source.participantLimit.toString()).equal(target.participantLimit.toString(), 'Лимит участников отменённого события должен соответствовать лимиту участников события до отмены');\r", + " pm.expect(source.paid.toString()).equal(target.paid.toString(), 'Стоимость отменённого события должна соответствовать стоимости события до отмены');\r", + "});\r", + "\r", + "pm.test(\"Событие должно иметь статус PENDING при создании и статус CANCELED после выполнения запроса\", function () {\r", + " pm.expect(source.state).equal(\"PENDING\");\r", + " pm.expect(target.state).equal(\"CANCELED\");\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{{request_body}}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id отменяемого события" + } + ] + }, + "description": "Обратите внимание: Отменить можно только событие в состоянии ожидания модерации." + }, + "response": [] + }, + { + "name": "Отмена своего запроса на участие в событии", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " const requestToJoin = await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " pm.collectionVariables.set('response', requestToJoin);\r", + " pm.collectionVariables.set('uid', submittedUser.id);\r", + " pm.collectionVariables.set('reqid', requestToJoin.id);\r", + "\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "const source = pm.collectionVariables.get('response');\r", + "\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", + "});\r", + "\r", + "pm.test(\"При создании у запроса на участие должен быть статус PENDING, а при удалении - CANCELED\", function () {\r", + " pm.expect(source.status).equal(\"PENDING\");\r", + " pm.expect(target.status).equal(\"CANCELED\");\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " const sourceCreated = new Date(source.created);\r", + " const targetCreated = new Date(target.created);\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор отменённого запроса на участие в событии должен соответствовать идентификатору запроса до отмены');\r", + " pm.expect(source.requester).equal(target.requester, 'Пользователь, отменяющий запрос на участие в событии, должен соответствовать текущему пользователю');\r", + " pm.expect(source.event).equal(target.event, 'Событие отменённого запроса на участие должно соответствовать запросу на участие в событии до отмены');\r", + " pm.test('Дата создания отменённого запроса на участие в событии должна соответствовать дате создания запроса до отмены', function () {\r", + " pm.expect(sourceCreated.getTime()).to.equal(targetCreated.getTime(), 'Время не совпадает');\r", + "});\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/users/:userId/requests/:requestId/cancel", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "requests", + ":requestId", + "cancel" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "requestId", + "value": "{{reqid}}", + "description": "(Required) id запроса на участие" + } + ] + } + }, + "response": [] + }, + { + "name": "Отклонение запроса на участие в событии", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " const requestToJoin = await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " pm.collectionVariables.set('response', requestToJoin);\r", + " pm.collectionVariables.set('uid', user.id);\r", + " pm.collectionVariables.set('eid', event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({requestIds: [requestToJoin.id],\r", + " status:\"REJECTED\"}),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[\"rejectedRequests\"][0];\r", + "\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " const sourceCreated = new Date(source.created);\r", + " const targetCreated = new Date(target.created);\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор запроса на участие в событии должен соответствовать идентификатору запроса на участие в событии указанного пользователя');\r", + " pm.expect(source.requester).equal(target.requester, 'Пользователь, запрашивающий доступ на участие в событии должен пользователю, отправившему запрос на участие в событии указанного пользователя ранее');\r", + " pm.expect(source.event).equal(target.event, 'Событие, запрос на участие в котором надо подтвердить, должно соответствовать событию указанного пользователя');\r", + " pm.test('Время создания запроса на участие в событии после подтверждения должно соответствовать времени создания запроса на участие в событии указанного пользователя до подтверждения', function () {\r", + " pm.expect(sourceCreated.getTime()).to.equal(targetCreated.getTime(), 'Время не совпадают');\r", + "});\r", + "});\r", + "\r", + "pm.test(\"Запрос на участие должен иметь статус PENDING при создании и статус REJECTED после выполнения запроса\", function () {\r", + " pm.expect(source.status).equal(\"PENDING\");\r", + " pm.expect(target.status).equal(\"REJECTED\");\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"requestIds\": [\n 1,\n 2,\n 3\n ],\n \"status\": \"CONFIRMED\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId/requests", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId", + "requests" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется\n- нельзя подтвердить заявку, если уже достигнут лимит по заявкам на данное событие\n- статус можно изменить только у заявок, находящихся в состоянии ожидания\n- если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки необходимо отклонить" + }, + "response": [] + }, + { + "name": "Изменение статуса (подтверждена, отменена) заявок на участие в событии текущего пользователя", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let eventBody = rnd.getEvent(category.id);\r", + " eventBody['requestModeration'] = true\r", + " let event = await api.addEvent(user.id, eventBody);\r", + " event = await api.publishEvent(event.id);\r", + " const submittedUser = await api.addUser(rnd.getUser());\r", + " const requestToJoin = await api.publishParticipationRequest(event.id, submittedUser.id);\r", + " pm.collectionVariables.set('response', requestToJoin);\r", + " pm.collectionVariables.set('uid', user.id);\r", + " pm.collectionVariables.set('eid', event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({requestIds: [requestToJoin.id],\r", + " status:\"CONFIRMED\"}),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json()[\"confirmedRequests\"][0];\r", + "\r", + "pm.test(\"Запрос на участие должен содержать поля: id, requester, event, status, created\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('requester');\r", + "pm.expect(target).to.have.property('event');\r", + "pm.expect(target).to.have.property('status');\r", + "pm.expect(target).to.have.property('created');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " const sourceCreated = new Date(source.created);\r", + " const targetCreated = new Date(target.created);\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор запроса на участие в событии должен соответствовать идентификатору запроса на участие в событии указанного пользователя');\r", + " pm.expect(source.requester).equal(target.requester, 'Пользователь, запрашивающий доступ на участие в событии должен пользователю, отправившему запрос на участие в событии указанного пользователя ранее');\r", + " pm.expect(source.event).equal(target.event, 'Событие, запрос на участие в котором надо подтвердить, должно соответствовать событию указанного пользователя');\r", + " pm.test('Время создания запроса на участие в событии после подтверждения должно соответствовать времени создания запроса на участие в событии указанного пользователя до подтверждения', function () {\r", + " pm.expect(sourceCreated.getTime()).to.equal(targetCreated.getTime(), 'Времена не совпадают');\r", + "});\r", + "});\r", + "\r", + "pm.test(\"Запрос на участие должен иметь статус PENDING при создании и статус CONFIRMED после выполнения запроса\", function () {\r", + " pm.expect(source.status).equal(\"PENDING\");\r", + " pm.expect(target.status).equal(\"CONFIRMED\");\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId/requests", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId", + "requests" + ], + "variable": [ + { + "key": "userId", + "value": "{{uid}}", + "description": "(Required) id текущего пользователя" + }, + { + "key": "eventId", + "value": "{{eid}}", + "description": "(Required) id события текущего пользователя" + } + ] + }, + "description": "Обратите внимание:\n- если для события лимит заявок равен 0 или отключена пре-модерация заявок, то подтверждение заявок не требуется\n- нельзя подтвердить заявку, если уже достигнут лимит по заявкам на данное событие\n- статус можно изменить только у заявок, находящихся в состоянии ожидания\n- если при подтверждении данной заявки, лимит заявок для события исчерпан, то все неподтверждённые заявки необходимо отклонить" + }, + "response": [] + } + ] + }, + { + "name": "Compilation", + "item": [ + { + "name": "Добавление новой подборки", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let compilation;\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " compilation = rnd.getCompilation(event.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(compilation),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = JSON.parse(pm.request.body.raw);\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Подборка должны содержать поля: id, title, pinned, events\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('pinned');\r", + "pm.expect(target).to.have.property('events');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(target.id).to.not.be.null;\r", + " pm.expect(target.title).to.be.a(\"string\");\r", + " pm.expect(target.events).to.be.an(\"array\");\r", + "\r", + " pm.expect(source.events[0]).equal(target.events[0].id, 'Идентификаторы событий в подборке должен быть идентичен идентификаторам, указанным при создании подборки ');\r", + " pm.expect(source.title).equal(target.title, 'Название подборки должно соответствовать указанному при создании');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations" + ] + } + }, + "response": [] + }, + { + "name": "Получение подборки событий по её id", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const compilation = await api.addCompilation(rnd.getCompilation());\r", + " pm.collectionVariables.set('response', compilation);\r", + " pm.collectionVariables.set('compid', compilation.id);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Подборка должны содержать поля: id, title, pinned, events\", function () {\r", + "pm.expect(target).to.have.property('id');\r", + "pm.expect(target).to.have.property('title');\r", + "pm.expect(target).to.have.property('pinned');\r", + "pm.expect(target).to.have.property('events');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.id).equal(target.id, 'Идентификатор подборки должен соответствовать идентификатору подборки добавленной ранее');\r", + " pm.expect(source.title).equal(target.title, 'Название подборки должно соответствовать названию подборки добавленной ранее');\r", + " pm.expect(source.pinned).equal(target.pinned, 'Закреплённость подборки должна соответствовать закреплённости подборки добавленной ранее');\r", + " pm.expect(source.events.join()).equal(target.events.join(), 'События подборки должны соответствовать событиям подборки добавленной ранее');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/compilations/:compId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "compilations", + ":compId" + ], + "variable": [ + { + "key": "compId", + "value": "{{compid}}", + "description": "(Required) id подборки" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение подборок событий", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let newComp = rnd.getCompilation();\r", + " newComp['pinned'] = true;\r", + " const compilation = await api.addCompilation(newComp);\r", + " pm.collectionVariables.set('response', compilation);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get('response');\r", + "const target = pm.response.json();\r", + "let founded;\r", + "target.forEach(function(element){if (element.id == source.id) founded = element});\r", + "\r", + "pm.test(\"Подборка должны содержать поля: id, title, pinned, events\", function () {\r", + "pm.expect(target[0]).to.have.property('id');\r", + "pm.expect(target[0]).to.have.property('title');\r", + "pm.expect(target[0]).to.have.property('pinned');\r", + "pm.expect(target[0]).to.have.property('events');\r", + "});\r", + "\r", + "pm.test(\"Данные в ответе должны соответствовать данным в запросе\", function () {\r", + " pm.expect(source.id).equal(founded.id, 'Идентификатор подборки должен соответствовать идентификатору подборки добавленной ранее');\r", + " pm.expect(source.title).equal(founded.title, 'Название подборки должно соответствовать названию подборки добавленной ранее');\r", + " pm.expect(source.pinned).equal(founded.pinned, 'Закрепленность подборки должна соответствовать закрепленности подборки добавленной ранее');\r", + " pm.expect(source.events.join()).equal(founded.events.join(), 'События подборки должны соответствовать событиям подборки добавленной ранее');\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/compilations?pinned=true&from=0&size=1000", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "compilations" + ], + "query": [ + { + "key": "pinned", + "value": "true", + "description": "искать только закрепленные/не закрепленные подборки" + }, + { + "key": "from", + "value": "0", + "description": "количество элементов, которые нужно пропустить для формирования текущего набора" + }, + { + "key": "size", + "value": "1000", + "description": "количество элементов в наборе" + } + ] + } + }, + "response": [] + }, + { + "name": "Удаление подборки", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});\r", + "\r", + "let source = pm.collectionVariables.get('response');\r", + "\r", + "pm.test(\"Подборка должна быть найдена до удаления\", function () {\r", + " pm.expect(source).not.to.be.null;\r", + "});\r", + "\r", + "let body\r", + "const req = {\r", + " url: \"http://localhost:8080/compilations?from=0&size=1000\" + pm.collectionVariables.get(\"uid\"),\r", + " method: \"GET\",\r", + " body: body == null ? \"\" : JSON.stringify(body),\r", + " header: { \"Content-Type\": \"application/json\" },\r", + " };\r", + "pm.sendRequest(req, (error, response) => {\r", + " pm.test(\"Подборка должна быть удалена после выполнения запроса\", function(){\r", + " response.json().forEach(element => {\r", + " if(element.id == pm.collectionVariables.get('compid')){\r", + " throw new Error(\"Подборка все еще находится в списке существующих\");\r", + " }\r", + " })\r", + " });\r", + "})\r", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const compilation = await api.addCompilation(rnd.getCompilation());\r", + " const foundedCompilation = await api.findCompilation(compilation.id);\r", + " pm.collectionVariables.set('compid', compilation.id);\r", + " pm.collectionVariables.set('response', foundedCompilation);\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/admin/compilations/:compId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations", + ":compId" + ], + "variable": [ + { + "key": "compId", + "value": "{{compid}}", + "description": "(Required) id подборки" + } + ] + } + }, + "response": [] + }, + { + "name": "Обновить информацию о подборке", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " const compilation = await api.addCompilation(rnd.getCompilation());\r", + " const foundedCompilation = await api.findCompilation(compilation.id);\r", + " pm.collectionVariables.set('compid', compilation.id);\r", + " pm.collectionVariables.set('response', foundedCompilation);\r", + " pm.collectionVariables.set('eid', event.id);\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify({\r", + " events : [event.id],\r", + " pinned: true,\r", + " title: rnd.getCompilation().name\r", + " }),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " // выполняем наш скрипт\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200\", function () {\r", + " pm.response.to.be.ok; \r", + "});\r", + "\r", + "source = pm.collectionVariables.get('response');\r", + "compId = pm.collectionVariables.get('compid');\r", + "eventId = pm.collectionVariables.get('eid');\r", + "\r", + "pm.test(\"Событие не должно быть найдено в подборке до добавления\", function () {\r", + " pm.expect(source.events.length).equal(0);\r", + "});\r", + "\r", + "pm.sendRequest({\r", + " url: pm.collectionVariables.get(\"baseUrl\") + \"/compilations/\" + compId,\r", + " method: 'GET',\r", + " }, (error, response) => {\r", + " \r", + " pm.test(\"Событие должно быть найдено в подборке после добавления\", function () {\r", + " pm.expect(response.json().events[0].id).equal(eventId);\r", + " });\r", + " });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{{request_body}}" + }, + "url": { + "raw": "{{baseUrl}}/admin/compilations/:compId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "compilations", + ":compId" + ], + "variable": [ + { + "key": "compId", + "value": "{{compid}}" + } + ] + } + }, + "response": [] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "API = class {", + " constructor(postman, verbose = false, baseUrl = \"http://localhost:8080\") {", + " this.baseUrl = baseUrl;", + " this.pm = postman;", + " this._verbose = verbose;", + " }", + "", + " async addUser(user, verbose=null) {", + " return this.post(\"/admin/users\", user, \"Ошибка при добавлении нового пользователя: \", verbose);", + " }", + "", + " async addCategory(category, verbose=null) {", + " return this.post(\"/admin/categories\", category, \"Ошибка при добавлении новой категории: \", verbose);", + " }", + "", + " async addEvent(userId, event, verbose=null) {", + " return this.post(\"/users/\" + userId + \"/events\", event, \"Ошибка при добавлении нового события: \", verbose);", + " }", + "", + " async addCompilation(compilation, verbose=null) {", + " return this.post(\"/admin/compilations\", compilation, \"Ошибка при добавлении новой подборки: \", verbose);", + " }", + "", + " async publishParticipationRequest(eventId, userId, verbose=null) {", + " return this.post('/users/' + userId + '/requests?eventId=' + eventId, null, \"Ошибка при добавлении нового запроса на участие в событии\", verbose);", + " }", + "", + " async publishEvent(eventId, verbose=null) {", + " return this.patch('/admin/events/' + eventId, {stateAction: \"PUBLISH_EVENT\"}, \"Ошибка при публикации события\", verbose);", + " }", + " ", + " async rejectEvent(eventId, verbose=null) {", + " return this.patch('/admin/events/' + eventId, {stateAction: \"REJECT_EVENT\"}, \"Ошибка при отмене события\", verbose);", + " }", + "", + " async acceptParticipationRequest(eventId, userId, reqId, verbose=null) {", + " return this.patch('/users/' + userId + '/events/' + eventId + '/requests', {requestIds:[reqId], status: \"CONFIRMED\"}, \"Ошибка при принятии заявки на участие в событии\", verbose);", + " }", + "", + " async findCategory(catId, verbose=null) {", + " return this.get('/categories/' + catId, null, \"Ошибка при поиске категории по id\", verbose);", + " }", + "", + " async findCompilation(compId, verbose=null) {", + " return this.get('/compilations/' + compId, null, \"Ошибка при поиске подборки по id\", verbose);", + " }", + "", + " async findEvent(eventId, verbose=null) {", + " return this.get('/events/' + eventId, null, \"Ошибка при поиске события по id\", verbose);", + " }", + "", + " async findUser(userId, verbose=null) {", + " return this.get('/admin/users?ids=' + userId, null, \"Ошибка при поиске пользователя по id\", verbose);", + " }", + "", + " async post(path, body, errorText = \"Ошибка при выполнении post-запроса: \", verbose=null) {", + " return this.sendRequest(\"POST\", path, body, errorText, verbose);", + " }", + "", + " async patch(path, body = null, errorText = \"Ошибка при выполнении patch-запроса: \", verbose=null) {", + " return this.sendRequest(\"PATCH\", path, body, errorText, verbose);", + " }", + "", + " async get(path, body = null, errorText = \"Ошибка при выполнении get-запроса: \", verbose=null) {", + " return this.sendRequest(\"GET\", path, body, errorText, verbose);", + " }", + " async sendRequest(method, path, body=null, errorText = \"Ошибка при выполнении запроса: \", verbose=null) {", + " return new Promise((resolve, reject) => {", + " verbose = verbose == null ? this._verbose : verbose;", + " const request = {", + " url: this.baseUrl + path,", + " method: method,", + " body: body == null ? \"\" : JSON.stringify(body),", + " header: { \"Content-Type\": \"application/json\" },", + " };", + " if(verbose) {", + " console.log(\"Отправляю запрос: \", request);", + " }", + "", + " try {", + " this.pm.sendRequest(request, (error, response) => {", + " if(error || (response.code >= 400 && response.code <= 599)) {", + " let err = error ? error : JSON.stringify(response.json());", + " console.error(\"При выполнении запроса к серверу возникла ошика.\\n\", err,", + " \"\\nДля отладки проблемы повторите такой же запрос к вашей программе \" + ", + " \"на локальном компьютере. Данные запроса:\\n\", JSON.stringify(request));", + "", + " reject(new Error(errorText + err));", + " }", + " if(verbose) {", + " console.log(\"Результат обработки запроса: код состояния - \", response.code, \", тело: \", response.json());", + " }", + " if (response.stream.length === 0){", + " reject(new Error('Отправлено пустое тело ответа'))", + " }else{", + " resolve(response.json());", + " }", + " });", + " ", + " } catch(err) {", + " if(verbose) {", + " console.error(errorText, err);", + " }", + " return Promise.reject(err);", + " }", + " });", + " }", + "};", + "", + "RandomUtils = class {", + " constructor() {}", + "", + " getUser() {", + " return {", + " name: pm.variables.replaceIn('{{$randomFullName}}'),", + " email: pm.variables.replaceIn('{{$randomEmail}}')", + " };", + " }", + "", + " getCategory() {", + " return {", + " name: pm.variables.replaceIn('{{$randomWord}}') + Math.floor(Math.random() * 10000 * Math.random()).toString()", + " };", + " }", + "", + " getEvent(categoryId) {", + " return {", + " annotation: pm.variables.replaceIn('{{$randomLoremParagraph}}'),", + " category: categoryId,", + " description: pm.variables.replaceIn('{{$randomLoremParagraphs}}'),", + " eventDate: this.getFutureDateTime(),", + " location: {", + " lat: parseFloat(pm.variables.replaceIn('{{$randomLatitude}}')),", + " lon: parseFloat(pm.variables.replaceIn('{{$randomLongitude}}')),", + " },", + " paid: pm.variables.replaceIn('{{$randomBoolean}}'),", + " participantLimit: pm.variables.replaceIn('{{$randomInt}}'),", + " requestModeration: pm.variables.replaceIn('{{$randomBoolean}}'),", + " title: pm.variables.replaceIn('{{$randomLoremSentence}}'),", + " }", + " }", + "", + " getCompilation(...eventIds) {", + " return {", + " title: pm.variables.replaceIn('{{$randomLoremSentence}}').slice(0, 50),", + " pinned: pm.variables.replaceIn('{{$randomBoolean}}'),", + " events: eventIds", + " };", + " }", + "", + "", + " getFutureDateTime(hourShift = 5, minuteShift=0, yearShift=0) {", + " let moment = require('moment');", + "", + " let m = moment();", + " m.add(hourShift, 'hour');", + " m.add(minuteShift, 'minute');", + " m.add(yearShift, 'year');", + "", + " return m.format('YYYY-MM-DD HH:mm:ss');", + " }", + "", + " getWord(length = 1) {", + " let result = '';", + " const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';", + " const charactersLength = characters.length;", + " let counter = 0;", + " while (counter < length) {", + " result += characters.charAt(Math.floor(Math.random() * charactersLength));", + " counter += 1;", + " }", + " return result;", + " }", + "}" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:8080", + "type": "string" + }, + { + "key": "name", + "value": "" + }, + { + "key": "usersCount", + "value": 1, + "type": "number" + }, + { + "key": "catid", + "value": 1, + "type": "number" + }, + { + "key": "request_body", + "value": "" + }, + { + "key": "mail", + "value": "" + }, + { + "key": "response", + "value": "" + }, + { + "key": "uid", + "value": 1, + "type": "number" + }, + { + "key": "catname", + "value": "" + }, + { + "key": "eid", + "value": 1, + "type": "number" + }, + { + "key": "compid", + "value": 1, + "type": "number" + }, + { + "key": "toCheck", + "value": "" + }, + { + "key": "newDataToSet", + "value": "" + }, + { + "key": "uid1", + "value": "" + }, + { + "key": "reqid", + "value": 1, + "type": "number" + }, + { + "key": "catId", + "value": "" + }, + { + "key": "confirmedRequests", + "value": "" + }, + { + "key": "responseArr", + "value": "" + }, + { + "key": "source1", + "value": "" + }, + { + "key": "source2", + "value": "" + }, + { + "key": "fromId", + "value": "0" + }, + { + "key": "source", + "value": "" + } + ] +} diff --git a/postman/ewm-stat-service.json b/postman/ewm-stat-service.json new file mode 100644 index 0000000..67516a7 --- /dev/null +++ b/postman/ewm-stat-service.json @@ -0,0 +1,1072 @@ +{ + "info": { + "_postman_id": "fd4ffdc0-705a-450d-8d39-7c4eef7743f8", + "name": "Test Explore With Me Plus - Stat - Microservices", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "23073145", + "_collection_link": "https://universal-shadow-295426.postman.co/workspace/My-Workspace~4200f6aa-0504-44b1-8a1d-707d0dcbd5ce/collection/13708500-fd4ffdc0-705a-450d-8d39-7c4eef7743f8?action=share&source=collection_link&creator=23073145" + }, + "item": [ + { + "name": "Сохранение информации о том, что к эндпоинту был запрос", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " let post;\r", + " try {\r", + " post = rnd.getPost();\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "\r", + " pm.request.body.update({\r", + " mode: 'raw',\r", + " raw: JSON.stringify(post),\r", + " options: { raw: { language: 'json' } }\r", + " });\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 201 и данные в формате json\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/hit", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "hit" + ] + }, + "description": "Сохранение информации о том, что на uri конкретного сервиса был отправлен запрос пользователем. Название сервиса, uri и ip пользователя указаны в теле запроса." + }, + "response": [] + }, + { + "name": "Получение статистики по посещениям. Обратите внимание: значение даты и времени нужно закодировать (например используя java.net.URLEncoder.encode)(Тест на /events/{id})", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event = await api.publishEvent(event.id);\r", + " pm.collectionVariables.set(\"uri\", '/events/' + event.id);\r", + "\r", + " pm.sendRequest({\r", + " url: \"http://localhost:8080/events/\" + event.id,\r", + " method: \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " baseUrl = pm.collectionVariables.get('baseUrl')\r", + " pm.sendRequest({\r", + " url: baseUrl + \"/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris=/events/\" + event.id.toString() + \"&unique=false\",\r", + " method: \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.collectionVariables.set('source', response.json());\r", + " pm.sendRequest({\r", + " url: \"http://localhost:8080/events/\" + event.id,\r", + " method: \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " });\r", + " });\r", + " });\r", + "\r", + " } catch (err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => { }, 1000);\r", + "\r", + "setTimeout(async () => {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + "},\r", + " 100\r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json()[0];\r", + "\r", + "pm.test(\"Посты должны содержать поля: app, uri, hits\", function () {\r", + " pm.expect(target).to.have.all.keys('app', 'uri', 'hits');\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get(\"source\")[0];\r", + "\r", + "\r", + "pm.test(\"После выполнения запроса GET /events/{id} должно увеличиться количество хитов.\", function(){\r", + " pm.expect(source.hits + 1).equal(target.hits, 'Количество хитов после выполнения запроса GET /events/{id} должно быть больше на 1.');\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris={{uri}}&unique=false", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "end", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)" + } + ] + } + }, + "response": [] + }, + { + "name": "Тест взаимодействия сервисов", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try { \r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " let event1 = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event1 = await api.publishEvent(event1.id);\r", + " let event2 = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " event2 = await api.publishEvent(event2.id);\r", + " pm.collectionVariables.set(\"uri\", '/events/' + event1.id + '&uris=/events/' + event2.id);\r", + "\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events/\" + event1.id,\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events/\" + event2.id,\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events/\" + event2.id,\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " });\r", + " });\r", + " });\r", + " \r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json();\r", + "\r", + "pm.test(\"Посты должны содержать поля: app, uri, hits\", function () {\r", + " pm.expect(target[0]).to.have.all.keys('app', 'uri', 'hits');\r", + " pm.expect(target[1]).to.have.all.keys('app', 'uri', 'hits');\r", + "});\r", + "\r", + "pm.test(\"В теле ответа должна соблюдаться сортировка по убыванию количества просмотров\", function(){\r", + " pm.expect(target[0].hits).to.be.above(target[1].hits);\r", + "});\r", + "\r", + "pm.test(\"Проверка соответствия реального количества просмотров событий и сохраненных хитов\", function(){\r", + " pm.expect(target[0].hits).equal(2);\r", + " pm.expect(target[1].hits).equal(1);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris={{uri}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "end", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "uris", + "value": "aliqua o", + "description": "Список uri для которых нужно выгрузить статистику", + "disabled": true + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Получение статистики по посещениям. Обратите внимание: значение даты и времени нужно закодировать (например используя java.net.URLEncoder.encode)(Тест на /events)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try { \r", + " pm.collectionVariables.set(\"uri\", '/events'); \r", + " \r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " baseUrl = pm.collectionVariables.get('baseUrl')\r", + " pm.sendRequest({\r", + " url: baseUrl + \"/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris=/events&unique=false\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.collectionVariables.set('source', response.json());\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " });\r", + " });\r", + " });\r", + " \r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json()[0];\r", + "\r", + "pm.test(\"Посты должны содержать поля: app, uri, hits\", function () {\r", + " pm.expect(target).to.have.all.keys('app', 'uri', 'hits');\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get(\"source\")[0];\r", + "\r", + "\r", + "pm.test(\"После выполнения запроса GET /events должно увеличиться количество хитов.\", function(){\r", + " pm.expect(source.hits + 1).equal(target.hits, 'Количество хитов после выполнения запроса GET /events должно быть больше на 1.');\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris={{uri}}&unique=false", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "end", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)" + } + ] + } + }, + "response": [] + }, + { + "name": "Получение статистики по посещениям. (Тест на опциональность и работу параметра unique)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const main = async () => {\r", + " const api = new API(pm);\r", + " const rnd = new RandomUtils();\r", + "\r", + " try { \r", + " pm.collectionVariables.set(\"uri\", '/events'); \r", + " \r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.sendRequest({\r", + " url : \"http://localhost:8080/events\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " baseUrl = pm.collectionVariables.get('baseUrl')\r", + " pm.sendRequest({\r", + " url: baseUrl + \"/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris=/events&unique=true\",\r", + " method : \"GET\",\r", + " header: { \"Content-Type\": \"application/json\" }\r", + " }, (error, response) => {\r", + " pm.collectionVariables.set('source', response.json());\r", + " });\r", + " });\r", + " });\r", + " } catch(err) {\r", + " console.error(\"Ошибка при подготовке тестовых данных.\", err);\r", + " }\r", + "};\r", + "\r", + "const interval = setInterval(() => {}, 1000);\r", + "\r", + "setTimeout(async () => \r", + " {\r", + " try {\r", + " await main();\r", + " } catch (e) {\r", + " console.error(e);\r", + " } finally {\r", + " clearInterval(interval);\r", + " }\r", + " }, \r", + " 100 \r", + ");" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 200 и данные в формате json при запросе без опционального параметра unique\", function () {\r", + " pm.response.to.be.ok; \r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "const target = pm.response.json()[0];\r", + "\r", + "pm.test(\"Посты должны содержать поля: app, uri, hits\", function () {\r", + " pm.expect(target).to.have.all.keys('app', 'uri', 'hits');\r", + "});\r", + "\r", + "const source = pm.collectionVariables.get(\"source\")[0];\r", + "\r", + "pm.test(\"Количество уникальных просмотров с одного ip должно равняться 1\", function () {\r", + " pm.expect(source.hits == 1);\r", + "})\r", + "\r", + "pm.test(\"Количество просмотров с одного ip должно быть больше 1\", function () {\r", + " pm.expect(target.hits > 1);\r", + "})" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?start=2020-05-05 00:00:00&end=2035-05-05 00:00:00&uris={{uri}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "end", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "uris", + "value": "aliqua o", + "description": "Список uri для которых нужно выгрузить статистику", + "disabled": true + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Получение статистики по посещениям. (Тест на верную обработку запроса с неверными датами начала и конца диапазона времени)", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.be.badRequest;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?start=2035-05-05 00:00:00&end=2020-05-05 00:00:00&uris={{uri}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "end", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Тест на верную обработку запроса без даты начала", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.be.badRequest;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?end=2020-05-05 00:00:00&uris={{uri}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")", + "disabled": true + }, + { + "key": "end", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Тест на верную обработку запроса без даты конца", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Ответ должен содержать код статуса 400\", function () {\r", + " pm.response.to.be.badRequest;\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/stats?start=2035-05-05 00:00:00&uris={{uri}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "stats" + ], + "query": [ + { + "key": "start", + "value": "2035-05-05 00:00:00", + "description": "(Required) Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")" + }, + { + "key": "end", + "value": "2020-05-05 00:00:00", + "description": "(Required) Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")", + "disabled": true + }, + { + "key": "uris", + "value": "{{uri}}", + "description": "Список uri для которых нужно выгрузить статистику" + }, + { + "key": "unique", + "value": "false", + "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)", + "disabled": true + } + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "// Адрес Eureka", + "const eurekaHost = 'http://localhost:8761';", + "const appNames = [\"STATS-SERVER\"];", + "", + "appNames.forEach((appName) => {", + " pm.sendRequest({", + " url: `${eurekaHost}/eureka/v2/apps/${appName}`,", + " method: 'GET',", + " header: {", + " 'Accept': 'application/json'", + " }", + "}, function (err, res) {", + " if (err) {", + " console.log('Ошибка запроса к Eureka: ', err);", + " return;", + " }", + "", + " // Проверяем успешность ответа", + " if (res.code === 200) {", + " let jsonData = res.json();", + " ", + " // Пытаемся достать первый инстанс сервиса (если таких несколько)", + " let instance = jsonData.application.instance[0];", + "", + " if (instance) {", + " let host = instance.hostName;", + " let port = instance.port['$']; // Порт сервиса", + " ", + " // Сохраняем host и port в переменные окружения", + " pm.collectionVariables.set(`baseUrl`, \"http://localhost:\" + port);", + " ", + " } else {", + " console.error('Инстанс сервиса не найден.');", + " }", + " } else {", + " console.error('Не удалось получить данные из Eureka: статус ', res.status);", + " }", + "});", + "})", + "API = class {", + " constructor(postman, verbose = false, baseUrl = \"http://localhost:9090\") {", + " this.baseUrl = baseUrl;", + " this.pm = postman;", + " this._verbose = verbose;", + " }", + "", + " async addPost(post, verbose=null) {", + " return this.post(\"/hit\", post, \"Ошибка при сохранении информации о запросе к эндпойнту: \", verbose);", + " } ", + "", + " async addUser(user, verbose=null) {", + " return this.post(\"/admin/users\", user, \"http://localhost:8080\", \"Ошибка при добавлении нового пользователя: \", verbose);", + " }", + "", + " async addCategory(category, verbose=null) {", + " return this.post(\"/admin/categories\", category, \"http://localhost:8080\", \"Ошибка при добавлении новой категории: \", verbose);", + " }", + "", + " async addEvent(userId, event, verbose=null) {", + " return this.post(\"/users/\" + userId + \"/events\", event, \"http://localhost:8080\", \"Ошибка при добавлении нового события: \", verbose);", + " }", + "", + " async publishEvent(eventId, verbose=null) {", + " return this.patch('/admin/events/' + eventId, {stateAction: \"PUBLISH_EVENT\"},\"Ошибка при публикации события\", verbose);", + " }", + "", + " async patch(path, body = null, errorText = \"Ошибка при выполнении patch-запроса: \", verbose=null) {", + " return this.sendRequest(\"PATCH\", path, \"http://localhost:8080\", body, errorText);", + " }", + "", + " async post(path, body, newBaseUrl=null, errorText = \"Ошибка при выполнении post-запроса: \", verbose=null) {", + " return this.sendRequest(\"POST\", path, newBaseUrl, body, errorText);", + " }", + "", + " async sendRequest(method, path, newBaseUrl=null, body=null, errorText = \"Ошибка при выполнении запроса: \", verbose=null) {", + " return new Promise((resolve, reject) => {", + " verbose = verbose == null ? this._verbose : verbose;", + " let request;", + " if (newBaseUrl==null)", + " request = {", + " url: this.baseUrl + path,", + " method: method,", + " body: body == null ? \"\" : JSON.stringify(body),", + " header: { \"Content-Type\": \"application/json\" },", + " };", + " else", + " request = {", + " url: newBaseUrl + path,", + " method: method,", + " body: body == null ? \"\" : JSON.stringify(body),", + " header: { \"Content-Type\": \"application/json\" },", + " };", + "", + " if(verbose) {", + " console.log(\"Отправляю запрос: \", request);", + " }", + "", + " try {", + " this.pm.sendRequest(request, (error, response) => {", + " if(error || (response.code >= 400 && response.code <= 599)) {", + " let err = error ? error : JSON.stringify(response.json());", + " console.error(\"При выполнении запроса к серверу возникла ошика.\\n\", err,", + " \"\\nДля отладки проблемы повторите такой же запрос к вашей программе \" + ", + " \"на локальном компьютере. Данные запроса:\\n\", JSON.stringify(request));", + "", + " reject(new Error(errorText + err));", + " }", + "", + " if(verbose) {", + " console.log(\"Результат обработки запроса: код состояния - \", response.code, \", тело: \", response.json());", + " }", + " try{", + " resolve(response.json());", + " } catch(err){", + " resolve(response);", + " }", + " ", + " });", + " } catch(err) {", + " if(verbose) {", + " console.error(errorText, err);", + " }", + " return Promise.reject(err);", + " }", + " });", + " }", + "};", + "", + "RandomUtils = class {", + " constructor() {}", + "", + " getPost() {", + " return {", + " app: \"ewm-main-service\",", + " uri: \"/events/\" + pm.variables.replaceIn('{{$randomInt}}'),", + " ip: pm.variables.replaceIn('{{$randomIP}}'),", + " timestamp: this.getPastDateTime()", + " }", + " }", + "", + " getUser() {", + " return {", + " name: pm.variables.replaceIn('{{$randomFullName}}'),", + " email: pm.variables.replaceIn('{{$randomEmail}}')", + " };", + " }", + "", + " getCategory() {", + " return {", + " name: pm.variables.replaceIn('{{$randomWord}}') + Math.floor(Math.random() * 100).toString()", + " };", + " }", + "", + " getEvent(categoryId) {", + " return {", + " annotation: pm.variables.replaceIn('{{$randomLoremParagraph}}'),", + " category: categoryId,", + " description: pm.variables.replaceIn('{{$randomLoremParagraphs}}'),", + " eventDate: this.getFutureDateTime(),", + " location: {", + " lat: parseFloat(pm.variables.replaceIn('{{$randomLatitude}}')),", + " lon: parseFloat(pm.variables.replaceIn('{{$randomLongitude}}')),", + " },", + " paid: pm.variables.replaceIn('{{$randomBoolean}}'),", + " participantLimit: pm.variables.replaceIn('{{$randomInt}}'),", + " requestModeration: pm.variables.replaceIn('{{$randomBoolean}}'),", + " title: pm.variables.replaceIn('{{$randomLoremSentence}}'),", + " }", + " }", + " ", + " getCompilation(...eventIds) { ", + " return { ", + " title: pm.variables.replaceIn('{{$randomLoremSentence}}'), ", + " pinned: pm.variables.replaceIn('{{$randomBoolean}}'), ", + " events: eventIds ", + " }; ", + " }", + "", + " getPastDateTime(hourShift = 5, minuteShift=0, yearShift=0) {", + " let moment = require('moment');", + "", + " let m = moment();", + " m.subtract(hourShift, 'hour');", + " m.subtract(minuteShift, 'minute');", + " m.subtract(yearShift, 'year');", + "", + " return m.format('YYYY-MM-DD HH:mm:ss');", + " }", + "", + " getFutureDateTime(hourShift = 5, minuteShift=0, yearShift=0) {", + " let moment = require('moment');", + "", + " let m = moment();", + " m.add(hourShift, 'hour');", + " m.add(minuteShift, 'minute');", + " m.add(yearShift, 'year');", + "", + " return m.format('YYYY-MM-DD HH:mm:ss');", + " }", + "}" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:9090", + "type": "string" + }, + { + "key": "uri", + "value": "1" + }, + { + "key": "source", + "value": "" + } + ] +} diff --git a/postman/feature.json b/postman/feature.json deleted file mode 100644 index 3fc3b7f..0000000 --- a/postman/feature.json +++ /dev/null @@ -1,829 +0,0 @@ -{ - "info": { - "_postman_id": "0b4cd248-a26a-4053-b877-6c6779f465ca", - "name": "Feature", - "description": "Тестирование комментариев", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "45621385", - "_collection_link": "https://belkinivan.postman.co/workspace/Belkin-Ivan's-Workspace~70934887-56e4-4bfd-8ac6-47a16ac3b83d/collection/45621385-0b4cd248-a26a-4053-b877-6c6779f465ca?action=share&source=collection_link&creator=45621385" - }, - "item": [ - { - "name": "Admin", - "item": [ - { - "name": "Удаление комментария администратором", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Код ответа должен быть 204\", function () {", - " pm.response.to.have.status(204);", - "});", - "", - "pm.test(\"Тело ответа должно быть пустым\", function () {", - " pm.response.to.have.body('');", - "});" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "const prepareAdminTest = async () => {", - " const api = new API(pm);", - " const rnd = new RandomUtils();", - " ", - " try {", - " const user = await api.addUser(rnd.getUser());", - " const category = await api.addCategory(rnd.getCategory());", - " let event = await api.addEvent(user.id, rnd.getEvent(category.id));", - " event = await api.publishEvent(event.id);", - " ", - " const commentData = {", - " text: \"Комментарий для тестирования удаления администратором\"", - " };", - " ", - " const comment = await api.post(", - " `/users/${user.id}/comments/${event.id}`, ", - " commentData, ", - " pm.collectionVariables.get(\"baseUrl\")", - " );", - " ", - " pm.collectionVariables.set(\"adminCommentId\", comment.id);", - " console.log(\"Создан комментарий для удаления администратором:\", comment.id);", - " ", - " } catch(err) {", - " console.error(\"Ошибка при подготовке комментария:\", err);", - " }", - "};", - "", - "prepareAdminTest();" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - } - ], - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{baseUrl}}/admin/comments/{{adminCommentId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "admin", - "comments", - "{{adminCommentId}}" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Private", - "item": [ - { - "name": "Создание комментария", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "const prepareCommentTest = async () => {", - " const api = new API(pm);", - " const rnd = new RandomUtils();", - " ", - " try {", - " // Создаем пользователя и событие", - " const user = await api.addUser(rnd.getUser());", - " const category = await api.addCategory(rnd.getCategory());", - " let event = await api.addEvent(user.id, rnd.getEvent(category.id));", - " event = await api.publishEvent(event.id);", - " ", - " // Сохраняем ID для использования в тестах", - " pm.collectionVariables.set(\"userId\", user.id);", - " pm.collectionVariables.set(\"eventId\", event.id);", - " ", - " // Подготавливаем данные комментария", - " const commentData = {", - " text: \"Комментарий к событию.\"", - " };", - " ", - " pm.request.body.update({", - " mode: 'raw',", - " raw: JSON.stringify(commentData),", - " options: { raw: { language: 'json' } }", - " });", - " ", - " console.log(\"Подготовлены данные для создания комментария:\", commentData);", - " ", - " } catch(err) {", - " console.error(\"Ошибка при подготовке данных:\", err);", - " }", - "};", - "", - "prepareCommentTest();" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - }, - { - "listen": "test", - "script": { - "exec": [ - "// Проверка успешного создания комментария", - "pm.test(\"Статус код должен быть 201\", function () {", - " pm.response.to.have.status(201);", - "});", - "", - "pm.test(\"Ответ должен содержать поля комментария\", function () {", - " const response = pm.response.json();", - " pm.expect(response).to.have.property('id');", - " pm.expect(response).to.have.property('text');", - " pm.expect(response).to.have.property('userId');", - " pm.expect(response).to.have.property('eventId');", - " pm.expect(response).to.have.property('createdDate');", - " ", - " // Сохраняем ID комментария для дальнейших тестов", - " pm.collectionVariables.set(\"commentId\", response.id);", - "});", - "", - "pm.test(\"Текст комментария должен совпадать с отправленным\", function () {", - " const response = pm.response.json();", - " const requestBody = JSON.parse(pm.request.body.raw);", - " pm.expect(response.text).to.equal(requestBody.text);", - "});", - "", - "pm.test(\"ID события должны совпадать с отправленным\", function () {", - " const response = pm.response.json();", - " const eventId = pm.collectionVariables.get(\"eventId\");", - " pm.expect(response.eventId).to.equal(parseInt(eventId));", - "});", - "", - "pm.test(\"ID пользователя должны совпадать с отправленным\", function () {", - " const response = pm.response.json();", - " const userId = pm.collectionVariables.get(\"userId\");", - " pm.expect(response.userId).to.equal(parseInt(userId));", - "});" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\"text\": \"Новый комментарий\"}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/users/{{userId}}/comments/{{eventId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users", - "{{userId}}", - "comments", - "{{eventId}}" - ] - } - }, - "response": [] - }, - { - "name": "Создание слишком длинного комментария", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "// Создаем комментарий длиннее 5000 символов", - "const longText = 'a'.repeat(5001);", - "const invalidComment = {", - " text: longText", - "};", - "", - "pm.request.body.update({", - " mode: 'raw',", - " raw: JSON.stringify(invalidComment),", - " options: { raw: { language: 'json' } }", - "});" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - }, - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Статус код должен быть 400\", function () {", - " pm.response.to.have.status(400);", - "});" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\"text\": \"a\".repeat(5001)}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/users/{{userId}}/comments/{{eventId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users", - "{{userId}}", - "comments", - "{{eventId}}" - ] - } - }, - "response": [] - }, - { - "name": "Создание пустого комментария", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "const emptyComment = {", - " text: \"\"", - "};", - "", - "pm.request.body.update({", - " mode: 'raw',", - " raw: JSON.stringify(emptyComment),", - " options: { raw: { language: 'json' } }", - "});" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - }, - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Статус код должен быть 400\", function () {", - " pm.response.to.have.status(400);", - "});" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\"text\": \"\"}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/users/{{userId}}/comments/{{eventId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users", - "{{userId}}", - "comments", - "{{eventId}}" - ] - } - }, - "response": [] - }, - { - "name": "Получение комментариев пользователя", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Статус код должен быть 200\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"В ответе должен быть список комментариев пользователя\", function () {", - " const response = pm.response.json();", - " const userId = pm.collectionVariables.get(\"userId\");", - " ", - " pm.expect(response).to.be.an('array');", - " ", - " response.forEach(comment => {", - " pm.expect(comment.userId).to.equal(parseInt(userId));", - " });", - "});" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{baseUrl}}/users/{{userId}}/comments?from=0&size=10", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users", - "{{userId}}", - "comments" - ], - "query": [ - { - "key": "from", - "value": "0" - }, - { - "key": "size", - "value": "10" - } - ] - } - }, - "response": [] - }, - { - "name": "Обновление комментария", - "event": [ - { - "listen": "prerequest", - "script": { - "exec": [ - "const updateData = {", - " text: \"Обновленный текст комментария\"", - "};", - "", - "pm.request.body.update({", - " mode: 'raw',", - " raw: JSON.stringify(updateData),", - " options: { raw: { language: 'json' } }", - "});" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - }, - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Статус код должен быть 200\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"Текст комментария должен быть обновленный\", function () {", - " const response = pm.response.json();", - " const requestBody = JSON.parse(pm.request.body.raw);", - " pm.expect(response.text).to.equal(requestBody.text);", - "});" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\"text\": \"Обновленный текст комментария\"}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/users/{{userId}}/comments/{{eventId}}/{{commentId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users", - "{{userId}}", - "comments", - "{{eventId}}", - "{{commentId}}" - ] - } - }, - "response": [] - }, - { - "name": "Удаление комментария пользователем", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Статус код должен быть 204\", function () {", - " pm.response.to.have.status(204);", - "});", - "", - "pm.test(\"Тело ответа должно быть пустым\", function () {", - " pm.response.to.have.body('');", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "{{baseUrl}}/users/{{userId}}/comments/{{commentId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users", - "{{userId}}", - "comments", - "{{commentId}}" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Public", - "item": [ - { - "name": "Получение комментариев события", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"Статус код должен быть 200\", function () {", - " pm.response.to.have.status(200);", - "});", - "", - "pm.test(\"В ответе должен быть список комментариев\", function () {", - " const response = pm.response.json();", - " pm.expect(response).to.be.an('array');", - " pm.expect(response.length).to.be.greaterThan(0);", - "});", - "", - "pm.test(\"Комментарии должны относиться к определенному событию\", function () {", - " const response = pm.response.json();", - " const eventId = pm.collectionVariables.get(\"eventId\");", - " ", - " response.forEach(comment => {", - " pm.expect(comment.eventId).to.equal(parseInt(eventId));", - " });", - "});", - "", - "pm.test(\"Комментарии должны быть отсортированы по дате создания\", function () {", - " const response = pm.response.json();", - " if (response.length > 1) {", - " const firstDate = new Date(response[0].createdDate);", - " const secondDate = new Date(response[1].createdDate);", - " pm.expect(firstDate.getTime()).to.be.greaterThan(secondDate.getTime());", - " }", - "});" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "const prepareCommentTest = async () => {\r", - " const api = new API(pm);\r", - " const rnd = new RandomUtils();\r", - " \r", - " try {\r", - " // Создаем пользователя и событие\r", - " const user = await api.addUser(rnd.getUser());\r", - " const category = await api.addCategory(rnd.getCategory());\r", - " let event = await api.addEvent(user.id, rnd.getEvent(category.id));\r", - " event = await api.publishEvent(event.id);\r", - " const comment = await api.addComment(user.id, event.id, rnd.getComment()); \r", - " // Сохраняем ID для использования в тестах\r", - " pm.collectionVariables.set(\"userId\", user.id);\r", - " pm.collectionVariables.set(\"eventId\", event.id);\r", - " \r", - " console.log(\"Подготовлены данные для создания комментария:\", comment);\r", - " \r", - " } catch(err) {\r", - " console.error(\"Ошибка при подготовке данных:\", err);\r", - " }\r", - "};\r", - "\r", - "prepareCommentTest();" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{baseUrl}}/comments/event/{{eventId}}?from=0&size=10", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "comments", - "event", - "{{eventId}}" - ], - "query": [ - { - "key": "from", - "value": "0" - }, - { - "key": "size", - "value": "10" - } - ] - } - }, - "response": [] - } - ] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "requests": {}, - "exec": [ - "// Класс для работы с API комментариев", - "API = class {", - " constructor(postman, verbose = false, baseUrl = \"http://localhost:8080\") {", - " this.baseUrl = baseUrl;", - " this.pm = postman;", - " this._verbose = verbose;", - " }", - "", - " async addUser(user, verbose=null) {", - " return this.post(\"/admin/users\", user, \"Ошибка при добавлении нового пользователя: \", verbose);", - " }", - "", - " async addCategory(category, verbose=null) {", - " return this.post(\"/admin/categories\", category, \"Ошибка при добавлении новой категории: \", verbose);", - " }", - "", - " async addComment(userId, eventId, comment, verbose=null) {", - " return this.post(\"/users/\" + userId + \"/comments/\" + eventId, comment, \"Ошибка при добавлении нового комментария: \", verbose);", - " }", - "", - " async addEvent(userId, event, verbose=null) {", - " return this.post(\"/users/\" + userId + \"/events\", event, \"Ошибка при добавлении нового события: \", verbose);", - " }", - "", - " async publishEvent(eventId, verbose=null) {", - " return this.patch('/admin/events/' + eventId, {stateAction: \"PUBLISH_EVENT\"}, \"Ошибка при публикации события\", verbose);", - " }", - "", - " async post(path, body, errorText = \"Ошибка при выполнении post-запроса: \", verbose=null) {", - " return this.sendRequest(\"POST\", path, body, errorText, verbose);", - " }", - "", - " async patch(path, body = null, errorText = \"Ошибка при выполнении patch-запроса: \", verbose=null) {", - " return this.sendRequest(\"PATCH\", path, body, errorText);", - " }", - "", - " async get(path, body = null, errorText = \"Ошибка при выполнении get-запроса: \", verbose=null) {", - " return this.sendRequest(\"GET\", path, body, errorText, verbose);", - " }", - "", - " async sendRequest(method, path, body=null, errorText = \"Ошибка при выполнении запроса: \", verbose=null) {", - " return new Promise((resolve, reject) => {", - " verbose = verbose == null ? this._verbose : verbose;", - " ", - " const request = {", - " url: this.baseUrl + path,", - " method: method,", - " body: body == null ? \"\" : JSON.stringify(body),", - " header: { \"Content-Type\": \"application/json\" },", - " };", - "", - " if(verbose) {", - " console.log(\"Отправляю запрос: \", request);", - " }", - "", - " try {", - " this.pm.sendRequest(request, (error, response) => {", - " if(error || (response.code >= 400 && response.code <= 599)) {", - " let err = error ? error : JSON.stringify(response.json());", - " console.error(\"При выполнении запроса к серверу возникла ошибка.\\n\", err,", - " \"\\nДля отладки проблемы повторите такой же запрос к вашей программе \" + ", - " \"на локальном компьютере. Данные запроса:\\n\", JSON.stringify(request));", - "", - " reject(new Error(errorText + err));", - " }", - "", - " if(verbose) {", - " console.log(\"Результат обработки запроса: код состояния - \", response.code, \", тело: \", response.json());", - " }", - " ", - " if (response.stream.length === 0){", - " reject(new Error('Отправлено пустое тело ответа'))", - " }else{", - " resolve(response.json());", - " }", - " });", - " } catch(err) {", - " if(verbose) {", - " console.error(errorText, err);", - " }", - " return Promise.reject(err);", - " }", - " });", - " }", - "};", - "", - "RandomUtils = class {", - " constructor() {}", - "", - " getUser() {", - " return {", - " name: pm.variables.replaceIn('{{$randomFullName}}'),", - " email: pm.variables.replaceIn('{{$randomEmail}}')", - " };", - " }", - "", - " getCategory() {", - " return {", - " name: pm.variables.replaceIn('{{$randomWord}}') + Math.floor(Math.random() * 100).toString()", - " };", - " }", - "", - " getComment() {", - " return {", - " text: pm.variables.replaceIn('{{$randomWord}}') + Math.floor(Math.random() * 100).toString()", - " };", - " }", - "", - " getEvent(categoryId) {", - " return {", - " annotation: \"Событие для проверки комментариев\",", - " category: categoryId,", - " description: \"Описание события для проверки комментариев\",", - " eventDate: this.getFutureDateTime(),", - " location: {", - " lat: 55.5555,", - " lon: 44.4444,", - " },", - " paid: false,", - " participantLimit: 0,", - " requestModeration: false,", - " title: \"Событие для проверки комментариев\",", - " }", - " }", - " ", - " getFutureDateTime(hourShift = 5, minuteShift=0, yearShift=0) {", - " let moment = require('moment');", - "", - " let m = moment();", - " m.add(hourShift, 'hour');", - " m.add(minuteShift, 'minute');", - " m.add(yearShift, 'year');", - "", - " return m.format('YYYY-MM-DD HH:mm:ss');", - " }", - "}" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "packages": {}, - "requests": {}, - "exec": [ - "" - ] - } - } - ], - "variable": [ - { - "key": "baseUrl", - "value": "http://localhost:8080" - }, - { - "key": "userId", - "value": "" - }, - { - "key": "eventId", - "value": "" - }, - { - "key": "commentId", - "value": "" - }, - { - "key": "adminCommentId", - "value": "" - }, - { - "key": "userEmail", - "value": "" - } - ] -} \ No newline at end of file diff --git a/stats/stats-dto/src/main/java/ru/yandex/practicum/CreateEndpointHitDto.java b/stats/stats-dto/src/main/java/ru/yandex/practicum/CreateEndpointHitDto.java index 8e8f523..d262ea8 100644 --- a/stats/stats-dto/src/main/java/ru/yandex/practicum/CreateEndpointHitDto.java +++ b/stats/stats-dto/src/main/java/ru/yandex/practicum/CreateEndpointHitDto.java @@ -9,6 +9,7 @@ import static ru.yandex.practicum.constants.DateTimeConstants.DATE_TIME_PATTERN; public record CreateEndpointHitDto( + @NotBlank(message = "Название приложения не может быть пустым") String app, diff --git a/stats/stats-dto/src/main/java/ru/yandex/practicum/StatsRequest.java b/stats/stats-dto/src/main/java/ru/yandex/practicum/StatsRequest.java index 307f942..7e31d6b 100644 --- a/stats/stats-dto/src/main/java/ru/yandex/practicum/StatsRequest.java +++ b/stats/stats-dto/src/main/java/ru/yandex/practicum/StatsRequest.java @@ -7,9 +7,13 @@ import static ru.yandex.practicum.constants.DateTimeConstants.DATE_TIME_PATTERN; public record StatsRequest( + LocalDateTime start, + LocalDateTime end, + List uris, + boolean unique ) { public static StatsRequest of(String start, String end, List uris, boolean unique) { diff --git a/stats/stats-dto/src/main/java/ru/yandex/practicum/ViewStatsDto.java b/stats/stats-dto/src/main/java/ru/yandex/practicum/ViewStatsDto.java index 541d276..660b8fd 100644 --- a/stats/stats-dto/src/main/java/ru/yandex/practicum/ViewStatsDto.java +++ b/stats/stats-dto/src/main/java/ru/yandex/practicum/ViewStatsDto.java @@ -1,8 +1,11 @@ package ru.yandex.practicum; public record ViewStatsDto( + String app, + String uri, + Long hits ) { } diff --git a/stats/stats-server/src/main/java/ru/yandex/practicum/exception/ErrorHandler.java b/stats/stats-server/src/main/java/ru/yandex/practicum/exception/ErrorHandler.java index ce7907f..294cc02 100644 --- a/stats/stats-server/src/main/java/ru/yandex/practicum/exception/ErrorHandler.java +++ b/stats/stats-server/src/main/java/ru/yandex/practicum/exception/ErrorHandler.java @@ -14,11 +14,13 @@ public ErrorResponse handleBadRequestException(BadRequestException exception, Ht return buildResponse(HttpStatus.BAD_REQUEST, exception, request); } - private ErrorResponse buildResponse(HttpStatus httpStatus, Exception exception, HttpServletRequest request) { + private ErrorResponse buildResponse( + HttpStatus httpStatus, Exception exception, HttpServletRequest request) { return buildResponse(httpStatus, exception.getMessage(), exception, request); } - private ErrorResponse buildResponse(HttpStatus httpStatus, String message, Exception exception, HttpServletRequest request) { + private ErrorResponse buildResponse( + HttpStatus httpStatus, String message, Exception exception, HttpServletRequest request) { String path = request.getRequestURI(); String httpMethod = request.getMethod(); int statusCode = httpStatus.value(); diff --git a/stats/stats-server/src/main/java/ru/yandex/practicum/exception/ErrorResponse.java b/stats/stats-server/src/main/java/ru/yandex/practicum/exception/ErrorResponse.java index 8f0532e..c8ea4fa 100644 --- a/stats/stats-server/src/main/java/ru/yandex/practicum/exception/ErrorResponse.java +++ b/stats/stats-server/src/main/java/ru/yandex/practicum/exception/ErrorResponse.java @@ -1,10 +1,15 @@ package ru.yandex.practicum.exception; public record ErrorResponse( + String path, + String httpMethod, + int statusCode, + String error, + String message ) { } \ No newline at end of file diff --git a/stats/stats-server/src/main/java/ru/yandex/practicum/model/EndpointHit.java b/stats/stats-server/src/main/java/ru/yandex/practicum/model/EndpointHit.java index e224bbf..47110dd 100644 --- a/stats/stats-server/src/main/java/ru/yandex/practicum/model/EndpointHit.java +++ b/stats/stats-server/src/main/java/ru/yandex/practicum/model/EndpointHit.java @@ -13,6 +13,7 @@ @Setter @EqualsAndHashCode(onlyExplicitlyIncluded = true) public class EndpointHit { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @EqualsAndHashCode.Include @@ -20,7 +21,10 @@ public class EndpointHit { private Long id; private String app; + private String uri; + private String ip; + private LocalDateTime timestamp; } diff --git a/stats/stats-server/src/main/java/ru/yandex/practicum/repository/StatsRepository.java b/stats/stats-server/src/main/java/ru/yandex/practicum/repository/StatsRepository.java index 21c10af..586129d 100644 --- a/stats/stats-server/src/main/java/ru/yandex/practicum/repository/StatsRepository.java +++ b/stats/stats-server/src/main/java/ru/yandex/practicum/repository/StatsRepository.java @@ -2,6 +2,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import ru.yandex.practicum.ViewStatsDto; import ru.yandex.practicum.model.EndpointHit; @@ -17,15 +18,19 @@ public interface StatsRepository extends JpaRepository { GROUP BY e.app, e.uri ORDER BY COUNT(DISTINCT e.ip) DESC """) - List getUniqueStats(LocalDateTime start, LocalDateTime end, List uris); + List getUniqueStats(@Param("start") LocalDateTime start, + @Param("end") LocalDateTime end, + @Param("uris") List uris); @Query(""" - SELECT new ru.yandex.practicum.ViewStatsDto(e.app, e.uri, COUNT(e)) + SELECT new ru.yandex.practicum.ViewStatsDto(e.app, e.uri, COUNT(e.ip)) FROM EndpointHit e WHERE e.timestamp BETWEEN :start AND :end AND (:uris IS NULL OR e.uri IN :uris) GROUP BY e.app, e.uri - ORDER BY COUNT(e) DESC + ORDER BY COUNT(e.ip) DESC """) - List getStats(LocalDateTime start, LocalDateTime end, List uris); + List getStats(@Param("start") LocalDateTime start, + @Param("end") LocalDateTime end, + @Param("uris") List uris); } diff --git a/stats/stats-server/src/main/resources/schema.sql b/stats/stats-server/src/main/resources/schema.sql index bb8b290..6af3298 100644 --- a/stats/stats-server/src/main/resources/schema.sql +++ b/stats/stats-server/src/main/resources/schema.sql @@ -1,3 +1,5 @@ +DROP TABLE IF EXISTS endpoint_hits; + create TABLE IF NOT EXISTS endpoint_hits ( hit_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, app VARCHAR(255) NOT NULL, From d2e4f3882e497cfd686457e271f7c24a7b9992a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Sat, 7 Feb 2026 14:25:22 +0300 Subject: [PATCH 03/10] docs: add comprehensive README with architecture and API documentation --- README.md | 125 +- ewm-main-service-spec.json | 2836 ----------------- ...main-service.json => ewm-main-service.json | 0 ...stat-service.json => ewm-stat-service.json | 0 ewm-stats-service-spec.json | 170 - 5 files changed, 68 insertions(+), 3063 deletions(-) delete mode 100644 ewm-main-service-spec.json rename postman/ewm-main-service.json => ewm-main-service.json (100%) rename postman/ewm-stat-service.json => ewm-stat-service.json (100%) delete mode 100644 ewm-stats-service-spec.json diff --git a/README.md b/README.md index 3671fb5..2879de0 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,68 @@ -# Комментарии — API (техническое задание) - -Краткое описание: набор REST-эндпоинтов для управления комментариями. Три уровня доступа: -- Admin — администраторские операции (удаление комментария по его идентификатору). -- Private — операции пользователя над собственными комментариями (создать, изменить, удалить, получить все свои комментарии). -- Public — публичный доступ (просмотр комментариев конкретного события). - ---- - -## AdminCommentController -Цель: операции управления всеми комментариями. - -- DELETE `/admin/comments/{commentId}` - - Описание: удалить комментарий (жёсткое удаление) по его идентификатору. - - Ограничения: без модерации комментариев. - - Права: только админ. - - Ответ: `204 No Content` при успехе. - ---- - -## PrivateCommentController -Цель: пользовательские операции над собственными комментариями. - -- POST `/users/{userId}/comments/{eventId}` - - Описание: создать новый комментарий от имени пользователя `userId`. - - Ограничения: длина комментария 5000 символов, проверка существования идентификаторов события и пользователя. - - Права: только аутентифицированный пользователь. - - Ответ: `201 Created` с созданным объектом. - -- PATCH `/users/{userId}/comments/{eventId}/{commentId}` - - Описание: обновить собственный комментарий. - - Ограничения: длина комментария 5000 символов, проверка существования идентификаторов комментария, события и пользователя. - - Права: только автор комментария. - - Ответ: обновлённый объект комментария. - -- DELETE `/users/{userId}/comments/{commentId}` - - Описание: удалить собственный комментарий (жёсткое удаление). - - Ограничения: проверка существования идентификаторов комментария и пользователя. - - Права: только автор комментария. - - Ответ: `204 No Content`. - -- GET `/users/{userId}/comments` - - Описание: получить список собственных комментариев. - - Ограничения: организовать пагинацию (по умолчанию по 10 комментариев на странице). - - Права: только аутентифицированный пользователь. - - Ответ: список комментариев. - ---- - -## PublicCommentController -Цель: публичный просмотр комментариев. - -- GET `/comments/event/{eventId}` - - Описание: получить публичный список комментариев к событию. - - Ограничения: организовать пагинацию (по умолчанию по 10 комментариев на странице). - - Доступ: публичный (без авторизации). - - Ответ: список публичных комментариев. \ No newline at end of file +## ExploreWithMe + +### Общая информация +Это микросервисная реализация приложения ExploreWithMe для поиска событий и участия в них. +Исходный монолит разделен на несколько независимых сервисов, взаимодействующих через API Gateway и Feign-клиенты. + +**Технологический стек**: Java 21, Spring Boot, Spring Cloud(Eureka, Gateway, OpenFeign, Config), MapStruct, Lombok, Apache Maven, Docker, PostgreSQL, Postman. + +## Основные сервисы +1. **event-service** - сервис управления событиями, категориями, подборками; +2. **user-service** - сервис управления пользователями.; +3. **request-service** - сервис управления запросами на участие в событии; +4. **comment-service** - сервис управления комментариями; +5. **stats-service** - сервис сбора и предоставления статистики; +6. **discovery-server** (Eureka) - регистрация и обнаружение сервисов; +7. **config-server** - централизованное хранение конфигураций для всех сервисов; +8. **gateway-server** - единая точка входа (API Gateway). + +### Вспомогательный модуль +- **interaction-api** - общие DTO, Feign-клиенты, исключения, перечисления + +### Конфигурация +Все настройки хранятся в **config-server**: +- `gateway-server.yml` - маршрутизация Gateway +- `user-service.yml` - настройки user-service +- `event-service.yml` - настройки event-service +- `request-service.yml` - настройки request-service +- `comment-service.yml` - настройки comment-service +- `stats-service.yml` - настройки stats-service + +Приложение имеет общую базу данных. Каждый сервис имеет собственную schema.sql, обеспечивая изоляцию данных. + +## Внутренний API для взаимодействия сервисов +Для взаимодействия между микросервисами используются Feign Clients и Internal Endpoints: +- GET /internal/events/{eventId} - возвращает полное описание события; +- GET /internal/requests/event/{eventId}/count/{status} - возвращает количество заявок события с определенным статусом; +- POST /internal/requests/events/count - возвращает количество подтвержденных заявок для событий; +- GET /internal/users/{userId} - возвращает краткую информацию о пользователе. + +## Внешний API +Взаимодействие с внешними запросами происходит через API Gateway, который перенаправляет внешние запросы к нужным сервисам.
+Примеры основных эндопинтов:
+### Публичные(доступны всем): +- GET /comments/events/{eventId} - поиск опубликованных комментариев к событию; +- GET /events - поиск событий; +- GET /categories - категории событий; +- GET //categories/{categoryId} - поиск категории по id; +- GET /compilations/{compId} - поиск подборки по id; +- +### Приватные(доступны для авторизованных пользователей): +- POST /users/{userId}/comments - добавить комментарий к событию; +- PATCH /users/{userId}/comments/{commentId} - изменить комментарий; +- GET /users/{userId}/events - события пользователя; +- GET /users/{userId}/events/{eventId}/requests - все заявки на участие в событии; +- PATCH /users/{userId}/requests/{requestId}/cancel - отмена заявки на участие; + +### Административные(доступны только администраторам): +- GET /admin/comments - поиск комментариев; +- DELETE /admin/comments/{commentId} - удалить комментарий; +- POST /admin/categories - создать категорию; +- PATCH /admin/compilations/{compId} - изменить подборку; +- GET /admin/events - расширенный поиск событий с фильтрами; +- POST /admin/users/ - создать пользователя; +- DELETE /admin/users/{userId} - удалить пользователя. + +### Полная спецификация OpenAPI доступна в файлах: +1. [ewm-main-service-spec.json](./ewm-main-service-spec.json) - основная спецификация +2. [ewm-stat-service-spec.json](./ewm-stat-service-spec.json) - спецификация статистики \ No newline at end of file diff --git a/ewm-main-service-spec.json b/ewm-main-service-spec.json deleted file mode 100644 index f28d141..0000000 --- a/ewm-main-service-spec.json +++ /dev/null @@ -1,2836 +0,0 @@ -{ - "openapi": "3.0.1", - "info": { - "description": "Documentation \"Explore With Me\" API v1.0", - "title": "\"Explore With Me\" API сервер", - "version": "1.0" - }, - "servers": [ - { - "description": "Generated server url", - "url": "http://localhost:8080" - } - ], - "tags": [ - { - "description": "Публичный API для работы с подборками событий", - "name": "Public: Подборки событий" - }, - { - "description": "API для работы с категориями", - "name": "Admin: Категории" - }, - { - "description": "Закрытый API для работы с событиями", - "name": "Private: События" - }, - { - "description": "Публичный API для работы с категориями", - "name": "Public: Категории" - }, - { - "description": "API для работы с событиями", - "name": "Admin: События" - }, - { - "description": "Публичный API для работы с событиями", - "name": "Public: События" - }, - { - "description": "Закрытый API для работы с запросами текущего пользователя на участие в событиях", - "name": "Private: Запросы на участие" - }, - { - "description": "API для работы с пользователями", - "name": "Admin: Пользователи" - }, - { - "description": "API для работы с подборками событий", - "name": "Admin: Подборки событий" - } - ], - "paths": { - "/admin/categories": { - "post": { - "description": "Обратите внимание: имя категории должно быть уникальным", - "operationId": "addCategory", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NewCategoryDto" - } - } - }, - "description": "данные добавляемой категории", - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CategoryDto" - } - } - }, - "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_category_name]; 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/categories/{catId}": { - "delete": { - "description": "Обратите внимание: с категорией не должно быть связано ни одного события.", - "operationId": "deleteCategory", - "parameters": [ - { - "description": "id категории", - "in": "path", - "name": "catId", - "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": "Category with id=27 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 category is not empty", - "timestamp": "2023-01-21 16:56:19" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Существуют события, связанные с категорией" - } - }, - "summary": "Удаление категории", - "tags": [ - "Admin: Категории" - ] - }, - "patch": { - "description": "Обратите внимание: имя категории должно быть уникальным", - "operationId": "updateCategory", - "parameters": [ - { - "description": "id категории", - "in": "path", - "name": "catId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CategoryDto" - } - } - }, - "description": "Данные категории для изменения", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CategoryDto" - } - } - }, - "description": "Данные категории изменены" - }, - "404": { - "content": { - "application/json": { - "example": { - "status": "NOT_FOUND", - "reason": "The required object was not found.", - "message": "Category with id=27 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_category_name]; 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/compilations": { - "post": { - "operationId": "saveCompilation", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NewCompilationDto" - } - } - }, - "description": "данные новой подборки", - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CompilationDto" - } - } - }, - "description": "Подборка добавлена" - }, - "400": { - "content": { - "application/json": { - "example": { - "status": "BAD_REQUEST", - "reason": "Incorrectly made request.", - "message": "Field: title. 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_compilation_name]; 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/compilations/{compId}": { - "delete": { - "operationId": "deleteCompilation", - "parameters": [ - { - "description": "id подборки", - "in": "path", - "name": "compId", - "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": "Compilation with id=11 was not found", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Подборка не найдена или недоступна" - } - }, - "summary": "Удаление подборки", - "tags": [ - "Admin: Подборки событий" - ] - }, - "patch": { - "operationId": "updateCompilation", - "parameters": [ - { - "description": "id подборки", - "in": "path", - "name": "compId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateCompilationRequest" - } - } - }, - "description": "данные для обновления подборки", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CompilationDto" - } - } - }, - "description": "Подборка обновлена" - }, - "404": { - "content": { - "application/json": { - "example": { - "status": "NOT_FOUND", - "reason": "The required object was not found.", - "message": "Category with id=27 was not found", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Подборка не найдена или недоступна" - } - }, - "summary": "Обновить информацию о подборке", - "tags": [ - "Admin: Подборки событий" - ] - } - }, - "/admin/events": { - "get": { - "description": "Эндпоинт возвращает полную информацию обо всех событиях подходящих под переданные условия\n\nВ случае, если по заданным фильтрам не найдено ни одного события, возвращает пустой список", - "operationId": "getEvents_2", - "parameters": [ - { - "description": "список id пользователей, чьи события нужно найти", - "in": "query", - "name": "users", - "required": false, - "schema": { - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - } - }, - { - "description": "список состояний в которых находятся искомые события", - "in": "query", - "name": "states", - "required": false, - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - { - "description": "список id категорий в которых будет вестись поиск", - "in": "query", - "name": "categories", - "required": false, - "schema": { - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - } - }, - { - "description": "дата и время не раньше которых должно произойти событие", - "in": "query", - "name": "rangeStart", - "required": false, - "schema": { - "type": "string" - } - }, - { - "description": "дата и время не позже которых должно произойти событие", - "in": "query", - "name": "rangeEnd", - "required": false, - "schema": { - "type": "string" - } - }, - { - "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/EventFullDto" - } - } - } - }, - "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: События" - ] - } - }, - "/admin/events/{eventId}": { - "patch": { - "description": "Редактирование данных любого события администратором. Валидация данных не требуется.\nОбратите внимание:\n - дата начала изменяемого события должна быть не ранее чем за час от даты публикации. (Ожидается код ошибки 409)\n- событие можно публиковать, только если оно в состоянии ожидания публикации (Ожидается код ошибки 409)\n- событие можно отклонить, только если оно еще не опубликовано (Ожидается код ошибки 409)", - "operationId": "updateEvent_1", - "parameters": [ - { - "description": "id события", - "in": "path", - "name": "eventId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateEventAdminRequest" - } - } - }, - "description": "Данные для изменения информации о событии", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventFullDto" - } - } - }, - "description": "Событие отредактировано" - }, - "404": { - "content": { - "application/json": { - "example": { - "status": "NOT_FOUND", - "reason": "The required object was not found.", - "message": "Event with id=2 was not found", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Событие не найдено или недоступно" - }, - "409": { - "content": { - "application/json": { - "example": { - "status": "FORBIDDEN", - "reason": "For the requested operation the conditions are not met.", - "message": "Cannot publish the event because it's not in the right state: PUBLISHED", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Событие не удовлетворяет правилам редактирования" - } - }, - "summary": "Редактирование данных события и его статуса (отклонение/публикация).", - "tags": [ - "Admin: События" - ] - } - }, - "/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": "В случае, если по заданным фильтрам не найдено ни одной категории, возвращает пустой список", - "operationId": "getCategories", - "parameters": [ - { - "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/CategoryDto" - } - } - } - }, - "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": [ - "Public: Категории" - ] - } - }, - "/categories/{catId}": { - "get": { - "description": "В случае, если категории с заданным id не найдено, возвращает статус код 404", - "operationId": "getCategory", - "parameters": [ - { - "description": "id категории", - "in": "path", - "name": "catId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CategoryDto" - } - } - }, - "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": "Category with id=19 was not found", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Категория не найдена или недоступна" - } - }, - "summary": "Получение информации о категории по её идентификатору", - "tags": [ - "Public: Категории" - ] - } - }, - "/compilations": { - "get": { - "description": "В случае, если по заданным фильтрам не найдено ни одной подборки, возвращает пустой список", - "operationId": "getCompilations", - "parameters": [ - { - "description": "искать только закрепленные/не закрепленные подборки", - "in": "query", - "name": "pinned", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "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/CompilationDto" - } - } - } - }, - "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": [ - "Public: Подборки событий" - ] - } - }, - "/compilations/{compId}": { - "get": { - "description": "В случае, если подборки с заданным id не найдено, возвращает статус код 404", - "operationId": "getCompilation", - "parameters": [ - { - "description": "id подборки", - "in": "path", - "name": "compId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CompilationDto" - } - } - }, - "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": "Compilation with id=84 was not found", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Подборка не найдена или недоступна" - } - }, - "summary": "Получение подборки событий по его id", - "tags": [ - "Public: Подборки событий" - ] - } - }, - "/events": { - "get": { - "description": "Обратите внимание: \n- это публичный эндпоинт, соответственно в выдаче должны быть только опубликованные события\n- текстовый поиск (по аннотации и подробному описанию) должен быть без учета регистра букв\n- если в запросе не указан диапазон дат [rangeStart-rangeEnd], то нужно выгружать события, которые произойдут позже текущей даты и времени\n- информация о каждом событии должна включать в себя количество просмотров и количество уже одобренных заявок на участие\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики\n\nВ случае, если по заданным фильтрам не найдено ни одного события, возвращает пустой список", - "operationId": "getEvents_1", - "parameters": [ - { - "description": "текст для поиска в содержимом аннотации и подробном описании события", - "in": "query", - "name": "text", - "required": false, - "schema": { - "maxLength": 7000, - "minLength": 1, - "type": "string" - } - }, - { - "description": "список идентификаторов категорий в которых будет вестись поиск", - "in": "query", - "name": "categories", - "required": false, - "schema": { - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - } - }, - { - "description": "поиск только платных/бесплатных событий", - "in": "query", - "name": "paid", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "description": "дата и время не раньше которых должно произойти событие", - "in": "query", - "name": "rangeStart", - "required": false, - "schema": { - "type": "string" - } - }, - { - "description": "дата и время не позже которых должно произойти событие", - "in": "query", - "name": "rangeEnd", - "required": false, - "schema": { - "type": "string" - } - }, - { - "description": "только события у которых не исчерпан лимит запросов на участие", - "in": "query", - "name": "onlyAvailable", - "required": false, - "schema": { - "type": "boolean", - "default": false - } - }, - { - "description": "Вариант сортировки: по дате события или по количеству просмотров", - "in": "query", - "name": "sort", - "required": false, - "schema": { - "type": "string", - "enum": [ - "EVENT_DATE", - "VIEWS" - ] - } - }, - { - "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/EventShortDto" - } - } - } - }, - "description": "События найдены" - }, - "400": { - "content": { - "application/json": { - "example": { - "status": "BAD_REQUEST", - "reason": "Incorrectly made request.", - "message": "Event must be published", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Запрос составлен некорректно" - } - }, - "summary": "Получение событий с возможностью фильтрации", - "tags": [ - "Public: События" - ] - } - }, - "/events/{id}": { - "get": { - "description": "Обратите внимание:\n- событие должно быть опубликовано\n- информация о событии должна включать в себя количество просмотров и количество подтвержденных запросов\n- информацию о том, что по этому эндпоинту был осуществлен и обработан запрос, нужно сохранить в сервисе статистики\n\nВ случае, если события с заданным id не найдено, возвращает статус код 404", - "operationId": "getEvent_1", - "parameters": [ - { - "description": "id события", - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventFullDto" - } - } - }, - "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": "Запрос составлен некорректно" - }, - "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": "Событие не найдено или недоступно" - } - }, - "summary": "Получение подробной информации об опубликованном событии по его идентификатору", - "tags": [ - "Public: События" - ] - } - }, - "/users/{userId}/events": { - "get": { - "description": "В случае, если по заданным фильтрам не найдено ни одного события, возвращает пустой список", - "operationId": "getEvents", - "parameters": [ - { - "description": "id текущего пользователя", - "in": "path", - "name": "userId", - "required": true, - "schema": { - "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/EventShortDto" - } - } - } - }, - "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: События" - ] - }, - "post": { - "description": "Обратите внимание: дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента", - "operationId": "addEvent", - "parameters": [ - { - "description": "id текущего пользователя", - "in": "path", - "name": "userId", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NewEventDto" - } - } - }, - "description": "данные добавляемого события", - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventFullDto" - } - } - }, - "description": "Событие добавлено" - }, - "400": { - "content": { - "application/json": { - "example": { - "status": "BAD_REQUEST", - "reason": "Incorrectly made request.", - "message": "Field: category. 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": "FORBIDDEN", - "reason": "For the requested operation the conditions are not met.", - "message": "Field: eventDate. Error: должно содержать дату, которая еще не наступила. Value: 2020-12-31T15:10:05", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Событие не удовлетворяет правилам создания" - } - }, - "summary": "Добавление нового события", - "tags": [ - "Private: События" - ] - } - }, - "/users/{userId}/events/{eventId}": { - "get": { - "description": "В случае, если события с заданным id не найдено, возвращает статус код 404", - "operationId": "getEvent", - "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": { - "$ref": "#/components/schemas/EventFullDto" - } - } - }, - "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": "Событие не найдено или недоступно" - } - }, - "summary": "Получение полной информации о событии добавленном текущим пользователем", - "tags": [ - "Private: События" - ] - }, - "patch": { - "description": "Обратите внимание:\n- изменить можно только отмененные события или события в состоянии ожидания модерации (Ожидается код ошибки 409)\n- дата и время на которые намечено событие не может быть раньше, чем через два часа от текущего момента (Ожидается код ошибки 409)\n", - "operationId": "updateEvent", - "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/UpdateEventUserRequest" - } - } - }, - "description": "Новые данные события", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EventFullDto" - } - } - }, - "description": "Событие обновлено" - }, - "400": { - "content": { - "application/json": { - "example": { - "status": "BAD_REQUEST", - "reason": "Incorrectly made request.", - "message": "Event must not be published", - "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=283 was not found", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Событие не найдено или недоступно" - }, - "409": { - "content": { - "application/json": { - "example": { - "status": "FORBIDDEN", - "reason": "For the requested operation the conditions are not met.", - "message": "Only pending or canceled events can be changed", - "timestamp": "2022-09-07 09:10:50" - }, - "schema": { - "$ref": "#/components/schemas/ApiError" - } - } - }, - "description": "Событие не удовлетворяет правилам редактирования" - } - }, - "summary": "Изменение события добавленного текущим пользователем", - "tags": [ - "Private: События" - ] - } - }, - "/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": "Сведения об ошибке" - }, - "CategoryDto": { - "required": [ - "name" - ], - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "Идентификатор категории", - "format": "int64", - "readOnly": true, - "example": 1 - }, - "name": { - "maxLength": 50, - "minLength": 1, - "type": "string", - "description": "Название категории", - "example": "Концерты" - } - }, - "description": "Категория" - }, - "CompilationDto": { - "required": [ - "id", - "pinned", - "title" - ], - "type": "object", - "properties": { - "events": { - "uniqueItems": true, - "type": "array", - "description": "Список событий входящих в подборку", - "example": [ - { - "annotation": "Эксклюзивность нашего шоу гарантирует привлечение максимальной зрительской аудитории", - "category": { - "id": 1, - "name": "Концерты" - }, - "confirmedRequests": 5, - "eventDate": "2024-03-10 14:30:00", - "id": 1, - "initiator": { - "id": 3, - "name": "Фёдоров Матвей" - }, - "paid": true, - "title": "Знаменитое шоу 'Летающая кукуруза'", - "views": 999 - }, - { - "annotation": "За почти три десятилетия группа 'Java Core' закрепились на сцене как группа, объединяющая поколения.", - "category": { - "id": 1, - "name": "Концерты" - }, - "confirmedRequests": 555, - "eventDate": "2025-09-13 21:00:00", - "id": 1, - "initiator": { - "id": 3, - "name": "Паша Петров" - }, - "paid": true, - "title": "Концерт рок-группы 'Java Core'", - "views": 991 - } - ], - "items": { - "$ref": "#/components/schemas/EventShortDto" - } - }, - "id": { - "type": "integer", - "description": "Идентификатор", - "format": "int64", - "example": 1 - }, - "pinned": { - "type": "boolean", - "description": "Закреплена ли подборка на главной странице сайта", - "example": true - }, - "title": { - "type": "string", - "description": "Заголовок подборки", - "example": "Летние концерты" - } - }, - "description": "Подборка событий" - }, - "EventFullDto": { - "required": [ - "annotation", - "category", - "eventDate", - "initiator", - "location", - "paid", - "title" - ], - "type": "object", - "properties": { - "annotation": { - "type": "string", - "description": "Краткое описание", - "example": "Эксклюзивность нашего шоу гарантирует привлечение максимальной зрительской аудитории" - }, - "category": { - "$ref": "#/components/schemas/CategoryDto" - }, - "confirmedRequests": { - "type": "integer", - "description": "Количество одобренных заявок на участие в данном событии", - "format": "int64", - "example": 5 - }, - "createdOn": { - "type": "string", - "description": "Дата и время создания события (в формате \"yyyy-MM-dd HH:mm:ss\")", - "example": "2022-09-06 11:00:23" - }, - "description": { - "type": "string", - "description": "Полное описание события", - "example": "Что получится, если соединить кукурузу и полёт? Создатели \"Шоу летающей кукурузы\" испытали эту идею на практике и воплотили в жизнь инновационный проект, предлагающий свежий взгляд на развлечения..." - }, - "eventDate": { - "type": "string", - "description": "Дата и время на которые намечено событие (в формате \"yyyy-MM-dd HH:mm:ss\")", - "example": "2024-12-31 15:10:05" - }, - "id": { - "type": "integer", - "description": "Идентификатор", - "format": "int64", - "example": 1 - }, - "initiator": { - "$ref": "#/components/schemas/UserShortDto" - }, - "location": { - "$ref": "#/components/schemas/Location" - }, - "paid": { - "type": "boolean", - "description": "Нужно ли оплачивать участие", - "example": true - }, - "participantLimit": { - "type": "integer", - "description": "Ограничение на количество участников. Значение 0 - означает отсутствие ограничения", - "format": "int32", - "example": 10, - "default": 0 - }, - "publishedOn": { - "type": "string", - "description": "Дата и время публикации события (в формате \"yyyy-MM-dd HH:mm:ss\")", - "example": "2022-09-06 15:10:05" - }, - "requestModeration": { - "type": "boolean", - "description": "Нужна ли пре-модерация заявок на участие", - "example": true, - "default": true - }, - "state": { - "type": "string", - "description": "Список состояний жизненного цикла события", - "example": "PUBLISHED", - "enum": [ - "PENDING", - "PUBLISHED", - "CANCELED" - ] - }, - "title": { - "type": "string", - "description": "Заголовок", - "example": "Знаменитое шоу 'Летающая кукуруза'" - }, - "views": { - "type": "integer", - "description": "Количество просмотрев события", - "format": "int64", - "example": 999 - } - } - }, - "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", - "category", - "eventDate", - "initiator", - "paid", - "title" - ], - "type": "object", - "properties": { - "annotation": { - "type": "string", - "description": "Краткое описание", - "example": "Эксклюзивность нашего шоу гарантирует привлечение максимальной зрительской аудитории" - }, - "category": { - "$ref": "#/components/schemas/CategoryDto" - }, - "confirmedRequests": { - "type": "integer", - "description": "Количество одобренных заявок на участие в данном событии", - "format": "int64", - "example": 5 - }, - "eventDate": { - "type": "string", - "description": "Дата и время на которые намечено событие (в формате \"yyyy-MM-dd HH:mm:ss\")", - "example": "2024-12-31 15:10:05" - }, - "id": { - "type": "integer", - "description": "Идентификатор", - "format": "int64", - "example": 1 - }, - "initiator": { - "$ref": "#/components/schemas/UserShortDto" - }, - "paid": { - "type": "boolean", - "description": "Нужно ли оплачивать участие", - "example": true - }, - "title": { - "type": "string", - "description": "Заголовок", - "example": "Знаменитое шоу 'Летающая кукуруза'" - }, - "views": { - "type": "integer", - "description": "Количество просмотрев события", - "format": "int64", - "example": 999 - } - }, - "description": "Краткая информация о событии", - "example": [ - { - "annotation": "Эксклюзивность нашего шоу гарантирует привлечение максимальной зрительской аудитории", - "category": { - "id": 1, - "name": "Концерты" - }, - "confirmedRequests": 5, - "eventDate": "2024-03-10 14:30:00", - "id": 1, - "initiator": { - "id": 3, - "name": "Фёдоров Матвей" - }, - "paid": true, - "title": "Знаменитое шоу 'Летающая кукуруза'", - "views": 999 - }, - { - "annotation": "За почти три десятилетия группа 'Java Core' закрепились на сцене как группа, объединяющая поколения.", - "category": { - "id": 1, - "name": "Концерты" - }, - "confirmedRequests": 555, - "eventDate": "2025-09-13 21:00:00", - "id": 1, - "initiator": { - "id": 3, - "name": "Паша Петров" - }, - "paid": true, - "title": "Концерт рок-группы 'Java Core'", - "views": 991 - } - ] - }, - "Location": { - "type": "object", - "properties": { - "lat": { - "type": "number", - "description": "Широта", - "format": "float", - "example": 55.754167 - }, - "lon": { - "type": "number", - "description": "Долгота", - "format": "float", - "example": 37.62 - } - }, - "description": "Широта и долгота места проведения события" - }, - "NewCategoryDto": { - "required": [ - "name" - ], - "type": "object", - "properties": { - "name": { - "maxLength": 50, - "minLength": 1, - "type": "string", - "description": "Название категории", - "example": "Концерты" - } - }, - "description": "Данные для добавления новой категории" - }, - "NewCompilationDto": { - "required": [ - "title" - ], - "type": "object", - "properties": { - "events": { - "uniqueItems": true, - "type": "array", - "description": "Список идентификаторов событий входящих в подборку", - "example": [ - 1, - 2, - 3 - ], - "items": { - "type": "integer", - "description": "Список идентификаторов событий входящих в подборку", - "format": "int64" - } - }, - "pinned": { - "type": "boolean", - "description": "Закреплена ли подборка на главной странице сайта", - "example": false, - "default": false - }, - "title": { - "maxLength": 50, - "minLength": 1, - "type": "string", - "description": "Заголовок подборки", - "example": "Летние концерты" - } - }, - "description": "Подборка событий" - }, - "NewEventDto": { - "required": [ - "annotation", - "category", - "description", - "eventDate", - "location", - "title" - ], - "type": "object", - "properties": { - "annotation": { - "maxLength": 2000, - "minLength": 20, - "type": "string", - "description": "Краткое описание события", - "example": "Сплав на байдарках похож на полет." - }, - "category": { - "type": "integer", - "description": "id категории к которой относится событие", - "format": "int64", - "example": 2 - }, - "description": { - "maxLength": 7000, - "minLength": 20, - "type": "string", - "description": "Полное описание события", - "example": "Сплав на байдарках похож на полет. На спокойной воде — это парение. На бурной, порожистой — выполнение фигур высшего пилотажа. И то, и другое дарят чувство обновления, феерические эмоции, яркие впечатления." - }, - "eventDate": { - "type": "string", - "description": "Дата и время на которые намечено событие. Дата и время указываются в формате \"yyyy-MM-dd HH:mm:ss\"", - "example": "2024-12-31 15:10:05" - }, - "location": { - "$ref": "#/components/schemas/Location" - }, - "paid": { - "type": "boolean", - "description": "Нужно ли оплачивать участие в событии", - "example": true, - "default": false - }, - "participantLimit": { - "type": "integer", - "description": "Ограничение на количество участников. Значение 0 - означает отсутствие ограничения", - "format": "int32", - "example": 10, - "default": 0 - }, - "requestModeration": { - "type": "boolean", - "description": "Нужна ли пре-модерация заявок на участие. Если true, то все заявки будут ожидать подтверждения инициатором события. Если false - то будут подтверждаться автоматически.", - "example": false, - "default": true - }, - "title": { - "maxLength": 120, - "minLength": 3, - "type": "string", - "description": "Заголовок события", - "example": "Сплав на байдарках" - } - }, - "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": "Данные нового пользователя" - }, - "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": { - "events": { - "uniqueItems": true, - "type": "array", - "description": "Список id событий подборки для полной замены текущего списка", - "items": { - "type": "integer", - "description": "Список id событий подборки для полной замены текущего списка", - "format": "int64" - } - }, - "pinned": { - "type": "boolean", - "description": "Закреплена ли подборка на главной странице сайта", - "example": true - }, - "title": { - "maxLength": 50, - "minLength": 1, - "type": "string", - "description": "Заголовок подборки", - "example": "Необычные фотозоны" - } - }, - "description": "Изменение информации о подборке событий. Если поле в запросе не указано (равно null) - значит изменение этих данных не треубется." - }, - "UpdateEventAdminRequest": { - "type": "object", - "properties": { - "annotation": { - "maxLength": 2000, - "minLength": 20, - "type": "string", - "description": "Новая аннотация", - "example": "Сап прогулки по рекам и каналам – это возможность увидеть Практикбург с другого ракурса" - }, - "category": { - "type": "integer", - "description": "Новая категория", - "format": "int64", - "example": 3 - }, - "description": { - "maxLength": 7000, - "minLength": 20, - "type": "string", - "description": "Новое описание", - "example": "От английского SUP - Stand Up Paddle — \"стоя на доске с веслом\", гавайская разновидность сёрфинга, в котором серфер, стоя на доске, катается на волнах и при этом гребет веслом, а не руками, как в классическом серфинге." - }, - "eventDate": { - "type": "string", - "description": "Новые дата и время на которые намечено событие. Дата и время указываются в формате \"yyyy-MM-dd HH:mm:ss\"", - "example": "2023-10-11 23:10:05" - }, - "location": { - "$ref": "#/components/schemas/Location" - }, - "paid": { - "type": "boolean", - "description": "Новое значение флага о платности мероприятия", - "example": true - }, - "participantLimit": { - "type": "integer", - "description": "Новый лимит пользователей", - "format": "int32", - "example": 7 - }, - "requestModeration": { - "type": "boolean", - "description": "Нужна ли пре-модерация заявок на участие", - "example": false - }, - "stateAction": { - "type": "string", - "description": "Новое состояние события", - "enum": [ - "PUBLISH_EVENT", - "REJECT_EVENT" - ] - }, - "title": { - "maxLength": 120, - "minLength": 3, - "type": "string", - "description": "Новый заголовок", - "example": "Сап прогулки по рекам и каналам" - } - }, - "description": "Данные для изменения информации о событии. Если поле в запросе не указано (равно null) - значит изменение этих данных не треубется." - }, - "UpdateEventUserRequest": { - "type": "object", - "properties": { - "annotation": { - "maxLength": 2000, - "minLength": 20, - "type": "string", - "description": "Новая аннотация", - "example": "Сап прогулки по рекам и каналам – это возможность увидеть Практикбург с другого ракурса" - }, - "category": { - "type": "integer", - "description": "Новая категория", - "format": "int64", - "example": 3 - }, - "description": { - "maxLength": 7000, - "minLength": 20, - "type": "string", - "description": "Новое описание", - "example": "От английского SUP - Stand Up Paddle — \"стоя на доске с веслом\", гавайская разновидность сёрфинга, в котором серфер, стоя на доске, катается на волнах и при этом гребет веслом, а не руками, как в классическом серфинге." - }, - "eventDate": { - "type": "string", - "description": "Новые дата и время на которые намечено событие. Дата и время указываются в формате \"yyyy-MM-dd HH:mm:ss\"", - "example": "2023-10-11 23:10:05" - }, - "location": { - "$ref": "#/components/schemas/Location" - }, - "paid": { - "type": "boolean", - "description": "Новое значение флага о платности мероприятия", - "example": true - }, - "participantLimit": { - "type": "integer", - "description": "Новый лимит пользователей", - "format": "int32", - "example": 7 - }, - "requestModeration": { - "type": "boolean", - "description": "Нужна ли пре-модерация заявок на участие", - "example": false - }, - "stateAction": { - "type": "string", - "description": "Изменение сотояния события", - "example": "CANCEL_REVIEW", - "enum": [ - "SEND_TO_REVIEW", - "CANCEL_REVIEW" - ] - }, - "title": { - "maxLength": 120, - "minLength": 3, - "type": "string", - "description": "Новый заголовок", - "example": "Сап прогулки по рекам и каналам" - } - }, - "description": "Данные для изменения информации о событии. Если поле в запросе не указано (равно null) - значит изменение этих данных не треубется." - }, - "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": "Пользователь (краткая информация)" - } - } - } -} diff --git a/postman/ewm-main-service.json b/ewm-main-service.json similarity index 100% rename from postman/ewm-main-service.json rename to ewm-main-service.json diff --git a/postman/ewm-stat-service.json b/ewm-stat-service.json similarity index 100% rename from postman/ewm-stat-service.json rename to ewm-stat-service.json diff --git a/ewm-stats-service-spec.json b/ewm-stats-service-spec.json deleted file mode 100644 index 436892a..0000000 --- a/ewm-stats-service-spec.json +++ /dev/null @@ -1,170 +0,0 @@ -{ - "openapi": "3.0.1", - "info": { - "title": "Stat service API", - "version": "v0" - }, - "servers": [ - { - "url": "http://localhost:9090", - "description": "Generated server url" - } - ], - "tags": [ - { - "name": "StatsController", - "description": "API для работы со статистикой посещений" - } - ], - "paths": { - "/hit": { - "post": { - "tags": [ - "StatsController" - ], - "summary": "Сохранение информации о том, что к эндпоинту был запрос", - "description": "Сохранение информации о том, что на uri конкретного сервиса был отправлен запрос пользователем. Название сервиса, uri и ip пользователя указаны в теле запроса.", - "operationId": "hit", - "requestBody": { - "description": "данные запроса", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EndpointHit" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "Информация сохранена" - } - } - } - }, - "/stats": { - "get": { - "tags": [ - "StatsController" - ], - "summary": "Получение статистики по посещениям. Обратите внимание: значение даты и времени нужно закодировать (например используя java.net.URLEncoder.encode) ", - "operationId": "getStats", - "parameters": [ - { - "name": "start", - "in": "query", - "description": "Дата и время начала диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "end", - "in": "query", - "description": "Дата и время конца диапазона за который нужно выгрузить статистику (в формате \"yyyy-MM-dd HH:mm:ss\")", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "uris", - "in": "query", - "description": "Список uri для которых нужно выгрузить статистику", - "required": false, - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - { - "name": "unique", - "in": "query", - "description": "Нужно ли учитывать только уникальные посещения (только с уникальным ip)", - "required": false, - "schema": { - "type": "boolean", - "default": false - } - } - ], - "responses": { - "200": { - "description": "Статистика собрана", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ViewStats" - } - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "EndpointHit": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "Идентификатор записи", - "format": "int64", - "readOnly": true, - "example": 1 - }, - "app": { - "type": "string", - "description": "Идентификатор сервиса для которого записывается информация", - "example": "ewm-main-service" - }, - "uri": { - "type": "string", - "description": "URI для которого был осуществлен запрос", - "example": "/events/1" - }, - "ip": { - "type": "string", - "description": "IP-адрес пользователя, осуществившего запрос", - "example": "192.163.0.1" - }, - "timestamp": { - "type": "string", - "description": "Дата и время, когда был совершен запрос к эндпоинту (в формате \"yyyy-MM-dd HH:mm:ss\")", - "example": "2022-09-06 11:00:23" - } - } - }, - "ViewStats": { - "type": "object", - "properties": { - "app": { - "type": "string", - "description": "Название сервиса", - "example": "ewm-main-service" - }, - "uri": { - "type": "string", - "description": "URI сервиса", - "example": "/events/1" - }, - "hits": { - "type": "integer", - "description": "Количество просмотров", - "format": "int64", - "example": 6 - } - } - } - } - } -} From 16bc47f37693621647c734f4e9f8f3adc6d9ecc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Sat, 7 Feb 2026 19:23:15 +0300 Subject: [PATCH 04/10] feat: implement fault tolerance for microservices --- .../controller/PrivateEventController.java | 20 --- .../practicum/mapper/LocationMapper.java | 6 +- .../java/ru/yandex/practicum/model/Event.java | 2 +- .../{EventLocation.java => Location.java} | 2 +- .../practicum/service/EventServiceImpl.java | 165 +++++++++++------- .../practicum/client/RequestClient.java | 13 +- .../client/RequestClientFallback.java | 19 +- .../practicum/client/UserClientFallback.java | 2 +- .../ServiceUnavailableException.java | 7 + .../controller/InternalRequestController.java | 10 -- .../repository/RequestRepository.java | 9 +- .../practicum/service/RequestService.java | 3 - .../practicum/service/RequestServiceImpl.java | 34 ---- .../src/main/resources/schema.sql | 2 +- .../yandex/practicum/mapper/UserMapper.java | 1 - 15 files changed, 127 insertions(+), 168 deletions(-) rename core/event-service/src/main/java/ru/yandex/practicum/model/{EventLocation.java => Location.java} (93%) create mode 100644 core/interaction-api/src/main/java/ru/yandex/practicum/exception/ServiceUnavailableException.java diff --git a/core/event-service/src/main/java/ru/yandex/practicum/controller/PrivateEventController.java b/core/event-service/src/main/java/ru/yandex/practicum/controller/PrivateEventController.java index f6331dc..a4dda89 100644 --- a/core/event-service/src/main/java/ru/yandex/practicum/controller/PrivateEventController.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/controller/PrivateEventController.java @@ -11,9 +11,7 @@ import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.client.RequestClient; import ru.yandex.practicum.dto.event.*; -import ru.yandex.practicum.dto.request.*; import ru.yandex.practicum.service.EventService; import java.util.List; @@ -25,7 +23,6 @@ @RequiredArgsConstructor public class PrivateEventController { private final EventService eventService; - private final RequestClient requestClient; @GetMapping public List getEvents(@PathVariable("userId") @Positive Long userId, @@ -63,21 +60,4 @@ public EventFullDto updateEvent(@PathVariable("userId") @Positive Long userId, log.debug("Controller: updateEvent userId={}, eventId={}, data={}", userId, eventId, request); return eventService.updateEvent(userId, eventId, request); } - - @GetMapping("/{eventId}/requests") - public List getRequestsByEvent(@PathVariable("userId") @Positive Long userId, - @PathVariable("eventId") @Positive Long eventId - ) { - log.debug("Controller: getRequestsByEvent userId={}, eventId={}", userId, eventId); - return requestClient.getRequestsByEvent(userId, eventId); - } - - @PatchMapping("/{eventId}/requests") - public EventRequestStatusUpdateResult updateRequestStatus(@PathVariable("userId") @Positive Long userId, - @PathVariable("eventId") @Positive Long eventId, - @RequestBody EventRequestStatusUpdateRequest request - ) { - log.debug("Controller: updateRequestStatus userId={}, eventId={}, data={}", userId, eventId, request); - return requestClient.updateRequestStatus(userId, eventId, request); - } } diff --git a/core/event-service/src/main/java/ru/yandex/practicum/mapper/LocationMapper.java b/core/event-service/src/main/java/ru/yandex/practicum/mapper/LocationMapper.java index 617d8c0..70a2515 100644 --- a/core/event-service/src/main/java/ru/yandex/practicum/mapper/LocationMapper.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/mapper/LocationMapper.java @@ -2,11 +2,11 @@ import org.mapstruct.Mapper; import ru.yandex.practicum.dto.event.LocationDto; -import ru.yandex.practicum.model.EventLocation; +import ru.yandex.practicum.model.Location; @Mapper(componentModel = "spring") public interface LocationMapper { - EventLocation toEventLocation(LocationDto dto); + Location toLocation(LocationDto dto); - LocationDto toDto(EventLocation entity); + LocationDto toDto(Location entity); } diff --git a/core/event-service/src/main/java/ru/yandex/practicum/model/Event.java b/core/event-service/src/main/java/ru/yandex/practicum/model/Event.java index 48c9eb5..a33d87f 100644 --- a/core/event-service/src/main/java/ru/yandex/practicum/model/Event.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/model/Event.java @@ -52,7 +52,7 @@ public class Event { @AttributeOverride(name = "lat", column = @Column(name = "lat")), @AttributeOverride(name = "lon", column = @Column(name = "lon")) }) - private EventLocation location; + private Location location; @Column(nullable = false) private Boolean paid = false; diff --git a/core/event-service/src/main/java/ru/yandex/practicum/model/EventLocation.java b/core/event-service/src/main/java/ru/yandex/practicum/model/Location.java similarity index 93% rename from core/event-service/src/main/java/ru/yandex/practicum/model/EventLocation.java rename to core/event-service/src/main/java/ru/yandex/practicum/model/Location.java index 10df78b..d7db8ec 100644 --- a/core/event-service/src/main/java/ru/yandex/practicum/model/EventLocation.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/model/Location.java @@ -10,7 +10,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -public class EventLocation { +public class Location { @NotNull(message = "Широта не может быть null") private Float lat; diff --git a/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java b/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java index 4f13130..b0d97ca 100644 --- a/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java @@ -22,14 +22,11 @@ import ru.yandex.practicum.repository.*; import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; @Slf4j @Service -@Transactional(readOnly = true) @RequiredArgsConstructor public class EventServiceImpl implements EventService { private final EventRepository eventRepository; @@ -44,7 +41,7 @@ public class EventServiceImpl implements EventService { public EventFullDto createEvent(Long userId, NewEventDto newEventDto) { validateDateEvent(newEventDto.eventDate(), 2); - UserShortDto userDto = userClient.getUser(userId); + checkUserExists(userId); Category category = categoryRepository.findById(newEventDto.category()) .orElseThrow(() -> new NotFoundException("Категория с id=" + newEventDto.category() + " не найдена")); @@ -55,7 +52,7 @@ public EventFullDto createEvent(Long userId, NewEventDto newEventDto) { log.info("Создано событие с id={}, title={}, initiatorId={}", event.getId(), event.getTitle(), userId); EventFullDto dto = eventMapper.toFullDto(event); - dto.setInitiator(userDto); + dto.setInitiator(getUserShortDto(userId)); dto.setConfirmedRequests(0L); dto.setViews(0L); return dto; @@ -63,7 +60,7 @@ public EventFullDto createEvent(Long userId, NewEventDto newEventDto) { @Override public List getEvents(Long userId, Pageable pageable) { - userClient.getUser(userId); + checkUserExists(userId); List events = eventRepository.findAllByInitiatorIdOrderByCreatedOnDesc(userId, pageable); @@ -75,14 +72,18 @@ public List getEvents(Long userId, Pageable pageable) { EventStatistics stats = getEventStatistics(eventIds); + Map userCache = new HashMap<>(); + return events.stream() .map(event -> { EventShortDto dto = eventMapper.toShortDto(event); - dto.setConfirmedRequests((long) stats.confirmedRequests() - .getOrDefault(event.getId(), 0L)); + dto.setConfirmedRequests(stats.confirmedRequests().getOrDefault(event.getId(), 0L)); dto.setViews(stats.views().getOrDefault(event.getId(), 0L)); - UserShortDto initiatorDto = userClient.getUser(event.getInitiatorId()); + UserShortDto initiatorDto = userCache.computeIfAbsent( + event.getInitiatorId(), + this::getUserShortDto + ); dto.setInitiator(initiatorDto); return dto; }) @@ -91,23 +92,29 @@ public List getEvents(Long userId, Pageable pageable) { @Override public EventFullDto getEvent(Long userId, Long eventId, String ip) { - userClient.getUser(userId); - - Event event = getEventOrThrow(eventId); + try { + userClient.getUser(userId); + Event event = getEventOrThrow(eventId); - if (!event.getInitiatorId().equals(userId)) { - throw new NotFoundException("Событие c id " + eventId + " не найдено у пользователя с id " + userId); - } + if (!event.getInitiatorId().equals(userId)) { + throw new NotFoundException("Событие c id " + eventId + " не найдено у пользователя с id " + userId); + } - saveHit("/events/" + eventId, ip); + saveHit("/events/" + eventId, ip); + return buildFullDto(event); - return buildFullDto(event); + } catch (Exception e) { + log.warn("Сервис пользователей недоступен или пользователь не найден, userId={}", userId); + Event event = getEventOrThrow(eventId); + saveHit("/events/" + eventId, ip); + return buildFullDto(event); + } } @Override @Transactional public EventFullDto updateEvent(Long userId, Long eventId, UpdateEventUserRequest request) { - userClient.getUser(userId); + checkUserExists(userId); Event event = getEventOrThrow(eventId); @@ -117,7 +124,9 @@ public EventFullDto updateEvent(Long userId, Long eventId, UpdateEventUserReques if (event.getState() == EventState.PUBLISHED) throw new ValidationException("Изменять можно только не опубликованные события"); - validateDateEvent(request.eventDate(), 2); + if (request.eventDate() != null) { + validateDateEvent(request.eventDate(), 2); + } Category category = null; if (request.category() != null) { @@ -176,15 +185,18 @@ public List getEventsAdmin(SearchEventAdminRequest request, Pageab .toList(); EventStatistics stats = getEventStatistics(searchEventIds); + Map userCache = new HashMap<>(); return events.stream() .map(event -> { EventFullDto dto = eventMapper.toFullDto(event); - dto.setConfirmedRequests((long) stats.confirmedRequests() - .getOrDefault(event.getId(), 0L)); + dto.setConfirmedRequests(stats.confirmedRequests().getOrDefault(event.getId(), 0L)); dto.setViews(stats.views().getOrDefault(event.getId(), 0L)); - UserShortDto initiatorDto = userClient.getUser(event.getInitiatorId()); + UserShortDto initiatorDto = userCache.computeIfAbsent( + event.getInitiatorId(), + this::getUserShortDto + ); dto.setInitiator(initiatorDto); return dto; }) @@ -194,7 +206,7 @@ public List getEventsAdmin(SearchEventAdminRequest request, Pageab @Override @Transactional public EventFullDto updateEventAdmin(Long eventId, UpdateEventAdminRequest request) { - Event event = eventRepository.findByIdNew(eventId) + Event event = eventRepository.findById(eventId) .orElseThrow(() -> new NotFoundException("Событие c id " + eventId + " не найдено")); if (request.stateAction() != null) { @@ -205,7 +217,9 @@ public EventFullDto updateEventAdmin(Long eventId, UpdateEventAdminRequest reque if (currentState != EventState.PENDING) throw new ConflictException( "Событие можно опубликовать только если оно в состоянии ожидания публикации"); - validateDateEvent(event.getEventDate(), 1); + if (event.getEventDate() != null) { + validateDateEvent(event.getEventDate(), 1); + } event.publish(); } if (stateActionAdmin == StateActionAdmin.REJECT_EVENT) { @@ -261,16 +275,18 @@ public List getEventsPublic( .toList(); EventStatistics stats = getEventStatistics(eventIds); + Map userCache = new HashMap<>(); List result = events.stream() .map(event -> { EventShortDto dto = eventMapper.toShortDto(event); - dto.setConfirmedRequests((long) stats.confirmedRequests() - .getOrDefault(event.getId(), 0L)); - + dto.setConfirmedRequests(stats.confirmedRequests().getOrDefault(event.getId(), 0L)); dto.setViews(stats.views().getOrDefault(event.getId(), 0L)); - UserShortDto initiatorDto = userClient.getUser(event.getInitiatorId()); + UserShortDto initiatorDto = userCache.computeIfAbsent( + event.getInitiatorId(), + this::getUserShortDto + ); dto.setInitiator(initiatorDto); return dto; }) @@ -293,7 +309,7 @@ public List getEventsPublic( @Override public EventFullDto getEventByIdPublic(Long eventId, String ip) { - Event event = eventRepository.findByIdNew(eventId) + Event event = eventRepository.findById(eventId) .filter(ev -> ev.getState() == EventState.PUBLISHED) .orElseThrow(() -> new NotFoundException("Событие c id " + eventId + " не найдено")); @@ -307,10 +323,10 @@ private EventFullDto buildFullDto(Event event) { Map views = getViewsForEvents(List.of(event.getId())); EventFullDto dto = eventMapper.toFullDto(event); - dto.setConfirmedRequests((long) confirmedRequests.getOrDefault(event.getId(), 0L)); + dto.setConfirmedRequests(confirmedRequests.getOrDefault(event.getId(), 0L)); dto.setViews(views.getOrDefault(event.getId(), 0L)); - UserShortDto initiatorDto = userClient.getUser(event.getInitiatorId()); + UserShortDto initiatorDto = getUserShortDto(event.getInitiatorId()); dto.setInitiator(initiatorDto); return dto; @@ -322,9 +338,13 @@ private void validateRangeStartAndEnd(LocalDateTime rangeStart, LocalDateTime ra } private void saveHit(String path, String ip) { - CreateEndpointHitDto endpointHitDto = new CreateEndpointHitDto( - "main-service", path, ip, LocalDateTime.now()); - statsClient.saveHit(endpointHitDto); + try { + CreateEndpointHitDto endpointHitDto = new CreateEndpointHitDto( + "main-service", path, ip, LocalDateTime.now()); + statsClient.saveHit(endpointHitDto); + } catch (Exception e) { + log.warn("Не удалось сохранить статистику для path={}, ip={}", path, ip, e); + } } private void validateDateEvent(LocalDateTime eventDate, long minHoursBeforeStartEvent) { @@ -333,42 +353,50 @@ private void validateDateEvent(LocalDateTime eventDate, long minHoursBeforeStart "Дата начала события не может быть ранее чем через " + minHoursBeforeStartEvent + " часа(ов)"); } - private Map getConfirmedRequests(List eventIds) { // ← Integer → Long + private Map getConfirmedRequests(List eventIds) { if (eventIds.isEmpty()) return Map.of(); - Map confirmedRequests = requestClient.getConfirmedRequestsCount(eventIds); - - Map result = eventIds.stream() - .collect(Collectors.toMap(id -> id, id -> 0L)); - - result.putAll(confirmedRequests); + try { + return requestClient.getConfirmedRequestsCount(eventIds); + } catch (Exception e) { + log.warn("Сервис запросов недоступен, используем значение по умолчанию 0. eventIds={}", eventIds); - return result; + return eventIds.stream() + .collect(Collectors.toMap( + id -> id, + id -> 0L + )); + } } private Map getViewsForEvents(List eventIds) { if (eventIds.isEmpty()) return Map.of(); - List uris = eventIds.stream() - .map(id -> "/events/" + id) - .toList(); - Event firstEvent = eventRepository.findFirstByOrderByCreatedOnAsc(); - LocalDateTime start = firstEvent != null ? firstEvent.getCreatedOn() : LocalDateTime.now().minusYears(1); - LocalDateTime end = LocalDateTime.now(); - - List stats = statsClient.getStats(start, end, uris, true); - Map views = eventIds.stream() .collect(Collectors.toMap(id -> id, id -> 0L)); - if (stats != null && !stats.isEmpty()) { - stats.forEach(stat -> { - Long eventId = getEventIdFromUri(stat.uri()); - if (eventId > -1L) { - views.put(eventId, stat.hits()); - } - }); + try { + List uris = eventIds.stream() + .map(id -> "/events/" + id) + .toList(); + + LocalDateTime start = eventRepository.findFirstByOrderByCreatedOnAsc().getCreatedOn(); + LocalDateTime end = LocalDateTime.now(); + + List stats = statsClient.getStats(start, end, uris, true); + + if (stats != null && !stats.isEmpty()) { + stats.forEach(stat -> { + Long eventId = getEventIdFromUri(stat.uri()); + if (eventId > -1L) { + views.put(eventId, stat.hits()); + } + }); + } + } catch (Exception e) { + log.warn("Сервис статистики недоступен, используем значение по умолчанию 0. eventIds={}", eventIds); } + return views; } @@ -385,9 +413,26 @@ private EventStatistics getEventStatistics(List eventIds) { return new EventStatistics(Map.of(), Map.of()); } - Map confirmedRequests = getConfirmedRequests(eventIds); // ← теперь Long + Map confirmedRequests = getConfirmedRequests(eventIds); Map views = getViewsForEvents(eventIds); return new EventStatistics(confirmedRequests, views); } + + private void checkUserExists(Long userId) { + try { + userClient.getUser(userId); + } catch (Exception e) { + log.warn("Сервис пользователей недоступен, пропускаем проверку. userId={}", userId); + } + } + + private UserShortDto getUserShortDto(Long userId) { + try { + return userClient.getUser(userId); + } catch (Exception e) { + log.warn("Сервис пользователей недоступен, используем заглушку. userId={}", userId); + return new UserShortDto(userId, "Пользователь-" + userId); + } + } } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java index 2212f5b..5a80474 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java @@ -2,7 +2,6 @@ import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; -import ru.yandex.practicum.dto.request.*; import ru.yandex.practicum.enums.RequestStatus; import java.util.List; @@ -18,16 +17,6 @@ public interface RequestClient { @GetMapping("/event/{eventId}/count/{status}") Long countByStatus(@PathVariable Long eventId, @PathVariable RequestStatus status); - @PostMapping("/events/count") + @PostMapping("/events/count/confirmed") Map getConfirmedRequestsCount(@RequestBody List eventIds); - - @GetMapping("/users/{userId}/events/{eventId}/requests") - List getRequestsByEvent(@PathVariable Long userId, @PathVariable Long eventId); - - @PatchMapping("/users/{userId}/events/{eventId}/requests") - EventRequestStatusUpdateResult updateRequestStatus( - @PathVariable Long userId, - @PathVariable Long eventId, - @RequestBody EventRequestStatusUpdateRequest request - ); } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClientFallback.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClientFallback.java index bc890fc..6b23c77 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClientFallback.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClientFallback.java @@ -2,7 +2,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import ru.yandex.practicum.dto.request.*; import ru.yandex.practicum.enums.RequestStatus; import java.util.List; @@ -21,21 +20,7 @@ public Long countByStatus(Long eventId, RequestStatus status) { @Override public Map getConfirmedRequestsCount(List eventIds) { - log.warn("Сервис запросов недоступен, eventIds={}", eventIds); - return eventIds.stream() - .collect(Collectors.toMap(id -> id, id -> 0L)); - } - - @Override - public List getRequestsByEvent(Long userId, Long eventId) { - log.warn("Сервис запросов недоступен, userId={}, eventId={}", userId, eventId); - return List.of(); - } - - @Override - public EventRequestStatusUpdateResult updateRequestStatus( - Long userId, Long eventId, EventRequestStatusUpdateRequest request) { - log.warn("Сервис запросов недоступен, userId={}, eventId={}, request={}", userId, eventId, request); - return new EventRequestStatusUpdateResult(List.of(), List.of()); + log.warn("Сервис запросов недоступен для batch, eventIds={}", eventIds); + return eventIds.stream().collect(Collectors.toMap(id -> id, id -> 0L)); } } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClientFallback.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClientFallback.java index f87a7cf..08aa7c7 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClientFallback.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClientFallback.java @@ -11,6 +11,6 @@ public class UserClientFallback implements UserClient { @Override public UserShortDto getUser(Long userId) { log.warn("Сервис пользователей недоступен"); - return null; + return new UserShortDto(userId, "Пользователь-" + userId); } } diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/exception/ServiceUnavailableException.java b/core/interaction-api/src/main/java/ru/yandex/practicum/exception/ServiceUnavailableException.java new file mode 100644 index 0000000..f19164a --- /dev/null +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/exception/ServiceUnavailableException.java @@ -0,0 +1,7 @@ +package ru.yandex.practicum.exception; + +public class ServiceUnavailableException extends RuntimeException { + public ServiceUnavailableException(String message) { + super(message); + } +} diff --git a/core/request-service/src/main/java/ru/yandex/practicum/controller/InternalRequestController.java b/core/request-service/src/main/java/ru/yandex/practicum/controller/InternalRequestController.java index 78ae449..49c7d68 100644 --- a/core/request-service/src/main/java/ru/yandex/practicum/controller/InternalRequestController.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/controller/InternalRequestController.java @@ -4,10 +4,6 @@ import org.springframework.web.bind.annotation.*; import ru.yandex.practicum.enums.RequestStatus; import ru.yandex.practicum.repository.RequestRepository; -import ru.yandex.practicum.service.RequestService; - -import java.util.List; -import java.util.Map; @RestController @RequestMapping("/internal/requests") @@ -15,15 +11,9 @@ public class InternalRequestController { private final RequestRepository requestRepository; - private final RequestService requestService; @GetMapping("/event/{eventId}/count/{status}") public Long countByStatus(@PathVariable Long eventId, @PathVariable RequestStatus status) { return requestRepository.countByEventIdAndStatus(eventId, status); } - - @PostMapping("/events/count") - public Map getConfirmedRequestsCount(@RequestBody List eventIds) { - return requestService.getConfirmedRequestsForEvents(eventIds); - } } diff --git a/core/request-service/src/main/java/ru/yandex/practicum/repository/RequestRepository.java b/core/request-service/src/main/java/ru/yandex/practicum/repository/RequestRepository.java index e44141e..fe99db6 100644 --- a/core/request-service/src/main/java/ru/yandex/practicum/repository/RequestRepository.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/repository/RequestRepository.java @@ -25,8 +25,9 @@ public interface RequestRepository extends JpaRepository { List findByIdIn(List ids); - @Query("SELECT r.eventId, COUNT(r) FROM Request r " + - "WHERE r.eventId IN :eventIds AND r.status = 'CONFIRMED' " + - "GROUP BY r.eventId") - List countConfirmedRequestsByEventIds(@Param("eventIds") List eventIds); + @Query("SELECT r.eventId, " + + "COUNT(r) " + + "FROM Request r " + + "WHERE r.eventId IN :eventIds AND r.status = 'CONFIRMED' GROUP BY r.eventId") + List countConfirmedByEventIds(@Param("eventIds") List eventIds); } diff --git a/core/request-service/src/main/java/ru/yandex/practicum/service/RequestService.java b/core/request-service/src/main/java/ru/yandex/practicum/service/RequestService.java index 87fe7f8..1cb75dd 100644 --- a/core/request-service/src/main/java/ru/yandex/practicum/service/RequestService.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/service/RequestService.java @@ -3,7 +3,6 @@ import ru.yandex.practicum.dto.request.*; import java.util.List; -import java.util.Map; public interface RequestService { List getRequestsByEvent(Long userId, Long eventId); @@ -16,6 +15,4 @@ EventRequestStatusUpdateResult updateRequestStatus(Long userId, ParticipationRequestDto addRequest(Long userId, Long eventId); ParticipationRequestDto cancelRequest(Long userId, Long requestId); - - Map getConfirmedRequestsForEvents(List eventIds); } diff --git a/core/request-service/src/main/java/ru/yandex/practicum/service/RequestServiceImpl.java b/core/request-service/src/main/java/ru/yandex/practicum/service/RequestServiceImpl.java index 6499c5a..eb24838 100644 --- a/core/request-service/src/main/java/ru/yandex/practicum/service/RequestServiceImpl.java +++ b/core/request-service/src/main/java/ru/yandex/practicum/service/RequestServiceImpl.java @@ -17,7 +17,6 @@ import ru.yandex.practicum.repository.RequestRepository; import java.util.*; -import java.util.stream.Collectors; @Slf4j @Service @@ -193,39 +192,6 @@ public EventRequestStatusUpdateResult updateRequestStatus(Long userId, Long even ); } - @Override - @Transactional(readOnly = true) - public Map getConfirmedRequestsForEvents(List eventIds) { - log.debug("Получение количества подтверждённых заявок для событий: {}", eventIds); - - if (eventIds == null || eventIds.isEmpty()) { - return Collections.emptyMap(); - } - - try { - List results = requestRepository.countConfirmedRequestsByEventIds(eventIds); - - Map resultMap = new HashMap<>(); - for (Long eventId : eventIds) { - resultMap.put(eventId, 0L); - } - - for (Object[] row : results) { - Long eventId = (Long) row[0]; - Long count = (Long) row[1]; - resultMap.put(eventId, count); - } - - log.debug("Результат: {}", resultMap); - return resultMap; - - } catch (Exception e) { - log.error("Ошибка при получении количества подтверждённых заявок", e); - return eventIds.stream() - .collect(Collectors.toMap(id -> id, id -> 0L)); - } - } - private void checkUserExists(Long userId) { try { userClient.getUser(userId); diff --git a/core/request-service/src/main/resources/schema.sql b/core/request-service/src/main/resources/schema.sql index b856da4..d798c76 100644 --- a/core/request-service/src/main/resources/schema.sql +++ b/core/request-service/src/main/resources/schema.sql @@ -1,7 +1,7 @@ DROP TABLE IF EXISTS requests CASCADE; -- Создание таблицы запросов -create TABLE IF NOT EXISTS requests ( +CREATE TABLE IF NOT EXISTS requests ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, event_id BIGINT NOT NULL, requester_id BIGINT NOT NULL, diff --git a/core/user-service/src/main/java/ru/yandex/practicum/mapper/UserMapper.java b/core/user-service/src/main/java/ru/yandex/practicum/mapper/UserMapper.java index b0a21e0..d7c7e91 100644 --- a/core/user-service/src/main/java/ru/yandex/practicum/mapper/UserMapper.java +++ b/core/user-service/src/main/java/ru/yandex/practicum/mapper/UserMapper.java @@ -7,7 +7,6 @@ import java.util.List; - @Mapper(componentModel = "spring") public interface UserMapper { From 606cdbf681506e2cfb7e07025c8123df362d762a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Sat, 7 Feb 2026 19:55:44 +0300 Subject: [PATCH 05/10] fix: correct config-server path and configuration for request-service --- core/request-service/src/main/resources/application.yaml | 3 --- .../{request-server => request-service}/request-service.yaml | 0 2 files changed, 3 deletions(-) rename infra/config-server/src/main/resources/config/{request-server => request-service}/request-service.yaml (100%) diff --git a/core/request-service/src/main/resources/application.yaml b/core/request-service/src/main/resources/application.yaml index b5e47b4..8dcd794 100644 --- a/core/request-service/src/main/resources/application.yaml +++ b/core/request-service/src/main/resources/application.yaml @@ -16,9 +16,6 @@ spring: server: port: 0 -stats-server: - url: http://localhost:9090 - eureka: instance: instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}} diff --git a/infra/config-server/src/main/resources/config/request-server/request-service.yaml b/infra/config-server/src/main/resources/config/request-service/request-service.yaml similarity index 100% rename from infra/config-server/src/main/resources/config/request-server/request-service.yaml rename to infra/config-server/src/main/resources/config/request-service/request-service.yaml From 38b6bbc03f02f5735aa5dc44aceeb12fb4f7f55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Sat, 7 Feb 2026 21:24:40 +0300 Subject: [PATCH 06/10] fix: add basePackages to @EnableFeignClients annotation --- .../src/main/java/ru/yandex/practicum/EventServiceApp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/event-service/src/main/java/ru/yandex/practicum/EventServiceApp.java b/core/event-service/src/main/java/ru/yandex/practicum/EventServiceApp.java index c88c2ba..dfdda16 100644 --- a/core/event-service/src/main/java/ru/yandex/practicum/EventServiceApp.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/EventServiceApp.java @@ -5,7 +5,7 @@ import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication -@EnableFeignClients +@EnableFeignClients(basePackages = "ru.yandex.practicum.client") public class EventServiceApp { public static void main(String[] args) { SpringApplication.run(EventServiceApp.class, args); From 469782671d3d59c071b62d5afbd2433c0f9b4b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Sat, 7 Feb 2026 21:37:28 +0300 Subject: [PATCH 07/10] fix: add @ConfigurationPropertiesScan annotation --- .../src/main/java/ru/yandex/practicum/EventServiceApp.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/event-service/src/main/java/ru/yandex/practicum/EventServiceApp.java b/core/event-service/src/main/java/ru/yandex/practicum/EventServiceApp.java index dfdda16..62b1f20 100644 --- a/core/event-service/src/main/java/ru/yandex/practicum/EventServiceApp.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/EventServiceApp.java @@ -2,10 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients(basePackages = "ru.yandex.practicum.client") +@ConfigurationPropertiesScan public class EventServiceApp { public static void main(String[] args) { SpringApplication.run(EventServiceApp.class, args); From b5c98f4012bcdd05041462194885d727065f31ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Sat, 7 Feb 2026 21:52:11 +0300 Subject: [PATCH 08/10] fix: correct event loading methods and remove duplicate database queries --- .../yandex/practicum/service/EventServiceImpl.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java b/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java index b0d97ca..8b2a9a2 100644 --- a/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java @@ -148,7 +148,7 @@ public EventFullDto updateEvent(Long userId, Long eventId, UpdateEventUserReques @Override public Event getEventOrThrow(Long eventId) { - return eventRepository.findById(eventId) + return eventRepository.findByIdNew(eventId) .orElseThrow(() -> new NotFoundException("Событие c id " + eventId + " не найдено")); } @@ -175,10 +175,11 @@ public List getEventsAdmin(SearchEventAdminRequest request, Pageab .map(Event::getId) .toList(); - if (eventIds.isEmpty()) return List.of(); - List events = eventRepository.findAllByEventIds(eventIds); + if (eventIds.isEmpty()) { + return List.of(); + } - if (events.isEmpty()) return List.of(); + List events = eventsPage.getContent(); List searchEventIds = events.stream() .map(Event::getId) @@ -206,7 +207,7 @@ public List getEventsAdmin(SearchEventAdminRequest request, Pageab @Override @Transactional public EventFullDto updateEventAdmin(Long eventId, UpdateEventAdminRequest request) { - Event event = eventRepository.findById(eventId) + Event event = eventRepository.findByIdNew(eventId) .orElseThrow(() -> new NotFoundException("Событие c id " + eventId + " не найдено")); if (request.stateAction() != null) { @@ -309,7 +310,7 @@ public List getEventsPublic( @Override public EventFullDto getEventByIdPublic(Long eventId, String ip) { - Event event = eventRepository.findById(eventId) + Event event = eventRepository.findByIdNew(eventId) .filter(ev -> ev.getState() == EventState.PUBLISHED) .orElseThrow(() -> new NotFoundException("Событие c id " + eventId + " не найдено")); From 4ee318069365dcea4089a76ad80c25eff604fbf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Sat, 7 Feb 2026 22:11:27 +0300 Subject: [PATCH 09/10] feat: add Feign retry config, actuator dependency and update confirmed requests logic --- .../practicum/service/EventServiceImpl.java | 24 +++++++++++-------- .../yandex/practicum/client/EventClient.java | 2 ++ .../practicum/client/RequestClient.java | 2 ++ .../yandex/practicum/client/UserClient.java | 2 ++ .../practicum/config/FeignRetryConfig.java | 16 +++++++++++++ infra/config-server/pom.xml | 5 ++++ 6 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 core/interaction-api/src/main/java/ru/yandex/practicum/config/FeignRetryConfig.java diff --git a/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java b/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java index 8b2a9a2..1000ecc 100644 --- a/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java +++ b/core/event-service/src/main/java/ru/yandex/practicum/service/EventServiceImpl.java @@ -357,17 +357,21 @@ private void validateDateEvent(LocalDateTime eventDate, long minHoursBeforeStart private Map getConfirmedRequests(List eventIds) { if (eventIds.isEmpty()) return Map.of(); - try { - return requestClient.getConfirmedRequestsCount(eventIds); - } catch (Exception e) { - log.warn("Сервис запросов недоступен, используем значение по умолчанию 0. eventIds={}", eventIds); - - return eventIds.stream() - .collect(Collectors.toMap( - id -> id, - id -> 0L - )); + Map result = new HashMap<>(); + + for (Long eventId : eventIds) { + try { + Long count = requestClient.countByStatus(eventId, RequestStatus.CONFIRMED); + result.put(eventId, count != null ? count : 0L); + log.debug("Получено {} подтвержденных запросов для события {}", count, eventId); + } catch (Exception e) { + log.warn("Не удалось получить подтвержденные запросы для события {}: {}", + eventId, e.getMessage()); + result.put(eventId, 0L); + } } + + return result; } private Map getViewsForEvents(List eventIds) { diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/EventClient.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/EventClient.java index ebfd010..494d803 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/client/EventClient.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/EventClient.java @@ -3,11 +3,13 @@ import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import ru.yandex.practicum.config.FeignRetryConfig; import ru.yandex.practicum.dto.event.EventFullDto; @FeignClient( name = "event-service", path = "/internal/events", + configuration = FeignRetryConfig.class, fallback = EventClientFallback.class ) public interface EventClient { diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java index 5a80474..48c759b 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/RequestClient.java @@ -2,6 +2,7 @@ import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; +import ru.yandex.practicum.config.FeignRetryConfig; import ru.yandex.practicum.enums.RequestStatus; import java.util.List; @@ -10,6 +11,7 @@ @FeignClient( name = "request-service", path = "/internal/requests", + configuration = FeignRetryConfig.class, fallback = RequestClientFallback.class ) public interface RequestClient { diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClient.java b/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClient.java index 3447a1f..be0804a 100644 --- a/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClient.java +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/client/UserClient.java @@ -3,11 +3,13 @@ import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import ru.yandex.practicum.config.FeignRetryConfig; import ru.yandex.practicum.dto.user.UserShortDto; @FeignClient( name = "user-service", path = "/internal/users", + configuration = FeignRetryConfig.class, fallback = UserClientFallback.class ) public interface UserClient { diff --git a/core/interaction-api/src/main/java/ru/yandex/practicum/config/FeignRetryConfig.java b/core/interaction-api/src/main/java/ru/yandex/practicum/config/FeignRetryConfig.java new file mode 100644 index 0000000..a1afe8c --- /dev/null +++ b/core/interaction-api/src/main/java/ru/yandex/practicum/config/FeignRetryConfig.java @@ -0,0 +1,16 @@ +package ru.yandex.practicum.config; + +import feign.Retryer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.TimeUnit; + +@Configuration +public class FeignRetryConfig { + + @Bean + public Retryer retryer() { + return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(2), 3); + } +} diff --git a/infra/config-server/pom.xml b/infra/config-server/pom.xml index 2bc2fd4..1591752 100644 --- a/infra/config-server/pom.xml +++ b/infra/config-server/pom.xml @@ -27,6 +27,11 @@ org.springframework.cloud spring-cloud-starter-netflix-eureka-client
+ + + org.springframework.boot + spring-boot-starter-actuator + From 3dd863f3b1b30bfc73f87709316b9aec3116d9d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=B0?= =?UTF-8?q?=D0=BD?= Date: Sat, 7 Feb 2026 22:27:33 +0300 Subject: [PATCH 10/10] docs: update project description in README --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 2879de0..1ad19c3 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ Для взаимодействия между микросервисами используются Feign Clients и Internal Endpoints: - GET /internal/events/{eventId} - возвращает полное описание события; - GET /internal/requests/event/{eventId}/count/{status} - возвращает количество заявок события с определенным статусом; -- POST /internal/requests/events/count - возвращает количество подтвержденных заявок для событий; - GET /internal/users/{userId} - возвращает краткую информацию о пользователе. ## Внешний API @@ -44,9 +43,7 @@ - GET /comments/events/{eventId} - поиск опубликованных комментариев к событию; - GET /events - поиск событий; - GET /categories - категории событий; -- GET //categories/{categoryId} - поиск категории по id; - GET /compilations/{compId} - поиск подборки по id; -- ### Приватные(доступны для авторизованных пользователей): - POST /users/{userId}/comments - добавить комментарий к событию; - PATCH /users/{userId}/comments/{commentId} - изменить комментарий;