diff --git a/README.md b/README.md index 18a246e..9b7cbaa 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # java-explore-with-me Template repository for ExploreWithMe project. + +Ссылка на pull-requests: https://github.com/AndreyMMGH/java-explore-with-me/pull/4 diff --git a/main/src/main/java/ru/practicum/comment/controller/CommentAdminController.java b/main/src/main/java/ru/practicum/comment/controller/CommentAdminController.java new file mode 100644 index 0000000..4bc90cd --- /dev/null +++ b/main/src/main/java/ru/practicum/comment/controller/CommentAdminController.java @@ -0,0 +1,45 @@ +package ru.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.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.comment.dto.CommentRequestDto; +import ru.practicum.comment.dto.CommentResponseDto; +import ru.practicum.comment.service.CommentService; + +import java.util.List; + +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping(path = "/admin/comments") +public class CommentAdminController { + private final CommentService commentService; + + @PatchMapping("/{commentId}") + public CommentResponseDto updateCommentByAdmin( + @PathVariable Long commentId, + @RequestBody @Valid CommentRequestDto commentRequestDto) { + log.info("PATCH /admin/comments/{}", commentId); + return commentService.updateCommentByAdmin(commentId, commentRequestDto); + } + + @GetMapping("/search") + public List searchComments(@RequestParam String text, + @RequestParam(defaultValue = "0") @PositiveOrZero Integer from, + @RequestParam(defaultValue = "10") @Positive Integer size) { + log.info("GET /admin/comments/{}", text); + return commentService.searchComments(text, from, size); + } + + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping("/{commentId}") + public void deleteCommentByAdmin(@PathVariable Long commentId) { + log.info("DELETE /admin/comments/{}", commentId); + commentService.deleteCommentByAdmin(commentId); + } +} diff --git a/main/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java b/main/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java new file mode 100644 index 0000000..a8c5565 --- /dev/null +++ b/main/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java @@ -0,0 +1,53 @@ +package ru.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.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import ru.practicum.comment.dto.CommentRequestDto; +import ru.practicum.comment.dto.CommentResponseDto; +import ru.practicum.comment.service.CommentService; + +import java.util.List; + +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping(path = "/users/{userId}/comments") +public class CommentPrivateController { + private final CommentService commentService; + + @ResponseStatus(HttpStatus.CREATED) + @PostMapping + public CommentResponseDto createComment(@PathVariable Long userId, + @RequestParam Long eventId, + @RequestBody @Valid CommentRequestDto commentRequestDto) { + log.info("Запрос на комментарий в событии с id= {}. POST /users/{}/comments", eventId, userId); + return commentService.createComment(userId, eventId, commentRequestDto); + } + + @PatchMapping("/{commentId}") + public CommentResponseDto updateComment(@PathVariable Long userId, + @PathVariable Long commentId, + @RequestBody @Valid CommentRequestDto commentRequestDto) { + log.info("PATCH /users/{}/comments/{}", userId, commentId); + return commentService.updateComment(userId, commentId, commentRequestDto); + } + + @GetMapping + public List getUserComments(@PathVariable Long userId, + @RequestParam(defaultValue = "0") @PositiveOrZero Integer from, + @RequestParam(defaultValue = "10") @Positive Integer size) { + log.info("GET /users/{}/comments?from={}&size={}", userId, from, size); + return commentService.getUserComments(userId, from, size); + } + + @ResponseStatus(HttpStatus.NO_CONTENT) + @DeleteMapping("/{commentId}") + public void deleteComment(@PathVariable Long userId, @PathVariable Long commentId) { + commentService.deleteComment(userId, commentId); + } +} diff --git a/main/src/main/java/ru/practicum/comment/controller/CommentPublicController.java b/main/src/main/java/ru/practicum/comment/controller/CommentPublicController.java new file mode 100644 index 0000000..c1bad60 --- /dev/null +++ b/main/src/main/java/ru/practicum/comment/controller/CommentPublicController.java @@ -0,0 +1,35 @@ +package ru.practicum.comment.controller; + +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import ru.practicum.comment.dto.CommentResponseDto; +import ru.practicum.comment.service.CommentService; + +import java.util.List; + +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping(path = "/events/{eventId}/comments") +public class CommentPublicController { + private final CommentService commentService; + + @GetMapping + public List getCommentsByEvent( + @PathVariable Long eventId, + @RequestParam(defaultValue = "0") @PositiveOrZero Integer from, + @RequestParam(defaultValue = "10") @Positive Integer size) { + log.info("GET /comments?eventId={}&from={}&size={}", eventId, from, size); + return commentService.getCommentsByEvent(eventId, from, size); + } + + @GetMapping("/{commentId}") + public CommentResponseDto getCommentById(@PathVariable Long eventId, + @PathVariable Long commentId) { + log.info("GET /events/{}/comments/{}", eventId, commentId); + return commentService.getCommentById(eventId, commentId); + } +} diff --git a/main/src/main/java/ru/practicum/comment/dto/CommentMapper.java b/main/src/main/java/ru/practicum/comment/dto/CommentMapper.java new file mode 100644 index 0000000..3aed919 --- /dev/null +++ b/main/src/main/java/ru/practicum/comment/dto/CommentMapper.java @@ -0,0 +1,36 @@ +package ru.practicum.comment.dto; + +import lombok.experimental.UtilityClass; +import ru.practicum.comment.model.Comment; +import ru.practicum.event.model.Event; +import ru.practicum.user.model.User; + +import java.time.LocalDateTime; + +@UtilityClass +public class CommentMapper { + public static CommentResponseDto toCommentResponseDto(Comment comment) { + return new CommentResponseDto( + comment.getId(), + comment.getText(), + comment.getEvent().getId(), + comment.getAuthor().getId(), + comment.getAuthor().getName(), + comment.getCreated(), + comment.getUpdated(), + comment.getIsEdited() + ); + } + + public static Comment toComment(CommentRequestDto commentRequestDto, User user, Event event) { + return new Comment( + null, + commentRequestDto.getText(), + event, + user, + LocalDateTime.now(), + null, + false + ); + } +} \ No newline at end of file diff --git a/main/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java b/main/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java new file mode 100644 index 0000000..a58bf7d --- /dev/null +++ b/main/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java @@ -0,0 +1,18 @@ +package ru.practicum.comment.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class CommentRequestDto { + @NotBlank(message = "Комментарий не может быть пустым") + @Size(min = 1, max = 2000, message = "Комментарий должен быть длинной от {min} до {max} символов") + private String text; +} diff --git a/main/src/main/java/ru/practicum/comment/dto/CommentResponseDto.java b/main/src/main/java/ru/practicum/comment/dto/CommentResponseDto.java new file mode 100644 index 0000000..ec5fdce --- /dev/null +++ b/main/src/main/java/ru/practicum/comment/dto/CommentResponseDto.java @@ -0,0 +1,26 @@ +package ru.practicum.comment.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class CommentResponseDto { + private Long id; + private String text; + private Long eventId; + private Long authorId; + private String authorName; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime created; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updated; + private Boolean isEdited; +} diff --git a/main/src/main/java/ru/practicum/comment/model/Comment.java b/main/src/main/java/ru/practicum/comment/model/Comment.java new file mode 100644 index 0000000..04bac9a --- /dev/null +++ b/main/src/main/java/ru/practicum/comment/model/Comment.java @@ -0,0 +1,55 @@ +package ru.practicum.comment.model; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import ru.practicum.event.model.Event; +import ru.practicum.user.model.User; + +import java.time.LocalDateTime; +import java.util.Objects; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Table(name = "comments") +@Entity +public class Comment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 2000) + private String text; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_id", nullable = false) + private Event event; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "author_id", nullable = false) + private User author; + + @Column(nullable = false) + private LocalDateTime created; + + private LocalDateTime updated; + + @Column(name = "is_edited", nullable = false) + private Boolean isEdited = false; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Comment comment)) return false; + return Objects.equals(id, comment.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/main/src/main/java/ru/practicum/comment/repository/CommentRepository.java b/main/src/main/java/ru/practicum/comment/repository/CommentRepository.java new file mode 100644 index 0000000..3336088 --- /dev/null +++ b/main/src/main/java/ru/practicum/comment/repository/CommentRepository.java @@ -0,0 +1,14 @@ +package ru.practicum.comment.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.comment.model.Comment; + +public interface CommentRepository extends JpaRepository { + Page findByEventIdOrderByCreatedAsc(Long eventId, Pageable pageable); + + Page findByAuthorId(Long userId, Pageable pageable); + + Page findByTextContainingIgnoreCase(String text, Pageable pageable); +} diff --git a/main/src/main/java/ru/practicum/comment/service/CommentService.java b/main/src/main/java/ru/practicum/comment/service/CommentService.java new file mode 100644 index 0000000..44da63e --- /dev/null +++ b/main/src/main/java/ru/practicum/comment/service/CommentService.java @@ -0,0 +1,26 @@ +package ru.practicum.comment.service; + +import ru.practicum.comment.dto.CommentRequestDto; +import ru.practicum.comment.dto.CommentResponseDto; + +import java.util.List; + +public interface CommentService { + CommentResponseDto createComment(Long userId, Long eventId, CommentRequestDto commentRequestDto); + + CommentResponseDto updateComment(Long userId, Long commentId, CommentRequestDto commentRequestDto); + + List getUserComments(Long userId, Integer from, Integer size); + + void deleteComment(Long userId, Long commentId); + + List getCommentsByEvent(Long eventId, Integer from, Integer size); + + CommentResponseDto getCommentById(Long eventId, Long commentId); + + CommentResponseDto updateCommentByAdmin(Long commentId, CommentRequestDto commentRequestDto); + + List searchComments(String text, Integer from, Integer size); + + void deleteCommentByAdmin(Long commentId); +} diff --git a/main/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java b/main/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java new file mode 100644 index 0000000..4a076db --- /dev/null +++ b/main/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java @@ -0,0 +1,168 @@ +package ru.practicum.comment.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.comment.dto.CommentMapper; +import ru.practicum.comment.dto.CommentRequestDto; +import ru.practicum.comment.dto.CommentResponseDto; +import ru.practicum.comment.model.Comment; +import ru.practicum.comment.repository.CommentRepository; +import ru.practicum.event.model.Event; +import ru.practicum.event.repository.EventRepository; +import ru.practicum.event.state.EventState; +import ru.practicum.exception.ConflictException; +import ru.practicum.exception.NotFoundException; +import ru.practicum.user.model.User; +import ru.practicum.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.List; + +@Transactional(readOnly = true) +@Slf4j +@RequiredArgsConstructor +@Service +public class CommentServiceImpl implements CommentService { + private final CommentRepository commentRepository; + private final UserRepository userRepository; + private final EventRepository eventRepository; + + @Transactional + @Override + public CommentResponseDto createComment(Long userId, Long eventId, CommentRequestDto commentRequestDto) { + User user = findUser(userId); + Event event = findEvent(eventId); + + return CommentMapper.toCommentResponseDto(commentRepository.save(CommentMapper.toComment(commentRequestDto, user, event))); + } + + @Transactional + @Override + public CommentResponseDto updateComment(Long userId, Long commentId, CommentRequestDto commentRequestDto) { + User user = findUser(userId); + Comment comment = findComment(commentId); + + validateAuthorComment(user, comment); + + comment.setText(commentRequestDto.getText()); + comment.setUpdated(LocalDateTime.now()); + comment.setIsEdited(true); + + return CommentMapper.toCommentResponseDto(commentRepository.save(comment)); + } + + @Override + public List getUserComments(Long userId, Integer from, Integer size) { + Pageable pageable = PageRequest.of(from / size, size, Sort.by("id").ascending()); + + findUser(userId); + + return commentRepository.findByAuthorId(userId, pageable) + .stream() + .map(CommentMapper::toCommentResponseDto) + .toList(); + } + + @Transactional + @Override + public void deleteComment(Long userId, Long commentId) { + User user = findUser(userId); + Comment comment = findComment(commentId); + + validateAuthorComment(user, comment); + + commentRepository.deleteById(commentId); + } + + @Override + public List getCommentsByEvent(Long eventId, Integer from, Integer size) { + Pageable pageable = PageRequest.of(from / size, size, Sort.by("id").ascending()); + + Event event = findEvent(eventId); + + validateEvents(event); + + return commentRepository.findByEventIdOrderByCreatedAsc(eventId, pageable) + .stream() + .map(CommentMapper::toCommentResponseDto) + .toList(); + } + + @Override + public CommentResponseDto getCommentById(Long eventId, Long commentId) { + Event event = findEvent(eventId); + Comment comment = findComment(commentId); + + validateEvents(event); + + if (!comment.getEvent().getId().equals(eventId)) { + log.warn("Комментарий не относится к событию с id: {}", eventId); + throw new ConflictException("Комментарий не относится к событию с id:" + eventId); + } + + return CommentMapper.toCommentResponseDto(comment); + } + + @Transactional + @Override + public CommentResponseDto updateCommentByAdmin(Long commentId, CommentRequestDto commentRequestDto) { + Comment comment = findComment(commentId); + + comment.setText(commentRequestDto.getText()); + comment.setUpdated(LocalDateTime.now()); + comment.setIsEdited(true); + + return CommentMapper.toCommentResponseDto(commentRepository.save(comment)); + } + + @Override + public List searchComments(String text, Integer from, Integer size) { + Pageable pageable = PageRequest.of(from / size, size, Sort.by("created").descending()); + return commentRepository.findByTextContainingIgnoreCase(text, pageable) + .stream() + .map(CommentMapper::toCommentResponseDto) + .toList(); + } + + @Transactional + @Override + public void deleteCommentByAdmin(Long commentId) { + findComment(commentId); + + commentRepository.deleteById(commentId); + } + + private User findUser(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new NotFoundException("Пользователь с id:" + userId + " не найден")); + } + + private Event findEvent(Long eventId) { + return eventRepository.findById(eventId) + .orElseThrow(() -> new NotFoundException("Событие с id:" + eventId + " не найдено")); + } + + private Comment findComment(Long commentId) { + return commentRepository.findById(commentId) + .orElseThrow(() -> new NotFoundException("Комментарий с id:" + commentId + " не найден")); + } + + private void validateAuthorComment(User user, Comment comment) { + if (!comment.getAuthor().equals(user)) { + log.warn("Автором комментария является другой пользователь"); + throw new ConflictException("Автором комментария является другой пользователь"); + } + } + + private void validateEvents(Event event) { + if (!EventState.PUBLISHED.equals(event.getState())) { + log.warn("Событие не опубликовано. Получить комментарий невозможно"); + throw new NotFoundException("Событие не опубликовано. Получить комментарий невозможно"); + } + } +} diff --git a/main/src/main/resources/schema.sql b/main/src/main/resources/schema.sql index d34c148..6d3ea4c 100644 --- a/main/src/main/resources/schema.sql +++ b/main/src/main/resources/schema.sql @@ -64,7 +64,20 @@ create table IF NOT EXISTS compilations ( create table IF NOT EXISTS compilations_events( compilation_id BIGINT NOT NULL, event_id BIGINT NOT NULL, - CONSTRAINT fk_compilations_events_compilation FOREIGN KEY (compilation_id) REFERENCES compilations (id) ON update CASCADE on delete cascade, - CONSTRAINT fk_compilations_events_event FOREIGN KEY (event_id) REFERENCES events (id) ON update CASCADE on delete cascade, + CONSTRAINT fk_compilations_events_compilation FOREIGN KEY (compilation_id) REFERENCES compilations (id) ON update CASCADE ON delete CASCADE, + CONSTRAINT fk_compilations_events_event FOREIGN KEY (event_id) REFERENCES events (id) ON update CASCADE ON delete CASCADE, CONSTRAINT pk_compilation_event PRIMARY KEY (compilation_id, event_id) +); + +create table IF NOT EXISTS comments( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + text VARCHAR(2000) NOT NULL, + event_id BIGINT NOT NULL, + author_id BIGINT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + updated TIMESTAMP WITHOUT TIME ZONE, + is_edited BOOLEAN NOT NULL, + CONSTRAINT fk_comment_event FOREIGN KEY (event_id) REFERENCES events (id) ON delete CASCADE, + CONSTRAINT fk_comment_user FOREIGN KEY (author_id) REFERENCES users(id) ON delete CASCADE, + CONSTRAINT pk_comment_id PRIMARY KEY (id) ); \ No newline at end of file diff --git a/postman/feature.json b/postman/feature.json new file mode 100644 index 0000000..05407e8 --- /dev/null +++ b/postman/feature.json @@ -0,0 +1,1068 @@ +{ + "info": { + "_postman_id": "b65e8980-fdd9-4688-b904-605dacafe9e6", + "name": "feature", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "43654686", + "_collection_link": "https://andrey-9181240.postman.co/workspace/Andrey's-Workspace~5140b5a4-be80-48b9-8b9c-7d2a1b4b76a6/collection/43654686-b65e8980-fdd9-4688-b904-605dacafe9e6?action=share&source=collection_link&creator=43654686" + }, + "item": [ + { + "name": "1. Создать категорию", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "//var timestamp = new Date().getTime();\r", + "var counter = parseInt(pm.environment.get(\"counter\") || \"1\")\r", + "\r", + "pm.environment.set('uniqueEmail', 'user' + counter + '@mail.ru');\r", + "pm.environment.set('uniqueOtherEmail', 'other_user' + counter + '@mail.ru');\r", + "pm.environment.set('uniqueCategory', 'Категория ' + counter);\r", + "pm.environment.set('uniqueEventTitle', 'Событие ' + counter);\r", + "pm.environment.set('uniqueUnpublishedEventTitle', 'Неопубликованное событие ' + counter);\r", + "pm.environment.set('uniqueCommentText', 'Комментарий ' + counter);\r", + "pm.environment.set('uniqueOtherCommentText', 'Другой комментарий ' + counter);\r", + "\r", + "pm.environment.set(\"counter\", counter + 1);" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса — 201\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "var jsonData = pm.response.json();\r", + "pm.environment.set(\"categoryId\", jsonData.id);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{ \"name\": \"{{uniqueCategory}}\" }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/categories", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "categories" + ] + } + }, + "response": [] + }, + { + "name": "2. Создать пользователя", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "var counter = parseInt(pm.environment.get(\"counter\") || \"1\")\r", + "\r", + "pm.environment.set('uniqueEmail', 'user' + counter + '@mail.ru');\r", + "pm.environment.set('uniqueOtherEmail', 'other_user' + counter + '@mail.ru');\r", + "pm.environment.set('uniqueCategory', 'Категория ' + counter);\r", + "pm.environment.set('uniqueEventTitle', 'Событие ' + counter);\r", + "pm.environment.set('uniqueUnpublishedEventTitle', 'Неопубликованное событие ' + counter);\r", + "pm.environment.set('uniqueCommentText', 'Комментарий ' + counter);\r", + "pm.environment.set('uniqueOtherCommentText', 'Другой комментарий ' + counter);\r", + "\r", + "pm.environment.set(\"counter\", counter + 1);" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса — 201\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "var jsonData = pm.response.json();\r", + "pm.environment.set(\"userId\", jsonData.id);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{ \r\n \"name\": \"Макс Иванов\", \r\n \"email\": \"{{uniqueEmail}}\" \r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "3. Создать событие", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Статус кода - 201\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "var jsonData = pm.response.json();\r", + "pm.environment.set(\"eventId\", jsonData.id);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"annotation\": \"Краткое описание, сжатый пересказ сути события\",\r\n \"category\": {{categoryId}},\r\n \"description\": \"Подробная информация о событии, такая как дата, время, место, формат, программа, спикеры, партнёры, цена \",\r\n \"eventDate\": \"2025-09-01 18:00:00\",\r\n \"location\": {\"lat\": 45.7558, \"lon\": 30.6176},\r\n \"paid\": true,\r\n \"participantLimit\": 50,\r\n \"requestModeration\": false,\r\n \"title\": \"Заголовок события\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}", + "events" + ] + } + }, + "response": [] + }, + { + "name": "4. Обновление события администратором", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса — 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "var jsonData = pm.response.json();\r", + "pm.test(\"Событие PUBLISHED\", function () {\r", + " pm.expect(jsonData.state).to.eql(\"PUBLISHED\");\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"stateAction\": \"PUBLISH_EVENT\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/events/{{eventId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "events", + "{{eventId}}" + ] + } + }, + "response": [] + }, + { + "name": "5. Создать комментарий", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса — 201\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "var jsonData = pm.response.json();\r", + "pm.environment.set(\"commentId\", jsonData.id);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"text\": \"Первый комментарий к событию.\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}/comments?eventId={{eventId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}", + "comments" + ], + "query": [ + { + "key": "eventId", + "value": "{{eventId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "6. Создать другого пользователя", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "var counter = parseInt(pm.environment.get(\"counter\") || \"1\")\r", + "\r", + "pm.environment.set('uniqueEmail', 'user' + counter + '@mail.ru');\r", + "pm.environment.set('uniqueOtherEmail', 'other_user' + counter + '@mail.ru');\r", + "pm.environment.set('uniqueCategory', 'Категория ' + counter);\r", + "pm.environment.set('uniqueEventTitle', 'Событие ' + counter);\r", + "pm.environment.set('uniqueUnpublishedEventTitle', 'Неопубликованное событие ' + counter);\r", + "pm.environment.set('uniqueCommentText', 'Комментарий ' + counter);\r", + "pm.environment.set('uniqueOtherCommentText', 'Другой комментарий ' + counter);\r", + "\r", + "pm.environment.set(\"counter\", counter + 1);" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса — 201\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "var jsonData = pm.response.json();\r", + "pm.environment.set(\"otherUserId\", jsonData.id);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{ \r\n \"name\": \"Другой пользователь\", \r\n \"email\": \"{{uniqueOtherEmail}}\" \r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "users" + ] + } + }, + "response": [] + }, + { + "name": "7. Создать другой комментарий", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса - 201\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "var jsonData = pm.response.json();\r", + "pm.environment.set(\"otherCommentId\", jsonData.id);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"text\": \"Комментарий другого пользователя\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/{{otherUserId}}/comments?eventId={{eventId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{otherUserId}}", + "comments" + ], + "query": [ + { + "key": "eventId", + "value": "{{eventId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "8. Создать неопубликованное событие", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса - 201\", function () {\r", + " pm.response.to.have.status(201);\r", + "});\r", + "var jsonData = pm.response.json();\r", + "pm.environment.set(\"unpublishedEventId\", jsonData.id);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"annotation\": \"Краткое описание, сжатый пересказ сути неопубликованного события\",\r\n \"category\": {{categoryId}},\r\n \"description\": \"Подробная информация о неопубликованном событии, такая как дата, время, место, формат, программа, спикеры, партнёры, цена \",\r\n \"eventDate\": \"2025-09-01 18:00:00\",\r\n \"location\": {\"lat\": 45.7558, \"lon\": 30.6176},\r\n \"paid\": true,\r\n \"participantLimit\": 50,\r\n \"requestModeration\": false,\r\n \"title\": \"Заголовок неопубликованного события\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}/events", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}", + "events" + ] + } + }, + "response": [] + }, + { + "name": "9. Получить комментарии по событию", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса - 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "pm.test(\"Ответ — это массив\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.be.an('array');\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/events/{{eventId}}/comments?from=0&size=10", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "events", + "{{eventId}}", + "comments" + ], + "query": [ + { + "key": "from", + "value": "0" + }, + { + "key": "size", + "value": "10" + } + ] + } + }, + "response": [] + }, + { + "name": "10. Получить комментарий по id события", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса - 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "pm.test(\"Идентификатор комментария соответствует запрошенному идентификатору\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData.id).to.eql(parseInt(pm.environment.get(\"commentId\")));\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/events/{{eventId}}/comments/{{commentId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "events", + "{{eventId}}", + "comments", + "{{commentId}}" + ] + } + }, + "response": [] + }, + { + "name": "11. Обновить комментарий по событию пользователя", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса - 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "var jsonData = pm.response.json();\r", + "pm.test(\"Текст комментария обновлен\", function () {\r", + " pm.expect(jsonData.text).to.eql(\"Обновленный комментарий пользователем\");\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"text\": \"Обновленный комментарий пользователем\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}/comments/{{commentId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}", + "comments", + "{{commentId}}" + ] + } + }, + "response": [] + }, + { + "name": "12. Удалить комментарий", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса - 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/users/{{userId}}/comments/{{commentId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}", + "comments", + "{{commentId}}" + ] + } + }, + "response": [] + }, + { + "name": "13. Создать комментарий", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса - 201\", function () { pm.response.to.have.status(201); });\r", + "var json = pm.response.json();\r", + "pm.environment.set(\"commentId\", json.id);" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"text\": \"Комментарий для админских тестов\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}/comments?eventId={{eventId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}", + "comments" + ], + "query": [ + { + "key": "eventId", + "value": "{{eventId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "14. Обновить комментарий администратором", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса - 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "var jsonData = pm.response.json();\r", + "pm.test(\"Комментарий отредактирован администратором\", function () {\r", + " pm.expect(jsonData.text).to.eql(\"Админский комментарий\");\r", + " pm.expect(jsonData.isEdited).to.be.true;\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"text\": \"Админский комментарий\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/comments/{{commentId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "comments", + "{{commentId}}" + ] + } + }, + "response": [] + }, + { + "name": "15. Поиск комментариев админом", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса 200\", function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "pm.test(\"Ответ — это массив\", function () {\r", + " var jsonData = pm.response.json();\r", + " pm.expect(jsonData).to.be.an('array');\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/admin/comments/search?text=Комментарий&from=0&size=10", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "comments", + "search" + ], + "query": [ + { + "key": "text", + "value": "Комментарий" + }, + { + "key": "from", + "value": "0" + }, + { + "key": "size", + "value": "10" + } + ] + } + }, + "response": [] + }, + { + "name": "16. Удалить комментарий админом", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса - 204\", function () {\r", + " pm.response.to.have.status(204);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/admin/comments/{{commentId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "comments", + "{{commentId}}" + ] + } + }, + "response": [] + }, + { + "name": "17. Создать пустой комментарий", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса — 400\", function () {\r", + " pm.response.to.have.status(400);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{ \"text\": \"\" }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}/comments?eventId={{eventId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}", + "comments" + ], + "query": [ + { + "key": "eventId", + "value": "{{eventId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "18. Обновить комментарий другого пользователя", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса — 409.\", function () {\r", + " pm.response.to.have.status(409);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{ \"text\": \"Попытка обновить чужой комментарий\" }", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/{{userId}}/comments/{{otherCommentId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "{{userId}}", + "comments", + "{{otherCommentId}}" + ] + } + }, + "response": [] + }, + { + "name": "19. Получить комментарии неопубликованного события", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса - 404\", function () {\r", + " pm.response.to.have.status(404);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/events/{{unpublishedEventId}}/comments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "events", + "{{unpublishedEventId}}", + "comments" + ] + } + }, + "response": [] + }, + { + "name": "20. Получить несуществующий комментарий", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Код статуса - 404\", function () {\r", + " pm.response.to.have.status(404);\r", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/events/{{eventId}}/comments/99999", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "events", + "{{eventId}}", + "comments", + "99999" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:8080", + "type": "string" + }, + { + "key": "uniqueEmail", + "value": "", + "type": "string" + }, + { + "key": "uniqueOtherEmail", + "value": "", + "type": "string" + }, + { + "key": "uniqueCategory", + "value": "", + "type": "string" + }, + { + "key": "uniqueEventTitle", + "value": "", + "type": "string" + }, + { + "key": "uniqueUnpublishedEventTitle", + "value": "", + "type": "string" + }, + { + "key": "uniqueCommentText", + "value": "", + "type": "string" + }, + { + "key": "uniqueOtherCommentText", + "value": "", + "type": "string" + }, + { + "key": "categoryId", + "value": "", + "type": "string" + }, + { + "key": "userId", + "value": "", + "type": "string" + }, + { + "key": "otherUserId", + "value": "", + "type": "string" + }, + { + "key": "eventId", + "value": "", + "type": "string" + }, + { + "key": "commentId", + "value": "", + "type": "string" + }, + { + "key": "otherCommentId", + "value": "", + "type": "string" + }, + { + "key": "unpublishedEventId", + "value": "", + "type": "string" + }, + { + "key": "counter", + "value": "0", + "type": "string" + } + ] +} \ No newline at end of file