Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
ruibaby committed Oct 9, 2023
2 parents f99b5c4 + 6411cef commit 3ab1cfb
Show file tree
Hide file tree
Showing 30 changed files with 1,325 additions and 169 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,19 @@ private static JavaMailSenderImpl createJavaMailSender(EmailSenderConfig emailSe
Properties props = javaMailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
if ("SSL".equals(emailSenderConfig.getEncryption())) {
props.put("mail.smtp.ssl.enable", "true");
}

if ("TLS".equals(emailSenderConfig.getEncryption())) {
props.put("mail.smtp.starttls.enable", "true");
}

if ("NONE".equals(emailSenderConfig.getEncryption())) {
props.put("mail.smtp.ssl.enable", "false");
props.put("mail.smtp.starttls.enable", "false");
}

if (log.isDebugEnabled()) {
props.put("mail.debug", "true");
}
Expand Down Expand Up @@ -154,6 +166,7 @@ static class EmailSenderConfig {
private String password;
private String host;
private Integer port;
private String encryption;

/**
* Gets email display name.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Set;
import lombok.Data;
import lombok.Getter;
import org.springframework.lang.NonNull;

/**
* Notification preference of user.
Expand All @@ -28,6 +29,7 @@ public static class ReasonTypeNotifier extends HashMap<String, NotifierSetting>
* @return if key of reasonType not exists, return default notifier, otherwise return the
* notifiers
*/
@NonNull
public Set<String> getNotifiers(String reasonType) {
var result = this.get(reasonType);
return result == null ? Set.of(DEFAULT_NOTIFIER)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@
public interface UserNotificationPreferenceService {

Mono<UserNotificationPreference> getByUser(String username);

Mono<Void> saveByUser(String username,
UserNotificationPreference userNotificationPreference);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package run.halo.app.notification;

import java.util.HashMap;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.infra.utils.JsonUtils;

Expand Down Expand Up @@ -39,6 +41,28 @@ public Mono<UserNotificationPreference> getByUser(String username) {
.defaultIfEmpty(new UserNotificationPreference());
}

@Override
public Mono<Void> saveByUser(String username,
UserNotificationPreference userNotificationPreference) {
var configName = buildUserPreferenceConfigMapName(username);
return client.fetch(ConfigMap.class, configName)
.switchIfEmpty(Mono.defer(() -> {
var configMap = new ConfigMap();
configMap.setMetadata(new Metadata());
configMap.getMetadata().setName(configName);
return client.create(configMap);
}))
.flatMap(config -> {
if (config.getData() == null) {
config.setData(new HashMap<>());
}
config.getData().put(NOTIFICATION_PREFERENCE,
JsonUtils.objectToJson(userNotificationPreference));
return client.update(config);
})
.then();
}

static String buildUserPreferenceConfigMapName(String username) {
return "user-preferences-" + username;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package run.halo.app.notification.endpoint;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;

import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuples;
import run.halo.app.core.extension.endpoint.CustomEndpoint;
import run.halo.app.core.extension.notification.NotifierDescriptor;
import run.halo.app.core.extension.notification.ReasonType;
import run.halo.app.extension.Comparators;
import run.halo.app.extension.GroupVersion;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.notification.UserNotificationPreference;
import run.halo.app.notification.UserNotificationPreferenceService;

/**
* Endpoint for user notification preferences.
*
* @author guqing
* @since 2.10.0
*/
@Component
@RequiredArgsConstructor
public class UserNotificationPreferencesEndpoint implements CustomEndpoint {

private final ReactiveExtensionClient client;
private final UserNotificationPreferenceService userNotificationPreferenceService;

@Override
public RouterFunction<ServerResponse> endpoint() {
return SpringdocRouteBuilder.route()
.nest(RequestPredicates.path("/userspaces/{username}"), userspaceScopedApis(),
builder -> {
})
.build();
}

Supplier<RouterFunction<ServerResponse>> userspaceScopedApis() {
var tag = "api.notification.halo.run/v1alpha1/Notification";
return () -> SpringdocRouteBuilder.route()
.GET("/notification-preferences", this::listNotificationPreferences,
builder -> builder.operationId("ListUserNotificationPreferences")
.description("List notification preferences for the authenticated user.")
.tag(tag)
.parameter(parameterBuilder()
.in(ParameterIn.PATH)
.name("username")
.description("Username")
.required(true)
)
.response(responseBuilder()
.implementation(ReasonTypeNotifierMatrix.class)
)
)
.POST("/notification-preferences", this::saveNotificationPreferences,
builder -> builder.operationId("SaveUserNotificationPreferences")
.description("Save notification preferences for the authenticated user.")
.tag(tag)
.parameter(parameterBuilder()
.in(ParameterIn.PATH)
.name("username")
.description("Username")
.required(true)
)
.requestBody(requestBodyBuilder()
.implementation(ReasonTypeNotifierCollectionRequest.class)
)
.response(responseBuilder().implementation(ReasonTypeNotifierMatrix.class))
)
.build();
}

private Mono<ServerResponse> saveNotificationPreferences(ServerRequest request) {
var username = request.pathVariable("username");
return request.bodyToMono(ReasonTypeNotifierCollectionRequest.class)
.flatMap(requestBody -> {
var reasonTypNotifiers = requestBody.reasonTypeNotifiers();
return userNotificationPreferenceService.getByUser(username)
.flatMap(preference -> {
var reasonTypeNotifierMap = preference.getReasonTypeNotifier();
reasonTypeNotifierMap.clear();
reasonTypNotifiers.forEach(reasonTypeNotifierRequest -> {
var reasonType = reasonTypeNotifierRequest.getReasonType();
var notifiers = reasonTypeNotifierRequest.getNotifiers();
var notifierSetting = new UserNotificationPreference.NotifierSetting();
notifierSetting.setNotifiers(
notifiers == null ? Set.of() : Set.copyOf(notifiers));
reasonTypeNotifierMap.put(reasonType, notifierSetting);
});
return userNotificationPreferenceService.saveByUser(username, preference);
});
})
.then(Mono.defer(() -> listReasonTypeNotifierMatrix(username)
.flatMap(result -> ServerResponse.ok().bodyValue(result)))
);
}

private Mono<ServerResponse> listNotificationPreferences(ServerRequest request) {
var username = request.pathVariable("username");
return listReasonTypeNotifierMatrix(username)
.flatMap(matrix -> ServerResponse.ok().bodyValue(matrix));
}

@NonNull
private static <T> Map<String, Integer> toNameIndexMap(List<T> collection,
Function<T, String> nameGetter) {
Map<String, Integer> indexMap = new HashMap<>();
for (int i = 0; i < collection.size(); i++) {
var item = collection.get(i);
indexMap.put(nameGetter.apply(item), i);
}
return indexMap;
}

Mono<ReasonTypeNotifierMatrix> listReasonTypeNotifierMatrix(String username) {
return client.list(ReasonType.class, null, Comparators.defaultComparator())
.map(reasonType -> new ReasonTypeInfo(reasonType.getMetadata().getName(),
reasonType.getSpec().getDisplayName(),
reasonType.getSpec().getDescription())
)
.collectList()
.flatMap(reasonTypes -> client.list(NotifierDescriptor.class, null,
Comparators.defaultComparator())
.map(notifier -> new NotifierInfo(notifier.getMetadata().getName(),
notifier.getSpec().getDisplayName(),
notifier.getSpec().getDescription())
)
.collectList()
.map(notifiers -> {
var matrix = new ReasonTypeNotifierMatrix()
.setReasonTypes(reasonTypes)
.setNotifiers(notifiers)
.setStateMatrix(new boolean[reasonTypes.size()][notifiers.size()]);
return Tuples.of(reasonTypes, matrix);
})
)
.flatMap(tuple2 -> {
var reasonTypes = tuple2.getT1();
var matrix = tuple2.getT2();

var reasonTypeIndexMap = toNameIndexMap(reasonTypes, ReasonTypeInfo::name);
var notifierIndexMap = toNameIndexMap(matrix.getNotifiers(), NotifierInfo::name);
var stateMatrix = matrix.getStateMatrix();

return userNotificationPreferenceService.getByUser(username)
.doOnNext(preference -> {
var reasonTypeNotifierMap = preference.getReasonTypeNotifier();
for (ReasonTypeInfo reasonType : reasonTypes) {
var reasonTypeIndex = reasonTypeIndexMap.get(reasonType.name());
var notifierNames =
reasonTypeNotifierMap.getNotifiers(reasonType.name());
for (String notifierName : notifierNames) {
var notifierIndex = notifierIndexMap.get(notifierName);
stateMatrix[reasonTypeIndex][notifierIndex] = true;
}
}
})
.thenReturn(matrix);
});
}

@Data
@Accessors(chain = true)
static class ReasonTypeNotifierMatrix {
private List<ReasonTypeInfo> reasonTypes;
private List<NotifierInfo> notifiers;
private boolean[][] stateMatrix;
}

record ReasonTypeInfo(String name, String displayName, String description) {
}

record NotifierInfo(String name, String displayName, String description) {
}

record ReasonTypeNotifierCollectionRequest(
@Schema(requiredMode = REQUIRED) List<ReasonTypeNotifierRequest> reasonTypeNotifiers) {
}

@Data
static class ReasonTypeNotifierRequest {
private String reasonType;
private List<String> notifiers;
}

@Override
public GroupVersion groupVersion() {
return GroupVersion.parseAPIVersion("api.notification.halo.run/v1alpha1");
}
}
12 changes: 12 additions & 0 deletions application/src/main/resources/extensions/notification.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ spec:
label: "端口号"
name: port
validation: required
- $formkit: select
if: "$enable"
label: "加密方式"
name: encryption
value: "SSL"
options:
- label: "SSL"
value: "SSL"
- label: "TLS"
value: "TLS"
- label: "不加密"
value: "NONE"
---
apiVersion: notification.halo.run/v1alpha1
kind: ReasonType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,6 @@ rules:
- apiGroups: [ "api.notification.halo.run" ]
resources: [ "notifiers/receiver-config" ]
verbs: [ "get", "update" ]
- apiGroups: [ "api.notification.halo.run" ]
resources: [ "notification-preferences" ]
verbs: [ "create", "list" ]
Loading

0 comments on commit 3ab1cfb

Please sign in to comment.