Skip to content

Commit

Permalink
feat: subscription support for expression-based subscribing
Browse files Browse the repository at this point in the history
  • Loading branch information
guqing committed Apr 18, 2024
1 parent 817963c commit 649b5d6
Show file tree
Hide file tree
Showing 21 changed files with 651 additions and 400 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ public static class InterestReason {
+ "interested in")
private String reasonType;

@Schema(requiredMode = REQUIRED, description = "The subject name of reason type to be"
@Schema(requiredMode = NOT_REQUIRED, description = "The subject name of reason type to be"
+ " interested in")
private ReasonSubject subject;

@Schema(requiredMode = NOT_REQUIRED, description = "The expression to be interested in")
private String expression;
}

@Data
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package run.halo.app.content.comment;

import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static run.halo.app.content.comment.ReplyNotificationSubscriptionHelper.identityFrom;

import com.fasterxml.jackson.core.type.TypeReference;
import java.util.Map;
Expand Down Expand Up @@ -29,7 +30,6 @@
import run.halo.app.infra.ExternalLinkProcessor;
import run.halo.app.infra.utils.JsonUtils;
import run.halo.app.notification.NotificationReasonEmitter;
import run.halo.app.notification.UserIdentity;
import run.halo.app.plugin.ExtensionComponentsFinder;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;

Expand Down Expand Up @@ -114,6 +114,7 @@ public void publishReasonBy(Comment comment) {
builder -> {
var attributes = CommentOnPostReasonData.builder()
.postName(subjectRef.getName())
.postOwner(post.getSpec().getOwner())
.postTitle(post.getSpec().getTitle())
.postUrl(postUrl)
.commenter(owner.getDisplayName())
Expand Down Expand Up @@ -144,8 +145,9 @@ boolean isPostOwner(Post post, Comment.CommentOwner commentOwner) {
}

@Builder
record CommentOnPostReasonData(String postName, String postTitle, String postUrl,
String commenter, String content, String commentName) {
record CommentOnPostReasonData(String postName, String postOwner, String postTitle,
String postUrl, String commenter, String content,
String commentName) {
}
}

Expand Down Expand Up @@ -180,6 +182,7 @@ public void publishReasonBy(Comment comment) {
builder -> {
var attributes = CommentOnPageReasonData.builder()
.pageName(subjectRef.getName())
.pageOwner(singlePage.getSpec().getOwner())
.pageTitle(singlePage.getSpec().getTitle())
.pageUrl(pageUrl)
.commenter(defaultIfBlank(owner.getDisplayName(), owner.getName()))
Expand Down Expand Up @@ -210,8 +213,9 @@ boolean isPageOwner(SinglePage page, Comment.CommentOwner commentOwner) {
}

@Builder
record CommentOnPageReasonData(String pageName, String pageTitle, String pageUrl,
String commenter, String content, String commentName) {
record CommentOnPageReasonData(String pageName, String pageOwner, String pageTitle,
String pageUrl, String commenter, String content,
String commentName) {
}
}

Expand All @@ -224,13 +228,6 @@ public static <T> Map<String, Object> toAttributeMap(T data) {
}
}

static UserIdentity identityFrom(Comment.CommentOwner owner) {
if (Comment.CommentOwner.KIND_EMAIL.equals(owner.getKind())) {
return UserIdentity.anonymousWithEmail(owner.getName());
}
return UserIdentity.of(owner.getName());
}

@Component
@RequiredArgsConstructor
static class NewReplyReasonPublisher {
Expand Down Expand Up @@ -272,14 +269,20 @@ public void publishReasonBy(Reply reply, Comment comment) {
.orElse(null);
var replyOwner = reply.getSpec().getOwner();

var repliedOwner = quoteReplyOptional
.map(quoteReply -> quoteReply.getSpec().getOwner())
.orElseGet(() -> comment.getSpec().getOwner());

var reasonAttributesBuilder = NewReplyReasonData.builder()
.commentContent(comment.getSpec().getContent())
.isQuoteReply(isQuoteReply)
.quoteContent(quoteReplyContent)
.commentName(comment.getMetadata().getName())
.replier(defaultIfBlank(replyOwner.getDisplayName(), replyOwner.getName()))
.content(reply.getSpec().getContent())
.replyName(reply.getMetadata().getName());
.replyName(reply.getMetadata().getName())
.replyOwner(identityFrom(replyOwner).name())
.repliedOwner(identityFrom(repliedOwner).name());

getCommentSubjectDisplay(comment.getSpec().getSubjectRef())
.ifPresent(subject -> {
Expand Down Expand Up @@ -337,7 +340,7 @@ record NewReplyReasonData(String commentContent, String commentSubjectTitle,
String commentSubjectUrl, boolean isQuoteReply,
String quoteContent,
String commentName, String replier, String content,
String replyName) {
String replyName, String replyOwner, String repliedOwner) {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import run.halo.app.core.extension.content.Reply;
import run.halo.app.core.extension.notification.Subscription;
import run.halo.app.notification.NotificationCenter;
import run.halo.app.notification.SubscriberEmailResolver;
import run.halo.app.notification.UserIdentity;

/**
* Reply notification subscription helper.
Expand All @@ -22,21 +22,14 @@
public class ReplyNotificationSubscriptionHelper {

private final NotificationCenter notificationCenter;
private final SubscriberEmailResolver subscriberEmailResolver;

/**
* Subscribe new reply reason for comment.
*
* @param comment comment
*/
public void subscribeNewReplyReasonForComment(Comment comment) {
var reasonSubject = Subscription.ReasonSubject.builder()
.apiVersion(comment.getApiVersion())
.kind(comment.getKind())
.name(comment.getMetadata().getName())
.build();
subscribeReply(reasonSubject,
Identity.fromCommentOwner(comment.getSpec().getOwner()));
subscribeReply(identityFrom(comment.getSpec().getOwner()));
}

/**
Expand All @@ -45,50 +38,36 @@ public void subscribeNewReplyReasonForComment(Comment comment) {
* @param reply reply
*/
public void subscribeNewReplyReasonForReply(Reply reply) {
var reasonSubject = Subscription.ReasonSubject.builder()
.apiVersion(reply.getApiVersion())
.kind(reply.getKind())
.name(reply.getMetadata().getName())
.build();
var subjectOwner = reply.getSpec().getOwner();
subscribeReply(reasonSubject,
Identity.fromCommentOwner(subjectOwner));
subscribeReply(identityFrom(subjectOwner));
}

void subscribeReply(Subscription.ReasonSubject reasonSubject,
Identity identity) {
void subscribeReply(UserIdentity identity) {
var subscriber = createSubscriber(identity);
if (subscriber == null) {
return;
}
var interestReason = new Subscription.InterestReason();
interestReason.setReasonType(NotificationReasonConst.SOMEONE_REPLIED_TO_YOU);
interestReason.setSubject(reasonSubject);
interestReason.setExpression("props.repliedOwner == '%s'".formatted(identity.name()));
notificationCenter.subscribe(subscriber, interestReason).block();
}

@Nullable
private Subscription.Subscriber createSubscriber(Identity author) {
private Subscription.Subscriber createSubscriber(UserIdentity author) {
if (StringUtils.isBlank(author.name())) {
return null;
}

Subscription.Subscriber subscriber;
if (author.isEmail()) {
subscriber = subscriberEmailResolver.ofEmail(author.name());
} else {
subscriber = new Subscription.Subscriber();
subscriber.setName(author.name());
}
Subscription.Subscriber subscriber = new Subscription.Subscriber();
subscriber.setName(author.name());
return subscriber;
}

record Identity(String name, boolean isEmail) {
public static Identity fromCommentOwner(Comment.CommentOwner commentOwner) {
if (Comment.CommentOwner.KIND_EMAIL.equals(commentOwner.getKind())) {
return new Identity(commentOwner.getName(), true);
}
return new Identity(commentOwner.getName(), false);
public static UserIdentity identityFrom(Comment.CommentOwner owner) {
if (Comment.CommentOwner.KIND_EMAIL.equals(owner.getKind())) {
return UserIdentity.anonymousWithEmail(owner.getName());
}
return UserIdentity.of(owner.getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,8 @@ void subscribeNewCommentNotification(Post post) {

var interestReason = new Subscription.InterestReason();
interestReason.setReasonType(NotificationReasonConst.NEW_COMMENT_ON_POST);
interestReason.setSubject(Subscription.ReasonSubject.builder()
.apiVersion(post.getApiVersion())
.kind(post.getKind())
.name(post.getMetadata().getName())
.build());
interestReason.setExpression(
"props.postOwner == '%s'".formatted(post.getSpec().getOwner()));
notificationCenter.subscribe(subscriber, interestReason).block();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,8 @@ void subscribeNewCommentNotification(SinglePage page) {

var interestReason = new Subscription.InterestReason();
interestReason.setReasonType(NotificationReasonConst.NEW_COMMENT_ON_PAGE);
interestReason.setSubject(Subscription.ReasonSubject.builder()
.apiVersion(page.getApiVersion())
.kind(page.getKind())
.name(page.getMetadata().getName())
.build());
interestReason.setExpression(
"props.pageOwner == '%s'".formatted(page.getSpec().getOwner()));
notificationCenter.subscribe(subscriber, interestReason).block();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,23 @@
import org.apache.commons.lang3.StringUtils;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Component;
import run.halo.app.content.NotificationReasonConst;
import run.halo.app.core.extension.Role;
import run.halo.app.core.extension.RoleBinding;
import run.halo.app.core.extension.User;
import run.halo.app.core.extension.UserConnection;
import run.halo.app.core.extension.attachment.Attachment;
import run.halo.app.core.extension.content.Comment;
import run.halo.app.core.extension.content.Post;
import run.halo.app.core.extension.content.Reply;
import run.halo.app.core.extension.content.SinglePage;
import run.halo.app.core.extension.notification.Subscription;
import run.halo.app.core.extension.service.AttachmentService;
import run.halo.app.core.extension.service.RoleService;
import run.halo.app.extension.Extension;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.GroupKind;
import run.halo.app.extension.GroupVersionKind;
import run.halo.app.extension.MetadataUtil;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
Expand All @@ -32,6 +40,7 @@
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.infra.utils.JsonUtils;
import run.halo.app.infra.utils.PathUtils;
import run.halo.app.notification.NotificationCenter;

@Slf4j
@Component
Expand All @@ -42,6 +51,7 @@ public class UserReconciler implements Reconciler<Request> {
private final ExternalUrlSupplier externalUrlSupplier;
private final RoleService roleService;
private final AttachmentService attachmentService;
private final NotificationCenter notificationCenter;
private final RetryTemplate retryTemplate = RetryTemplate.builder()
.maxAttempts(20)
.fixedBackoff(300)
Expand All @@ -60,6 +70,9 @@ public Result reconcile(Request request) {
ensureRoleNamesAnno(request.name());
updatePermalink(request.name());
handleAvatar(request.name());

// cleanup history data and remove this line in the future
removeInternalSubscription(request.name());
});
return new Result(false, null);
}
Expand Down Expand Up @@ -104,6 +117,40 @@ private void handleAvatar(String name) {
});
}

private void removeInternalSubscription(String username) {
var subscriber = new Subscription.Subscriber();
subscriber.setName(username);

var commentOnPost =
createInterestReason(NotificationReasonConst.NEW_COMMENT_ON_POST, Post.class);
notificationCenter.unsubscribe(subscriber, commentOnPost).block();

var commentOnPage =
createInterestReason(NotificationReasonConst.NEW_COMMENT_ON_PAGE, SinglePage.class);
notificationCenter.unsubscribe(subscriber, commentOnPage).block();

var replyOnComment =
createInterestReason(NotificationReasonConst.SOMEONE_REPLIED_TO_YOU, Comment.class);
notificationCenter.unsubscribe(subscriber, replyOnComment).block();

var replyOnReply =
createInterestReason(NotificationReasonConst.SOMEONE_REPLIED_TO_YOU, Reply.class);
notificationCenter.unsubscribe(subscriber, replyOnReply).block();
}

<E extends Extension> Subscription.InterestReason createInterestReason(String type,
Class<E> extensionClass) {
var interestReason = new Subscription.InterestReason();
interestReason.setReasonType(type);

var reasonSubject = new Subscription.ReasonSubject();
var gvk = GroupVersionKind.fromExtension(extensionClass);
reasonSubject.setKind(gvk.kind());
reasonSubject.setApiVersion(gvk.groupVersion().toString());
interestReason.setSubject(reasonSubject);
return interestReason;
}

private void ensureRoleNamesAnno(String name) {
client.fetch(User.class, name).ifPresent(user -> {
Map<String, String> annotations = MetadataUtil.nullSafeAnnotations(user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,10 @@ public void onApplicationEvent(@NonNull ApplicationContextInitializedEvent event
indexSpecs.add(new IndexSpec()
.setName("spec.reason.subject")
.setIndexFunc(simpleAttribute(Subscription.class,
subscription -> subscription.getSpec().getReason().getSubject().toString()))
subscription -> {
var subject = subscription.getSpec().getReason().getSubject();
return subject == null ? null : subject.toString();
}))
);
indexSpecs.add(new IndexSpec()
.setName("spec.subscriber")
Expand Down
Loading

0 comments on commit 649b5d6

Please sign in to comment.