diff --git a/backend/src/main/java/online/wesal/wesal/controller/PostController.java b/backend/src/main/java/online/wesal/wesal/controller/PostController.java index 812cb9f..c649145 100644 --- a/backend/src/main/java/online/wesal/wesal/controller/PostController.java +++ b/backend/src/main/java/online/wesal/wesal/controller/PostController.java @@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import online.wesal.wesal.dto.ApiResponse; import online.wesal.wesal.dto.PostCreateRequestDTO; +import online.wesal.wesal.dto.PostLikeRequestDTO; import online.wesal.wesal.dto.PostResponseDTO; import online.wesal.wesal.entity.Post; import online.wesal.wesal.service.PostService; @@ -109,4 +110,78 @@ public class PostController { return ResponseEntity.status(500).body(ApiResponse.error("Something went wrong.. We're sorry but try again later")); } } + + @PostMapping(value = "/like", consumes = "application/json", produces = "application/json") + @Operation(summary = "Like post", description = "Like a post. Returns the updated post with new like count.") + public ResponseEntity> likePost( + @Valid @RequestBody PostLikeRequestDTO 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"; + return error.getDefaultMessage(); + }) + .findFirst() + .orElse("Invalid input"); + return ResponseEntity.badRequest().body(ApiResponse.error(errorMessage)); + } + + try { + PostResponseDTO response = postService.likePost(request.getPostId()); + 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("User 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")); + } + } + + @PostMapping(value = "/unlike", consumes = "application/json", produces = "application/json") + @Operation(summary = "Unlike post", description = "Unlike a post. Returns the updated post with new like count.") + public ResponseEntity> unlikePost( + @Valid @RequestBody PostLikeRequestDTO 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"; + return error.getDefaultMessage(); + }) + .findFirst() + .orElse("Invalid input"); + return ResponseEntity.badRequest().body(ApiResponse.error(errorMessage)); + } + + try { + PostResponseDTO response = postService.unlikePost(request.getPostId()); + 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("User 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")); + } + } } \ No newline at end of file diff --git a/backend/src/main/java/online/wesal/wesal/dto/PostLikeRequestDTO.java b/backend/src/main/java/online/wesal/wesal/dto/PostLikeRequestDTO.java new file mode 100644 index 0000000..ff7410d --- /dev/null +++ b/backend/src/main/java/online/wesal/wesal/dto/PostLikeRequestDTO.java @@ -0,0 +1,25 @@ +package online.wesal.wesal.dto; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; + +public class PostLikeRequestDTO { + + @NotNull(message = "Post ID is required") + @Positive(message = "Post ID must be a positive number") + private Long postId; + + public PostLikeRequestDTO() {} + + public PostLikeRequestDTO(Long postId) { + this.postId = postId; + } + + public Long getPostId() { + return postId; + } + + public void setPostId(Long postId) { + this.postId = postId; + } +} \ No newline at end of file diff --git a/backend/src/main/java/online/wesal/wesal/entity/PostLike.java b/backend/src/main/java/online/wesal/wesal/entity/PostLike.java new file mode 100644 index 0000000..6edd44b --- /dev/null +++ b/backend/src/main/java/online/wesal/wesal/entity/PostLike.java @@ -0,0 +1,63 @@ +package online.wesal.wesal.entity; + +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "post_likes", uniqueConstraints = { + @UniqueConstraint(columnNames = {"post_id", "user_id"}) +}) +public class PostLike { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, name = "post_id") + private Long postId; + + @Column(nullable = false, name = "user_id") + private Long userId; + + @Column(nullable = false) + private LocalDateTime createdAt = LocalDateTime.now(); + + public PostLike() {} + + public PostLike(Long postId, Long userId) { + this.postId = postId; + this.userId = userId; + } + + 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 getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } +} \ No newline at end of file diff --git a/backend/src/main/java/online/wesal/wesal/repository/PostLikeRepository.java b/backend/src/main/java/online/wesal/wesal/repository/PostLikeRepository.java new file mode 100644 index 0000000..9a85d0f --- /dev/null +++ b/backend/src/main/java/online/wesal/wesal/repository/PostLikeRepository.java @@ -0,0 +1,15 @@ +package online.wesal.wesal.repository; + +import online.wesal.wesal.entity.PostLike; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface PostLikeRepository extends JpaRepository { + Optional findByPostIdAndUserId(Long postId, Long userId); + boolean existsByPostIdAndUserId(Long postId, Long userId); + void deleteByPostIdAndUserId(Long postId, Long userId); + long countByPostId(Long postId); +} \ No newline at end of file diff --git a/backend/src/main/java/online/wesal/wesal/service/PostService.java b/backend/src/main/java/online/wesal/wesal/service/PostService.java index e11e2e7..e79cb16 100644 --- a/backend/src/main/java/online/wesal/wesal/service/PostService.java +++ b/backend/src/main/java/online/wesal/wesal/service/PostService.java @@ -2,11 +2,14 @@ package online.wesal.wesal.service; import online.wesal.wesal.dto.PostResponseDTO; import online.wesal.wesal.entity.Post; +import online.wesal.wesal.entity.PostLike; import online.wesal.wesal.entity.User; +import online.wesal.wesal.repository.PostLikeRepository; 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.time.LocalDateTime; import java.util.List; @@ -19,6 +22,9 @@ public class PostService { @Autowired private PostRepository postRepository; + @Autowired + private PostLikeRepository postLikeRepository; + @Autowired private UserRepository userRepository; @@ -64,4 +70,57 @@ public class PostService { post = postRepository.save(post); return new PostResponseDTO(post, currentUser); } + + @Transactional + public PostResponseDTO likePost(Long postId) { + User currentUser = userService.getCurrentUser(); + Long userId = currentUser.getId(); + + // Check if post exists + Post post = postRepository.findById(postId) + .orElseThrow(() -> new RuntimeException("Post not found")); + + // Check if user already liked this post + if (!postLikeRepository.existsByPostIdAndUserId(postId, userId)) { + // Add like record + PostLike postLike = new PostLike(postId, userId); + postLikeRepository.save(postLike); + + // Update post likes count + post.setLikes(post.getLikes() + 1); + post = postRepository.save(post); + } + + // Get creator for response + User creator = userRepository.findById(post.getCreatorId()) + .orElseThrow(() -> new RuntimeException("Creator not found")); + + return new PostResponseDTO(post, creator); + } + + @Transactional + public PostResponseDTO unlikePost(Long postId) { + User currentUser = userService.getCurrentUser(); + Long userId = currentUser.getId(); + + // Check if post exists + Post post = postRepository.findById(postId) + .orElseThrow(() -> new RuntimeException("Post not found")); + + // Check if user has liked this post + if (postLikeRepository.existsByPostIdAndUserId(postId, userId)) { + // Remove like record + postLikeRepository.deleteByPostIdAndUserId(postId, userId); + + // Update post likes count + post.setLikes(Math.max(0, post.getLikes() - 1)); + post = postRepository.save(post); + } + + // Get creator for response + User creator = userRepository.findById(post.getCreatorId()) + .orElseThrow(() -> new RuntimeException("Creator not found")); + + return new PostResponseDTO(post, creator); + } } \ No newline at end of file