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..95f7055 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentAdminController.java @@ -0,0 +1,25 @@ +package ru.practicum.comment.controller; + +import ru.practicum.comment.service.CommentService; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("admin/comments") +public class CommentAdminController { + private final CommentService commentService; + + @DeleteMapping("/{commentId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteComment(@PathVariable long commentId) { + log.info("Admin delete comment requested for commentId={}", 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 new file mode 100644 index 0000000..545c5bd --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPrivateController.java @@ -0,0 +1,69 @@ +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.dto.UpdateCommentDto; +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}/comments") +public class CommentPrivateController { + private final CommentService commentService; + + @GetMapping + 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.getAllCommentsOfUserPaged(request); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public CommentDto createComment( + @PathVariable long userId, @Valid @RequestBody NewCommentDto newComment) { + CommentsCreateRequest request = new CommentsCreateRequest(userId, newComment); + log.info("Create new comment requested"); + return commentService.createComment(request); + } + + @PatchMapping("/{commentId}") + public CommentDto updateComment( + @PathVariable long userId, + @PathVariable long commentId, + @Valid @RequestBody UpdateCommentDto updateComment) { + + CommentsUpdateRequest request = new CommentsUpdateRequest(userId, commentId, updateComment); + + log.info("Update comment requested by userId:{}, commentId:{}", userId, commentId); + + return commentService.updateComment(request); + } + + @DeleteMapping("/{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..1ce80f3 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/controller/CommentPublicController.java @@ -0,0 +1,24 @@ +package ru.practicum.comment.controller; + +import ru.practicum.comment.dto.CommentDto; +import ru.practicum.comment.service.CommentService; + +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/comments") +public class CommentPublicController { + private final CommentService commentService; + + @GetMapping("/{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/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..5da54fc --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/dto/NewCommentDto.java @@ -0,0 +1,7 @@ +package ru.practicum.comment.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +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/mapper/CommentMapper.java b/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java new file mode 100644 index 0000000..b217995 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/mapper/CommentMapper.java @@ -0,0 +1,29 @@ +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; + +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, 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/model/Comment.java b/main-service/src/main/java/ru/practicum/comment/model/Comment.java new file mode 100644 index 0000000..0dcbc60 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/model/Comment.java @@ -0,0 +1,43 @@ +package ru.practicum.comment.model; + +import java.time.LocalDateTime; + +import jakarta.persistence.*; + +import ru.practicum.event.model.Event; +import ru.practicum.user.model.User; + +import lombok.experimental.FieldDefaults; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "comment") +@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/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..927fcc9 --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/repository/CommentRepository.java @@ -0,0 +1,29 @@ +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; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface CommentRepository extends JpaRepository { + + @EntityGraph(attributePaths = "author") + 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/comment/service/CommentService.java b/main-service/src/main/java/ru/practicum/comment/service/CommentService.java new file mode 100644 index 0000000..951cb0c --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentService.java @@ -0,0 +1,21 @@ +package ru.practicum.comment.service; + +import java.util.Collection; + +import ru.practicum.comment.dto.CommentDto; + +public interface CommentService { + Collection getAllCommentsPaged(CommentsPublicGetRequest request); + + Collection getAllCommentsOfUserPaged(CommentsPrivateGetRequest request); + + void deleteComment(long commentId); + + void deleteCommentByUser(long userId, long commentId); + + 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 new file mode 100644 index 0000000..1cb083a --- /dev/null +++ b/main-service/src/main/java/ru/practicum/comment/service/CommentServiceImpl.java @@ -0,0 +1,122 @@ +package ru.practicum.comment.service; + +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; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +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) { + 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 + @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 commentId) { + commentRepository.deleteById(commentId); + } + + @Override + 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) { + Event event = getEventByIdOrThrow(request.newComment().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) { + 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.setEdited(true); + Comment saved = commentRepository.save(comment); + 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( + 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)); + } +} 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..62651e3 --- /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, 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..1e432fe --- /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.UpdateCommentDto; + +public record CommentsUpdateRequest(long userId, long commentId, UpdateCommentDto updateComment) {} 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/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); + } } 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/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); } 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) { 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 +); diff --git a/postman/feature.json b/postman/feature.json new file mode 100644 index 0000000..1fb6bad --- /dev/null +++ b/postman/feature.json @@ -0,0 +1,997 @@ +{ + "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": "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": { + "exec": [ + "const api = new API(pm);", + "const rnd = new RandomUtils();", + "", + "(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": { + "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() {", + " pm.expect(pm.response.json()).to.be.an('array').that.is.not.empty;", + "})" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "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": "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();", + "", + "(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);", + " }", + "})();" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "test", + "script": { + "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);", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"eventId\": \"{{eventIdForCreate}}\",\n \"text\": \"This is a test comment for the event\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/users/:userId/comments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "comments" + ], + "variable": [ + { + "key": "userId", + "value": "{{commentAuthorId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Private: Update comment", + "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);", + " ", + " 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);", + " } catch(err) {", + " console.error('Ошибка при подготовке тестовых данных для Update Comment:', err);", + " }", + "})();" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "test", + "script": { + "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;", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "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": { + "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 + '/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": { + "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);", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/users/:userId/comments?from=0&size=10", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId", + "comments" + ], + "query": [ + { + "key": "from", + "value": "0" + }, + { + "key": "size", + "value": "10" + } + ], + "variable": [ + { + "key": "userId", + "value": "{{getAllUserId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Private: Delete comment by user", + "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);", + " ", + " 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);", + " } catch(err) {", + " console.error('Ошибка при подготовке тестовых данных для Delete Comment by User:', err);", + " }", + "})();" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Ответ должен иметь статус код 204', function () {", + " pm.response.to.have.status(204);", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "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": { + "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 + '/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": { + "exec": [ + "pm.test('Ответ должен иметь статус код 204', function () {", + " pm.response.to.have.status(204);", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/admin/comments/:commentId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "admin", + "comments", + ":commentId" + ], + "variable": [ + { + "key": "commentId", + "value": "{{deleteByAdminCommentId}}" + } + ] + } + }, + "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 + \"/comments\", {eventId: eventId, 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