Skip to content

Commit

Permalink
feat: add the notification mechanism implementation (#4527)
Browse files Browse the repository at this point in the history
#### What type of PR is this?
/kind feature
/milestone 2.10.x
/area core

#### What this PR does / why we need it:
新增消息和通知机制的实现

how to test it?
1. 执行以下命令配置发件服务
```shell
curl -u admin:admin -X POST 'http://localhost:8090/apis/api.console.halo.run/v1alpha1/notifiers/default-email-notifier/senderConfig' \
--header 'Content-Type: application/json' \
--data-raw '{
    "displayName": "Halo Team",
    "username": "{发件使用的邮箱}",
    "password": "{发件邮箱密码}",
    "host": "smtp.exmail.qq.com",
    "port": "587"
}'
```
2. 评论文章或页面可以收到通知
3. 文章/页面作者是评论者不发送新评论通知,回复者是评论作者不发送回复通知

#### Which issue(s) this PR fixes:
Fixes #4045

#### Does this PR introduce a user-facing change?
```release-note
新增消息和通知机制的实现
```
  • Loading branch information
guqing authored Sep 28, 2023
1 parent 45787e1 commit 9454f44
Show file tree
Hide file tree
Showing 95 changed files with 6,574 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@
* @author guqing
* @since 2.0.0
*/
public interface CommentSubject<T extends Extension> extends ExtensionPoint {
public interface CommentSubject<T extends Extension> extends ExtensionPoint {

Mono<T> get(String name);

default Mono<SubjectDisplay> getSubjectDisplay(String name) {
return Mono.empty();
}

boolean supports(Ref ref);

record SubjectDisplay(String title, String url, String kindName) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package run.halo.app.core.extension.notification;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import io.swagger.v3.oas.annotations.media.Schema;
import java.time.Instant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import run.halo.app.extension.AbstractExtension;
import run.halo.app.extension.GVK;

/**
* <p>{@link Notification} is a custom extension that used to store notification information for
* inner use, it's on-site notification.</p>
*
* <p>Supports the following operations:</p>
* <ul>
* <li>Marked as read: {@link NotificationSpec#setUnread(boolean)}</li>
* <li>Get the last read time: {@link NotificationSpec#getLastReadAt()}</li>
* <li>Filter by recipient: {@link NotificationSpec#getRecipient()}</li>
* </ul>
*
* @author guqing
* @see Reason
* @see ReasonType
* @since 2.10.0
*/
@Data
@EqualsAndHashCode(callSuper = true)
@GVK(group = "notification.halo.run", version = "v1alpha1", kind = "Notification", plural =
"notifications", singular = "notification")
public class Notification extends AbstractExtension {

@Schema
private NotificationSpec spec;

@Data
public static class NotificationSpec {
@Schema(requiredMode = REQUIRED, minLength = 1, description = "The name of user")
private String recipient;

@Schema(requiredMode = REQUIRED, minLength = 1, description = "The name of reason")
private String reason;

@Schema(requiredMode = REQUIRED, minLength = 1)
private String title;

@Schema(requiredMode = REQUIRED)
private String rawContent;

@Schema(requiredMode = REQUIRED)
private String htmlContent;

private boolean unread;

private Instant lastReadAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package run.halo.app.core.extension.notification;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import run.halo.app.extension.AbstractExtension;
import run.halo.app.extension.GVK;

/**
* <p>{@link NotificationTemplate} is a custom extension that defines a notification template.</p>
* <p>It describes the notification template's name, description, and the template content.</p>
* <p>{@link Spec#getReasonSelector()} is used to select the template by reasonType and language,
* if multiple templates are matched, the best match will be selected. This is useful when you
* want to override the default template.</p>
*
* @author guqing
* @since 2.10.0
*/
@Data
@EqualsAndHashCode(callSuper = true)
@GVK(group = "notification.halo.run", version = "v1alpha1", kind = "NotificationTemplate",
plural = "notificationtemplates", singular = "notificationtemplate")
public class NotificationTemplate extends AbstractExtension {

@Schema
private Spec spec;

@Data
@Schema(name = "NotificationTemplateSpec")
public static class Spec {
@Schema
private ReasonSelector reasonSelector;

@Schema
private Template template;
}

@Data
@Schema(name = "TemplateContent")
public static class Template {
@Schema(requiredMode = REQUIRED, minLength = 1)
private String title;

private String htmlBody;

private String rawBody;
}

@Data
public static class ReasonSelector {
@Schema(requiredMode = REQUIRED, minLength = 1)
private String reasonType;

@Schema(requiredMode = REQUIRED, minLength = 1, defaultValue = "default")
private String language;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package run.halo.app.core.extension.notification;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import run.halo.app.extension.AbstractExtension;
import run.halo.app.extension.GVK;

/**
* <p>{@link NotifierDescriptor} is a custom extension that defines a notifier.</p>
* <p>It describes the notifier's name, description, and the extension name of the notifier to
* let the user know what the notifier is and what it can do in the UI and also let the
* {@code NotificationCenter} know how to load the notifier and prepare the notifier's settings.</p>
*
* @author guqing
* @since 2.10.0
*/
@Data
@EqualsAndHashCode(callSuper = true)
@GVK(group = "notification.halo.run", version = "v1alpha1", kind = "NotifierDescriptor",
plural = "notifierDescriptors", singular = "notifierDescriptor")
public class NotifierDescriptor extends AbstractExtension {

@Schema
private Spec spec;

@Data
@Schema(name = "NotifierDescriptorSpec")
public static class Spec {
@Schema(requiredMode = REQUIRED, minLength = 1)
private String displayName;

private String description;

@Schema(requiredMode = REQUIRED, minLength = 1)
private String notifierExtName;

private SettingRef senderSettingRef;

private SettingRef receiverSettingRef;
}

@Data
@Schema(name = "NotifierSettingRef")
public static class SettingRef {
@Schema(requiredMode = REQUIRED)
private String name;

@Schema(requiredMode = REQUIRED)
private String group;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package run.halo.app.core.extension.notification;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import run.halo.app.extension.AbstractExtension;
import run.halo.app.extension.GVK;
import run.halo.app.notification.ReasonAttributes;

/**
* <p>{@link Reason} is a custom extension that defines a reason for a notification, It represents
* an instance of a {@link ReasonType}.</p>
* <p>It can be understood as an event that triggers a notification.</p>
*
* @author guqing
* @since 2.10.0
*/
@Data
@EqualsAndHashCode(callSuper = true)
@GVK(group = "notification.halo.run", version = "v1alpha1", kind = "Reason", plural =
"reasons", singular = "reason")
public class Reason extends AbstractExtension {

@Schema
private Spec spec;

@Data
@Schema(name = "ReasonSpec")
public static class Spec {
@Schema(requiredMode = REQUIRED)
private String reasonType;

@Schema(requiredMode = REQUIRED)
private Subject subject;

@Schema(requiredMode = REQUIRED)
private String author;

@Schema(implementation = ReasonAttributes.class, requiredMode = NOT_REQUIRED,
description = "Attributes used to transfer data")
private ReasonAttributes attributes;
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "ReasonSubject")
public static class Subject {
@Schema(requiredMode = REQUIRED)
private String apiVersion;

@Schema(requiredMode = REQUIRED)
private String kind;

@Schema(requiredMode = REQUIRED)
private String name;

@Schema(requiredMode = REQUIRED)
private String title;

private String url;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package run.halo.app.core.extension.notification;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import run.halo.app.extension.AbstractExtension;
import run.halo.app.extension.GVK;

/**
* <p>{@link ReasonType} is a custom extension that defines a type of reason.</p>
* <p>One {@link ReasonType} can have multiple {@link Reason}s to notify.</p>
*
* @author guqing
* @see NotificationTemplate
* @see Reason
* @since 2.10.0
*/
@Data
@EqualsAndHashCode(callSuper = true)
@GVK(group = "notification.halo.run", version = "v1alpha1", kind = "ReasonType",
plural = "reasontypes", singular = "reasontype")
public class ReasonType extends AbstractExtension {
public static final String LOCALIZED_RESOURCE_NAME_ANNO =
"notification.halo.run/localized-resource-name";

@Schema
private Spec spec;

@Data
@Schema(name = "ReasonTypeSpec")
public static class Spec {

@Schema(requiredMode = REQUIRED, minLength = 1)
private String displayName;

@Schema(requiredMode = REQUIRED, minLength = 1)
private String description;

private List<ReasonProperty> properties;
}

@Data
public static class ReasonProperty {
@Schema(requiredMode = REQUIRED, minLength = 1)
private String name;

@Schema(requiredMode = REQUIRED, minLength = 1)
private String type;

private String description;

@Schema(defaultValue = "false")
private boolean optional;
}
}
Loading

0 comments on commit 9454f44

Please sign in to comment.