Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add comments to events #3

Merged
merged 3 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# java-explore-with-me
Template repository for ExploreWithMe project.

# Link to the pull request
https://github.com/ayzatmr/java-explore-with-me/pull/3
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public interface AdminEventRepository extends JpaRepository<Event, Long>, JpaSpe
@Query("SELECT e FROM Event e " +
"JOIN FETCH e.category c " +
"JOIN FETCH e.initiator i " +
"JOIN FETCH e.location " +
"LEFT JOIN FETCH e.comments " +
"WHERE e.id = ?1")
Optional<Event> findFullEventById(Long eventId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import lombok.*;
import lombok.experimental.FieldDefaults;
import ru.practicum.common.enums.EventState;
import ru.practicum.user.dto.CommentDto;

import java.time.LocalDateTime;
import java.util.List;

import static ru.practicum.common.model.Constants.DATE_TIME_FORMAT;

Expand Down Expand Up @@ -50,4 +52,5 @@ public class EventFullDto {

LocationDto location;

List<CommentDto> comments;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import lombok.experimental.FieldDefaults;
import ru.practicum.user.dto.CommentDto;

import java.time.LocalDateTime;
import java.util.List;

import static ru.practicum.common.model.Constants.DATE_TIME_FORMAT;

Expand Down Expand Up @@ -32,4 +34,6 @@ public class EventShortDto {
long confirmedRequests;

long views;

List<CommentDto> comments;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ru.practicum.common.mapper;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import ru.practicum.common.model.Comment;
import ru.practicum.user.dto.CommentDto;
import ru.practicum.user.dto.NewCommentDto;
import ru.practicum.user.dto.UpdateCommentDto;

import java.util.List;

@Mapper(componentModel = "spring", uses = UserMapper.class)
public interface CommentMapper {

CommentDto toDto(Comment comment);

Comment toModel(NewCommentDto newCommentDto);

@Mapping(source = "commentId", target = "id")
Comment toModel(UpdateCommentDto updateCommentDto);

List<NewCommentDto> toDtoList(List<Comment> comments);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE;

@Mapper(componentModel = "spring", uses = {LocationMapper.class, UserMapper.class, CategoryMapper.class})
@Mapper(componentModel = "spring", uses = {LocationMapper.class, UserMapper.class, CategoryMapper.class, CommentMapper.class})
public interface EventMapper {

NewEvent toModel(NewEventDto newEventDto);
Expand Down
39 changes: 39 additions & 0 deletions ewm-service/src/main/java/ru/practicum/common/model/Comment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package ru.practicum.common.model;

import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import ru.practicum.user.models.User;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "event_comments")
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
@Builder
public class Comment {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вместо множественных private лучше использовать @FieldDefaults


private String text;

@Column(name = "created")
@CreationTimestamp
private LocalDateTime postedOn;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
@ToString.Exclude
private User author;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "event_id")
@ToString.Exclude
private Event event;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public class Event {
@ToString.Exclude
final List<Compilation> compilations = new ArrayList<>();

@OneToMany
@JoinColumn(name = "event_id")
@ToString.Exclude
final List<Comment> comments = new ArrayList<>();

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
Expand Down Expand Up @@ -88,4 +93,8 @@ public int addParticipant() {
public void addToCompilation(Compilation compilation) {
compilations.add(compilation);
}

public void addCommentToEvent(Comment comment) {
comments.add(comment);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ru.practicum.user.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import ru.practicum.common.dto.EventFullDto;
import ru.practicum.user.dto.NewCommentDto;
import ru.practicum.user.dto.UpdateCommentDto;
import ru.practicum.user.service.CommentServiceImpl;

import javax.validation.Valid;

@RestController
@RequestMapping("/events")
@RequiredArgsConstructor
public class CommentsController {

private final CommentServiceImpl commentService;

@PostMapping("/{eventId}/comments/{userId}")
@ResponseStatus(HttpStatus.CREATED)
public EventFullDto addCommentToEvent(@PathVariable Long userId,
@PathVariable Long eventId,
@RequestBody @Valid NewCommentDto addCommentDto) {
return commentService.addCommentToEvent(userId, eventId, addCommentDto);
}

@PatchMapping("/{eventId}/comments/{userId}")
public EventFullDto updateComment(@PathVariable Long userId,
@PathVariable Long eventId,
@RequestBody @Valid UpdateCommentDto updateCommentDto) {
return commentService.updateComment(userId, eventId, updateCommentDto);
}

@DeleteMapping("/{userId}/comments/{commentId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteComment(@PathVariable Long userId,
@PathVariable Long commentId) {
commentService.deleteComment(userId, commentId);
}
}
27 changes: 27 additions & 0 deletions ewm-service/src/main/java/ru/practicum/user/dto/CommentDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ru.practicum.user.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import lombok.experimental.FieldDefaults;
import ru.practicum.common.dto.UserShortDto;

import java.time.LocalDateTime;

import static ru.practicum.common.model.Constants.DATE_TIME_FORMAT;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class CommentDto {

Long id;

String text;

UserShortDto author;

@JsonFormat(pattern = DATE_TIME_FORMAT)
LocalDateTime postedOn;
}
19 changes: 19 additions & 0 deletions ewm-service/src/main/java/ru/practicum/user/dto/NewCommentDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ru.practicum.user.dto;

import lombok.*;
import lombok.experimental.FieldDefaults;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class NewCommentDto {

@NotBlank
@Size(min = 2, max = 2000)
String text;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ru.practicum.user.dto;

import lombok.*;
import lombok.experimental.FieldDefaults;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import javax.validation.constraints.Size;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class UpdateCommentDto {

@NotNull
@Positive
Long commentId;

@NotBlank
@Size(min = 1, max = 2000)
String text;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ru.practicum.user.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import ru.practicum.common.model.Comment;

import java.util.Optional;

public interface CommentRepository extends JpaRepository<Comment, Long> {

@Query("SELECT c FROM Comment c " +
"JOIN FETCH c.author a " +
"WHERE c.id = ?1")
Optional<Comment> findCommentById(Long commentId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ru.practicum.user.service;

import ru.practicum.common.dto.EventFullDto;
import ru.practicum.user.dto.NewCommentDto;
import ru.practicum.user.dto.UpdateCommentDto;

public interface CommentService {

EventFullDto addCommentToEvent(Long userId, Long eventId, NewCommentDto commentDto);

EventFullDto updateComment(Long userId, Long commentId, UpdateCommentDto updateCommentDto);

void deleteComment(Long userId, Long commentId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package ru.practicum.user.service;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import ru.practicum.admin.repository.AdminEventRepository;
import ru.practicum.admin.repository.UserRepository;
import ru.practicum.common.dto.EventFullDto;
import ru.practicum.common.exception.AlreadyExistException;
import ru.practicum.common.exception.ObjectNotFoundException;
import ru.practicum.common.mapper.CommentMapper;
import ru.practicum.common.mapper.EventMapper;
import ru.practicum.common.model.Comment;
import ru.practicum.common.model.Event;
import ru.practicum.user.dto.NewCommentDto;
import ru.practicum.user.dto.UpdateCommentDto;
import ru.practicum.user.models.User;
import ru.practicum.user.repository.CommentRepository;

import javax.transaction.Transactional;

@Service
@RequiredArgsConstructor
public class CommentServiceImpl implements CommentService {

private final UserRepository userRepository;
private final AdminEventRepository eventRepository;
private final CommentRepository commentRepository;
private final CommentMapper commentMapper;
private final EventMapper eventMapper;

@Override
@Transactional
public EventFullDto addCommentToEvent(Long userId, Long eventId, NewCommentDto commentDto) {
User user = getUser(userId);
Event event = getEvent(eventId);
Comment comment = commentMapper.toModel(commentDto);
comment.setAuthor(user);
comment.setEvent(event);
Comment savedComment = commentRepository.save(comment);
event.addCommentToEvent(savedComment);
return eventMapper.toDto(eventRepository.save(event));
}

@Override
@Transactional
public EventFullDto updateComment(Long userId, Long eventId, UpdateCommentDto updateComment) {
getUser(userId);
Comment comment = getComment(updateComment.getCommentId());
validateAuthor(userId, comment);
comment.setText(updateComment.getText());
commentRepository.save(comment);
return eventMapper.toDto(getEvent(eventId));
}

@Override
public void deleteComment(Long userId, Long commentId) {
getUser(userId);
Comment comment = getComment(commentId);
validateAuthor(userId, comment);
commentRepository.deleteById(commentId);
}

private void validateAuthor(Long userId, Comment comment) {
if (!comment.getAuthor().getId().equals(userId)) {
throw new AlreadyExistException("User is not an author of the comment");
}
}

private Comment getComment(Long commentId) {
return commentRepository.findCommentById(commentId)
.orElseThrow(() -> new ObjectNotFoundException("Comment is not found"));
}

private Event getEvent(Long id) {
return eventRepository.findFullEventById(id)
.orElseThrow(() -> new ObjectNotFoundException("Event is not found"));
}

private User getUser(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new ObjectNotFoundException("User is not found."));
}
}
Loading
Loading