Merge pull request #8 from sBubshait/feature/post-comments
Comments and Replies to Comments in Backend
This commit is contained in:
commit
6ebf8f4f2d
@ -1,11 +1,9 @@
|
|||||||
package online.wesal.wesal.config;
|
package online.wesal.wesal.config;
|
||||||
|
|
||||||
import online.wesal.wesal.service.UserDetailsServiceImpl;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
@ -24,9 +22,6 @@ import java.util.Arrays;
|
|||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserDetailsServiceImpl userDetailsService;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||||
|
|
||||||
@ -35,13 +30,6 @@ public class SecurityConfig {
|
|||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
public DaoAuthenticationProvider authenticationProvider() {
|
|
||||||
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
|
||||||
authProvider.setUserDetailsService(userDetailsService);
|
|
||||||
authProvider.setPasswordEncoder(passwordEncoder());
|
|
||||||
return authProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||||
@ -71,7 +59,6 @@ public class SecurityConfig {
|
|||||||
.requestMatchers("/admin/**").hasRole("ADMIN")
|
.requestMatchers("/admin/**").hasRole("ADMIN")
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.authenticationProvider(authenticationProvider())
|
|
||||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
|
|||||||
@ -0,0 +1,88 @@
|
|||||||
|
package online.wesal.wesal.controller;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import online.wesal.wesal.dto.ApiResponse;
|
||||||
|
import online.wesal.wesal.dto.CommentCreateRequestDTO;
|
||||||
|
import online.wesal.wesal.dto.CommentResponseDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import online.wesal.wesal.service.CommentService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/posts/comments")
|
||||||
|
@CrossOrigin(origins = "*")
|
||||||
|
@Tag(name = "Comments", description = "Comment management endpoints")
|
||||||
|
public class CommentController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CommentService commentService;
|
||||||
|
|
||||||
|
@PostMapping(value = "/create", consumes = "application/json", produces = "application/json")
|
||||||
|
@Operation(summary = "Create comment", description = "Create a new comment or reply to an existing comment with 3-level nesting")
|
||||||
|
public ResponseEntity<ApiResponse<CommentResponseDTO>> createComment(
|
||||||
|
@Valid @RequestBody CommentCreateRequestDTO request,
|
||||||
|
BindingResult bindingResult,
|
||||||
|
Authentication authentication) {
|
||||||
|
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
String errorMessage = bindingResult.getFieldErrors().stream()
|
||||||
|
.map(error -> {
|
||||||
|
String field = error.getField();
|
||||||
|
if ("postId".equals(field)) return "Valid post ID is required";
|
||||||
|
if ("body".equals(field)) return "Comment body is required and cannot exceed 1000 characters";
|
||||||
|
return error.getDefaultMessage();
|
||||||
|
})
|
||||||
|
.findFirst()
|
||||||
|
.orElse("Invalid input");
|
||||||
|
return ResponseEntity.badRequest().body(ApiResponse.error(errorMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
CommentResponseDTO response = commentService.createComment(
|
||||||
|
request.getPostId(),
|
||||||
|
request.getBody(),
|
||||||
|
request.getReplyComment()
|
||||||
|
);
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
String message;
|
||||||
|
if (e.getMessage().contains("Post not found")) {
|
||||||
|
message = "Post not found";
|
||||||
|
} else if (e.getMessage().contains("Parent comment not found")) {
|
||||||
|
message = "Comment you're replying to not found";
|
||||||
|
} else if (e.getMessage().contains("User not found") || e.getMessage().contains("creator not found")) {
|
||||||
|
message = "Authentication error. Please log in again.";
|
||||||
|
} else {
|
||||||
|
message = "Something went wrong.. We're sorry but try again later";
|
||||||
|
}
|
||||||
|
return ResponseEntity.badRequest().body(ApiResponse.error(message));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.status(500).body(ApiResponse.error("Something went wrong.. We're sorry but try again later"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
@Operation(summary = "Get comments for post", description = "Get all comments for a specific post in hierarchical structure with nested replies")
|
||||||
|
public ResponseEntity<ApiResponse<List<CommentResponseDTO>>> getCommentsByPostId(
|
||||||
|
@RequestParam Long postId,
|
||||||
|
Authentication authentication) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (postId == null || postId <= 0) {
|
||||||
|
return ResponseEntity.badRequest().body(ApiResponse.error("Valid post ID is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CommentResponseDTO> response = commentService.getCommentsByPostId(postId);
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.status(500).body(ApiResponse.error("Something went wrong.. We're sorry but try again later"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package online.wesal.wesal.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
public class CommentCreateRequestDTO {
|
||||||
|
|
||||||
|
@NotNull(message = "Post ID is required")
|
||||||
|
private Long postId;
|
||||||
|
|
||||||
|
@NotBlank(message = "Comment body is required")
|
||||||
|
@Size(max = 1000, message = "Comment body cannot exceed 1000 characters")
|
||||||
|
private String body;
|
||||||
|
|
||||||
|
private Long replyComment;
|
||||||
|
|
||||||
|
public CommentCreateRequestDTO() {}
|
||||||
|
|
||||||
|
public CommentCreateRequestDTO(Long postId, String body) {
|
||||||
|
this.postId = postId;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommentCreateRequestDTO(Long postId, String body, Long replyComment) {
|
||||||
|
this.postId = postId;
|
||||||
|
this.body = body;
|
||||||
|
this.replyComment = replyComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getPostId() {
|
||||||
|
return postId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPostId(Long postId) {
|
||||||
|
this.postId = postId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBody() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBody(String body) {
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getReplyComment() {
|
||||||
|
return replyComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReplyComment(Long replyComment) {
|
||||||
|
this.replyComment = replyComment;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
package online.wesal.wesal.dto;
|
||||||
|
|
||||||
|
import online.wesal.wesal.entity.Comment;
|
||||||
|
import online.wesal.wesal.entity.User;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class CommentResponseDTO {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String postId;
|
||||||
|
private CreatorDTO creator;
|
||||||
|
private String body;
|
||||||
|
private LocalDateTime creationDate;
|
||||||
|
private String replyComment;
|
||||||
|
private String displayReplyComment;
|
||||||
|
private CreatorDTO replyUser;
|
||||||
|
private Integer level;
|
||||||
|
private List<CommentResponseDTO> replies;
|
||||||
|
|
||||||
|
public CommentResponseDTO() {}
|
||||||
|
|
||||||
|
public CommentResponseDTO(Comment comment, User creator, User replyUser) {
|
||||||
|
this.id = String.valueOf(comment.getId());
|
||||||
|
this.postId = String.valueOf(comment.getPostId());
|
||||||
|
this.creator = new CreatorDTO(creator);
|
||||||
|
this.body = comment.getBody();
|
||||||
|
this.creationDate = comment.getCreationDate();
|
||||||
|
this.replyComment = comment.getReplyComment() != null ? String.valueOf(comment.getReplyComment()) : null;
|
||||||
|
this.displayReplyComment = comment.getDisplayReplyComment() != null ? String.valueOf(comment.getDisplayReplyComment()) : null;
|
||||||
|
this.replyUser = replyUser != null ? new CreatorDTO(replyUser) : null;
|
||||||
|
this.level = comment.getLevel();
|
||||||
|
this.replies = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPostId() {
|
||||||
|
return postId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPostId(String postId) {
|
||||||
|
this.postId = postId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CreatorDTO getCreator() {
|
||||||
|
return creator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreator(CreatorDTO creator) {
|
||||||
|
this.creator = creator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBody() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBody(String body) {
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreationDate() {
|
||||||
|
return creationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreationDate(LocalDateTime creationDate) {
|
||||||
|
this.creationDate = creationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReplyComment() {
|
||||||
|
return replyComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReplyComment(String replyComment) {
|
||||||
|
this.replyComment = replyComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayReplyComment() {
|
||||||
|
return displayReplyComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayReplyComment(String displayReplyComment) {
|
||||||
|
this.displayReplyComment = displayReplyComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CreatorDTO getReplyUser() {
|
||||||
|
return replyUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReplyUser(CreatorDTO replyUser) {
|
||||||
|
this.replyUser = replyUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLevel(Integer level) {
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CommentResponseDTO> getReplies() {
|
||||||
|
return replies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReplies(List<CommentResponseDTO> replies) {
|
||||||
|
this.replies = replies;
|
||||||
|
}
|
||||||
|
}
|
||||||
130
backend/src/main/java/online/wesal/wesal/entity/Comment.java
Normal file
130
backend/src/main/java/online/wesal/wesal/entity/Comment.java
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package online.wesal.wesal.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "comments")
|
||||||
|
public class Comment {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false, name = "post_id")
|
||||||
|
private Long postId;
|
||||||
|
|
||||||
|
@Column(nullable = false, name = "creator_id")
|
||||||
|
private Long creatorId;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 1000)
|
||||||
|
@NotBlank
|
||||||
|
private String body;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDateTime creationDate = LocalDateTime.now();
|
||||||
|
|
||||||
|
@Column(name = "reply_comment")
|
||||||
|
private Long replyComment;
|
||||||
|
|
||||||
|
@Column(name = "display_reply_comment")
|
||||||
|
private Long displayReplyComment;
|
||||||
|
|
||||||
|
@Column(name = "reply_user")
|
||||||
|
private Long replyUser;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Integer level = 1;
|
||||||
|
|
||||||
|
public Comment() {}
|
||||||
|
|
||||||
|
public Comment(Long postId, Long creatorId, String body) {
|
||||||
|
this.postId = postId;
|
||||||
|
this.creatorId = creatorId;
|
||||||
|
this.body = body;
|
||||||
|
this.level = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comment(Long postId, Long creatorId, String body, Long replyComment, Long displayReplyComment, Long replyUser, Integer level) {
|
||||||
|
this.postId = postId;
|
||||||
|
this.creatorId = creatorId;
|
||||||
|
this.body = body;
|
||||||
|
this.replyComment = replyComment;
|
||||||
|
this.displayReplyComment = displayReplyComment;
|
||||||
|
this.replyUser = replyUser;
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getPostId() {
|
||||||
|
return postId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPostId(Long postId) {
|
||||||
|
this.postId = postId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCreatorId() {
|
||||||
|
return creatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatorId(Long creatorId) {
|
||||||
|
this.creatorId = creatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBody() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBody(String body) {
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreationDate() {
|
||||||
|
return creationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreationDate(LocalDateTime creationDate) {
|
||||||
|
this.creationDate = creationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getReplyComment() {
|
||||||
|
return replyComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReplyComment(Long replyComment) {
|
||||||
|
this.replyComment = replyComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getDisplayReplyComment() {
|
||||||
|
return displayReplyComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayReplyComment(Long displayReplyComment) {
|
||||||
|
this.displayReplyComment = displayReplyComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getReplyUser() {
|
||||||
|
return replyUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReplyUser(Long replyUser) {
|
||||||
|
this.replyUser = replyUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLevel(Integer level) {
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package online.wesal.wesal.repository;
|
||||||
|
|
||||||
|
import online.wesal.wesal.entity.Comment;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface CommentRepository extends JpaRepository<Comment, Long> {
|
||||||
|
|
||||||
|
List<Comment> findByPostIdOrderByCreationDateDesc(Long postId);
|
||||||
|
|
||||||
|
@Query("SELECT c FROM Comment c WHERE c.postId = :postId ORDER BY c.creationDate ASC")
|
||||||
|
List<Comment> findByPostIdOrderByCreationDateAsc(@Param("postId") Long postId);
|
||||||
|
|
||||||
|
Optional<Comment> findByIdAndPostId(Long id, Long postId);
|
||||||
|
|
||||||
|
long countByPostId(Long postId);
|
||||||
|
|
||||||
|
@Query("SELECT c FROM Comment c WHERE c.replyComment = :replyComment ORDER BY c.creationDate ASC")
|
||||||
|
List<Comment> findRepliesByReplyComment(@Param("replyComment") Long replyComment);
|
||||||
|
}
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
package online.wesal.wesal.service;
|
||||||
|
|
||||||
|
import online.wesal.wesal.dto.CommentResponseDTO;
|
||||||
|
import online.wesal.wesal.entity.Comment;
|
||||||
|
import online.wesal.wesal.entity.Post;
|
||||||
|
import online.wesal.wesal.entity.User;
|
||||||
|
import online.wesal.wesal.repository.CommentRepository;
|
||||||
|
import online.wesal.wesal.repository.PostRepository;
|
||||||
|
import online.wesal.wesal.repository.UserRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CommentService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CommentRepository commentRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PostRepository postRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public CommentResponseDTO createComment(Long postId, String body, Long replyComment) {
|
||||||
|
User currentUser = userService.getCurrentUser();
|
||||||
|
|
||||||
|
Post post = postRepository.findById(postId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Post not found"));
|
||||||
|
|
||||||
|
Comment comment;
|
||||||
|
User replyUser = null;
|
||||||
|
|
||||||
|
if (replyComment == null) {
|
||||||
|
comment = new Comment(postId, currentUser.getId(), body);
|
||||||
|
} else {
|
||||||
|
Comment parentComment = commentRepository.findByIdAndPostId(replyComment, postId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Parent comment not found"));
|
||||||
|
|
||||||
|
replyUser = userRepository.findById(parentComment.getCreatorId())
|
||||||
|
.orElseThrow(() -> new RuntimeException("Parent comment creator not found"));
|
||||||
|
|
||||||
|
Integer newLevel;
|
||||||
|
Long displayReplyComment;
|
||||||
|
|
||||||
|
if (parentComment.getLevel() >= 3) {
|
||||||
|
newLevel = 3;
|
||||||
|
Comment rootComment = findRootCommentForLevel3(parentComment);
|
||||||
|
displayReplyComment = rootComment.getId();
|
||||||
|
} else {
|
||||||
|
newLevel = parentComment.getLevel() + 1;
|
||||||
|
displayReplyComment = replyComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
comment = new Comment(postId, currentUser.getId(), body, replyComment, displayReplyComment, replyUser.getId(), newLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
comment = commentRepository.save(comment);
|
||||||
|
|
||||||
|
post.setComments(post.getComments() + 1);
|
||||||
|
postRepository.save(post);
|
||||||
|
|
||||||
|
return new CommentResponseDTO(comment, currentUser, replyUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comment findRootCommentForLevel3(Comment comment) {
|
||||||
|
if (comment.getLevel() <= 2) {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comment.getReplyComment() != null) {
|
||||||
|
Comment parentComment = commentRepository.findById(comment.getReplyComment())
|
||||||
|
.orElse(comment);
|
||||||
|
return findRootCommentForLevel3(parentComment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CommentResponseDTO> getCommentsByPostId(Long postId) {
|
||||||
|
List<Comment> comments = commentRepository.findByPostIdOrderByCreationDateAsc(postId);
|
||||||
|
|
||||||
|
List<Long> creatorIds = comments.stream()
|
||||||
|
.map(Comment::getCreatorId)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<Long> replyUserIds = comments.stream()
|
||||||
|
.map(Comment::getReplyUser)
|
||||||
|
.filter(id -> id != null)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
creatorIds.addAll(replyUserIds);
|
||||||
|
|
||||||
|
Map<Long, User> users = userRepository.findAllById(creatorIds.stream().distinct().collect(Collectors.toList())).stream()
|
||||||
|
.collect(Collectors.toMap(User::getId, user -> user));
|
||||||
|
|
||||||
|
Map<Long, CommentResponseDTO> commentMap = comments.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
Comment::getId,
|
||||||
|
comment -> new CommentResponseDTO(comment,
|
||||||
|
users.get(comment.getCreatorId()),
|
||||||
|
comment.getReplyUser() != null ? users.get(comment.getReplyUser()) : null)
|
||||||
|
));
|
||||||
|
|
||||||
|
List<CommentResponseDTO> rootComments = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Comment comment : comments) {
|
||||||
|
CommentResponseDTO dto = commentMap.get(comment.getId());
|
||||||
|
|
||||||
|
if (comment.getDisplayReplyComment() == null) {
|
||||||
|
rootComments.add(dto);
|
||||||
|
} else {
|
||||||
|
CommentResponseDTO parent = commentMap.get(comment.getDisplayReplyComment());
|
||||||
|
if (parent != null) {
|
||||||
|
parent.getReplies().add(dto);
|
||||||
|
} else {
|
||||||
|
rootComments.add(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootComments;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,8 @@ import 'services/notification_service.dart';
|
|||||||
import 'services/auth_service.dart';
|
import 'services/auth_service.dart';
|
||||||
import 'services/user_service.dart';
|
import 'services/user_service.dart';
|
||||||
|
|
||||||
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||||
@ -21,6 +23,7 @@ class MyApp extends StatelessWidget {
|
|||||||
title: 'Wesal',
|
title: 'Wesal',
|
||||||
theme: ThemeData(primarySwatch: Colors.blue, fontFamily: 'Roboto'),
|
theme: ThemeData(primarySwatch: Colors.blue, fontFamily: 'Roboto'),
|
||||||
home: SplashScreen(),
|
home: SplashScreen(),
|
||||||
|
navigatorKey: navigatorKey,
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import '../constants/api_constants.dart';
|
import '../constants/api_constants.dart';
|
||||||
|
import '../main.dart';
|
||||||
|
|
||||||
class AuthService {
|
class AuthService {
|
||||||
static const FlutterSecureStorage _storage = FlutterSecureStorage();
|
static const FlutterSecureStorage _storage = FlutterSecureStorage();
|
||||||
@ -87,4 +89,13 @@ class AuthService {
|
|||||||
static Future<void> clearUserData() async {
|
static Future<void> clearUserData() async {
|
||||||
await _storage.delete(key: _userDataKey);
|
await _storage.delete(key: _userDataKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> handleAuthenticationError() async {
|
||||||
|
await logout();
|
||||||
|
|
||||||
|
final context = navigatorKey.currentContext;
|
||||||
|
if (context != null && context.mounted) {
|
||||||
|
Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
import '../constants/api_constants.dart';
|
import '../constants/api_constants.dart';
|
||||||
import '../models/invitation_models.dart';
|
import '../models/invitation_models.dart';
|
||||||
import 'http_service.dart';
|
import 'http_service.dart';
|
||||||
|
import 'auth_service.dart';
|
||||||
|
|
||||||
class InvitationsService {
|
class InvitationsService {
|
||||||
static Future<Map<String, dynamic>> getAllInvitations() async {
|
static Future<Map<String, dynamic>> getAllInvitations() async {
|
||||||
@ -23,16 +24,12 @@ class InvitationsService {
|
|||||||
invitationsResponse.message ?? 'Failed to fetch invitations',
|
invitationsResponse.message ?? 'Failed to fetch invitations',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (response.statusCode == 401) {
|
} else if (response.statusCode == 401 || response.statusCode == 403) {
|
||||||
|
await AuthService.handleAuthenticationError();
|
||||||
return {
|
return {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Session expired. Please login again.',
|
'message': 'Session expired. Please login again.',
|
||||||
};
|
};
|
||||||
} else if (response.statusCode == 403) {
|
|
||||||
return {
|
|
||||||
'success': false,
|
|
||||||
'message': 'Access denied. Invalid credentials.',
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'success': false,
|
'success': false,
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import '../models/post_models.dart';
|
import '../models/post_models.dart';
|
||||||
import 'http_service.dart';
|
import 'http_service.dart';
|
||||||
|
import 'auth_service.dart';
|
||||||
|
|
||||||
class PostService {
|
class PostService {
|
||||||
static Future<Map<String, dynamic>> getAllPosts() async {
|
static Future<Map<String, dynamic>> getAllPosts() async {
|
||||||
try {
|
try {
|
||||||
final response = await HttpService.get('/posts/all');
|
final response = await HttpService.get('/posts/all');
|
||||||
|
|
||||||
final responseData = jsonDecode(response.body);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
|
final responseData = jsonDecode(response.body);
|
||||||
return {
|
return {
|
||||||
'success': responseData['status'] ?? false,
|
'success': responseData['status'] ?? false,
|
||||||
'message': responseData['message'] ?? '',
|
'message': responseData['message'] ?? '',
|
||||||
@ -19,12 +19,28 @@ class PostService {
|
|||||||
.toList() ??
|
.toList() ??
|
||||||
[],
|
[],
|
||||||
};
|
};
|
||||||
|
} else if (response.statusCode == 401 || response.statusCode == 403) {
|
||||||
|
await AuthService.handleAuthenticationError();
|
||||||
|
return {
|
||||||
|
'success': false,
|
||||||
|
'message': 'Session expired. Please login again.',
|
||||||
|
'posts': <Post>[],
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
|
try {
|
||||||
|
final responseData = jsonDecode(response.body);
|
||||||
return {
|
return {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': responseData['message'] ?? 'Failed to fetch posts',
|
'message': responseData['message'] ?? 'Failed to fetch posts',
|
||||||
'posts': <Post>[],
|
'posts': <Post>[],
|
||||||
};
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
'success': false,
|
||||||
|
'message': 'Server error (${response.statusCode})',
|
||||||
|
'posts': <Post>[],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error fetching posts: $e');
|
print('Error fetching posts: $e');
|
||||||
|
|||||||
@ -21,16 +21,12 @@ class UserService {
|
|||||||
final data = jsonDecode(response.body);
|
final data = jsonDecode(response.body);
|
||||||
await AuthService.saveUserData(data);
|
await AuthService.saveUserData(data);
|
||||||
return {'success': true, 'data': data};
|
return {'success': true, 'data': data};
|
||||||
} else if (response.statusCode == 401) {
|
} else if (response.statusCode == 401 || response.statusCode == 403) {
|
||||||
|
await AuthService.handleAuthenticationError();
|
||||||
return {
|
return {
|
||||||
'success': false,
|
'success': false,
|
||||||
'message': 'Session expired. Please login again.',
|
'message': 'Session expired. Please login again.',
|
||||||
};
|
};
|
||||||
} else if (response.statusCode == 403) {
|
|
||||||
return {
|
|
||||||
'success': false,
|
|
||||||
'message': 'Access denied. Invalid credentials.',
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'success': false,
|
'success': false,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user