feat: allow images in posts
This commit is contained in:
parent
38214bd1e9
commit
55c2d231c7
@ -44,6 +44,7 @@ public class PostController {
|
|||||||
.map(error -> {
|
.map(error -> {
|
||||||
String field = error.getField();
|
String field = error.getField();
|
||||||
if ("body".equals(field)) return "Post body is required and cannot exceed 2000 characters";
|
if ("body".equals(field)) return "Post body is required and cannot exceed 2000 characters";
|
||||||
|
if ("images".equals(field)) return "Post cannot have more than 10 images";
|
||||||
return error.getDefaultMessage();
|
return error.getDefaultMessage();
|
||||||
})
|
})
|
||||||
.findFirst()
|
.findFirst()
|
||||||
@ -52,7 +53,7 @@ public class PostController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
PostResponseDTO response = postService.createPostWithResponse(request.getBody());
|
PostResponseDTO response = postService.createPostWithResponse(request.getBody(), request.getImages());
|
||||||
return ResponseEntity.ok(ApiResponse.success(response));
|
return ResponseEntity.ok(ApiResponse.success(response));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
String message;
|
String message;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package online.wesal.wesal.dto;
|
|||||||
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Size;
|
import jakarta.validation.constraints.Size;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class PostCreateRequestDTO {
|
public class PostCreateRequestDTO {
|
||||||
|
|
||||||
@ -9,12 +10,20 @@ public class PostCreateRequestDTO {
|
|||||||
@Size(max = 2000, message = "Post body cannot exceed 2000 characters")
|
@Size(max = 2000, message = "Post body cannot exceed 2000 characters")
|
||||||
private String body;
|
private String body;
|
||||||
|
|
||||||
|
@Size(max = 10, message = "Post cannot have more than 10 images")
|
||||||
|
private List<String> images;
|
||||||
|
|
||||||
public PostCreateRequestDTO() {}
|
public PostCreateRequestDTO() {}
|
||||||
|
|
||||||
public PostCreateRequestDTO(String body) {
|
public PostCreateRequestDTO(String body) {
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PostCreateRequestDTO(String body, List<String> images) {
|
||||||
|
this.body = body;
|
||||||
|
this.images = images;
|
||||||
|
}
|
||||||
|
|
||||||
public String getBody() {
|
public String getBody() {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
@ -22,4 +31,12 @@ public class PostCreateRequestDTO {
|
|||||||
public void setBody(String body) {
|
public void setBody(String body) {
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getImages() {
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImages(List<String> images) {
|
||||||
|
this.images = images;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@ package online.wesal.wesal.dto;
|
|||||||
import online.wesal.wesal.entity.Post;
|
import online.wesal.wesal.entity.Post;
|
||||||
import online.wesal.wesal.entity.User;
|
import online.wesal.wesal.entity.User;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class PostResponseDTO {
|
public class PostResponseDTO {
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ public class PostResponseDTO {
|
|||||||
private String comments;
|
private String comments;
|
||||||
private boolean liked;
|
private boolean liked;
|
||||||
private LocalDateTime creationDate;
|
private LocalDateTime creationDate;
|
||||||
|
private List<String> images;
|
||||||
|
|
||||||
public PostResponseDTO() {}
|
public PostResponseDTO() {}
|
||||||
|
|
||||||
@ -24,6 +26,7 @@ public class PostResponseDTO {
|
|||||||
this.comments = String.valueOf(post.getComments());
|
this.comments = String.valueOf(post.getComments());
|
||||||
this.liked = false; // Default value, will be set by service
|
this.liked = false; // Default value, will be set by service
|
||||||
this.creationDate = post.getCreationDate();
|
this.creationDate = post.getCreationDate();
|
||||||
|
this.images = post.getImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PostResponseDTO(Post post, User creator, boolean liked) {
|
public PostResponseDTO(Post post, User creator, boolean liked) {
|
||||||
@ -34,6 +37,7 @@ public class PostResponseDTO {
|
|||||||
this.comments = String.valueOf(post.getComments());
|
this.comments = String.valueOf(post.getComments());
|
||||||
this.liked = liked;
|
this.liked = liked;
|
||||||
this.creationDate = post.getCreationDate();
|
this.creationDate = post.getCreationDate();
|
||||||
|
this.images = post.getImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
@ -91,4 +95,12 @@ public class PostResponseDTO {
|
|||||||
public void setCreationDate(LocalDateTime creationDate) {
|
public void setCreationDate(LocalDateTime creationDate) {
|
||||||
this.creationDate = creationDate;
|
this.creationDate = creationDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getImages() {
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImages(List<String> images) {
|
||||||
|
this.images = images;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -15,6 +15,7 @@ public class PostWithLikesResponseDTO {
|
|||||||
private boolean liked;
|
private boolean liked;
|
||||||
private LocalDateTime creationDate;
|
private LocalDateTime creationDate;
|
||||||
private List<LikedUserDTO> likedUsers;
|
private List<LikedUserDTO> likedUsers;
|
||||||
|
private List<String> images;
|
||||||
|
|
||||||
public PostWithLikesResponseDTO() {}
|
public PostWithLikesResponseDTO() {}
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ public class PostWithLikesResponseDTO {
|
|||||||
this.liked = liked;
|
this.liked = liked;
|
||||||
this.creationDate = post.getCreationDate();
|
this.creationDate = post.getCreationDate();
|
||||||
this.likedUsers = likedUsers;
|
this.likedUsers = likedUsers;
|
||||||
|
this.images = post.getImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
@ -92,4 +94,12 @@ public class PostWithLikesResponseDTO {
|
|||||||
public void setLikedUsers(List<LikedUserDTO> likedUsers) {
|
public void setLikedUsers(List<LikedUserDTO> likedUsers) {
|
||||||
this.likedUsers = likedUsers;
|
this.likedUsers = likedUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getImages() {
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImages(List<String> images) {
|
||||||
|
this.images = images;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -2,7 +2,12 @@ package online.wesal.wesal.entity;
|
|||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "posts")
|
@Table(name = "posts")
|
||||||
@ -28,6 +33,9 @@ public class Post {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private LocalDateTime creationDate = LocalDateTime.now();
|
private LocalDateTime creationDate = LocalDateTime.now();
|
||||||
|
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
private String images = "[]";
|
||||||
|
|
||||||
public Post() {}
|
public Post() {}
|
||||||
|
|
||||||
public Post(Long creatorId, String body) {
|
public Post(Long creatorId, String body) {
|
||||||
@ -35,6 +43,12 @@ public class Post {
|
|||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Post(Long creatorId, String body, List<String> images) {
|
||||||
|
this.creatorId = creatorId;
|
||||||
|
this.body = body;
|
||||||
|
this.setImages(images);
|
||||||
|
}
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@ -82,4 +96,33 @@ public class Post {
|
|||||||
public void setCreationDate(LocalDateTime creationDate) {
|
public void setCreationDate(LocalDateTime creationDate) {
|
||||||
this.creationDate = creationDate;
|
this.creationDate = creationDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getImages() {
|
||||||
|
if (images == null || images.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
return mapper.readValue(images, new TypeReference<List<String>>() {});
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImages(List<String> images) {
|
||||||
|
if (images == null || images.isEmpty()) {
|
||||||
|
this.images = "[]";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to 10 images
|
||||||
|
List<String> limitedImages = images.size() > 10 ? images.subList(0, 10) : images;
|
||||||
|
|
||||||
|
try {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
this.images = mapper.writeValueAsString(limitedImages);
|
||||||
|
} catch (Exception e) {
|
||||||
|
this.images = "[]";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -31,6 +31,9 @@ public class CommentService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SubscriptionNotificationService subscriptionNotificationService;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public CommentResponseDTO createComment(Long postId, String body, Long replyComment) {
|
public CommentResponseDTO createComment(Long postId, String body, Long replyComment) {
|
||||||
User currentUser = userService.getCurrentUser();
|
User currentUser = userService.getCurrentUser();
|
||||||
@ -70,6 +73,14 @@ public class CommentService {
|
|||||||
post.setComments(post.getComments() + 1);
|
post.setComments(post.getComments() + 1);
|
||||||
postRepository.save(post);
|
postRepository.save(post);
|
||||||
|
|
||||||
|
// Send notification to post creator if they're subscribed to "postcomments" and it's not their own comment
|
||||||
|
User postCreator = userRepository.findById(post.getCreatorId()).orElse(null);
|
||||||
|
if (postCreator != null && !postCreator.getId().equals(currentUser.getId())) {
|
||||||
|
String title = String.format("%s replied to your post", currentUser.getDisplayName());
|
||||||
|
String excerpt = subscriptionNotificationService.truncateText(body, 60);
|
||||||
|
subscriptionNotificationService.sendNotificationToUser(postCreator, "postcomments", title, excerpt);
|
||||||
|
}
|
||||||
|
|
||||||
return new CommentResponseDTO(comment, currentUser, replyUser);
|
return new CommentResponseDTO(comment, currentUser, replyUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,10 +34,24 @@ public class PostService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SubscriptionNotificationService subscriptionNotificationService;
|
||||||
|
|
||||||
public Post createPost(String body) {
|
public Post createPost(String body) {
|
||||||
|
return createPost(body, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Post createPost(String body, List<String> images) {
|
||||||
User currentUser = userService.getCurrentUser();
|
User currentUser = userService.getCurrentUser();
|
||||||
Post post = new Post(currentUser.getId(), body);
|
Post post = new Post(currentUser.getId(), body, images);
|
||||||
return postRepository.save(post);
|
Post savedPost = postRepository.save(post);
|
||||||
|
|
||||||
|
// Send notification to users subscribed to "newposts"
|
||||||
|
String title = String.format("%s just posted", currentUser.getDisplayName());
|
||||||
|
String excerpt = subscriptionNotificationService.truncateText(body, 80);
|
||||||
|
subscriptionNotificationService.sendNotificationToSubscription("newposts", title, excerpt);
|
||||||
|
|
||||||
|
return savedPost;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PostResponseDTO> getAllRecentPosts() {
|
public List<PostResponseDTO> getAllRecentPosts() {
|
||||||
@ -93,9 +107,19 @@ public class PostService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PostResponseDTO createPostWithResponse(String body) {
|
public PostResponseDTO createPostWithResponse(String body) {
|
||||||
|
return createPostWithResponse(body, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PostResponseDTO createPostWithResponse(String body, List<String> images) {
|
||||||
User currentUser = userService.getCurrentUser();
|
User currentUser = userService.getCurrentUser();
|
||||||
Post post = new Post(currentUser.getId(), body);
|
Post post = new Post(currentUser.getId(), body, images);
|
||||||
post = postRepository.save(post);
|
post = postRepository.save(post);
|
||||||
|
|
||||||
|
// Send notification to users subscribed to "newposts"
|
||||||
|
String title = String.format("%s just posted", currentUser.getDisplayName());
|
||||||
|
String excerpt = subscriptionNotificationService.truncateText(body, 80);
|
||||||
|
subscriptionNotificationService.sendNotificationToSubscription("newposts", title, excerpt);
|
||||||
|
|
||||||
return new PostResponseDTO(post, currentUser, false); // New post is not liked by default
|
return new PostResponseDTO(post, currentUser, false); // New post is not liked by default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -74,4 +75,47 @@ public class SubscriptionNotificationService {
|
|||||||
logger.error("Failed to send notifications for subscription '{}': {}", subscriptionName, e.getMessage(), e);
|
logger.error("Failed to send notifications for subscription '{}': {}", subscriptionName, e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void sendNotificationToUser(User user, String subscriptionName, String title, String body) {
|
||||||
|
if (user == null || user.getFcmToken() == null || user.getFcmToken().trim().isEmpty()) {
|
||||||
|
logger.warn("User has no valid FCM token, skipping notification");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is subscribed
|
||||||
|
if (!user.getSubscriptions().contains(subscriptionName)) {
|
||||||
|
logger.debug("User {} is not subscribed to '{}', skipping notification", user.getDisplayName(), subscriptionName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
MulticastMessage message = MulticastMessage.builder()
|
||||||
|
.setNotification(Notification.builder()
|
||||||
|
.setTitle(title)
|
||||||
|
.setBody(body)
|
||||||
|
.build())
|
||||||
|
.addAllTokens(Collections.singletonList(user.getFcmToken()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
BatchResponse response = FirebaseMessaging.getInstance().sendEachForMulticast(message);
|
||||||
|
|
||||||
|
if (response.getSuccessCount() > 0) {
|
||||||
|
logger.info("Successfully sent notification to {}", user.getDisplayName());
|
||||||
|
} else {
|
||||||
|
logger.error("Failed to send notification to {}: {}",
|
||||||
|
user.getDisplayName(), response.getResponses().get(0).getException().getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to send notification to {}: {}", user.getDisplayName(), e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String truncateText(String text, int maxLength) {
|
||||||
|
if (text == null || text.length() <= maxLength) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return text.substring(0, maxLength).trim() + "...";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user