fix: incorrect unsubscribe link for email notification (#4695)

#### What type of PR is this?
/kind bug
/area core
/milestone 2.10.x

#### What this PR does / why we need it:
修复邮件通知中取消订阅链接不正确的问题

#### Does this PR introduce a user-facing change?
```release-note
修复邮件通知中取消订阅链接不正确的问题
```
pull/4703/head
guqing 2023-10-10 12:12:32 +08:00 committed by GitHub
parent d97c1e6e3d
commit 1ff1b4f2a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 33 additions and 34 deletions

View File

@ -5,7 +5,6 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
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 @@ public class DefaultNotificationCenter implements NotificationCenter {
.subscription(subscription)
.reasonType(notificationContent.reasonType())
.notificationTitle(notificationContent.title())
.reasonAttributes(notificationContent.reasonAttributes())
.notificationRawBody(defaultString(notificationContent.rawBody()))
.notificationHtmlBody(defaultString(notificationContent.htmlBody()))
.build()
@ -180,12 +180,13 @@ public class DefaultNotificationCenter implements NotificationCenter {
});
}
@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<NotificationContext> notificationContextFrom(NotificationElement element) {
@ -198,7 +199,7 @@ public class DefaultNotificationCenter implements NotificationCenter {
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 @@ public class DefaultNotificationCenter implements NotificationCenter {
});
}
@Builder
record NotificationContent(String title, String rawBody, String htmlBody, ReasonType reasonType,
Map<String, Object> reasonProperties) {
}
Mono<NotificationContent> inferenceTemplate(Reason reason, Subscription subscription,
Locale locale) {
var reasonTypeName = reason.getSpec().getReasonType();
@ -252,7 +248,7 @@ public class DefaultNotificationCenter implements NotificationCenter {
.flatMap(reasonType -> notificationTemplateSelector.select(reasonTypeName, locale)
.flatMap(template -> {
final var templateContent = template.getSpec().getTemplate();
Map<String, Object> model = toReasonAttributes(reason);
var model = toReasonAttributes(reason);
var identity = UserIdentity.of(subscriber.getName());
var subscriberInfo = new HashMap<>();
if (identity.isAnonymous()) {
@ -266,7 +262,7 @@ public class DefaultNotificationCenter implements NotificationCenter {
var builder = NotificationContent.builder()
.reasonType(reasonType)
.reasonProperties(model);
.reasonAttributes(model);
var titleMono = notificationTemplateRender
.render(templateContent.getTitle(), model)
@ -285,17 +281,22 @@ public class DefaultNotificationCenter implements NotificationCenter {
);
}
@Builder
record NotificationContent(String title, String rawBody, String htmlBody, ReasonType reasonType,
ReasonAttributes reasonAttributes) {
}
String getUnsubscribeUrl(Subscription subscription) {
return subscriptionRouter.getUnsubscribeUrl(subscription);
}
private Map<String, Object> toReasonAttributes(Reason reason) {
Map<String, Object> 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<ReasonType> getReasonType(String reasonTypeName) {

View File

@ -145,12 +145,11 @@ public class EmailNotifier implements ReactiveNotifier {
Mono<String> appendHtmlBodyFooter(ReasonAttributes attributes) {
return notificationTemplateRender.render("""
---
<div class="footer" style="font-size: 12px; color: #666">
<div class="footer" style="font-size: 12px; color: #666;">
<a th:href="${site.url}" th:text="${site.title}"></a>
<p class="unsubscribe">
&mdash;<br />
<a th:href="|${site.url}/console|"></a>
<a th:href="|${site.url}/console/users/-/notifications|"></a>
<a th:href="${unsubscribeUrl}"></a>
</p>

View File

@ -1,9 +1,11 @@
package run.halo.app.notification.endpoint;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import java.net.URI;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
@ -82,7 +84,8 @@ public class SubscriptionRouter {
public String getUnsubscribeUrl(Subscription subscription) {
var name = subscription.getMetadata().getName();
var token = subscription.getSpec().getUnsubscribeToken();
return UriComponentsBuilder.fromUri(externalUrlSupplier.get())
var externalUrl = defaultIfNull(externalUrlSupplier.getRaw(), URI.create("/"));
return UriComponentsBuilder.fromUriString(externalUrl.toString())
.path(UNSUBSCRIBE_PATTERN)
.queryParam("token", token)
.build(name)

View File

@ -3,6 +3,7 @@ package run.halo.app.notification.endpoint;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import java.net.MalformedURLException;
import java.net.URI;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -11,7 +12,6 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import run.halo.app.core.extension.notification.Subscription;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.infra.ExternalUrlSupplier;
/**
@ -23,9 +23,6 @@ import run.halo.app.infra.ExternalUrlSupplier;
@ExtendWith(MockitoExtension.class)
class SubscriptionRouterTest {
@Mock
private ReactiveExtensionClient client;
@Mock
private ExternalUrlSupplier externalUrlSupplier;
@ -33,8 +30,8 @@ class SubscriptionRouterTest {
SubscriptionRouter subscriptionRouter;
@Test
void getUnsubscribeUrlTest() {
when(externalUrlSupplier.get()).thenReturn(URI.create("https://halo.run"));
void getUnsubscribeUrlTest() throws MalformedURLException {
when(externalUrlSupplier.getRaw()).thenReturn(URI.create("https://halo.run").toURL());
var subscription = new Subscription();
subscription.setMetadata(new Metadata());
subscription.getMetadata().setName("fake-subscription");
@ -46,5 +43,4 @@ class SubscriptionRouterTest {
+ "/subscriptions/fake-subscription/unsubscribe"
+ "?token=fake-unsubscribe-token");
}
}
}