diff --git a/.gitignore b/.gitignore index 1f6cf1bae4..db223874fd 100755 --- a/.gitignore +++ b/.gitignore @@ -74,4 +74,5 @@ application-local.properties !application/src/test/resources/themes/*.zip !application/src/main/resources/themes/*.zip application/src/main/resources/console/ +application/src/main/resources/uc/ application/src/main/resources/presets/ diff --git a/README.md b/README.md index 9936433890..c2041ffc6f 100755 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ ## 快速开始 ```bash -docker run -it -d --name halo -p 8090:8090 -v ~/.halo2:/root/.halo2 halohub/halo:2.9 +docker run -it -d --name halo -p 8090:8090 -v ~/.halo2:/root/.halo2 halohub/halo:2.10 ``` 以上仅作为体验使用,详细部署文档请查阅: diff --git a/application/build.gradle b/application/build.gradle index 97c0e93025..71baa80605 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.springframework.boot' version '3.1.4' + id 'org.springframework.boot' version '3.1.5' id 'io.spring.dependency-management' version '1.1.0' id "com.gorylenko.gradle-git-properties" version "2.3.2" id "checkstyle" @@ -78,7 +78,7 @@ tasks.register('downloadPluginPresets', Download) { 'https://github.com/halo-dev/plugin-search-widget/releases/download/v1.2.0/plugin-search-widget-1.2.0.jar', 'https://github.com/halo-dev/plugin-sitemap/releases/download/v1.1.1/plugin-sitemap-1.1.1.jar', 'https://github.com/halo-dev/plugin-feed/releases/download/v1.2.0/plugin-feed-1.2.0.jar', - 'https://github.com/halo-dev/plugin-app-store/releases/download/v1.0.0-beta.1/plugin-app-store-1.0.0-beta.1.jar' + 'https://github.com/halo-dev/plugin-app-store/releases/download/v1.0.0-beta.2/plugin-app-store-1.0.0-beta.2.jar' ]) dest 'src/main/resources/presets/plugins' } diff --git a/application/src/main/java/run/halo/app/config/WebFluxConfig.java b/application/src/main/java/run/halo/app/config/WebFluxConfig.java index 0fb0cea992..7bb8f6193a 100644 --- a/application/src/main/java/run/halo/app/config/WebFluxConfig.java +++ b/application/src/main/java/run/halo/app/config/WebFluxConfig.java @@ -29,14 +29,13 @@ import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.function.BodyInserters; 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 org.springframework.web.reactive.resource.EncodedResourceResolver; import org.springframework.web.reactive.resource.PathResourceResolver; import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; import org.springframework.web.reactive.result.view.ViewResolver; import reactor.core.publisher.Mono; -import run.halo.app.console.ConsoleProxyFilter; +import run.halo.app.console.ProxyFilter; import run.halo.app.console.WebSocketRequestPredicate; import run.halo.app.core.extension.endpoint.CustomEndpoint; import run.halo.app.core.extension.endpoint.CustomEndpointsBuilder; @@ -104,11 +103,21 @@ RouterFunction consoleIndexRedirection() { .and(path("/console/**").and(path("/console/assets/**").negate())) .and(accept(MediaType.TEXT_HTML)) .and(new WebSocketRequestPredicate().negate()); - return route(consolePredicate, this::serveConsoleIndex); + return route(consolePredicate, + request -> this.serveIndex(haloProp.getConsole().getLocation() + "index.html")); } - private Mono serveConsoleIndex(ServerRequest request) { - var indexLocation = haloProp.getConsole().getLocation() + "index.html"; + @Bean + RouterFunction ucIndexRedirect() { + var consolePredicate = method(HttpMethod.GET) + .and(path("/uc/**").and(path("/uc/assets/**").negate())) + .and(accept(MediaType.TEXT_HTML)) + .and(new WebSocketRequestPredicate().negate()); + return route(consolePredicate, + request -> this.serveIndex(haloProp.getUc().getLocation() + "index.html")); + } + + private Mono serveIndex(String indexLocation) { var indexResource = applicationContext.getResource(indexLocation); try { return ServerResponse.ok() @@ -142,6 +151,15 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { .addResolver(new EncodedResourceResolver()) .addResolver(new PathResourceResolver()); + // For uc assets + registry.addResourceHandler("/uc/assets/**") + .addResourceLocations(haloProp.getUc().getLocation() + "assets/") + .setCacheControl(cacheControl) + .setUseLastModified(useLastModified) + .resourceChain(true) + .addResolver(new EncodedResourceResolver()) + .addResolver(new PathResourceResolver()); + // Additional resource mappings var staticResources = haloProp.getAttachment().getResourceMappings(); staticResources.forEach(staticResource -> { @@ -172,7 +190,14 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { @ConditionalOnProperty(name = "halo.console.proxy.enabled", havingValue = "true") @Bean - ConsoleProxyFilter consoleProxyFilter() { - return new ConsoleProxyFilter(haloProp); + ProxyFilter consoleProxyFilter() { + return new ProxyFilter("/console/**", haloProp.getConsole().getProxy()); + } + + + @ConditionalOnProperty(name = "halo.uc.proxy.enabled", havingValue = "true") + @Bean + ProxyFilter ucProxyFilter() { + return new ProxyFilter("/uc/**", haloProp.getUc().getProxy()); } } diff --git a/application/src/main/java/run/halo/app/console/ConsoleProxyFilter.java b/application/src/main/java/run/halo/app/console/ProxyFilter.java similarity index 87% rename from application/src/main/java/run/halo/app/console/ConsoleProxyFilter.java rename to application/src/main/java/run/halo/app/console/ProxyFilter.java index 512a79b031..854d810ab1 100644 --- a/application/src/main/java/run/halo/app/console/ConsoleProxyFilter.java +++ b/application/src/main/java/run/halo/app/console/ProxyFilter.java @@ -13,11 +13,10 @@ import org.springframework.web.server.WebFilterChain; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; -import run.halo.app.infra.properties.ConsoleProperties.ProxyProperties; -import run.halo.app.infra.properties.HaloProperties; +import run.halo.app.infra.properties.ProxyProperties; @Slf4j -public class ConsoleProxyFilter implements WebFilter { +public class ProxyFilter implements WebFilter { private final ProxyProperties proxyProperties; @@ -25,14 +24,15 @@ public class ConsoleProxyFilter implements WebFilter { private final WebClient webClient; - public ConsoleProxyFilter(HaloProperties haloProperties) { - this.proxyProperties = haloProperties.getConsole().getProxy(); - var consoleMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/console/**"); + public ProxyFilter(String pattern, ProxyProperties proxyProperties) { + this.proxyProperties = proxyProperties; + var consoleMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, pattern); consoleMatcher = new AndServerWebExchangeMatcher(consoleMatcher, new NegatedServerWebExchangeMatcher(new WebSocketServerWebExchangeMatcher())); this.consoleMatcher = consoleMatcher; this.webClient = WebClient.create(proxyProperties.getEndpoint().toString()); - log.info("Initialized ConsoleProxyFilter to proxy console"); + log.debug("Initialized ProxyFilter to proxy {} to endpoint {}", pattern, + proxyProperties.getEndpoint()); } @Override diff --git a/application/src/main/java/run/halo/app/content/comment/ReplyNotificationSubscriptionHelper.java b/application/src/main/java/run/halo/app/content/comment/ReplyNotificationSubscriptionHelper.java index 0a086330e8..38879160b7 100644 --- a/application/src/main/java/run/halo/app/content/comment/ReplyNotificationSubscriptionHelper.java +++ b/application/src/main/java/run/halo/app/content/comment/ReplyNotificationSubscriptionHelper.java @@ -1,6 +1,8 @@ package run.halo.app.content.comment; +import io.micrometer.common.util.StringUtils; import lombok.RequiredArgsConstructor; +import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import run.halo.app.content.NotificationReasonConst; import run.halo.app.core.extension.content.Comment; @@ -56,13 +58,21 @@ public void subscribeNewReplyReasonForReply(Reply reply) { void subscribeReply(Subscription.ReasonSubject reasonSubject, Identity identity) { var subscriber = createSubscriber(identity); + if (subscriber == null) { + return; + } var interestReason = new Subscription.InterestReason(); interestReason.setReasonType(NotificationReasonConst.SOMEONE_REPLIED_TO_YOU); interestReason.setSubject(reasonSubject); notificationCenter.subscribe(subscriber, interestReason).block(); } + @Nullable private Subscription.Subscriber createSubscriber(Identity author) { + if (StringUtils.isBlank(author.name())) { + return null; + } + Subscription.Subscriber subscriber; if (author.isEmail()) { subscriber = subscriberEmailResolver.ofEmail(author.name()); diff --git a/application/src/main/java/run/halo/app/core/extension/endpoint/PluginEndpoint.java b/application/src/main/java/run/halo/app/core/extension/endpoint/PluginEndpoint.java index 0a3ca14c33..d6d015c1ff 100644 --- a/application/src/main/java/run/halo/app/core/extension/endpoint/PluginEndpoint.java +++ b/application/src/main/java/run/halo/app/core/extension/endpoint/PluginEndpoint.java @@ -40,7 +40,9 @@ import java.util.function.Predicate; import java.util.function.Supplier; import lombok.AllArgsConstructor; +import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.pf4j.PluginState; import org.reactivestreams.Publisher; import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; import org.springframework.beans.factory.DisposableBean; @@ -201,6 +203,27 @@ public RouterFunction endpoint() { .response(responseBuilder() .implementation(Plugin.class)) ) + .PUT("plugins/{name}/plugin-state", this::changePluginRunningState, + builder -> builder.operationId("ChangePluginRunningState") + .description("Change the running state of a plugin by name.") + .tag(tag) + .parameter(parameterBuilder() + .name("name") + .in(ParameterIn.PATH) + .required(true) + .implementation(String.class) + ) + .requestBody(requestBodyBuilder() + .required(true) + .content(contentBuilder() + .mediaType(MediaType.APPLICATION_JSON_VALUE) + .schema(schemaBuilder() + .implementation(RunningStateRequest.class)) + ) + ) + .response(responseBuilder() + .implementation(Plugin.class)) + ) .GET("plugins", this::list, builder -> { builder.operationId("ListPlugins") .tag(tag) @@ -255,6 +278,56 @@ public RouterFunction endpoint() { .build(); } + Mono changePluginRunningState(ServerRequest request) { + final var name = request.pathVariable("name"); + return request.bodyToMono(RunningStateRequest.class) + .flatMap(runningState -> { + final var enable = runningState.isEnable(); + return client.get(Plugin.class, name) + .flatMap(plugin -> { + plugin.getSpec().setEnabled(enable); + return client.update(plugin); + }) + .flatMap(plugin -> { + if (runningState.isAsync()) { + return Mono.just(plugin); + } + return waitForPluginToMeetExpectedState(name, p -> { + // when enabled = true,excepted phase = started || failed + // when enabled = false,excepted phase = !started + var phase = p.statusNonNull().getPhase(); + if (enable) { + return PluginState.STARTED.equals(phase) + || PluginState.FAILED.equals(phase); + } + return !PluginState.STARTED.equals(phase); + }); + }); + }) + .flatMap(plugin -> ServerResponse.ok().bodyValue(plugin)); + } + + Mono waitForPluginToMeetExpectedState(String name, Predicate predicate) { + return Mono.defer(() -> client.get(Plugin.class, name) + .map(plugin -> { + if (predicate.test(plugin)) { + return plugin; + } + throw new IllegalStateException("Plugin " + name + " is not in expected state"); + }) + ) + .retryWhen(Retry.backoff(10, Duration.ofMillis(100)) + .filter(IllegalStateException.class::isInstance) + ); + } + + @Data + @Schema(name = "PluginRunningStateRequest") + static class RunningStateRequest { + private boolean enable; + private boolean async; + } + private Mono fetchJsBundle(ServerRequest request) { Optional versionOption = request.queryParam("v"); return versionOption.map(s -> diff --git a/application/src/main/java/run/halo/app/core/extension/endpoint/PostEndpoint.java b/application/src/main/java/run/halo/app/core/extension/endpoint/PostEndpoint.java index c15247bd6d..b888db5422 100644 --- a/application/src/main/java/run/halo/app/core/extension/endpoint/PostEndpoint.java +++ b/application/src/main/java/run/halo/app/core/extension/endpoint/PostEndpoint.java @@ -8,7 +8,7 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn; import java.time.Duration; import java.util.Objects; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springdoc.core.fn.builders.schema.Builder; import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; @@ -42,9 +42,10 @@ */ @Slf4j @Component -@AllArgsConstructor +@RequiredArgsConstructor public class PostEndpoint implements CustomEndpoint { + private int maxAttemptsWaitForPublish = 10; private final PostService postService; private final ReactiveExtensionClient client; @@ -243,7 +244,7 @@ private Mono awaitPostPublished(String postName) { }) .switchIfEmpty(Mono.error( () -> new RetryException("Retry to check post publish status")))) - .retryWhen(Retry.fixedDelay(10, Duration.ofMillis(200)) + .retryWhen(Retry.backoff(maxAttemptsWaitForPublish, Duration.ofMillis(100)) .filter(t -> t instanceof RetryException)); } @@ -278,4 +279,11 @@ Mono listPost(ServerRequest request) { return postService.listPost(postQuery) .flatMap(listedPosts -> ServerResponse.ok().bodyValue(listedPosts)); } + + /** + * Convenient for testing, to avoid waiting too long for post published when testing. + */ + public void setMaxAttemptsWaitForPublish(int maxAttempts) { + this.maxAttemptsWaitForPublish = maxAttempts; + } } diff --git a/application/src/main/java/run/halo/app/core/extension/reconciler/PostReconciler.java b/application/src/main/java/run/halo/app/core/extension/reconciler/PostReconciler.java index 68e8585354..fafdd67a2c 100644 --- a/application/src/main/java/run/halo/app/core/extension/reconciler/PostReconciler.java +++ b/application/src/main/java/run/halo/app/core/extension/reconciler/PostReconciler.java @@ -13,6 +13,7 @@ import java.util.Optional; import java.util.Set; import lombok.AllArgsConstructor; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.jsoup.Jsoup; import org.springframework.context.ApplicationEvent; @@ -102,12 +103,20 @@ public Result reconcile(Request request) { post.getMetadata().setAnnotations(annotations); } + if (!annotations.containsKey(Post.PUBLISHED_LABEL)) { + labels.put(Post.PUBLISHED_LABEL, BooleanUtils.FALSE); + } + var status = post.getStatus(); if (status == null) { status = new Post.PostStatus(); post.setStatus(status); } + if (post.isPublished() && post.getSpec().getPublishTime() == null) { + post.getSpec().setPublishTime(Instant.now()); + } + // calculate the sha256sum var configSha256sum = Hashing.sha256().hashString(post.getSpec().toString(), UTF_8) .toString(); diff --git a/application/src/main/java/run/halo/app/core/extension/reconciler/SinglePageReconciler.java b/application/src/main/java/run/halo/app/core/extension/reconciler/SinglePageReconciler.java index 441d95dae6..ad46213b5e 100644 --- a/application/src/main/java/run/halo/app/core/extension/reconciler/SinglePageReconciler.java +++ b/application/src/main/java/run/halo/app/core/extension/reconciler/SinglePageReconciler.java @@ -112,6 +112,10 @@ void subscribeNewCommentNotification(SinglePage page) { private void reconcileSpec(String name) { client.fetch(SinglePage.class, name).ifPresent(page -> { + if (page.isPublished() && page.getSpec().getPublishTime() == null) { + page.getSpec().setPublishTime(Instant.now()); + } + // un-publish if necessary if (page.isPublished() && Objects.equals(false, page.getSpec().getPublish())) { unPublish(name); diff --git a/application/src/main/java/run/halo/app/core/extension/service/DefaultRoleService.java b/application/src/main/java/run/halo/app/core/extension/service/DefaultRoleService.java index 567685b4bb..a9f98f0a35 100644 --- a/application/src/main/java/run/halo/app/core/extension/service/DefaultRoleService.java +++ b/application/src/main/java/run/halo/app/core/extension/service/DefaultRoleService.java @@ -54,7 +54,7 @@ public Mono contains(Collection source, Collection cand if (source.contains(SuperAdminInitializer.SUPER_ROLE_NAME)) { return Mono.just(true); } - return listDependencies(new HashSet<>(source), shouldFilterHidden(true)) + return listDependencies(new HashSet<>(source), shouldFilterHidden(false)) .map(role -> role.getMetadata().getName()) .collect(Collectors.toSet()) .map(roleNames -> roleNames.containsAll(candidates)); diff --git a/application/src/main/java/run/halo/app/core/extension/service/impl/PluginServiceImpl.java b/application/src/main/java/run/halo/app/core/extension/service/impl/PluginServiceImpl.java index ab029aa69d..7db4fb7ca6 100644 --- a/application/src/main/java/run/halo/app/core/extension/service/impl/PluginServiceImpl.java +++ b/application/src/main/java/run/halo/app/core/extension/service/impl/PluginServiceImpl.java @@ -26,7 +26,6 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.stereotype.Component; import org.springframework.util.Assert; -import org.springframework.util.StreamUtils; import org.springframework.web.server.ServerWebInputException; import reactor.core.Exceptions; import reactor.core.publisher.Flux; @@ -179,9 +178,15 @@ public Flux uglifyCssBundle() { return BundleResourceUtils.getJsBundleResource(pluginManager, pluginName, BundleResourceUtils.CSS_BUNDLE); }) - .flatMap(resource -> DataBufferUtils.read(resource, - DefaultDataBufferFactory.sharedInstance, StreamUtils.BUFFER_SIZE) - ); + .flatMap(resource -> { + try { + return DataBufferUtils.read(resource, DefaultDataBufferFactory.sharedInstance, + (int) resource.contentLength()); + } catch (IOException e) { + log.error("Failed to read plugin css bundle resource", e); + return Flux.empty(); + } + }); } @Override diff --git a/application/src/main/java/run/halo/app/infra/properties/ConsoleProperties.java b/application/src/main/java/run/halo/app/infra/properties/ConsoleProperties.java index dda8be00dc..8bf245a459 100644 --- a/application/src/main/java/run/halo/app/infra/properties/ConsoleProperties.java +++ b/application/src/main/java/run/halo/app/infra/properties/ConsoleProperties.java @@ -1,7 +1,6 @@ package run.halo.app.infra.properties; import jakarta.validation.Valid; -import java.net.URI; import lombok.Data; @Data @@ -12,17 +11,4 @@ public class ConsoleProperties { @Valid private ProxyProperties proxy = new ProxyProperties(); - @Data - public static class ProxyProperties { - - /** - * Console endpoint in development environment to be proxied. e.g.: http://localhost:8090/ - */ - private URI endpoint; - - /** - * Indicates if the proxy behaviour is enabled. Default is false - */ - private boolean enabled = false; - } } diff --git a/application/src/main/java/run/halo/app/infra/properties/HaloProperties.java b/application/src/main/java/run/halo/app/infra/properties/HaloProperties.java index 5a29cf2316..b3b01db4e6 100644 --- a/application/src/main/java/run/halo/app/infra/properties/HaloProperties.java +++ b/application/src/main/java/run/halo/app/infra/properties/HaloProperties.java @@ -54,6 +54,9 @@ public class HaloProperties implements Validator { @Valid private final ConsoleProperties console = new ConsoleProperties(); + @Valid + private final UcProperties uc = new UcProperties(); + @Valid private final ThemeProperties theme = new ThemeProperties(); diff --git a/application/src/main/java/run/halo/app/infra/properties/ProxyProperties.java b/application/src/main/java/run/halo/app/infra/properties/ProxyProperties.java new file mode 100644 index 0000000000..275180de28 --- /dev/null +++ b/application/src/main/java/run/halo/app/infra/properties/ProxyProperties.java @@ -0,0 +1,18 @@ +package run.halo.app.infra.properties; + +import java.net.URI; +import lombok.Data; + +@Data +public class ProxyProperties { + + /** + * Console endpoint in development environment to be proxied. e.g.: http://localhost:8090/ + */ + private URI endpoint; + + /** + * Indicates if the proxy behaviour is enabled. Default is false + */ + private boolean enabled = false; +} diff --git a/application/src/main/java/run/halo/app/infra/properties/ThemeProperties.java b/application/src/main/java/run/halo/app/infra/properties/ThemeProperties.java index b4decf3f34..7f5f11c911 100644 --- a/application/src/main/java/run/halo/app/infra/properties/ThemeProperties.java +++ b/application/src/main/java/run/halo/app/infra/properties/ThemeProperties.java @@ -9,6 +9,11 @@ public class ThemeProperties { @Valid private final Initializer initializer = new Initializer(); + /** + * Indicates whether the generator meta needs to be disabled. + */ + private boolean generatorMetaDisabled; + @Data public static class Initializer { diff --git a/application/src/main/java/run/halo/app/infra/properties/UcProperties.java b/application/src/main/java/run/halo/app/infra/properties/UcProperties.java new file mode 100644 index 0000000000..46fd7c2ad0 --- /dev/null +++ b/application/src/main/java/run/halo/app/infra/properties/UcProperties.java @@ -0,0 +1,14 @@ +package run.halo.app.infra.properties; + +import jakarta.validation.Valid; +import lombok.Data; + +@Data +public class UcProperties { + + private String location = "classpath:/uc/"; + + @Valid + private ProxyProperties proxy = new ProxyProperties(); + +} diff --git a/application/src/main/java/run/halo/app/notification/DefaultNotificationCenter.java b/application/src/main/java/run/halo/app/notification/DefaultNotificationCenter.java index d1fd5702ad..47bad079d3 100644 --- a/application/src/main/java/run/halo/app/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/run/halo/app/notification/DefaultNotificationCenter.java @@ -5,7 +5,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Locale; -import java.util.Map; import java.util.Optional; import java.util.function.BiPredicate; import java.util.function.Function; @@ -135,6 +134,7 @@ Mono prepareNotificationElement(Subscription subscription, .subscription(subscription) .reasonType(notificationContent.reasonType()) .notificationTitle(notificationContent.title()) + .reasonAttributes(notificationContent.reasonAttributes()) .notificationRawBody(defaultString(notificationContent.rawBody())) .notificationHtmlBody(defaultString(notificationContent.htmlBody())) .build() @@ -180,12 +180,13 @@ Mono createNotification(NotificationElement notificationElement) { }); } - @Builder - record NotificationElement(ReasonType reasonType, Reason reason, - Subscription subscription, NotifierDescriptor descriptor, - String notificationTitle, - String notificationRawBody, - String notificationHtmlBody) { + private ReasonAttributes toReasonAttributes(Reason reason) { + var model = new ReasonAttributes(); + var attributes = reason.getSpec().getAttributes(); + if (attributes != null) { + model.putAll(attributes); + } + return model; } Mono notificationContextFrom(NotificationElement element) { @@ -198,7 +199,7 @@ Mono notificationContextFrom(NotificationElement element) { messagePayload.setTitle(element.notificationTitle()); messagePayload.setRawBody(element.notificationRawBody()); messagePayload.setHtmlBody(element.notificationHtmlBody()); - messagePayload.setAttributes(reason.getSpec().getAttributes()); + messagePayload.setAttributes(element.reasonAttributes()); var message = new NotificationContext.Message(); message.setRecipient(subscription.getSpec().getSubscriber().getName()); @@ -239,11 +240,6 @@ Mono notificationContextFrom(NotificationElement element) { }); } - @Builder - record NotificationContent(String title, String rawBody, String htmlBody, ReasonType reasonType, - Map reasonProperties) { - } - Mono inferenceTemplate(Reason reason, Subscription subscription, Locale locale) { var reasonTypeName = reason.getSpec().getReasonType(); @@ -252,7 +248,7 @@ Mono inferenceTemplate(Reason reason, Subscription subscrip .flatMap(reasonType -> notificationTemplateSelector.select(reasonTypeName, locale) .flatMap(template -> { final var templateContent = template.getSpec().getTemplate(); - Map model = toReasonAttributes(reason); + var model = toReasonAttributes(reason); var identity = UserIdentity.of(subscriber.getName()); var subscriberInfo = new HashMap<>(); if (identity.isAnonymous()) { @@ -266,7 +262,7 @@ Mono inferenceTemplate(Reason reason, Subscription subscrip var builder = NotificationContent.builder() .reasonType(reasonType) - .reasonProperties(model); + .reasonAttributes(model); var titleMono = notificationTemplateRender .render(templateContent.getTitle(), model) @@ -285,17 +281,22 @@ Mono inferenceTemplate(Reason reason, Subscription subscrip ); } + @Builder + record NotificationContent(String title, String rawBody, String htmlBody, ReasonType reasonType, + ReasonAttributes reasonAttributes) { + } + String getUnsubscribeUrl(Subscription subscription) { return subscriptionRouter.getUnsubscribeUrl(subscription); } - private Map toReasonAttributes(Reason reason) { - Map model = new HashMap<>(); - var attributes = reason.getSpec().getAttributes(); - if (attributes != null) { - model.putAll(attributes); - } - return model; + @Builder + record NotificationElement(ReasonType reasonType, Reason reason, + Subscription subscription, NotifierDescriptor descriptor, + String notificationTitle, + String notificationRawBody, + String notificationHtmlBody, + ReasonAttributes reasonAttributes) { } Mono getReasonType(String reasonTypeName) { diff --git a/application/src/main/java/run/halo/app/notification/EmailNotifier.java b/application/src/main/java/run/halo/app/notification/EmailNotifier.java index 50139df413..e819d1d77c 100644 --- a/application/src/main/java/run/halo/app/notification/EmailNotifier.java +++ b/application/src/main/java/run/halo/app/notification/EmailNotifier.java @@ -43,6 +43,11 @@ public Mono notify(NotificationContext context) { var emailSenderConfig = JsonUtils.DEFAULT_JSON_MAPPER.convertValue(senderConfig, EmailSenderConfig.class); + if (!emailSenderConfig.isEnable()) { + log.debug("Email notifier is disabled, skip sending email."); + return Mono.empty(); + } + JavaMailSenderImpl javaMailSender = getJavaMailSender(emailSenderConfig); String recipient = context.getMessage().getRecipient(); @@ -51,6 +56,11 @@ public Mono notify(NotificationContext context) { var payload = context.getMessage().getPayload(); return subscriberEmailResolver.resolve(subscriber) .flatMap(toEmail -> { + if (StringUtils.isBlank(toEmail)) { + log.debug("Cannot resolve email for subscriber: [{}], skip sending email.", + subscriber); + return Mono.empty(); + } var htmlMono = appendHtmlBodyFooter(payload.getAttributes()) .doOnNext(footer -> { if (StringUtils.isNotBlank(payload.getHtmlBody())) { @@ -97,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"); } @@ -123,12 +145,11 @@ Mono appendRawBodyFooter(ReasonAttributes attributes) { Mono appendHtmlBodyFooter(ReasonAttributes attributes) { return notificationTemplateRender.render(""" - --- -