feat: allow images in posts
This commit is contained in:
parent
38214bd1e9
commit
55c2d231c7
@ -44,6 +44,7 @@ public class PostController {
|
||||
.map(error -> {
|
||||
String field = error.getField();
|
||||
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();
|
||||
})
|
||||
.findFirst()
|
||||
@ -52,7 +53,7 @@ public class PostController {
|
||||
}
|
||||
|
||||
try {
|
||||
PostResponseDTO response = postService.createPostWithResponse(request.getBody());
|
||||
PostResponseDTO response = postService.createPostWithResponse(request.getBody(), request.getImages());
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
} catch (RuntimeException e) {
|
||||
String message;
|
||||
|
||||
@ -2,6 +2,7 @@ package online.wesal.wesal.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
public class PostCreateRequestDTO {
|
||||
|
||||
@ -9,12 +10,20 @@ public class PostCreateRequestDTO {
|
||||
@Size(max = 2000, message = "Post body cannot exceed 2000 characters")
|
||||
private String body;
|
||||
|
||||
@Size(max = 10, message = "Post cannot have more than 10 images")
|
||||
private List<String> images;
|
||||
|
||||
public PostCreateRequestDTO() {}
|
||||
|
||||
public PostCreateRequestDTO(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public PostCreateRequestDTO(String body, List<String> images) {
|
||||
this.body = body;
|
||||
this.images = images;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
@ -22,4 +31,12 @@ public class PostCreateRequestDTO {
|
||||
public void setBody(String 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.User;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public class PostResponseDTO {
|
||||
|
||||
@ -13,6 +14,7 @@ public class PostResponseDTO {
|
||||
private String comments;
|
||||
private boolean liked;
|
||||
private LocalDateTime creationDate;
|
||||
private List<String> images;
|
||||
|
||||
public PostResponseDTO() {}
|
||||
|
||||
@ -24,6 +26,7 @@ public class PostResponseDTO {
|
||||
this.comments = String.valueOf(post.getComments());
|
||||
this.liked = false; // Default value, will be set by service
|
||||
this.creationDate = post.getCreationDate();
|
||||
this.images = post.getImages();
|
||||
}
|
||||
|
||||
public PostResponseDTO(Post post, User creator, boolean liked) {
|
||||
@ -34,6 +37,7 @@ public class PostResponseDTO {
|
||||
this.comments = String.valueOf(post.getComments());
|
||||
this.liked = liked;
|
||||
this.creationDate = post.getCreationDate();
|
||||
this.images = post.getImages();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
@ -91,4 +95,12 @@ public class PostResponseDTO {
|
||||
public void setCreationDate(LocalDateTime 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 LocalDateTime creationDate;
|
||||
private List<LikedUserDTO> likedUsers;
|
||||
private List<String> images;
|
||||
|
||||
public PostWithLikesResponseDTO() {}
|
||||
|
||||
@ -27,6 +28,7 @@ public class PostWithLikesResponseDTO {
|
||||
this.liked = liked;
|
||||
this.creationDate = post.getCreationDate();
|
||||
this.likedUsers = likedUsers;
|
||||
this.images = post.getImages();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
@ -92,4 +94,12 @@ public class PostWithLikesResponseDTO {
|
||||
public void setLikedUsers(List<LikedUserDTO> 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.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.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@Entity
|
||||
@Table(name = "posts")
|
||||
@ -28,6 +33,9 @@ public class Post {
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime creationDate = LocalDateTime.now();
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String images = "[]";
|
||||
|
||||
public Post() {}
|
||||
|
||||
public Post(Long creatorId, String body) {
|
||||
@ -35,6 +43,12 @@ public class Post {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public Post(Long creatorId, String body, List<String> images) {
|
||||
this.creatorId = creatorId;
|
||||
this.body = body;
|
||||
this.setImages(images);
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
@ -82,4 +96,33 @@ public class Post {
|
||||
public void setCreationDate(LocalDateTime 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
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private SubscriptionNotificationService subscriptionNotificationService;
|
||||
|
||||
@Transactional
|
||||
public CommentResponseDTO createComment(Long postId, String body, Long replyComment) {
|
||||
User currentUser = userService.getCurrentUser();
|
||||
@ -70,6 +73,14 @@ public class CommentService {
|
||||
post.setComments(post.getComments() + 1);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -34,10 +34,24 @@ public class PostService {
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private SubscriptionNotificationService subscriptionNotificationService;
|
||||
|
||||
public Post createPost(String body) {
|
||||
return createPost(body, null);
|
||||
}
|
||||
|
||||
public Post createPost(String body, List<String> images) {
|
||||
User currentUser = userService.getCurrentUser();
|
||||
Post post = new Post(currentUser.getId(), body);
|
||||
return postRepository.save(post);
|
||||
Post post = new Post(currentUser.getId(), body, images);
|
||||
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() {
|
||||
@ -93,9 +107,19 @@ public class PostService {
|
||||
}
|
||||
|
||||
public PostResponseDTO createPostWithResponse(String body) {
|
||||
return createPostWithResponse(body, null);
|
||||
}
|
||||
|
||||
public PostResponseDTO createPostWithResponse(String body, List<String> images) {
|
||||
User currentUser = userService.getCurrentUser();
|
||||
Post post = new Post(currentUser.getId(), body);
|
||||
Post post = new Post(currentUser.getId(), body, images);
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -74,4 +75,47 @@ public class SubscriptionNotificationService {
|
||||
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