mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-21 22:41:05 +08:00
Merge pull request #52 from nagisa77/codex/add-subscription-functionality
Implement subscriptions
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
package com.openisle.controller;
|
||||
|
||||
import com.openisle.service.SubscriptionService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/** Endpoints for subscribing to posts, comments and users. */
|
||||
@RestController
|
||||
@RequestMapping("/api/subscriptions")
|
||||
@RequiredArgsConstructor
|
||||
public class SubscriptionController {
|
||||
private final SubscriptionService subscriptionService;
|
||||
|
||||
@PostMapping("/posts/{postId}")
|
||||
public void subscribePost(@PathVariable Long postId, Authentication auth) {
|
||||
subscriptionService.subscribePost(auth.getName(), postId);
|
||||
}
|
||||
|
||||
@DeleteMapping("/posts/{postId}")
|
||||
public void unsubscribePost(@PathVariable Long postId, Authentication auth) {
|
||||
subscriptionService.unsubscribePost(auth.getName(), postId);
|
||||
}
|
||||
|
||||
@PostMapping("/comments/{commentId}")
|
||||
public void subscribeComment(@PathVariable Long commentId, Authentication auth) {
|
||||
subscriptionService.subscribeComment(auth.getName(), commentId);
|
||||
}
|
||||
|
||||
@DeleteMapping("/comments/{commentId}")
|
||||
public void unsubscribeComment(@PathVariable Long commentId, Authentication auth) {
|
||||
subscriptionService.unsubscribeComment(auth.getName(), commentId);
|
||||
}
|
||||
|
||||
@PostMapping("/users/{username}")
|
||||
public void subscribeUser(@PathVariable String username, Authentication auth) {
|
||||
subscriptionService.subscribeUser(auth.getName(), username);
|
||||
}
|
||||
|
||||
@DeleteMapping("/users/{username}")
|
||||
public void unsubscribeUser(@PathVariable String username, Authentication auth) {
|
||||
subscriptionService.unsubscribeUser(auth.getName(), username);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.openisle.service.ImageUploader;
|
||||
import com.openisle.service.UserService;
|
||||
import com.openisle.service.PostService;
|
||||
import com.openisle.service.CommentService;
|
||||
import com.openisle.service.SubscriptionService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -24,6 +25,7 @@ public class UserController {
|
||||
private final ImageUploader imageUploader;
|
||||
private final PostService postService;
|
||||
private final CommentService commentService;
|
||||
private final SubscriptionService subscriptionService;
|
||||
|
||||
@Value("${app.upload.check-type:true}")
|
||||
private boolean checkImageType;
|
||||
@@ -86,6 +88,20 @@ public class UserController {
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
@GetMapping("/{username}/following")
|
||||
public java.util.List<UserDto> following(@PathVariable String username) {
|
||||
return subscriptionService.getSubscribedUsers(username).stream()
|
||||
.map(this::toDto)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
@GetMapping("/{username}/followers")
|
||||
public java.util.List<UserDto> followers(@PathVariable String username) {
|
||||
return subscriptionService.getSubscribers(username).stream()
|
||||
.map(this::toDto)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
@GetMapping("/{username}/all")
|
||||
public ResponseEntity<UserAggregateDto> userAggregate(@PathVariable String username,
|
||||
@RequestParam(value = "postsLimit", required = false) Integer postsLimit,
|
||||
@@ -112,6 +128,8 @@ public class UserController {
|
||||
dto.setUsername(user.getUsername());
|
||||
dto.setEmail(user.getEmail());
|
||||
dto.setAvatar(user.getAvatar());
|
||||
dto.setFollowers(subscriptionService.countSubscribers(user.getUsername()));
|
||||
dto.setFollowing(subscriptionService.countSubscribed(user.getUsername()));
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -140,6 +158,8 @@ public class UserController {
|
||||
private String username;
|
||||
private String email;
|
||||
private String avatar;
|
||||
private long followers;
|
||||
private long following;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
27
src/main/java/com/openisle/model/CommentSubscription.java
Normal file
27
src/main/java/com/openisle/model/CommentSubscription.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.openisle.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/** Subscription to a comment for replies notifications. */
|
||||
@Entity
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@Table(name = "comment_subscriptions",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "comment_id"}))
|
||||
public class CommentSubscription {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "user_id")
|
||||
private User user;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "comment_id")
|
||||
private Comment comment;
|
||||
}
|
||||
@@ -11,5 +11,9 @@ public enum NotificationType {
|
||||
/** Someone reacted to your post or comment */
|
||||
REACTION,
|
||||
/** Your post under review was approved or rejected */
|
||||
POST_REVIEWED
|
||||
POST_REVIEWED,
|
||||
/** A subscribed post received a new comment */
|
||||
POST_UPDATED,
|
||||
/** A user you subscribe to created a post or comment */
|
||||
USER_ACTIVITY
|
||||
}
|
||||
|
||||
27
src/main/java/com/openisle/model/PostSubscription.java
Normal file
27
src/main/java/com/openisle/model/PostSubscription.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.openisle.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/** Subscription to a post for update notifications. */
|
||||
@Entity
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@Table(name = "post_subscriptions",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "post_id"}))
|
||||
public class PostSubscription {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "user_id")
|
||||
private User user;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "post_id")
|
||||
private Post post;
|
||||
}
|
||||
27
src/main/java/com/openisle/model/UserSubscription.java
Normal file
27
src/main/java/com/openisle/model/UserSubscription.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.openisle.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/** Following relationship between users. */
|
||||
@Entity
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@Table(name = "user_subscriptions",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"subscriber_id", "target_id"}))
|
||||
public class UserSubscription {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "subscriber_id")
|
||||
private User subscriber;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "target_id")
|
||||
private User target;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.openisle.repository;
|
||||
|
||||
import com.openisle.model.Comment;
|
||||
import com.openisle.model.CommentSubscription;
|
||||
import com.openisle.model.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface CommentSubscriptionRepository extends JpaRepository<CommentSubscription, Long> {
|
||||
List<CommentSubscription> findByComment(Comment comment);
|
||||
List<CommentSubscription> findByUser(User user);
|
||||
Optional<CommentSubscription> findByUserAndComment(User user, Comment comment);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.openisle.repository;
|
||||
|
||||
import com.openisle.model.Post;
|
||||
import com.openisle.model.PostSubscription;
|
||||
import com.openisle.model.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface PostSubscriptionRepository extends JpaRepository<PostSubscription, Long> {
|
||||
List<PostSubscription> findByPost(Post post);
|
||||
List<PostSubscription> findByUser(User user);
|
||||
Optional<PostSubscription> findByUserAndPost(User user, Post post);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.openisle.repository;
|
||||
|
||||
import com.openisle.model.User;
|
||||
import com.openisle.model.UserSubscription;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface UserSubscriptionRepository extends JpaRepository<UserSubscription, Long> {
|
||||
List<UserSubscription> findBySubscriber(User subscriber);
|
||||
List<UserSubscription> findByTarget(User target);
|
||||
Optional<UserSubscription> findBySubscriberAndTarget(User subscriber, User target);
|
||||
long countByTarget(User target);
|
||||
long countBySubscriber(User subscriber);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import com.openisle.repository.CommentRepository;
|
||||
import com.openisle.repository.PostRepository;
|
||||
import com.openisle.repository.UserRepository;
|
||||
import com.openisle.service.NotificationService;
|
||||
import com.openisle.service.SubscriptionService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -22,6 +23,7 @@ public class CommentService {
|
||||
private final PostRepository postRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final NotificationService notificationService;
|
||||
private final SubscriptionService subscriptionService;
|
||||
|
||||
public Comment addComment(String username, Long postId, String content) {
|
||||
User author = userRepository.findByUsername(username)
|
||||
@@ -36,6 +38,16 @@ public class CommentService {
|
||||
if (!author.getId().equals(post.getAuthor().getId())) {
|
||||
notificationService.createNotification(post.getAuthor(), NotificationType.COMMENT_REPLY, post, comment, null);
|
||||
}
|
||||
for (User u : subscriptionService.getPostSubscribers(postId)) {
|
||||
if (!u.getId().equals(author.getId())) {
|
||||
notificationService.createNotification(u, NotificationType.POST_UPDATED, post, comment, null);
|
||||
}
|
||||
}
|
||||
for (User u : subscriptionService.getSubscribers(author.getUsername())) {
|
||||
if (!u.getId().equals(author.getId())) {
|
||||
notificationService.createNotification(u, NotificationType.USER_ACTIVITY, post, comment, null);
|
||||
}
|
||||
}
|
||||
return comment;
|
||||
}
|
||||
|
||||
@@ -53,6 +65,21 @@ public class CommentService {
|
||||
if (!author.getId().equals(parent.getAuthor().getId())) {
|
||||
notificationService.createNotification(parent.getAuthor(), NotificationType.COMMENT_REPLY, parent.getPost(), comment, null);
|
||||
}
|
||||
for (User u : subscriptionService.getCommentSubscribers(parentId)) {
|
||||
if (!u.getId().equals(author.getId())) {
|
||||
notificationService.createNotification(u, NotificationType.COMMENT_REPLY, parent.getPost(), comment, null);
|
||||
}
|
||||
}
|
||||
for (User u : subscriptionService.getPostSubscribers(parent.getPost().getId())) {
|
||||
if (!u.getId().equals(author.getId())) {
|
||||
notificationService.createNotification(u, NotificationType.POST_UPDATED, parent.getPost(), comment, null);
|
||||
}
|
||||
}
|
||||
for (User u : subscriptionService.getSubscribers(author.getUsername())) {
|
||||
if (!u.getId().equals(author.getId())) {
|
||||
notificationService.createNotification(u, NotificationType.USER_ACTIVITY, parent.getPost(), comment, null);
|
||||
}
|
||||
}
|
||||
return comment;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.openisle.repository.PostRepository;
|
||||
import com.openisle.repository.UserRepository;
|
||||
import com.openisle.repository.CategoryRepository;
|
||||
import com.openisle.repository.TagRepository;
|
||||
import com.openisle.service.SubscriptionService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -25,6 +26,7 @@ public class PostService {
|
||||
private final TagRepository tagRepository;
|
||||
private final PublishMode publishMode;
|
||||
private final NotificationService notificationService;
|
||||
private final SubscriptionService subscriptionService;
|
||||
|
||||
@org.springframework.beans.factory.annotation.Autowired
|
||||
public PostService(PostRepository postRepository,
|
||||
@@ -32,12 +34,14 @@ public class PostService {
|
||||
CategoryRepository categoryRepository,
|
||||
TagRepository tagRepository,
|
||||
NotificationService notificationService,
|
||||
SubscriptionService subscriptionService,
|
||||
@Value("${app.post.publish-mode:DIRECT}") PublishMode publishMode) {
|
||||
this.postRepository = postRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.categoryRepository = categoryRepository;
|
||||
this.tagRepository = tagRepository;
|
||||
this.notificationService = notificationService;
|
||||
this.subscriptionService = subscriptionService;
|
||||
this.publishMode = publishMode;
|
||||
}
|
||||
|
||||
@@ -64,7 +68,14 @@ public class PostService {
|
||||
post.setCategory(category);
|
||||
post.setTags(new java.util.HashSet<>(tags));
|
||||
post.setStatus(publishMode == PublishMode.REVIEW ? PostStatus.PENDING : PostStatus.PUBLISHED);
|
||||
return postRepository.save(post);
|
||||
post = postRepository.save(post);
|
||||
// notify followers of author
|
||||
for (User u : subscriptionService.getSubscribers(author.getUsername())) {
|
||||
if (!u.getId().equals(author.getId())) {
|
||||
notificationService.createNotification(u, NotificationType.USER_ACTIVITY, post, null, null);
|
||||
}
|
||||
}
|
||||
return post;
|
||||
}
|
||||
|
||||
public Post viewPost(Long id, String viewer) {
|
||||
|
||||
102
src/main/java/com/openisle/service/SubscriptionService.java
Normal file
102
src/main/java/com/openisle/service/SubscriptionService.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package com.openisle.service;
|
||||
|
||||
import com.openisle.model.*;
|
||||
import com.openisle.repository.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SubscriptionService {
|
||||
private final PostSubscriptionRepository postSubRepo;
|
||||
private final CommentSubscriptionRepository commentSubRepo;
|
||||
private final UserSubscriptionRepository userSubRepo;
|
||||
private final UserRepository userRepo;
|
||||
private final PostRepository postRepo;
|
||||
private final CommentRepository commentRepo;
|
||||
|
||||
public void subscribePost(String username, Long postId) {
|
||||
User user = userRepo.findByUsername(username).orElseThrow();
|
||||
Post post = postRepo.findById(postId).orElseThrow();
|
||||
postSubRepo.findByUserAndPost(user, post).orElseGet(() -> {
|
||||
PostSubscription ps = new PostSubscription();
|
||||
ps.setUser(user);
|
||||
ps.setPost(post);
|
||||
return postSubRepo.save(ps);
|
||||
});
|
||||
}
|
||||
|
||||
public void unsubscribePost(String username, Long postId) {
|
||||
User user = userRepo.findByUsername(username).orElseThrow();
|
||||
Post post = postRepo.findById(postId).orElseThrow();
|
||||
postSubRepo.findByUserAndPost(user, post).ifPresent(postSubRepo::delete);
|
||||
}
|
||||
|
||||
public void subscribeComment(String username, Long commentId) {
|
||||
User user = userRepo.findByUsername(username).orElseThrow();
|
||||
Comment comment = commentRepo.findById(commentId).orElseThrow();
|
||||
commentSubRepo.findByUserAndComment(user, comment).orElseGet(() -> {
|
||||
CommentSubscription cs = new CommentSubscription();
|
||||
cs.setUser(user);
|
||||
cs.setComment(comment);
|
||||
return commentSubRepo.save(cs);
|
||||
});
|
||||
}
|
||||
|
||||
public void unsubscribeComment(String username, Long commentId) {
|
||||
User user = userRepo.findByUsername(username).orElseThrow();
|
||||
Comment comment = commentRepo.findById(commentId).orElseThrow();
|
||||
commentSubRepo.findByUserAndComment(user, comment).ifPresent(commentSubRepo::delete);
|
||||
}
|
||||
|
||||
public void subscribeUser(String username, String targetName) {
|
||||
if (username.equals(targetName)) return;
|
||||
User subscriber = userRepo.findByUsername(username).orElseThrow();
|
||||
User target = userRepo.findByUsername(targetName).orElseThrow();
|
||||
userSubRepo.findBySubscriberAndTarget(subscriber, target).orElseGet(() -> {
|
||||
UserSubscription us = new UserSubscription();
|
||||
us.setSubscriber(subscriber);
|
||||
us.setTarget(target);
|
||||
return userSubRepo.save(us);
|
||||
});
|
||||
}
|
||||
|
||||
public void unsubscribeUser(String username, String targetName) {
|
||||
User subscriber = userRepo.findByUsername(username).orElseThrow();
|
||||
User target = userRepo.findByUsername(targetName).orElseThrow();
|
||||
userSubRepo.findBySubscriberAndTarget(subscriber, target).ifPresent(userSubRepo::delete);
|
||||
}
|
||||
|
||||
public List<User> getSubscribedUsers(String username) {
|
||||
User user = userRepo.findByUsername(username).orElseThrow();
|
||||
return userSubRepo.findBySubscriber(user).stream().map(UserSubscription::getTarget).toList();
|
||||
}
|
||||
|
||||
public List<User> getSubscribers(String username) {
|
||||
User user = userRepo.findByUsername(username).orElseThrow();
|
||||
return userSubRepo.findByTarget(user).stream().map(UserSubscription::getSubscriber).toList();
|
||||
}
|
||||
|
||||
public List<User> getPostSubscribers(Long postId) {
|
||||
Post post = postRepo.findById(postId).orElseThrow();
|
||||
return postSubRepo.findByPost(post).stream().map(PostSubscription::getUser).toList();
|
||||
}
|
||||
|
||||
public List<User> getCommentSubscribers(Long commentId) {
|
||||
Comment c = commentRepo.findById(commentId).orElseThrow();
|
||||
return commentSubRepo.findByComment(c).stream().map(CommentSubscription::getUser).toList();
|
||||
}
|
||||
|
||||
|
||||
public long countSubscribers(String username) {
|
||||
User user = userRepo.findByUsername(username).orElseThrow();
|
||||
return userSubRepo.countByTarget(user);
|
||||
}
|
||||
|
||||
public long countSubscribed(String username) {
|
||||
User user = userRepo.findByUsername(username).orElseThrow();
|
||||
return userSubRepo.countBySubscriber(user);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user