add a button for testing push notification settings

This commit is contained in:
Athou
2026-02-19 22:13:11 +01:00
parent 6861fe303b
commit de7e4e9c69
35 changed files with 367 additions and 60 deletions

View File

@@ -40,9 +40,9 @@ public class FeedUpdateNotifier {
}
UserSettings settings = unitOfWork.call(() -> userSettingsDAO.findByUser(sub.getUser()));
if (settings != null && settings.getPushNotificationType() != null) {
if (settings != null && settings.getPushNotifications() != null && settings.getPushNotifications().getType() != null) {
for (FeedEntry entry : entries) {
pushNotificationService.notify(settings, sub, entry);
pushNotificationService.notify(settings.getPushNotifications(), sub, entry);
}
}
}

View File

@@ -3,6 +3,8 @@ package com.commafeed.backend.model;
import java.sql.Types;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
@@ -144,22 +146,6 @@ public class UserSettings extends AbstractModel {
private boolean unreadCountFavicon;
private boolean disablePullToRefresh;
@Enumerated(EnumType.STRING)
@Column(name = "push_notification_type", length = 16)
private PushNotificationType pushNotificationType;
@Column(name = "push_notification_server_url", length = 1024)
private String pushNotificationServerUrl;
@Column(name = "push_notification_user_id", length = 512)
private String pushNotificationUserId;
@Column(name = "push_notification_user_secret", length = 512)
private String pushNotificationUserSecret;
@Column(name = "push_notification_topic", length = 256)
private String pushNotificationTopic;
private boolean email;
private boolean gmail;
private boolean facebook;
@@ -169,4 +155,28 @@ public class UserSettings extends AbstractModel {
private boolean instapaper;
private boolean buffer;
@Embedded
private PushNotificationUserSettings pushNotifications = new PushNotificationUserSettings();
@Embeddable
@Getter
@Setter
public static class PushNotificationUserSettings {
@Enumerated(EnumType.STRING)
@Column(name = "push_notification_type", length = 16)
private PushNotificationType type;
@Column(name = "push_notification_server_url", length = 1024)
private String serverUrl;
@Column(name = "push_notification_user_id", length = 512)
private String userId;
@Column(name = "push_notification_user_secret", length = 512)
private String userSecret;
@Column(name = "push_notification_topic", length = 256)
private String topic;
}
}

View File

@@ -25,7 +25,7 @@ import com.commafeed.backend.HttpClientFactory;
import com.commafeed.backend.Urls;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.UserSettings;
import com.commafeed.backend.model.UserSettings.PushNotificationUserSettings;
import io.vertx.core.json.JsonObject;
import lombok.extern.slf4j.Slf4j;
@@ -44,12 +44,12 @@ public class PushNotificationService {
this.config = config;
}
public void notify(UserSettings settings, FeedSubscription subscription, FeedEntry entry) {
if (!config.pushNotifications().enabled() || settings.getPushNotificationType() == null) {
public void notify(PushNotificationUserSettings settings, FeedSubscription subscription, FeedEntry entry) {
if (!config.pushNotifications().enabled() || settings.getType() == null) {
return;
}
log.debug("sending {} push notification for entry {} in feed {}", settings.getPushNotificationType(), entry.getId(),
log.debug("sending {} push notification for entry {} in feed {}", settings.getType(), entry.getId(),
subscription.getFeed().getId());
String entryTitle = entry.getContent() != null ? entry.getContent().getTitle() : null;
String entryUrl = entry.getUrl();
@@ -60,11 +60,11 @@ public class PushNotificationService {
}
try {
switch (settings.getPushNotificationType()) {
switch (settings.getType()) {
case NTFY -> sendNtfy(settings, feedTitle, entryTitle, entryUrl);
case GOTIFY -> sendGotify(settings, feedTitle, entryTitle, entryUrl);
case PUSHOVER -> sendPushover(settings, feedTitle, entryTitle, entryUrl);
default -> throw new IllegalStateException("unsupported notification type: " + settings.getPushNotificationType());
default -> throw new IllegalStateException("unsupported notification type: " + settings.getType());
}
} catch (IOException e) {
throw new PushNotificationException("Failed to send external notification", e);
@@ -73,9 +73,9 @@ public class PushNotificationService {
meter.mark();
}
private void sendNtfy(UserSettings settings, String feedTitle, String entryTitle, String entryUrl) throws IOException {
String serverUrl = Urls.removeTrailingSlash(settings.getPushNotificationServerUrl());
String topic = settings.getPushNotificationTopic();
private void sendNtfy(PushNotificationUserSettings settings, String feedTitle, String entryTitle, String entryUrl) throws IOException {
String serverUrl = Urls.removeTrailingSlash(settings.getServerUrl());
String topic = settings.getTopic();
if (StringUtils.isBlank(serverUrl) || StringUtils.isBlank(topic)) {
log.warn("ntfy notification skipped: missing server URL or topic");
@@ -91,8 +91,8 @@ public class PushNotificationService {
request.addHeader("Click", entryUrl);
}
if (StringUtils.isNotBlank(settings.getPushNotificationUserSecret())) {
request.addHeader("Authorization", "Bearer " + settings.getPushNotificationUserSecret());
if (StringUtils.isNotBlank(settings.getUserSecret())) {
request.addHeader("Authorization", "Bearer " + settings.getUserSecret());
}
httpClient.execute(request, response -> {
@@ -103,9 +103,10 @@ public class PushNotificationService {
});
}
private void sendGotify(UserSettings settings, String feedTitle, String entryTitle, String entryUrl) throws IOException {
String serverUrl = Urls.removeTrailingSlash(settings.getPushNotificationServerUrl());
String token = settings.getPushNotificationUserSecret();
private void sendGotify(PushNotificationUserSettings settings, String feedTitle, String entryTitle, String entryUrl)
throws IOException {
String serverUrl = Urls.removeTrailingSlash(settings.getServerUrl());
String token = settings.getUserSecret();
if (StringUtils.isBlank(serverUrl) || StringUtils.isBlank(token)) {
log.warn("gotify notification skipped: missing server URL or token");
@@ -134,9 +135,10 @@ public class PushNotificationService {
});
}
private void sendPushover(UserSettings settings, String feedTitle, String entryTitle, String entryUrl) throws IOException {
String token = settings.getPushNotificationUserSecret();
String userKey = settings.getPushNotificationUserId();
private void sendPushover(PushNotificationUserSettings settings, String feedTitle, String entryTitle, String entryUrl)
throws IOException {
String token = settings.getUserSecret();
String userKey = settings.getUserId();
if (StringUtils.isBlank(token) || StringUtils.isBlank(userKey)) {
log.warn("pushover notification skipped: missing token or user key");

View File

@@ -38,19 +38,26 @@ import com.commafeed.backend.Urls;
import com.commafeed.backend.dao.UserDAO;
import com.commafeed.backend.dao.UserRoleDAO;
import com.commafeed.backend.dao.UserSettingsDAO;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryContent;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole;
import com.commafeed.backend.model.UserRole.Role;
import com.commafeed.backend.model.UserSettings;
import com.commafeed.backend.model.UserSettings.IconDisplayMode;
import com.commafeed.backend.model.UserSettings.PushNotificationUserSettings;
import com.commafeed.backend.model.UserSettings.ReadingMode;
import com.commafeed.backend.model.UserSettings.ReadingOrder;
import com.commafeed.backend.model.UserSettings.ScrollMode;
import com.commafeed.backend.service.MailService;
import com.commafeed.backend.service.PasswordEncryptionService;
import com.commafeed.backend.service.PushNotificationService;
import com.commafeed.backend.service.UserService;
import com.commafeed.backend.service.db.DatabaseStartupService;
import com.commafeed.frontend.model.Settings;
import com.commafeed.frontend.model.Settings.PushNotificationSettings;
import com.commafeed.frontend.model.UserModel;
import com.commafeed.frontend.model.request.InitialSetupRequest;
import com.commafeed.frontend.model.request.PasswordResetConfirmationRequest;
@@ -84,6 +91,7 @@ public class UserREST {
private final MailService mailService;
private final CommaFeedConfiguration config;
private final UriInfo uri;
private final PushNotificationService pushNotificationService;
@Path("/settings")
@GET
@@ -126,11 +134,13 @@ public class UserREST {
s.setDisablePullToRefresh(settings.isDisablePullToRefresh());
s.setPrimaryColor(settings.getPrimaryColor());
s.getPushNotificationSettings().setType(settings.getPushNotificationType());
s.getPushNotificationSettings().setServerUrl(settings.getPushNotificationServerUrl());
s.getPushNotificationSettings().setUserId(settings.getPushNotificationUserId());
s.getPushNotificationSettings().setUserSecret(settings.getPushNotificationUserSecret());
s.getPushNotificationSettings().setTopic(settings.getPushNotificationTopic());
if (settings.getPushNotifications() != null) {
s.getPushNotificationSettings().setType(settings.getPushNotifications().getType());
s.getPushNotificationSettings().setServerUrl(settings.getPushNotifications().getServerUrl());
s.getPushNotificationSettings().setUserId(settings.getPushNotifications().getUserId());
s.getPushNotificationSettings().setUserSecret(settings.getPushNotifications().getUserSecret());
s.getPushNotificationSettings().setTopic(settings.getPushNotifications().getTopic());
}
} else {
s.setReadingMode(ReadingMode.UNREAD);
s.setReadingOrder(ReadingOrder.DESC);
@@ -196,11 +206,13 @@ public class UserREST {
s.setDisablePullToRefresh(settings.isDisablePullToRefresh());
s.setPrimaryColor(settings.getPrimaryColor());
s.setPushNotificationType(settings.getPushNotificationSettings().getType());
s.setPushNotificationServerUrl(settings.getPushNotificationSettings().getServerUrl());
s.setPushNotificationUserId(settings.getPushNotificationSettings().getUserId());
s.setPushNotificationUserSecret(settings.getPushNotificationSettings().getUserSecret());
s.setPushNotificationTopic(settings.getPushNotificationSettings().getTopic());
PushNotificationUserSettings ps = new PushNotificationUserSettings();
ps.setType(settings.getPushNotificationSettings().getType());
ps.setServerUrl(settings.getPushNotificationSettings().getServerUrl());
ps.setUserId(settings.getPushNotificationSettings().getUserId());
ps.setUserSecret(settings.getPushNotificationSettings().getUserSecret());
ps.setTopic(settings.getPushNotificationSettings().getTopic());
s.setPushNotifications(ps);
s.setEmail(settings.getSharingSettings().isEmail());
s.setGmail(settings.getSharingSettings().isGmail());
@@ -216,6 +228,37 @@ public class UserREST {
}
@Path("/pushNotificationTest")
@POST
@Transactional
@Operation(summary = "Send a test push notification")
public Response sendTestPushNotification(@Parameter(required = true) PushNotificationSettings settings) {
FeedSubscription sub = new FeedSubscription();
sub.setTitle("CommaFeed Test Feed");
sub.setFeed(new Feed());
FeedEntryContent content = new FeedEntryContent();
content.setTitle("Test Entry");
FeedEntry entry = new FeedEntry();
entry.setContent(content);
PushNotificationUserSettings pushSettings = new PushNotificationUserSettings();
pushSettings.setType(settings.getType());
pushSettings.setServerUrl(settings.getServerUrl());
pushSettings.setUserId(settings.getUserId());
pushSettings.setUserSecret(settings.getUserSecret());
pushSettings.setTopic(settings.getTopic());
try {
pushNotificationService.notify(pushSettings, sub, entry);
} catch (Exception e) {
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getCause().getMessage()).type(MediaType.TEXT_PLAIN).build();
}
return Response.ok().build();
}
@Path("/profile")
@GET
@Transactional

View File

@@ -22,8 +22,8 @@ import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryContent;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.UserSettings;
import com.commafeed.backend.model.UserSettings.PushNotificationType;
import com.commafeed.backend.model.UserSettings.PushNotificationUserSettings;
@ExtendWith(MockServerExtension.class)
class PushNotificationServiceTest {
@@ -31,7 +31,7 @@ class PushNotificationServiceTest {
private MockServerClient mockServerClient;
private PushNotificationService pushNotificationService;
private CommaFeedConfiguration config;
private UserSettings userSettings;
private PushNotificationUserSettings userSettings;
private FeedSubscription subscription;
private FeedEntry entry;
@@ -51,7 +51,7 @@ class PushNotificationServiceTest {
this.pushNotificationService = new PushNotificationService(httpClientFactory, metricRegistry, config);
this.userSettings = new UserSettings();
this.userSettings = new PushNotificationUserSettings();
this.subscription = createSubscription("Test Feed");
this.entry = createEntry("Test Entry", "http://example.com/entry");
@@ -59,10 +59,10 @@ class PushNotificationServiceTest {
@Test
void testNtfyNotification() {
userSettings.setPushNotificationType(PushNotificationType.NTFY);
userSettings.setPushNotificationServerUrl("http://localhost:" + mockServerClient.getPort());
userSettings.setPushNotificationTopic("test-topic");
userSettings.setPushNotificationUserSecret("test-secret");
userSettings.setType(PushNotificationType.NTFY);
userSettings.setServerUrl("http://localhost:" + mockServerClient.getPort());
userSettings.setTopic("test-topic");
userSettings.setUserSecret("test-secret");
mockServerClient.when(HttpRequest.request()
.withMethod("POST")
@@ -77,9 +77,9 @@ class PushNotificationServiceTest {
@Test
void testGotifyNotification() {
userSettings.setPushNotificationType(PushNotificationType.GOTIFY);
userSettings.setPushNotificationServerUrl("http://localhost:" + mockServerClient.getPort());
userSettings.setPushNotificationUserSecret("gotify-token");
userSettings.setType(PushNotificationType.GOTIFY);
userSettings.setServerUrl("http://localhost:" + mockServerClient.getPort());
userSettings.setUserSecret("gotify-token");
mockServerClient.when(HttpRequest.request()
.withMethod("POST")
@@ -107,9 +107,9 @@ class PushNotificationServiceTest {
@Test
void testPushNotificationDisabled() {
Mockito.when(config.pushNotifications().enabled()).thenReturn(false);
userSettings.setPushNotificationType(PushNotificationType.NTFY);
userSettings.setPushNotificationServerUrl("http://localhost:" + mockServerClient.getPort());
userSettings.setPushNotificationTopic("test-topic");
userSettings.setType(PushNotificationType.NTFY);
userSettings.setServerUrl("http://localhost:" + mockServerClient.getPort());
userSettings.setTopic("test-topic");
Assertions.assertDoesNotThrow(() -> pushNotificationService.notify(userSettings, subscription, entry));
mockServerClient.verifyZeroInteractions();