From e87e1371d8f7ea1f5e1ee86a5c5fc4859d9fdb01 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 07:59:12 +0300 Subject: [PATCH 01/18] feat: add comment model --- .../ru/practicum/comments/dto/CommentDto.java | 7 ++++ .../ru/practicum/comments/model/Comment.java | 41 +++++++++++++++++++ main-service/src/main/resources/schema.sql | 36 +++++++++++----- 3 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 main-service/src/main/java/ru/practicum/comments/dto/CommentDto.java create mode 100644 main-service/src/main/java/ru/practicum/comments/model/Comment.java diff --git a/main-service/src/main/java/ru/practicum/comments/dto/CommentDto.java b/main-service/src/main/java/ru/practicum/comments/dto/CommentDto.java new file mode 100644 index 0000000..01daa89 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comments/dto/CommentDto.java @@ -0,0 +1,7 @@ +package ru.practicum.comments.dto; + +/** + * DTO for {@link ru.practicum.comments.model.Comment} + */ +public record CommentDto() { +} diff --git a/main-service/src/main/java/ru/practicum/comments/model/Comment.java b/main-service/src/main/java/ru/practicum/comments/model/Comment.java new file mode 100644 index 0000000..781039a --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comments/model/Comment.java @@ -0,0 +1,41 @@ +package ru.practicum.comments.model; + +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.FieldDefaults; +import ru.practicum.event.model.Event; +import ru.practicum.user.model.User; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "category") +@FieldDefaults(level = AccessLevel.PRIVATE) +public class Comment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @Column(nullable = false) + String text; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "author_id", nullable = false) + User author; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "event_id", nullable = false) + Event event; + + @Column(nullable = false) + LocalDateTime created; + + @Column(nullable = false) + boolean edited; +} diff --git a/main-service/src/main/resources/schema.sql b/main-service/src/main/resources/schema.sql index 33d7a92..c915b3e 100644 --- a/main-service/src/main/resources/schema.sql +++ b/main-service/src/main/resources/schema.sql @@ -1,17 +1,17 @@ -CREATE TABLE category +CREATE TABLE IF NOT EXISTS category ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE ); -CREATE TABLE users +CREATE TABLE IF NOT EXISTS users ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(250) NOT NULL, email VARCHAR(254) NOT NULL UNIQUE ); -CREATE TABLE event +CREATE TABLE IF NOT EXISTS event ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, title VARCHAR(120) NOT NULL, @@ -35,14 +35,14 @@ CREATE TABLE event CONSTRAINT fk_event_user FOREIGN KEY (initiator_id) REFERENCES users (id) ); -CREATE TABLE compilation +CREATE TABLE IF NOT EXISTS compilation ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, title VARCHAR(50) NOT NULL, pinned BOOLEAN DEFAULT FALSE ); -CREATE TABLE compilation_events +CREATE TABLE IF NOT EXISTS compilation_events ( compilation_id BIGINT NOT NULL, event_id BIGINT NOT NULL, @@ -51,13 +51,27 @@ CREATE TABLE compilation_events CONSTRAINT fk_event FOREIGN KEY (event_id) REFERENCES event (id) ON DELETE CASCADE ); -CREATE TABLE participation_request +CREATE TABLE IF NOT EXISTS participation_request ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - created TIMESTAMP NOT NULL, - status VARCHAR(20) NOT NULL, - event_id BIGINT NOT NULL, - requester_id BIGINT NOT NULL, + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + created TIMESTAMP NOT NULL, + status VARCHAR(20) NOT NULL, + event_id BIGINT NOT NULL, + requester_id BIGINT NOT NULL, + CONSTRAINT fk_request_event FOREIGN KEY (event_id) REFERENCES event (id), CONSTRAINT fk_request_user FOREIGN KEY (requester_id) REFERENCES users (id) ); + +CREATE TABLE IF NOT EXISTS comment +( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + text VARCHAR(500) NOT NULL, + author_id BIGINT NOT NULL, + event_id BIGINT NOT NULL, + created TIMESTAMP NOT NULL, + edited BOOLEAN NOT NULL DEFAULT FALSE, + + CONSTRAINT fk_comment_author FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE CASCADE, + CONSTRAINT fk_comment_event FOREIGN KEY (event_id) REFERENCES event (id) ON DELETE CASCADE +); From d0d0aea5dc0ad7635ddf873aabf517a055a6f839 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 08:56:40 +0300 Subject: [PATCH 02/18] feat: add dto --- .../java/ru/practicum/comment/dto/CommentDto.java | 8 ++++++++ .../java/ru/practicum/comment/dto/NewCommentDto.java | 6 ++++++ .../{comments => comment}/model/Comment.java | 12 +++++++----- .../java/ru/practicum/comments/dto/CommentDto.java | 7 ------- 4 files changed, 21 insertions(+), 12 deletions(-) create mode 100644 main-service/src/main/java/ru/practicum/comment/dto/CommentDto.java create mode 100644 main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java rename main-service/src/main/java/ru/practicum/{comments => comment}/model/Comment.java (92%) delete mode 100644 main-service/src/main/java/ru/practicum/comments/dto/CommentDto.java diff --git a/main-service/src/main/java/ru/practicum/comment/dto/CommentDto.java b/main-service/src/main/java/ru/practicum/comment/dto/CommentDto.java new file mode 100644 index 0000000..dbc17e4 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/dto/CommentDto.java @@ -0,0 +1,8 @@ +package ru.practicum.comment.dto; + +import java.time.LocalDateTime; + +import ru.practicum.user.dto.UserShortDto; + +public record CommentDto( + Long id, String text, UserShortDto author, LocalDateTime created, boolean edited) {} diff --git a/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java b/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java new file mode 100644 index 0000000..54c32d4 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java @@ -0,0 +1,6 @@ +package ru.practicum.comment.dto; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; + +public record NewCommentDto(@NotBlank @Max(500) String text) {} diff --git a/main-service/src/main/java/ru/practicum/comments/model/Comment.java b/main-service/src/main/java/ru/practicum/comment/model/Comment.java similarity index 92% rename from main-service/src/main/java/ru/practicum/comments/model/Comment.java rename to main-service/src/main/java/ru/practicum/comment/model/Comment.java index 781039a..0dcbc60 100644 --- a/main-service/src/main/java/ru/practicum/comments/model/Comment.java +++ b/main-service/src/main/java/ru/practicum/comment/model/Comment.java @@ -1,12 +1,14 @@ -package ru.practicum.comments.model; +package ru.practicum.comment.model; + +import java.time.LocalDateTime; import jakarta.persistence.*; -import lombok.*; -import lombok.experimental.FieldDefaults; + import ru.practicum.event.model.Event; import ru.practicum.user.model.User; -import java.time.LocalDateTime; +import lombok.experimental.FieldDefaults; +import lombok.*; @Entity @Getter @@ -14,7 +16,7 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@Table(name = "category") +@Table(name = "comment") @FieldDefaults(level = AccessLevel.PRIVATE) public class Comment { diff --git a/main-service/src/main/java/ru/practicum/comments/dto/CommentDto.java b/main-service/src/main/java/ru/practicum/comments/dto/CommentDto.java deleted file mode 100644 index 01daa89..0000000 --- a/main-service/src/main/java/ru/practicum/comments/dto/CommentDto.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.practicum.comments.dto; - -/** - * DTO for {@link ru.practicum.comments.model.Comment} - */ -public record CommentDto() { -} From f3aef3bbac4c41df663ada50b536e44e1d09e01b Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 08:57:00 +0300 Subject: [PATCH 03/18] feat: add existsById method in events repo --- .../java/ru/practicum/event/repository/EventRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java b/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java index 9c07c7a..915fa46 100644 --- a/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java +++ b/main-service/src/main/java/ru/practicum/event/repository/EventRepository.java @@ -90,4 +90,6 @@ static Predicate createPredicate(EventsPublicGetRequest request) { Page findByInitiator_Id(Long initiatorId, Pageable pageable); Set findAllByIdIn(Collection ids); + + boolean existsById(Long eventId); } From 6db76a0809538d31d4eaf2128b9a429946bd1fd1 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 08:57:35 +0300 Subject: [PATCH 04/18] feat: add comment controllers --- .../controller/CommentAdminController.java | 27 +++++++ .../controller/CommentPrivateController.java | 70 +++++++++++++++++++ .../controller/CommentPublicController.java | 36 ++++++++++ 3 files changed, 133 insertions(+) create mode 100644 main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java create mode 100644 main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java create mode 100644 main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java new file mode 100644 index 0000000..a688396 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java @@ -0,0 +1,27 @@ +package ru.practicum.comment.controller; + +import ru.practicum.comment.service.CommentService; + +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("admin/events/{eventId}/comments") +public class CommentAdminController { + private final CommentService commentService; + + @DeleteMapping("/{commentId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteComment(@PathVariable long eventId, @PathVariable long commentId) { + log.info("Admin delete comment requested for eventId={}, commentId={}", eventId, commentId); + + commentService.deleteComment(eventId, commentId); + } +} diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java new file mode 100644 index 0000000..423e9c8 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java @@ -0,0 +1,70 @@ +package ru.practicum.comment.controller; + +import java.util.Collection; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; + +import ru.practicum.comment.dto.CommentDto; +import ru.practicum.comment.dto.NewCommentDto; +import ru.practicum.comment.service.*; + +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/users/{userId}") +public class CommentPrivateController { + private final CommentService commentService; + + @GetMapping("/comments") + public Collection getAllCommentsPaged( + @PathVariable Long userId, + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { + log.info("Private get all comments requested by userId:{}", userId); + CommentsPrivateGetRequest request = new CommentsPrivateGetRequest(userId, from, size); + return commentService.getAllCommentsPaged(request); + } + + @PostMapping("/events/{eventId}/comments") + @ResponseStatus(HttpStatus.CREATED) + public CommentDto createComment( + @PathVariable long userId, + @PathVariable long eventId, + @Valid @RequestBody NewCommentDto newComment) { + CommentsCreateRequest request = new CommentsCreateRequest(userId, eventId, newComment); + log.info("Create new comment requested"); + return commentService.createComment(request); + } + + @PatchMapping("/comments/{commentId}") + public CommentDto updateComment( + @PathVariable long userId, + @PathVariable long commentId, + @Valid @RequestBody NewCommentDto updateComment) { + + CommentsUpdateRequest request = new CommentsUpdateRequest(userId, commentId, updateComment); + + log.info("Update comment requested by userId:{}, commentId:{}", userId, commentId); + + return commentService.updateComment(request); + } + + @DeleteMapping("/comments/{commentId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteComment(@PathVariable long userId, @PathVariable long commentId) { + + log.info("Delete comment requested by userId:{}, commentId:{}", userId, commentId); + + commentService.deleteCommentByUser(userId, commentId); + } +} diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java new file mode 100644 index 0000000..81ecbe1 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java @@ -0,0 +1,36 @@ +package ru.practicum.comment.controller; + +import java.util.Collection; + +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; + +import ru.practicum.comment.dto.CommentDto; +import ru.practicum.comment.service.CommentService; +import ru.practicum.comment.service.CommentsPublicGetRequest; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/events/{eventId}/comments") +public class CommentPublicController { + CommentService commentService; + + @GetMapping + public Collection getComments( + @PathVariable Long eventId, + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { + + CommentsPublicGetRequest request = new CommentsPublicGetRequest(eventId, from, size); + log.info("All comments for event {} requested with params {}", eventId, request); + return commentService.getAllCommentsPaged(request); + } +} From 7d19a575de16886caa439d6127091cd0f21e1fe1 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 08:57:50 +0300 Subject: [PATCH 05/18] feat: add comment mapper --- .../comment/mapper/CommentMapper.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java diff --git a/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java b/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java new file mode 100644 index 0000000..82b67c6 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java @@ -0,0 +1,29 @@ +package ru.practicum.comment.mapper; + +import ru.practicum.comment.dto.CommentDto; +import ru.practicum.comment.dto.NewCommentDto; +import ru.practicum.comment.model.Comment; +import ru.practicum.user.mapper.UserMapper; +import ru.practicum.user.model.User; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class CommentMapper { + + public CommentDto toCommentDto(Comment comment, User author) { + return new CommentDto( + comment.getId(), + comment.getText(), + UserMapper.mapToUserShortDto(author), + comment.getCreated(), + comment.isEdited()); + } + + public Comment toEntity(NewCommentDto newCommentDto) { + String newCommentDtoText = newCommentDto.text(); + Comment comment = new Comment(); + comment.setText(newCommentDtoText); + return comment; + } +} From 62c59a591d513a3879f2c17bea009e3b913ab9fc Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 08:58:10 +0300 Subject: [PATCH 06/18] feat: add comment service and it empty implementation --- .../comment/service/CommentService.java | 19 +++++++ .../comment/service/CommentServiceImpl.java | 51 +++++++++++++++++++ .../service/CommentsCreateRequest.java | 5 ++ .../service/CommentsPrivateGetRequest.java | 11 ++++ .../service/CommentsPublicGetRequest.java | 11 ++++ .../service/CommentsUpdateRequest.java | 5 ++ 6 files changed, 102 insertions(+) create mode 100644 main-service/src/main/java/ru/practicum/comment/service/CommentService.java create mode 100644 main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java create mode 100644 main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java create mode 100644 main-service/src/main/java/ru/practicum/comment/service/CommentsPrivateGetRequest.java create mode 100644 main-service/src/main/java/ru/practicum/comment/service/CommentsPublicGetRequest.java create mode 100644 main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentService.java b/main-service/src/main/java/ru/practicum/comment/service/CommentService.java new file mode 100644 index 0000000..98b487e --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentService.java @@ -0,0 +1,19 @@ +package ru.practicum.comment.service; + +import java.util.Collection; + +import ru.practicum.comment.dto.CommentDto; + +public interface CommentService { + Collection getAllCommentsPaged(CommentsPublicGetRequest request); + + Collection getAllCommentsPaged(CommentsPrivateGetRequest request); + + void deleteComment(long eventId, long commentId); + + void deleteCommentByUser(long userId, long commentId); + + CommentDto createComment(CommentsCreateRequest request); + + CommentDto updateComment(CommentsUpdateRequest request); +} diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java b/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java new file mode 100644 index 0000000..663baea --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java @@ -0,0 +1,51 @@ +package ru.practicum.comment.service; + +import java.util.Collection; +import java.util.List; + +import ru.practicum.comment.dto.CommentDto; +import ru.practicum.comment.repository.CommentRepository; +import ru.practicum.event.repository.EventRepository; +import ru.practicum.user.repository.UserRepository; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CommentServiceImpl implements CommentService { + private final CommentRepository commentRepository; + private final UserRepository userRepository; + private final EventRepository eventRepository; + + @Override + public Collection getAllCommentsPaged(CommentsPublicGetRequest request) { + return List.of(); + } + + @Override + public Collection getAllCommentsPaged(CommentsPrivateGetRequest request) { + return List.of(); + } + + @Override + public void deleteComment(long eventId, long commentId) {} + + @Override + public void deleteCommentByUser(long userId, long commentId) {} + + @Override + public CommentDto createComment(CommentsCreateRequest request) { + return null; + } + + @Override + public CommentDto updateComment(CommentsUpdateRequest request) { + return null; + } +} diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java b/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java new file mode 100644 index 0000000..bdfcec9 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java @@ -0,0 +1,5 @@ +package ru.practicum.comment.service; + +import ru.practicum.comment.dto.NewCommentDto; + +public record CommentsCreateRequest(long userId, long eventId, NewCommentDto newComment) {} diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentsPrivateGetRequest.java b/main-service/src/main/java/ru/practicum/comment/service/CommentsPrivateGetRequest.java new file mode 100644 index 0000000..26901be --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentsPrivateGetRequest.java @@ -0,0 +1,11 @@ +package ru.practicum.comment.service; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +public record CommentsPrivateGetRequest(long userId, int from, int size) { + public Pageable getPageable() { + int page = from / size; + return PageRequest.of(page, size); + } +} diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentsPublicGetRequest.java b/main-service/src/main/java/ru/practicum/comment/service/CommentsPublicGetRequest.java new file mode 100644 index 0000000..80b889b --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentsPublicGetRequest.java @@ -0,0 +1,11 @@ +package ru.practicum.comment.service; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +public record CommentsPublicGetRequest(long eventId, int from, int size) { + public Pageable getPageable() { + int page = from / size; + return PageRequest.of(page, size); + } +} diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java b/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java new file mode 100644 index 0000000..8581028 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java @@ -0,0 +1,5 @@ +package ru.practicum.comment.service; + +import ru.practicum.comment.dto.NewCommentDto; + +public record CommentsUpdateRequest(long userId, long commentId, NewCommentDto updateComment) {} From 3b14c703d22bfbad4f565d704cda5225f1ab0c6b Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 08:58:53 +0300 Subject: [PATCH 07/18] feat: add comment repo --- .../ru/practicum/comment/repository/CommentRepository.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java diff --git a/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java b/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java new file mode 100644 index 0000000..93ccd97 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java @@ -0,0 +1,7 @@ +package ru.practicum.comment.repository; + +import ru.practicum.comment.model.Comment; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommentRepository extends JpaRepository {} From 165dbf01a0d24da70c9c9201a4e882fa5b7f58c1 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 09:37:54 +0300 Subject: [PATCH 08/18] feat: implement comment service methods --- .../controller/CommentAdminController.java | 8 +- .../controller/CommentPrivateController.java | 2 +- .../comment/mapper/CommentMapper.java | 10 +-- .../comment/repository/CommentRepository.java | 13 ++- .../comment/service/CommentService.java | 4 +- .../comment/service/CommentServiceImpl.java | 80 +++++++++++++++++-- 6 files changed, 96 insertions(+), 21 deletions(-) diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java index a688396..1aab0a9 100644 --- a/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java @@ -13,15 +13,15 @@ @Validated @RestController @RequiredArgsConstructor -@RequestMapping("admin/events/{eventId}/comments") +@RequestMapping("admin/comments") public class CommentAdminController { private final CommentService commentService; @DeleteMapping("/{commentId}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void deleteComment(@PathVariable long eventId, @PathVariable long commentId) { - log.info("Admin delete comment requested for eventId={}, commentId={}", eventId, commentId); + public void deleteComment(@PathVariable long commentId) { + log.info("Admin delete comment requested for commentId={}", commentId); - commentService.deleteComment(eventId, commentId); + commentService.deleteComment(commentId); } } diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java index 423e9c8..c9896b5 100644 --- a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java @@ -32,7 +32,7 @@ public Collection getAllCommentsPaged( @RequestParam(defaultValue = "10") @Positive int size) { log.info("Private get all comments requested by userId:{}", userId); CommentsPrivateGetRequest request = new CommentsPrivateGetRequest(userId, from, size); - return commentService.getAllCommentsPaged(request); + return commentService.getAllCommentsOfUserPaged(request); } @PostMapping("/events/{eventId}/comments") diff --git a/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java b/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java index 82b67c6..b217995 100644 --- a/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java +++ b/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java @@ -1,8 +1,11 @@ package ru.practicum.comment.mapper; +import java.time.LocalDateTime; + import ru.practicum.comment.dto.CommentDto; import ru.practicum.comment.dto.NewCommentDto; import ru.practicum.comment.model.Comment; +import ru.practicum.event.model.Event; import ru.practicum.user.mapper.UserMapper; import ru.practicum.user.model.User; @@ -20,10 +23,7 @@ public CommentDto toCommentDto(Comment comment, User author) { comment.isEdited()); } - public Comment toEntity(NewCommentDto newCommentDto) { - String newCommentDtoText = newCommentDto.text(); - Comment comment = new Comment(); - comment.setText(newCommentDtoText); - return comment; + public Comment toEntity(NewCommentDto newCommentDto, User author, Event event) { + return new Comment(null, newCommentDto.text(), author, event, LocalDateTime.now(), false); } } diff --git a/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java b/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java index 93ccd97..0752d9b 100644 --- a/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java +++ b/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java @@ -1,7 +1,18 @@ package ru.practicum.comment.repository; +import java.util.List; + import ru.practicum.comment.model.Comment; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; -public interface CommentRepository extends JpaRepository {} +public interface CommentRepository extends JpaRepository { + + @EntityGraph(attributePaths = "author") + Page findAllByEventId(Long eventId, Pageable pageable); + + List findAllByAuthorId(Long authorId, Pageable pageable); +} diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentService.java b/main-service/src/main/java/ru/practicum/comment/service/CommentService.java index 98b487e..559adc4 100644 --- a/main-service/src/main/java/ru/practicum/comment/service/CommentService.java +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentService.java @@ -7,9 +7,9 @@ public interface CommentService { Collection getAllCommentsPaged(CommentsPublicGetRequest request); - Collection getAllCommentsPaged(CommentsPrivateGetRequest request); + Collection getAllCommentsOfUserPaged(CommentsPrivateGetRequest request); - void deleteComment(long eventId, long commentId); + void deleteComment(long commentId); void deleteCommentByUser(long userId, long commentId); diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java b/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java index 663baea..b6f3208 100644 --- a/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java @@ -2,12 +2,20 @@ import java.util.Collection; import java.util.List; +import java.util.Optional; import ru.practicum.comment.dto.CommentDto; +import ru.practicum.comment.mapper.CommentMapper; +import ru.practicum.comment.model.Comment; import ru.practicum.comment.repository.CommentRepository; +import ru.practicum.event.model.Event; import ru.practicum.event.repository.EventRepository; +import ru.practicum.exception.ForbiddenAccessException; +import ru.practicum.exception.NotFoundException; +import ru.practicum.user.model.User; import ru.practicum.user.repository.UserRepository; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,35 +25,91 @@ @Slf4j @Service @RequiredArgsConstructor -@Transactional(readOnly = true) +@Transactional public class CommentServiceImpl implements CommentService { private final CommentRepository commentRepository; private final UserRepository userRepository; private final EventRepository eventRepository; @Override + @Transactional(readOnly = true) public Collection getAllCommentsPaged(CommentsPublicGetRequest request) { - return List.of(); + if (!eventRepository.existsById(request.eventId())) { + throw new NotFoundException("Event with id=%d not found".formatted(request.eventId())); + } + + Page comments = + commentRepository.findAllByEventId(request.eventId(), request.getPageable()); + + return comments.stream() + .map(comment -> CommentMapper.toCommentDto(comment, comment.getAuthor())) + .toList(); } @Override - public Collection getAllCommentsPaged(CommentsPrivateGetRequest request) { - return List.of(); + @Transactional(readOnly = true) + public Collection getAllCommentsOfUserPaged(CommentsPrivateGetRequest request) { + User user = getUserByIdOrThrow(request.userId()); + + List comments = + commentRepository.findAllByAuthorId(request.userId(), request.getPageable()); + + return comments.stream().map(comment -> CommentMapper.toCommentDto(comment, user)).toList(); } @Override - public void deleteComment(long eventId, long commentId) {} + public void deleteComment(long commentId) { + commentRepository.deleteById(commentId); + } @Override - public void deleteCommentByUser(long userId, long commentId) {} + public void deleteCommentByUser(long userId, long commentId) { + User user = getUserByIdOrThrow(userId); + Comment comment = getCommentByIdOrThrow(commentId); + if (!comment.getAuthor().getId().equals(user.getId())) { + throw new ForbiddenAccessException("You are not allowed to delete others comments"); + } + commentRepository.deleteById(commentId); + } @Override public CommentDto createComment(CommentsCreateRequest request) { - return null; + Event event = getEventByIdOrThrow(request.eventId()); + User user = getUserByIdOrThrow(request.userId()); + + Comment newComment = CommentMapper.toEntity(request.newComment(), user, event); + Comment saved = commentRepository.save(newComment); + return CommentMapper.toCommentDto(saved, user); } @Override public CommentDto updateComment(CommentsUpdateRequest request) { - return null; + User user = getUserByIdOrThrow(request.userId()); + Comment comment = getCommentByIdOrThrow(request.commentId()); + if (!comment.getAuthor().getId().equals(user.getId())) { + throw new ForbiddenAccessException("You are not allowed to update others comments"); + } + + comment.setText(request.updateComment().text()); + Comment saved = commentRepository.save(comment); + return CommentMapper.toCommentDto(saved, user); + } + + private Event getEventByIdOrThrow(Long eventId) { + Optional eventOptional = eventRepository.findById(eventId); + return eventOptional.orElseThrow( + NotFoundException.supplier("Event with id=%d not found", eventId)); + } + + private User getUserByIdOrThrow(Long userId) { + Optional optionalUser = userRepository.findById(userId); + return optionalUser.orElseThrow( + NotFoundException.supplier("User with id=%d not found", userId)); + } + + private Comment getCommentByIdOrThrow(Long commentId) { + Optional optionalComment = commentRepository.findById(commentId); + return optionalComment.orElseThrow( + NotFoundException.supplier("Comment with id=%d not found", commentId)); } } From 40c7f1dc51a7ab7529dd7aca2b119261c1879da6 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 10:31:21 +0300 Subject: [PATCH 09/18] fix: fixes --- .../practicum/comment/controller/CommentPublicController.java | 2 +- .../src/main/java/ru/practicum/comment/dto/NewCommentDto.java | 4 ++-- .../java/ru/practicum/comment/service/CommentServiceImpl.java | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java index 81ecbe1..857c547 100644 --- a/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java @@ -21,7 +21,7 @@ @RequiredArgsConstructor @RequestMapping("/events/{eventId}/comments") public class CommentPublicController { - CommentService commentService; + private final CommentService commentService; @GetMapping public Collection getComments( diff --git a/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java b/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java index 54c32d4..cf89ab6 100644 --- a/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java +++ b/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java @@ -1,6 +1,6 @@ package ru.practicum.comment.dto; -import jakarta.validation.constraints.Max; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; -public record NewCommentDto(@NotBlank @Max(500) String text) {} +public record NewCommentDto(@NotBlank @Size(max = 500) String text) {} diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java b/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java index b6f3208..d98c756 100644 --- a/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java @@ -24,8 +24,8 @@ @Slf4j @Service -@RequiredArgsConstructor @Transactional +@RequiredArgsConstructor public class CommentServiceImpl implements CommentService { private final CommentRepository commentRepository; private final UserRepository userRepository; @@ -91,6 +91,7 @@ public CommentDto updateComment(CommentsUpdateRequest request) { } comment.setText(request.updateComment().text()); + comment.setEdited(true); Comment saved = commentRepository.save(comment); return CommentMapper.toCommentDto(saved, user); } From fbd6873f7bca49e95eba962e30cde9045ceaf1dd Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 10:31:28 +0300 Subject: [PATCH 10/18] feat: add postman tests --- postman/feature.json | 813 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 813 insertions(+) create mode 100644 postman/feature.json diff --git a/postman/feature.json b/postman/feature.json new file mode 100644 index 0000000..600075f --- /dev/null +++ b/postman/feature.json @@ -0,0 +1,813 @@ +{ + "info": { + "_postman_id": "f958e545-a649-474e-9fc1-9aa6ce6a0f36", + "name": "Comments Feature Tests", + "description": "Comprehensive test suite for Comments API endpoints", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "27311667", + "_collection_link": "https://go.postman.co/collection/27311667-f958e545-a649-474e-9fc1-9aa6ce6a0f36?source=collection_link" + }, + "item": [ + { + "name": "Public: Get comments for event", + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const api = new API(pm);", + "const rnd = new RandomUtils();", + "", + "(async function(){", + " try {", + " const user = await api.addUser(rnd.getUser());", + " const category = await api.addCategory(rnd.getCategory());", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", + " await api.publishEvent(event.id);", + " pm.environment.set('eventIdForPublicGet', event.id);", + " } catch(err) {", + " console.error('Ошибка при подготовке тестовых данных для Public Get Comments:', err);", + " }", + "})();" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Ответ должен иметь статус код 200', function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test('Ответ должен быть массивом', function () {", + " pm.expect(pm.response.json()).to.be.an('array');", + "});" + ] + } + } + ], + "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" + } + ], + "variable": [ + { + "key": "eventId", + "value": "{{eventIdForPublicGet}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Private: Create comment", + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const api = new API(pm);", + "const rnd = new RandomUtils();", + "", + "(async function(){", + " try {", + " const eventOwner = await api.addUser(rnd.getUser());", + " const commentAuthor = await api.addUser(rnd.getUser());", + " const category = await api.addCategory(rnd.getCategory());", + " const event = await api.addEvent(eventOwner.id, rnd.getEvent(category.id));", + " await api.publishEvent(event.id);", + " ", + " pm.environment.set('commentAuthorId', commentAuthor.id);", + " pm.environment.set('eventIdForCreate', event.id);", + " } catch(err) {", + " console.error('Ошибка при подготовке тестовых данных для Create Comment:', err);", + " }", + "})();" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Ответ должен иметь статус код 201', function () {", + " pm.response.to.have.status(201);", + "});", + "", + "pm.test('Комментарий должен содержать корректные поля', function () {", + " const comment = pm.response.json();", + " pm.expect(comment).to.have.property('id');", + " pm.expect(comment).to.have.property('text');", + " pm.expect(comment).to.have.property('author');", + " pm.expect(comment).to.have.property('created');", + " pm.expect(comment).to.have.property('edited');", + "});", + "", + "pm.test('Текст комментария совпадает с отправленным', function () {", + " const comment = pm.response.json();", + " pm.expect(comment.text).to.eql('This is a test comment for the event');", + "});", + "", + "pm.test('Комментарий изначально не отредактирован', function () {", + " const comment = pm.response.json();", + " pm.expect(comment.edited).to.be.false;", + "});", + "", + "pm.test('Автор комментария совпадает с отправителем', function () {", + " const comment = pm.response.json();", + " const authorId = parseInt(pm.environment.get('commentAuthorId'));", + " pm.expect(comment.author.id).to.eql(authorId);", + "});" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"text\": \"This is a test comment for the event\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId/comments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId", + "comments" + ], + "variable": [ + { + "key": "userId", + "value": "{{commentAuthorId}}" + }, + { + "key": "eventId", + "value": "{{eventIdForCreate}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Private: Update comment", + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const api = new API(pm);", + "const rnd = new RandomUtils();", + "", + "(async function(){", + " try {", + " const user = await api.addUser(rnd.getUser());", + " const category = await api.addCategory(rnd.getCategory());", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", + " await api.publishEvent(event.id);", + " ", + " const comment = await api.post('/users/' + user.id + '/events/' + event.id + '/comments', ", + " { text: 'Original comment text' }, 'Ошибка при создании комментария: ');", + " ", + " pm.environment.set('updateUserId', user.id);", + " pm.environment.set('updateCommentId', comment.id);", + " } catch(err) {", + " console.error('Ошибка при подготовке тестовых данных для Update Comment:', err);", + " }", + "})();" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Ответ должен иметь статус код 200', function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test('Текст комментария обновлен', function () {", + " const comment = pm.response.json();", + " pm.expect(comment.text).to.eql('Updated comment text');", + "});", + "", + "pm.test('Комментарий отмечен как отредактированный', function () {", + " const comment = pm.response.json();", + " pm.expect(comment.edited).to.be.true;", + "});" + ] + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"text\": \"Updated comment text\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/comments/:commentId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "comments", + ":commentId" + ], + "variable": [ + { + "key": "userId", + "value": "{{updateUserId}}" + }, + { + "key": "commentId", + "value": "{{updateCommentId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Private: Get all user comments", + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const api = new API(pm);", + "const rnd = new RandomUtils();", + "", + "(async function(){", + " try {", + " const user = await api.addUser(rnd.getUser());", + " const category = await api.addCategory(rnd.getCategory());", + " const event1 = await api.addEvent(user.id, rnd.getEvent(category.id));", + " const event2 = await api.addEvent(user.id, rnd.getEvent(category.id));", + " await api.publishEvent(event1.id);", + " await api.publishEvent(event2.id);", + " ", + " await api.post('/users/' + user.id + '/events/' + event1.id + '/comments', ", + " { text: 'First comment' }, 'Ошибка при создании первого комментария: ');", + " await api.post('/users/' + user.id + '/events/' + event2.id + '/comments', ", + " { text: 'Second comment' }, 'Ошибка при создании второго комментария: ');", + " ", + " pm.environment.set('getAllUserId', user.id);", + " } catch(err) {", + " console.error('Ошибка при подготовке тестовых данных для Get All Comments:', err);", + " }", + "})();" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Ответ должен иметь статус код 200', function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test('Ответ должен быть массивом', function () {", + " pm.expect(pm.response.json()).to.be.an('array');", + "});", + "", + "pm.test('Все комментарии принадлежат пользователю', function () {", + " const comments = pm.response.json();", + " const userId = parseInt(pm.environment.get('getAllUserId'));", + " comments.forEach(comment => {", + " pm.expect(comment.author.id).to.eql(userId);", + " });", + "});", + "", + "pm.test('Возвращено как минимум 2 комментария', function () {", + " const comments = pm.response.json();", + " pm.expect(comments.length).to.be.at.least(2);", + "});" + ] + } + } + ], + "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" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{getAllUserId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Private: Delete comment by user", + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const api = new API(pm);", + "const rnd = new RandomUtils();", + "", + "(async function(){", + " try {", + " const user = await api.addUser(rnd.getUser());", + " const category = await api.addCategory(rnd.getCategory());", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", + " await api.publishEvent(event.id);", + " ", + " const comment = await api.post('/users/' + user.id + '/events/' + event.id + '/comments', ", + " { text: 'Comment to be deleted by user' }, 'Ошибка при создании комментария: ');", + " ", + " pm.environment.set('deleteByUserUserId', user.id);", + " pm.environment.set('deleteByUserCommentId', comment.id);", + " } catch(err) {", + " console.error('Ошибка при подготовке тестовых данных для Delete Comment by User:', err);", + " }", + "})();" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Ответ должен иметь статус код 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/users/:userId/comments/:commentId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "comments", + ":commentId" + ], + "variable": [ + { + "key": "userId", + "value": "{{deleteByUserUserId}}" + }, + { + "key": "commentId", + "value": "{{deleteByUserCommentId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Admin: Delete comment", + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const api = new API(pm);", + "const rnd = new RandomUtils();", + "", + "(async function(){", + " try {", + " const user = await api.addUser(rnd.getUser());", + " const category = await api.addCategory(rnd.getCategory());", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", + " await api.publishEvent(event.id);", + " ", + " const comment = await api.post('/users/' + user.id + '/events/' + event.id + '/comments', ", + " { text: 'Comment to be deleted by admin' }, 'Ошибка при создании комментария: ');", + " ", + " pm.environment.set('deleteByAdminCommentId', comment.id);", + " } catch(err) {", + " console.error('Ошибка при подготовке тестовых данных для Admin Delete Comment:', err);", + " }", + "})();" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Ответ должен иметь статус код 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/admin/comments/:commentId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "comments", + ":commentId" + ], + "variable": [ + { + "key": "commentId", + "value": "{{deleteByAdminCommentId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Validation: Create comment with empty text", + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const api = new API(pm);", + "const rnd = new RandomUtils();", + "", + "(async function(){", + " try {", + " const user = await api.addUser(rnd.getUser());", + " const category = await api.addCategory(rnd.getCategory());", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", + " await api.publishEvent(event.id);", + " ", + " pm.environment.set('validationUserId', user.id);", + " pm.environment.set('validationEventId', event.id);", + " } catch(err) {", + " console.error('Ошибка при подготовке тестовых данных для валидации:', err);", + " }", + "})();" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Ответ должен иметь статус код 400', function () {", + " pm.response.to.have.status(400);", + "});" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"text\": \"\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId/comments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId", + "comments" + ], + "variable": [ + { + "key": "userId", + "value": "{{validationUserId}}" + }, + { + "key": "eventId", + "value": "{{validationEventId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Validation: Create comment with text > 500 chars", + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const api = new API(pm);", + "const rnd = new RandomUtils();", + "", + "(async function(){", + " try {", + " const user = await api.addUser(rnd.getUser());", + " const category = await api.addCategory(rnd.getCategory());", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", + " await api.publishEvent(event.id);", + " ", + " pm.environment.set('validationLongUserId', user.id);", + " pm.environment.set('validationLongEventId', event.id);", + " } catch(err) {", + " console.error('Ошибка при подготовке тестовых данных для валидации длинного текста:', err);", + " }", + "})();" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Ответ должен иметь статус код 400', function () {", + " pm.response.to.have.status(400);", + "});" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/events/:eventId/comments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "events", + ":eventId", + "comments" + ], + "variable": [ + { + "key": "userId", + "value": "{{validationLongUserId}}" + }, + { + "key": "eventId", + "value": "{{validationLongEventId}}" + } + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "requests": {}, + "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 publishEvent(eventId, verbose=null) {", + " return this.patch('/admin/events/' + eventId, {stateAction: \"PUBLISH_EVENT\"}, \"Ошибка при публикации события\", verbose);", + " }", + "", + " async addComment(userId, eventId, text, verbose=null) {", + " return this.post(\"/users/\" + userId + \"/events/\" + eventId + \"/comments\", {text: text}, \"Ошибка при добавлении комментария: \", 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){", + " resolve(null);", + " }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}}'),", + " }", + " }", + "", + " 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" + } + ] +} \ No newline at end of file From 25b199e2d39f4cb1d11bef649cd86cfb2f1aae4e Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 20:43:30 +0300 Subject: [PATCH 11/18] fix: remove @validated annotation --- .../ru/practicum/comment/controller/CommentAdminController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java index 1aab0a9..95f7055 100644 --- a/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java @@ -3,14 +3,12 @@ import ru.practicum.comment.service.CommentService; import org.springframework.http.HttpStatus; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j -@Validated @RestController @RequiredArgsConstructor @RequestMapping("admin/comments") From 1deedf1f99ec779957464a48bc54648808ef216c Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 20:43:54 +0300 Subject: [PATCH 12/18] fix: rename newCommentDto to CommentRequestDto --- .../comment/controller/CommentPrivateController.java | 6 +++--- .../dto/{NewCommentDto.java => CommentRequestDto.java} | 2 +- .../java/ru/practicum/comment/mapper/CommentMapper.java | 4 ++-- .../ru/practicum/comment/service/CommentsCreateRequest.java | 4 ++-- .../ru/practicum/comment/service/CommentsUpdateRequest.java | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) rename main-service/src/main/java/ru/practicum/comment/dto/{NewCommentDto.java => CommentRequestDto.java} (63%) diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java index c9896b5..24471ef 100644 --- a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java @@ -7,7 +7,7 @@ import jakarta.validation.constraints.PositiveOrZero; import ru.practicum.comment.dto.CommentDto; -import ru.practicum.comment.dto.NewCommentDto; +import ru.practicum.comment.dto.CommentRequestDto; import ru.practicum.comment.service.*; import org.springframework.http.HttpStatus; @@ -40,7 +40,7 @@ public Collection getAllCommentsPaged( public CommentDto createComment( @PathVariable long userId, @PathVariable long eventId, - @Valid @RequestBody NewCommentDto newComment) { + @Valid @RequestBody CommentRequestDto newComment) { CommentsCreateRequest request = new CommentsCreateRequest(userId, eventId, newComment); log.info("Create new comment requested"); return commentService.createComment(request); @@ -50,7 +50,7 @@ public CommentDto createComment( public CommentDto updateComment( @PathVariable long userId, @PathVariable long commentId, - @Valid @RequestBody NewCommentDto updateComment) { + @Valid @RequestBody CommentRequestDto updateComment) { CommentsUpdateRequest request = new CommentsUpdateRequest(userId, commentId, updateComment); diff --git a/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java b/main-service/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java similarity index 63% rename from main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java rename to main-service/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java index cf89ab6..8bc56de 100644 --- a/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java +++ b/main-service/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java @@ -3,4 +3,4 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -public record NewCommentDto(@NotBlank @Size(max = 500) String text) {} +public record CommentRequestDto(@NotBlank @Size(max = 500) String text) {} diff --git a/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java b/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java index b217995..cd6e974 100644 --- a/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java +++ b/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java @@ -3,7 +3,7 @@ import java.time.LocalDateTime; import ru.practicum.comment.dto.CommentDto; -import ru.practicum.comment.dto.NewCommentDto; +import ru.practicum.comment.dto.CommentRequestDto; import ru.practicum.comment.model.Comment; import ru.practicum.event.model.Event; import ru.practicum.user.mapper.UserMapper; @@ -23,7 +23,7 @@ public CommentDto toCommentDto(Comment comment, User author) { comment.isEdited()); } - public Comment toEntity(NewCommentDto newCommentDto, User author, Event event) { + public Comment toEntity(CommentRequestDto newCommentDto, User author, Event event) { return new Comment(null, newCommentDto.text(), author, event, LocalDateTime.now(), false); } } diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java b/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java index bdfcec9..73b17cd 100644 --- a/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java @@ -1,5 +1,5 @@ package ru.practicum.comment.service; -import ru.practicum.comment.dto.NewCommentDto; +import ru.practicum.comment.dto.CommentRequestDto; -public record CommentsCreateRequest(long userId, long eventId, NewCommentDto newComment) {} +public record CommentsCreateRequest(long userId, long eventId, CommentRequestDto newComment) {} diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java b/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java index 8581028..3032ee0 100644 --- a/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java @@ -1,5 +1,5 @@ package ru.practicum.comment.service; -import ru.practicum.comment.dto.NewCommentDto; +import ru.practicum.comment.dto.CommentRequestDto; -public record CommentsUpdateRequest(long userId, long commentId, NewCommentDto updateComment) {} +public record CommentsUpdateRequest(long userId, long commentId, CommentRequestDto updateComment) {} From 5496d4f6f78f5b0e308a5c843ed1d22965b6c248 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 21:01:12 +0300 Subject: [PATCH 13/18] fix: move api methods --- .../controller/CommentPrivateController.java | 5 ++--- .../controller/CommentPublicController.java | 21 ++++++------------- .../comment/dto/CommentRequestDto.java | 4 +++- .../comment/service/CommentService.java | 2 ++ .../comment/service/CommentServiceImpl.java | 8 ++++++- .../service/CommentsCreateRequest.java | 2 +- .../controller/EventPublicController.java | 15 +++++++++++++ 7 files changed, 36 insertions(+), 21 deletions(-) diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java index 24471ef..26ddd12 100644 --- a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java @@ -35,13 +35,12 @@ public Collection getAllCommentsPaged( return commentService.getAllCommentsOfUserPaged(request); } - @PostMapping("/events/{eventId}/comments") + @PostMapping() @ResponseStatus(HttpStatus.CREATED) public CommentDto createComment( @PathVariable long userId, - @PathVariable long eventId, @Valid @RequestBody CommentRequestDto newComment) { - CommentsCreateRequest request = new CommentsCreateRequest(userId, eventId, newComment); + CommentsCreateRequest request = new CommentsCreateRequest(userId, newComment); log.info("Create new comment requested"); return commentService.createComment(request); } diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java index 857c547..d5fdc22 100644 --- a/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java @@ -1,13 +1,7 @@ package ru.practicum.comment.controller; -import java.util.Collection; - -import jakarta.validation.constraints.Positive; -import jakarta.validation.constraints.PositiveOrZero; - import ru.practicum.comment.dto.CommentDto; import ru.practicum.comment.service.CommentService; -import ru.practicum.comment.service.CommentsPublicGetRequest; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -19,18 +13,15 @@ @Validated @RestController @RequiredArgsConstructor -@RequestMapping("/events/{eventId}/comments") +@RequestMapping("/comments") public class CommentPublicController { private final CommentService commentService; - @GetMapping - public Collection getComments( - @PathVariable Long eventId, - @RequestParam(defaultValue = "0") @PositiveOrZero int from, - @RequestParam(defaultValue = "10") @Positive int size) { + @GetMapping("/{commentId}") + public CommentDto getComments( + @PathVariable Long commentId) { - CommentsPublicGetRequest request = new CommentsPublicGetRequest(eventId, from, size); - log.info("All comments for event {} requested with params {}", eventId, request); - return commentService.getAllCommentsPaged(request); + log.info("Get comment by id={} requested", commentId); + return commentService.getById(commentId); } } diff --git a/main-service/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java b/main-service/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java index 8bc56de..289411b 100644 --- a/main-service/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java +++ b/main-service/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java @@ -3,4 +3,6 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -public record CommentRequestDto(@NotBlank @Size(max = 500) String text) {} +public record CommentRequestDto( + Long eventId, + @NotBlank @Size(max = 500) String text) {} diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentService.java b/main-service/src/main/java/ru/practicum/comment/service/CommentService.java index 559adc4..951cb0c 100644 --- a/main-service/src/main/java/ru/practicum/comment/service/CommentService.java +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentService.java @@ -16,4 +16,6 @@ public interface CommentService { CommentDto createComment(CommentsCreateRequest request); CommentDto updateComment(CommentsUpdateRequest request); + + CommentDto getById(Long commentId); } diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java b/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java index d98c756..1cb083a 100644 --- a/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java @@ -74,7 +74,7 @@ public void deleteCommentByUser(long userId, long commentId) { @Override public CommentDto createComment(CommentsCreateRequest request) { - Event event = getEventByIdOrThrow(request.eventId()); + Event event = getEventByIdOrThrow(request.newComment().eventId()); User user = getUserByIdOrThrow(request.userId()); Comment newComment = CommentMapper.toEntity(request.newComment(), user, event); @@ -96,6 +96,12 @@ public CommentDto updateComment(CommentsUpdateRequest request) { return CommentMapper.toCommentDto(saved, user); } + @Override + public CommentDto getById(Long commentId) { + Comment comment = getCommentByIdOrThrow(commentId); + return CommentMapper.toCommentDto(comment, comment.getAuthor()); + } + private Event getEventByIdOrThrow(Long eventId) { Optional eventOptional = eventRepository.findById(eventId); return eventOptional.orElseThrow( diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java b/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java index 73b17cd..a77354e 100644 --- a/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java @@ -2,4 +2,4 @@ import ru.practicum.comment.dto.CommentRequestDto; -public record CommentsCreateRequest(long userId, long eventId, CommentRequestDto newComment) {} +public record CommentsCreateRequest(long userId, CommentRequestDto newComment) {} diff --git a/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java b/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java index 2f4d5ee..a9ba7e1 100644 --- a/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java +++ b/main-service/src/main/java/ru/practicum/event/controller/EventPublicController.java @@ -8,6 +8,9 @@ import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.PositiveOrZero; +import ru.practicum.comment.dto.CommentDto; +import ru.practicum.comment.service.CommentService; +import ru.practicum.comment.service.CommentsPublicGetRequest; import ru.practicum.event.dto.EventFullDto; import ru.practicum.event.dto.EventShortDto; import ru.practicum.event.service.EventService; @@ -27,6 +30,7 @@ @RequestMapping("/events") public class EventPublicController { private final EventService eventService; + private final CommentService commentService; @GetMapping() public Collection getEventsFiltered( @@ -66,4 +70,15 @@ public EventFullDto getEventById(@PathVariable Long eventId, HttpServletRequest log.info("Public get event with eventId={} requested", eventId); return eventService.getById(eventId, request); } + + @GetMapping("/{eventId}/comments") + public Collection getComments( + @PathVariable Long eventId, + @RequestParam(defaultValue = "0") @PositiveOrZero int from, + @RequestParam(defaultValue = "10") @Positive int size) { + + CommentsPublicGetRequest request = new CommentsPublicGetRequest(eventId, from, size); + log.info("All comments for event {} requested with params {}", eventId, request); + return commentService.getAllCommentsPaged(request); + } } From c8da68a272664dd97c8ef99f44e03f0e81ea1103 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 21:04:10 +0300 Subject: [PATCH 14/18] Revert "fix: rename newCommentDto to CommentRequestDto" This reverts commit 1deedf1f --- .../comment/controller/CommentPrivateController.java | 6 +++--- .../dto/{CommentRequestDto.java => NewCommentDto.java} | 5 +++-- .../java/ru/practicum/comment/mapper/CommentMapper.java | 4 ++-- .../ru/practicum/comment/service/CommentsCreateRequest.java | 4 ++-- .../ru/practicum/comment/service/CommentsUpdateRequest.java | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) rename main-service/src/main/java/ru/practicum/comment/dto/{CommentRequestDto.java => NewCommentDto.java} (62%) diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java index 26ddd12..e4c2eb6 100644 --- a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java @@ -7,7 +7,7 @@ import jakarta.validation.constraints.PositiveOrZero; import ru.practicum.comment.dto.CommentDto; -import ru.practicum.comment.dto.CommentRequestDto; +import ru.practicum.comment.dto.NewCommentDto; import ru.practicum.comment.service.*; import org.springframework.http.HttpStatus; @@ -39,7 +39,7 @@ public Collection getAllCommentsPaged( @ResponseStatus(HttpStatus.CREATED) public CommentDto createComment( @PathVariable long userId, - @Valid @RequestBody CommentRequestDto newComment) { + @Valid @RequestBody NewCommentDto newComment) { CommentsCreateRequest request = new CommentsCreateRequest(userId, newComment); log.info("Create new comment requested"); return commentService.createComment(request); @@ -49,7 +49,7 @@ public CommentDto createComment( public CommentDto updateComment( @PathVariable long userId, @PathVariable long commentId, - @Valid @RequestBody CommentRequestDto updateComment) { + @Valid @RequestBody NewCommentDto updateComment) { CommentsUpdateRequest request = new CommentsUpdateRequest(userId, commentId, updateComment); diff --git a/main-service/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java b/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java similarity index 62% rename from main-service/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java rename to main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java index 289411b..beb5b23 100644 --- a/main-service/src/main/java/ru/practicum/comment/dto/CommentRequestDto.java +++ b/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java @@ -1,8 +1,9 @@ package ru.practicum.comment.dto; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -public record CommentRequestDto( - Long eventId, +public record NewCommentDto( + @NotNull Long eventId, @NotBlank @Size(max = 500) String text) {} diff --git a/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java b/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java index cd6e974..b217995 100644 --- a/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java +++ b/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java @@ -3,7 +3,7 @@ import java.time.LocalDateTime; import ru.practicum.comment.dto.CommentDto; -import ru.practicum.comment.dto.CommentRequestDto; +import ru.practicum.comment.dto.NewCommentDto; import ru.practicum.comment.model.Comment; import ru.practicum.event.model.Event; import ru.practicum.user.mapper.UserMapper; @@ -23,7 +23,7 @@ public CommentDto toCommentDto(Comment comment, User author) { comment.isEdited()); } - public Comment toEntity(CommentRequestDto newCommentDto, User author, Event event) { + public Comment toEntity(NewCommentDto newCommentDto, User author, Event event) { return new Comment(null, newCommentDto.text(), author, event, LocalDateTime.now(), false); } } diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java b/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java index a77354e..62651e3 100644 --- a/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentsCreateRequest.java @@ -1,5 +1,5 @@ package ru.practicum.comment.service; -import ru.practicum.comment.dto.CommentRequestDto; +import ru.practicum.comment.dto.NewCommentDto; -public record CommentsCreateRequest(long userId, CommentRequestDto newComment) {} +public record CommentsCreateRequest(long userId, NewCommentDto newComment) {} diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java b/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java index 3032ee0..8581028 100644 --- a/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java @@ -1,5 +1,5 @@ package ru.practicum.comment.service; -import ru.practicum.comment.dto.CommentRequestDto; +import ru.practicum.comment.dto.NewCommentDto; -public record CommentsUpdateRequest(long userId, long commentId, CommentRequestDto updateComment) {} +public record CommentsUpdateRequest(long userId, long commentId, NewCommentDto updateComment) {} From e7375febafa2a62a01cf0e0c83034bf7654f82a0 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 21:10:01 +0300 Subject: [PATCH 15/18] fix: add updatecommentdto --- .../comment/controller/CommentPrivateController.java | 8 ++++---- .../comment/controller/CommentPublicController.java | 3 +-- .../main/java/ru/practicum/comment/dto/NewCommentDto.java | 4 +--- .../java/ru/practicum/comment/dto/UpdateCommentDto.java | 6 ++++++ .../practicum/comment/service/CommentsUpdateRequest.java | 4 ++-- 5 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 main-service/src/main/java/ru/practicum/comment/dto/UpdateCommentDto.java diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java index e4c2eb6..0aebdde 100644 --- a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java @@ -8,6 +8,7 @@ import ru.practicum.comment.dto.CommentDto; import ru.practicum.comment.dto.NewCommentDto; +import ru.practicum.comment.dto.UpdateCommentDto; import ru.practicum.comment.service.*; import org.springframework.http.HttpStatus; @@ -35,11 +36,10 @@ public Collection getAllCommentsPaged( return commentService.getAllCommentsOfUserPaged(request); } - @PostMapping() + @PostMapping @ResponseStatus(HttpStatus.CREATED) public CommentDto createComment( - @PathVariable long userId, - @Valid @RequestBody NewCommentDto newComment) { + @PathVariable long userId, @Valid @RequestBody NewCommentDto newComment) { CommentsCreateRequest request = new CommentsCreateRequest(userId, newComment); log.info("Create new comment requested"); return commentService.createComment(request); @@ -49,7 +49,7 @@ public CommentDto createComment( public CommentDto updateComment( @PathVariable long userId, @PathVariable long commentId, - @Valid @RequestBody NewCommentDto updateComment) { + @Valid @RequestBody UpdateCommentDto updateComment) { CommentsUpdateRequest request = new CommentsUpdateRequest(userId, commentId, updateComment); diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java index d5fdc22..3e60f2c 100644 --- a/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java @@ -18,8 +18,7 @@ public class CommentPublicController { private final CommentService commentService; @GetMapping("/{commentId}") - public CommentDto getComments( - @PathVariable Long commentId) { + public CommentDto getComments(@PathVariable Long commentId) { log.info("Get comment by id={} requested", commentId); return commentService.getById(commentId); diff --git a/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java b/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java index beb5b23..5da54fc 100644 --- a/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java +++ b/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java @@ -4,6 +4,4 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -public record NewCommentDto( - @NotNull Long eventId, - @NotBlank @Size(max = 500) String text) {} +public record NewCommentDto(@NotNull Long eventId, @NotBlank @Size(max = 500) String text) {} diff --git a/main-service/src/main/java/ru/practicum/comment/dto/UpdateCommentDto.java b/main-service/src/main/java/ru/practicum/comment/dto/UpdateCommentDto.java new file mode 100644 index 0000000..5179118 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/dto/UpdateCommentDto.java @@ -0,0 +1,6 @@ +package ru.practicum.comment.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record UpdateCommentDto(@NotBlank @Size(max = 500) String text) {} diff --git a/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java b/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java index 8581028..1e432fe 100644 --- a/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentsUpdateRequest.java @@ -1,5 +1,5 @@ package ru.practicum.comment.service; -import ru.practicum.comment.dto.NewCommentDto; +import ru.practicum.comment.dto.UpdateCommentDto; -public record CommentsUpdateRequest(long userId, long commentId, NewCommentDto updateComment) {} +public record CommentsUpdateRequest(long userId, long commentId, UpdateCommentDto updateComment) {} From dafe4b02854fe82bd4f2af8bb034f98403629809 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 21:45:41 +0300 Subject: [PATCH 16/18] spotless apply --- .../comment/controller/CommentPrivateController.java | 8 ++++---- .../comment/controller/CommentPublicController.java | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java index 0aebdde..545c5bd 100644 --- a/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java @@ -22,11 +22,11 @@ @Validated @RestController @RequiredArgsConstructor -@RequestMapping("/users/{userId}") +@RequestMapping("/users/{userId}/comments") public class CommentPrivateController { private final CommentService commentService; - @GetMapping("/comments") + @GetMapping public Collection getAllCommentsPaged( @PathVariable Long userId, @RequestParam(defaultValue = "0") @PositiveOrZero int from, @@ -45,7 +45,7 @@ public CommentDto createComment( return commentService.createComment(request); } - @PatchMapping("/comments/{commentId}") + @PatchMapping("/{commentId}") public CommentDto updateComment( @PathVariable long userId, @PathVariable long commentId, @@ -58,7 +58,7 @@ public CommentDto updateComment( return commentService.updateComment(request); } - @DeleteMapping("/comments/{commentId}") + @DeleteMapping("/{commentId}") @ResponseStatus(HttpStatus.NO_CONTENT) public void deleteComment(@PathVariable long userId, @PathVariable long commentId) { diff --git a/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java b/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java index 3e60f2c..1ce80f3 100644 --- a/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java @@ -3,14 +3,12 @@ import ru.practicum.comment.dto.CommentDto; import ru.practicum.comment.service.CommentService; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j -@Validated @RestController @RequiredArgsConstructor @RequestMapping("/comments") From 4a1de62af41fbe95cbe8af8748d815039419b41e Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 22:05:37 +0300 Subject: [PATCH 17/18] feat: add comments count in events public requests --- .../comment/repository/CommentRepository.java | 11 +++++ .../comment/repository/EventCommentCount.java | 3 ++ .../service/CompilationsServiceImpl.java | 1 + .../ru/practicum/event/dto/EventFullDto.java | 3 +- .../ru/practicum/event/dto/EventShortDto.java | 3 +- .../practicum/event/mapper/EventMapper.java | 12 +++-- .../event/service/EventServiceImpl.java | 45 ++++++++++++++----- 7 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 main-service/src/main/java/ru/practicum/comment/repository/EventCommentCount.java diff --git a/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java b/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java index 0752d9b..927fcc9 100644 --- a/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java +++ b/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java @@ -8,6 +8,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface CommentRepository extends JpaRepository { @@ -15,4 +17,13 @@ public interface CommentRepository extends JpaRepository { Page findAllByEventId(Long eventId, Pageable pageable); List findAllByAuthorId(Long authorId, Pageable pageable); + + @Query( + """ + select new ru.practicum.comment.repository.EventCommentCount(c.event.id, count(c)) + from Comment c + where c.event.id in :eventIds + group by c.event.id + """) + List countCommentsByEventIds(@Param("eventIds") List eventIds); } diff --git a/main-service/src/main/java/ru/practicum/comment/repository/EventCommentCount.java b/main-service/src/main/java/ru/practicum/comment/repository/EventCommentCount.java new file mode 100644 index 0000000..b75e1a9 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/repository/EventCommentCount.java @@ -0,0 +1,3 @@ +package ru.practicum.comment.repository; + +public record EventCommentCount(Long eventId, Long count) {} diff --git a/main-service/src/main/java/ru/practicum/compilation/service/CompilationsServiceImpl.java b/main-service/src/main/java/ru/practicum/compilation/service/CompilationsServiceImpl.java index e4e3d1c..7fc2b6f 100644 --- a/main-service/src/main/java/ru/practicum/compilation/service/CompilationsServiceImpl.java +++ b/main-service/src/main/java/ru/practicum/compilation/service/CompilationsServiceImpl.java @@ -132,6 +132,7 @@ private CompilationDto toDto(Compilation compilation, Map confirmedR EventMapper.mapToShortDto( event, confirmedRequests.getOrDefault(event.getId(), 0L), + null, null)) .toList(); diff --git a/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java b/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java index 6c5788b..84e02b3 100644 --- a/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java +++ b/main-service/src/main/java/ru/practicum/event/dto/EventFullDto.java @@ -22,4 +22,5 @@ public record EventFullDto( boolean requestModeration, EventState state, String title, - Long views) {} + Long views, + Long commentaries) {} diff --git a/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java b/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java index 5f102c4..168eabe 100644 --- a/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java +++ b/main-service/src/main/java/ru/practicum/event/dto/EventShortDto.java @@ -14,4 +14,5 @@ public record EventShortDto( UserShortDto initiator, boolean paid, String title, - Long views) {} + Long views, + Long commentaries) {} diff --git a/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java b/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java index 4df4931..c3e2414 100644 --- a/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java +++ b/main-service/src/main/java/ru/practicum/event/mapper/EventMapper.java @@ -35,7 +35,8 @@ public Event mapToEntity( newEventDto.title()); } - public EventFullDto mapToFullDto(Event event, long confirmedRequests, Long views) { + public EventFullDto mapToFullDto( + Event event, long confirmedRequests, Long views, Long commentaries) { return new EventFullDto( event.getAnnotation(), CategoryMapper.mapToDto(event.getCategory()), @@ -52,10 +53,12 @@ public EventFullDto mapToFullDto(Event event, long confirmedRequests, Long views event.getRequestModeration(), event.getState(), event.getTitle(), - views); + views, + commentaries); } - public EventShortDto mapToShortDto(Event event, long confirmedRequests, Long views) { + public EventShortDto mapToShortDto( + Event event, long confirmedRequests, Long views, Long commentaries) { return new EventShortDto( event.getAnnotation(), CategoryMapper.mapToDto(event.getCategory()), @@ -65,7 +68,8 @@ public EventShortDto mapToShortDto(Event event, long confirmedRequests, Long vie UserMapper.mapToUserShortDto(event.getInitiator()), event.getPaid(), event.getTitle(), - views); + views, + commentaries); } public void updateEventFromDto( diff --git a/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java b/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java index 246a538..f049b2e 100644 --- a/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java +++ b/main-service/src/main/java/ru/practicum/event/service/EventServiceImpl.java @@ -10,6 +10,8 @@ import ru.practicum.category.model.Category; import ru.practicum.category.repository.CategoryRepository; import ru.practicum.client.StatsClient; +import ru.practicum.comment.repository.CommentRepository; +import ru.practicum.comment.repository.EventCommentCount; import ru.practicum.dto.ViewStatsDto; import ru.practicum.event.controller.EventSortBy; import ru.practicum.event.dto.*; @@ -47,6 +49,7 @@ public class EventServiceImpl implements EventService { private final CategoryRepository categoryRepository; private final StatsClient statsClient; private final ParticipationRequestRepository requestRepository; + private final CommentRepository commentRepository; @Override public EventFullDto getById(Long eventId, HttpServletRequest request) { @@ -63,8 +66,13 @@ public EventFullDto getById(Long eventId, HttpServletRequest request) { Map confirmedRequests = getConfirmedRequests(Set.of(event)); + Map commentariesCount = getCommentariesCount(Set.of(event)); + return EventMapper.mapToFullDto( - event, confirmedRequests.getOrDefault(event.getId(), 0L), statsDto.hits()); + event, + confirmedRequests.getOrDefault(event.getId(), 0L), + statsDto.hits(), + commentariesCount.getOrDefault(event.getId(), 0L)); } @Override @@ -75,10 +83,13 @@ public Collection getEvents(EventsPublicGetRequest getRequest) { statsClient.hit(getRequest.httpRequest()); + Set eventsSet = events.stream().collect(Collectors.toSet()); + Map statsForEvents = getStatsMapForEvents(events); - Map confirmedRequests = - getConfirmedRequests(events.stream().collect(Collectors.toSet())); + Map confirmedRequests = getConfirmedRequests(eventsSet); + + Map commentariesCount = getCommentariesCount(eventsSet); List eventsList = events.stream() @@ -87,7 +98,8 @@ public Collection getEvents(EventsPublicGetRequest getRequest) { EventMapper.mapToShortDto( event, confirmedRequests.getOrDefault(event.getId(), 0L), - statsForEvents.get(event.getId()))) + statsForEvents.get(event.getId()), + commentariesCount.getOrDefault(event.getId(), 0L))) .toList(); if (EventSortBy.VIEWS.equals(getRequest.sort())) { @@ -114,7 +126,8 @@ public Collection getEvents(EventsAdminGetRequest getRequest) { EventMapper.mapToFullDto( event, confirmedRequests.getOrDefault(event.getId(), 0L), - statsForEvents.get(event.getId()))) + statsForEvents.get(event.getId()), + null)) .toList(); } @@ -135,7 +148,8 @@ public Collection getEvents(EventsPrivateGetRequest getRequest) { EventMapper.mapToShortDto( event, confirmedRequests.getOrDefault(event.getId(), 0L), - statsForEvents.get(event.getId()))) + statsForEvents.get(event.getId()), + null)) .toList(); } @@ -155,7 +169,7 @@ public EventFullDto createEvent(Long userId, NewEventDto newEventDto) { } Event saved = eventRepository.save(event); - return EventMapper.mapToFullDto(saved, 0, 0L); + return EventMapper.mapToFullDto(saved, 0, 0L, 0L); } @Override @@ -173,7 +187,7 @@ public EventFullDto getByUserById(Long userId, Long eventId) { Map confirmedRequests = getConfirmedRequests(Set.of(event)); return EventMapper.mapToFullDto( - event, confirmedRequests.getOrDefault(event.getId(), 0L), statsDto.hits()); + event, confirmedRequests.getOrDefault(event.getId(), 0L), statsDto.hits(), null); } @Override @@ -200,7 +214,7 @@ public EventFullDto updateEvent(Long eventId, UpdateEventAdminRequest updateRequ Map confirmedRequests = getConfirmedRequests(Set.of(event)); return EventMapper.mapToFullDto( - saved, confirmedRequests.getOrDefault(event.getId(), 0L), null); + saved, confirmedRequests.getOrDefault(event.getId(), 0L), null, null); } @Override @@ -233,7 +247,18 @@ public EventFullDto updateEventByUser( Map confirmedRequests = getConfirmedRequests(Set.of(event)); return EventMapper.mapToFullDto( - saved, confirmedRequests.getOrDefault(event.getId(), 0L), null); + saved, confirmedRequests.getOrDefault(event.getId(), 0L), null, null); + } + + private Map getCommentariesCount(Set events) { + if (events.isEmpty()) { + return Map.of(); + } + + List eventIds = events.stream().map(Event::getId).toList(); + + return commentRepository.countCommentsByEventIds(eventIds).stream() + .collect(Collectors.toMap(EventCommentCount::eventId, EventCommentCount::count)); } private Map getStatsMapForEvents(Page events) { From 827184f4aa5f7137afc224bbe2ebe44c4052e254 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 19 Jan 2026 22:07:04 +0300 Subject: [PATCH 18/18] feat: update postman tests --- postman/feature.json | 580 ++++++++++++++++++++++++++++--------------- 1 file changed, 382 insertions(+), 198 deletions(-) diff --git a/postman/feature.json b/postman/feature.json index 600075f..1fb6bad 100644 --- a/postman/feature.json +++ b/postman/feature.json @@ -8,13 +8,229 @@ "_collection_link": "https://go.postman.co/collection/27311667-f958e545-a649-474e-9fc1-9aa6ce6a0f36?source=collection_link" }, "item": [ + { + "name": "validation", + "item": [ + { + "name": "Validation: Create comment with empty text", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const api = new API(pm);", + "const rnd = new RandomUtils();", + "", + "(async function(){", + " try {", + " const user = await api.addUser(rnd.getUser());", + " const category = await api.addCategory(rnd.getCategory());", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", + " await api.publishEvent(event.id);", + " ", + " pm.environment.set('validationUserId', user.id);", + " pm.environment.set('validationEventId', event.id);", + " } catch(err) {", + " console.error('Ошибка при подготовке тестовых данных для валидации:', err);", + " }", + "})();" + ], + "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": "{\n \"eventId\": \"{{validationEventId}}\",\n \"text\": \"\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/comments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "comments" + ], + "variable": [ + { + "key": "userId", + "value": "{{validationUserId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Validation: Create comment with text > 500 chars", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const api = new API(pm);", + "const rnd = new RandomUtils();", + "", + "(async function(){", + " try {", + " const user = await api.addUser(rnd.getUser());", + " const category = await api.addCategory(rnd.getCategory());", + " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", + " await api.publishEvent(event.id);", + " ", + " pm.environment.set('validationLongUserId', user.id);", + " pm.environment.set('validationLongEventId', event.id);", + " } catch(err) {", + " console.error('Ошибка при подготовке тестовых данных для валидации длинного текста:', err);", + " }", + "})();" + ], + "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": "{\n \"eventId\": \"{{validationLongEventId}}\",\n \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/comments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "comments" + ], + "variable": [ + { + "key": "userId", + "value": "{{validationLongUserId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Validation: Create comment without eventId", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "const api = new API(pm);\r", + "const rnd = new RandomUtils();\r", + "\r", + "(async function(){\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", + " await api.publishEvent(event.id);\r", + " \r", + " pm.environment.set('validationLongUserId', user.id);\r", + " pm.environment.set('validationLongEventId', event.id);\r", + " } catch(err) {\r", + " console.error('Ошибка при подготовке тестовых данных для валидации длинного текста:', err);\r", + " }\r", + "})();" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"eventId\": \"\",\r\n \"text\": \"valid text\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/comments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "comments" + ], + "variable": [ + { + "key": "userId", + "value": "" + } + ] + } + }, + "response": [] + } + ] + }, { "name": "Public: Get comments for event", "event": [ { "listen": "prerequest", "script": { - "type": "text/javascript", "exec": [ "const api = new API(pm);", "const rnd = new RandomUtils();", @@ -22,21 +238,25 @@ "(async function(){", " try {", " const user = await api.addUser(rnd.getUser());", + " const user2 = await api.addUser(rnd.getUser());", " const category = await api.addCategory(rnd.getCategory());", " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", " await api.publishEvent(event.id);", " pm.environment.set('eventIdForPublicGet', event.id);", + " const comment = await api.addComment(user2.id, event.id, 'Comment text')", " } catch(err) {", " console.error('Ошибка при подготовке тестовых данных для Public Get Comments:', err);", " }", "})();" - ] + ], + "type": "text/javascript", + "packages": {}, + "requests": {} } }, { "listen": "test", "script": { - "type": "text/javascript", "exec": [ "pm.test('Ответ должен иметь статус код 200', function () {", " pm.response.to.have.status(200);", @@ -44,8 +264,15 @@ "", "pm.test('Ответ должен быть массивом', function () {", " pm.expect(pm.response.json()).to.be.an('array');", - "});" - ] + "});", + "", + "pm.test(\"Ответ должен быть не пустым\", function() {", + " pm.expect(pm.response.json()).to.be.an('array').that.is.not.empty;", + "})" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} } } ], @@ -83,12 +310,108 @@ "response": [] }, { - "name": "Private: Create comment", + "name": "Public: Get comment by id", "event": [ { "listen": "prerequest", "script": { + "exec": [ + "const api = new API(pm);\r", + "const rnd = new RandomUtils();\r", + "\r", + "(async function(){\r", + " try {\r", + " const user = await api.addUser(rnd.getUser());\r", + " const category = await api.addCategory(rnd.getCategory());\r", + " const event1 = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " const event2 = await api.addEvent(user.id, rnd.getEvent(category.id));\r", + " await api.publishEvent(event1.id);\r", + " await api.publishEvent(event2.id);\r", + " \r", + " await api.post('/users/' + user.id + '/comments', \r", + " { eventId: event1.id, text: 'First comment' }, 'Ошибка при создании первого комментария: ');\r", + " const comment = await api.post('/users/' + user.id + '/comments', \r", + " { eventId: event2.id, text: 'Second comment' }, 'Ошибка при создании второго комментария: ');\r", + " \r", + " pm.environment.set('commentForRequestId', comment.id);\r", + " pm.environment.set('commentAuthorId', user.id);\r", + " } catch(err) {\r", + " console.error('Ошибка при подготовке тестовых данных для Get All Comments:', err);\r", + " }\r", + "})();" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Ответ должен иметь статус код 200', function () {\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test('Комментарий должен содержать корректные поля', function () {\r", + " const comment = pm.response.json();\r", + " pm.expect(comment).to.have.property('id');\r", + " pm.expect(comment).to.have.property('text');\r", + " pm.expect(comment).to.have.property('author');\r", + " pm.expect(comment).to.have.property('created');\r", + " pm.expect(comment).to.have.property('edited');\r", + "});\r", + "\r", + "pm.test('Текст комментария совпадает с отправленным', function () {\r", + " const comment = pm.response.json();\r", + " pm.expect(comment.text).to.eql('Second comment');\r", + "});\r", + "\r", + "pm.test('Комментарий изначально не отредактирован', function () {\r", + " const comment = pm.response.json();\r", + " pm.expect(comment.edited).to.be.false;\r", + "});\r", + "\r", + "pm.test('Автор комментария совпадает с отправителем', function () {\r", + " const comment = pm.response.json();\r", + " const authorId = parseInt(pm.environment.get('commentAuthorId'));\r", + " pm.expect(comment.author.id).to.eql(authorId);\r", + "});" + ], "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/comments/:commentId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "comments", + ":commentId" + ], + "variable": [ + { + "key": "commentId", + "value": "{{commentForRequestId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Private: Create comment", + "event": [ + { + "listen": "prerequest", + "script": { "exec": [ "const api = new API(pm);", "const rnd = new RandomUtils();", @@ -107,13 +430,15 @@ " console.error('Ошибка при подготовке тестовых данных для Create Comment:', err);", " }", "})();" - ] + ], + "type": "text/javascript", + "packages": {}, + "requests": {} } }, { "listen": "test", "script": { - "type": "text/javascript", "exec": [ "pm.test('Ответ должен иметь статус код 201', function () {", " pm.response.to.have.status(201);", @@ -143,7 +468,10 @@ " const authorId = parseInt(pm.environment.get('commentAuthorId'));", " pm.expect(comment.author.id).to.eql(authorId);", "});" - ] + ], + "type": "text/javascript", + "packages": {}, + "requests": {} } } ], @@ -157,28 +485,22 @@ ], "body": { "mode": "raw", - "raw": "{\n \"text\": \"This is a test comment for the event\"\n}" + "raw": "{\n \"eventId\": \"{{eventIdForCreate}}\",\n \"text\": \"This is a test comment for the event\"\n}" }, "url": { - "raw": "{{baseUrl}}/users/:userId/events/:eventId/comments", + "raw": "{{baseUrl}}/users/:userId/comments", "host": [ "{{baseUrl}}" ], "path": [ "users", ":userId", - "events", - ":eventId", "comments" ], "variable": [ { "key": "userId", "value": "{{commentAuthorId}}" - }, - { - "key": "eventId", - "value": "{{eventIdForCreate}}" } ] } @@ -191,7 +513,6 @@ { "listen": "prerequest", "script": { - "type": "text/javascript", "exec": [ "const api = new API(pm);", "const rnd = new RandomUtils();", @@ -203,8 +524,8 @@ " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", " await api.publishEvent(event.id);", " ", - " const comment = await api.post('/users/' + user.id + '/events/' + event.id + '/comments', ", - " { text: 'Original comment text' }, 'Ошибка при создании комментария: ');", + " const comment = await api.post('/users/' + user.id + '/comments', ", + " {eventId: event.id, text: 'Original comment text' }, 'Ошибка при создании комментария: ');", " ", " pm.environment.set('updateUserId', user.id);", " pm.environment.set('updateCommentId', comment.id);", @@ -212,13 +533,15 @@ " console.error('Ошибка при подготовке тестовых данных для Update Comment:', err);", " }", "})();" - ] + ], + "type": "text/javascript", + "packages": {}, + "requests": {} } }, { "listen": "test", "script": { - "type": "text/javascript", "exec": [ "pm.test('Ответ должен иметь статус код 200', function () {", " pm.response.to.have.status(200);", @@ -233,7 +556,10 @@ " const comment = pm.response.json();", " pm.expect(comment.edited).to.be.true;", "});" - ] + ], + "type": "text/javascript", + "packages": {}, + "requests": {} } } ], @@ -280,7 +606,6 @@ { "listen": "prerequest", "script": { - "type": "text/javascript", "exec": [ "const api = new API(pm);", "const rnd = new RandomUtils();", @@ -294,23 +619,25 @@ " await api.publishEvent(event1.id);", " await api.publishEvent(event2.id);", " ", - " await api.post('/users/' + user.id + '/events/' + event1.id + '/comments', ", - " { text: 'First comment' }, 'Ошибка при создании первого комментария: ');", - " await api.post('/users/' + user.id + '/events/' + event2.id + '/comments', ", - " { text: 'Second comment' }, 'Ошибка при создании второго комментария: ');", + " await api.post('/users/' + user.id + '/comments', ", + " { eventId: event1.id, text: 'First comment' }, 'Ошибка при создании первого комментария: ');", + " await api.post('/users/' + user.id + '/comments', ", + " { eventId: event2.id, text: 'Second comment' }, 'Ошибка при создании второго комментария: ');", " ", " pm.environment.set('getAllUserId', user.id);", " } catch(err) {", " console.error('Ошибка при подготовке тестовых данных для Get All Comments:', err);", " }", "})();" - ] + ], + "type": "text/javascript", + "packages": {}, + "requests": {} } }, { "listen": "test", "script": { - "type": "text/javascript", "exec": [ "pm.test('Ответ должен иметь статус код 200', function () {", " pm.response.to.have.status(200);", @@ -332,7 +659,10 @@ " const comments = pm.response.json();", " pm.expect(comments.length).to.be.at.least(2);", "});" - ] + ], + "type": "text/javascript", + "packages": {}, + "requests": {} } } ], @@ -375,7 +705,6 @@ { "listen": "prerequest", "script": { - "type": "text/javascript", "exec": [ "const api = new API(pm);", "const rnd = new RandomUtils();", @@ -387,8 +716,8 @@ " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", " await api.publishEvent(event.id);", " ", - " const comment = await api.post('/users/' + user.id + '/events/' + event.id + '/comments', ", - " { text: 'Comment to be deleted by user' }, 'Ошибка при создании комментария: ');", + " const comment = await api.post('/users/' + user.id + '/comments', ", + " { eventId: event.id, text: 'Comment to be deleted by user' }, 'Ошибка при создании комментария: ');", " ", " pm.environment.set('deleteByUserUserId', user.id);", " pm.environment.set('deleteByUserCommentId', comment.id);", @@ -396,18 +725,23 @@ " console.error('Ошибка при подготовке тестовых данных для Delete Comment by User:', err);", " }", "})();" - ] + ], + "type": "text/javascript", + "packages": {}, + "requests": {} } }, { "listen": "test", "script": { - "type": "text/javascript", "exec": [ "pm.test('Ответ должен иметь статус код 204', function () {", " pm.response.to.have.status(204);", "});" - ] + ], + "type": "text/javascript", + "packages": {}, + "requests": {} } } ], @@ -445,7 +779,6 @@ { "listen": "prerequest", "script": { - "type": "text/javascript", "exec": [ "const api = new API(pm);", "const rnd = new RandomUtils();", @@ -457,26 +790,31 @@ " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", " await api.publishEvent(event.id);", " ", - " const comment = await api.post('/users/' + user.id + '/events/' + event.id + '/comments', ", - " { text: 'Comment to be deleted by admin' }, 'Ошибка при создании комментария: ');", + " const comment = await api.post('/users/' + user.id + '/comments', ", + " {eventId: event.id, text: 'Comment to be deleted by admin' }, 'Ошибка при создании комментария: ');", " ", " pm.environment.set('deleteByAdminCommentId', comment.id);", " } catch(err) {", " console.error('Ошибка при подготовке тестовых данных для Admin Delete Comment:', err);", " }", "})();" - ] + ], + "type": "text/javascript", + "packages": {}, + "requests": {} } }, { "listen": "test", "script": { - "type": "text/javascript", "exec": [ "pm.test('Ответ должен иметь статус код 204', function () {", " pm.response.to.have.status(204);", "});" - ] + ], + "type": "text/javascript", + "packages": {}, + "requests": {} } } ], @@ -502,160 +840,6 @@ } }, "response": [] - }, - { - "name": "Validation: Create comment with empty text", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const api = new API(pm);", - "const rnd = new RandomUtils();", - "", - "(async function(){", - " try {", - " const user = await api.addUser(rnd.getUser());", - " const category = await api.addCategory(rnd.getCategory());", - " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", - " await api.publishEvent(event.id);", - " ", - " pm.environment.set('validationUserId', user.id);", - " pm.environment.set('validationEventId', event.id);", - " } catch(err) {", - " console.error('Ошибка при подготовке тестовых данных для валидации:', err);", - " }", - "})();" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test('Ответ должен иметь статус код 400', function () {", - " pm.response.to.have.status(400);", - "});" - ] - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"text\": \"\"\n}" - }, - "url": { - "raw": "{{baseUrl}}/users/:userId/events/:eventId/comments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users", - ":userId", - "events", - ":eventId", - "comments" - ], - "variable": [ - { - "key": "userId", - "value": "{{validationUserId}}" - }, - { - "key": "eventId", - "value": "{{validationEventId}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Validation: Create comment with text > 500 chars", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const api = new API(pm);", - "const rnd = new RandomUtils();", - "", - "(async function(){", - " try {", - " const user = await api.addUser(rnd.getUser());", - " const category = await api.addCategory(rnd.getCategory());", - " const event = await api.addEvent(user.id, rnd.getEvent(category.id));", - " await api.publishEvent(event.id);", - " ", - " pm.environment.set('validationLongUserId', user.id);", - " pm.environment.set('validationLongEventId', event.id);", - " } catch(err) {", - " console.error('Ошибка при подготовке тестовых данных для валидации длинного текста:', err);", - " }", - "})();" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test('Ответ должен иметь статус код 400', function () {", - " pm.response.to.have.status(400);", - "});" - ] - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.\"\n}" - }, - "url": { - "raw": "{{baseUrl}}/users/:userId/events/:eventId/comments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users", - ":userId", - "events", - ":eventId", - "comments" - ], - "variable": [ - { - "key": "userId", - "value": "{{validationLongUserId}}" - }, - { - "key": "eventId", - "value": "{{validationLongEventId}}" - } - ] - } - }, - "response": [] } ], "event": [ @@ -689,7 +873,7 @@ " }", "", " async addComment(userId, eventId, text, verbose=null) {", - " return this.post(\"/users/\" + userId + \"/events/\" + eventId + \"/comments\", {text: text}, \"Ошибка при добавлении комментария: \", verbose);", + " return this.post(\"/users/\" + userId + \"/comments\", {eventId: eventId, text: text}, \"Ошибка при добавлении комментария: \", verbose);", " }", "", " async post(path, body, errorText = \"Ошибка при выполнении post-запроса: \", verbose=null) {",