diff --git a/src/client/src/app/components/user-settings/user-settings.component.ts b/src/client/src/app/components/user-settings/user-settings.component.ts
index 7e8930c..14d1f16 100644
--- a/src/client/src/app/components/user-settings/user-settings.component.ts
+++ b/src/client/src/app/components/user-settings/user-settings.component.ts
@@ -12,7 +12,7 @@ import { FloatLabelModule } from 'primeng/floatlabel';
import { InputSwitchModule } from 'primeng/inputswitch';
import { MessagesModule } from 'primeng/messages';
import { ProgressSpinnerModule } from 'primeng/progressspinner';
-import { filter, map, Subject } from 'rxjs';
+import { filter, map, Subject, timer } from 'rxjs';
import { SavedFadingMessageComponent } from '../+common/saved-fading-message.component';
import { isActionBusy, hasActionFailed } from '../../+state/action-state';
@@ -132,6 +132,7 @@ export class UserSettingsComponent {
);
protected readonly isUpdatingPushSubscription = signal(false);
+ protected readonly sendPush = signal(false);
protected readonly isLoading = computed(() => isActionBusy(this._loadActionState()));
protected readonly isUpdating = computed(() => isActionBusy(this._updateActionState()));
protected readonly hasFailed = computed(() => hasActionFailed(this._loadActionState()));
@@ -142,6 +143,7 @@ export class UserSettingsComponent {
notifyEventStart: this.getSaveState('notifyOnEventStart'),
notifyEventUpdate: this.getSaveState('notifyOnEventUpdated'),
notifyTimeslotStart: this.getSaveState('notifyOnTimeslotStart'),
+ testSend: toObservable(this.sendPush),
};
constructor() {
@@ -166,6 +168,14 @@ export class UserSettingsComponent {
this._store.dispatch(updateUserSettingsAction(changes));
}
+ protected async sendTestNotification() {
+ console.log('test');
+ this.sendPush.set(true);
+ timer(1000).subscribe(() => {
+ this.sendPush.set(false);
+ });
+ }
+
protected async toggleNotifications(enabled: boolean) {
if (!this.notificationsPossible) return;
this.isUpdatingPushSubscription.set(true);
diff --git a/src/client/src/app/i18n/de.json b/src/client/src/app/i18n/de.json
index 2685084..8c45720 100644
--- a/src/client/src/app/i18n/de.json
+++ b/src/client/src/app/i18n/de.json
@@ -41,6 +41,10 @@
"title": "Benachrichtigungen",
"enabledOnAllDevices": "Auf allen Geräten aktiviert",
"eanbled": "Auf diesem Gerät aktiviert",
+ "testNotification": {
+ "text": "Testbenachrichtigung",
+ "buttonText": "senden"
+ },
"errors": {
"notSupported": "Benachrichtigungen werden von diesem Browser nicht unterstützt. Gegenebenfalls muss die Webseite auf dem Home-Bildschirm installiert werden.",
"notGranted": {
diff --git a/src/client/src/app/i18n/en.json b/src/client/src/app/i18n/en.json
index 1b3c642..e4ffd98 100644
--- a/src/client/src/app/i18n/en.json
+++ b/src/client/src/app/i18n/en.json
@@ -41,6 +41,10 @@
"title": "Notifications",
"enabledOnAllDevices": "Enabled on all devices",
"eanbled": "Enabled on this device",
+ "testNotification": {
+ "text": "test notification",
+ "buttonText": "send"
+ },
"errors": {
"notSupported": "Notifications are not supported by this browser. Probably this website needs to be installed onto the homescreen.",
"notGranted": {
From f762a05b79a6b4cf58a420b237d3ab0602f4531e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Schmidt?=
<9435005+AnSch1510@users.noreply.github.com>
Date: Tue, 2 Jul 2024 22:45:10 +0200
Subject: [PATCH 02/12] server working
---
.../Models/Push/PushNotificationData.cs | 12 ++
.../Notifications/SendNotificationEndpoint.cs | 140 ++++++++++++++++++
2 files changed, 152 insertions(+)
create mode 100644 src/server/host/Endpoints/Notifications/SendNotificationEndpoint.cs
diff --git a/src/server/domain/Models/Push/PushNotificationData.cs b/src/server/domain/Models/Push/PushNotificationData.cs
index 0cd9ad2..2cae2cf 100644
--- a/src/server/domain/Models/Push/PushNotificationData.cs
+++ b/src/server/domain/Models/Push/PushNotificationData.cs
@@ -195,6 +195,18 @@ public string GetBody(string lang) =>
};
}
+ public record TestNotification(string Title, string Body) : IPushNotificationData
+ {
+ public string Type => "test-notification";
+
+ public Dictionary
OnActionClick =>
+ new() { { "default", new($"/events") } };
+
+ public string GetTitle(string lang) => Title;
+
+ public string GetBody(string lang) => Body;
+ }
+
private static string NormalizeLang(string lang)
{
if (lang.StartsWith("de", StringComparison.OrdinalIgnoreCase))
diff --git a/src/server/host/Endpoints/Notifications/SendNotificationEndpoint.cs b/src/server/host/Endpoints/Notifications/SendNotificationEndpoint.cs
new file mode 100644
index 0000000..2580da9
--- /dev/null
+++ b/src/server/host/Endpoints/Notifications/SendNotificationEndpoint.cs
@@ -0,0 +1,140 @@
+using System.ComponentModel.DataAnnotations;
+using FastEndpoints;
+using FluentValidation;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
+using Microsoft.IdentityModel.Tokens;
+using MinigolfFriday.Data;
+using MinigolfFriday.Data.Entities;
+using MinigolfFriday.Domain.Models;
+using MinigolfFriday.Domain.Models.Push;
+using MinigolfFriday.Domain.Models.RealtimeEvents;
+using MinigolfFriday.Host.Common;
+using MinigolfFriday.Host.Mappers;
+using MinigolfFriday.Host.Services;
+using MinigolfFriday.Host.Utilities;
+
+namespace MinigolfFriday.Host.Endpoints.Notifications;
+
+public record SendNotificationRequest(string? UserId, string? Title, string? Body);
+
+public class SendNotificationRequestValidator : Validator
+{
+ public SendNotificationRequestValidator(IIdService idService)
+ {
+ // RuleFor(x => x.UserId).NotNull().ValidSqid(idService.User);
+ }
+}
+
+public class SendNotificationEndpoint(
+ DatabaseContext databaseContext,
+ IJwtService jwtService,
+ IIdService idService,
+ IUserPushSubscriptionMapper userPushSubscriptionMapper,
+ IWebPushService webPushService
+) : Endpoint
+{
+ public override void Configure()
+ {
+ Post("");
+ Group();
+ this.ProducesErrors(EndpointErrors.UserNotFound, EndpointErrors.UserIdNotInClaims);
+ }
+
+ public override async Task HandleAsync(SendNotificationRequest req, CancellationToken ct)
+ {
+ if (!jwtService.TryGetUserId(User, out var userId))
+ {
+ Logger.LogWarning(EndpointErrors.UserIdNotInClaims);
+ await this.SendErrorAsync(EndpointErrors.UserIdNotInClaims, ct);
+ return;
+ }
+ if (!req.UserId.IsNullOrEmpty())
+ {
+ // TODO: check if we are admin - if not, this must not be possible
+ userId = idService.User.DecodeSingle(req.UserId);
+ }
+
+ var user = null as UserEntity;
+ try
+ {
+ user = await databaseContext
+ .Users.Include(x => x.Settings)
+ .FirstAsync(x => x.Id == userId, ct);
+ }
+ catch (System.Exception)
+ {
+ Logger.LogWarning(EndpointErrors.UserNotFound, req.UserId);
+ await this.SendErrorAsync(EndpointErrors.UserNotFound, req.UserId, ct);
+ return;
+ }
+
+ Logger.LogWarning("send to userId: {} ", user.Alias);
+
+ var notifications = await databaseContext
+ .Users.Where(u => u.Id == userId)
+ .Select(u => new
+ {
+ Subscriptions = u.PushSubscriptions.Select(x => new UserPushSubscription(
+ x.Id,
+ x.UserId,
+ x.Lang,
+ x.Endpoint,
+ x.P256DH,
+ x.Auth
+ )),
+ NotificationData = new PushNotificationData.TestNotification(
+ req.Title.IsNullOrEmpty() ? "Body" : req.Title,
+ req.Body.IsNullOrEmpty() ? "Test" : req.Body
+ )
+ })
+ .ToArrayAsync(ct);
+ foreach (var notification in notifications)
+ await webPushService.SendAsync(
+ notification.Subscriptions,
+ notification.NotificationData,
+ ct
+ );
+
+ // var user = await databaseContext
+ // .Users.Include(x => x.Settings)
+ // .FirstAsync(x => x.Id == userId, ct);
+
+ // if (user.Settings == null)
+ // {
+ // user.Settings = new()
+ // {
+ // EnableNotifications = req.EnableNotifications ?? true,
+ // NotifyOnEventPublish = req.NotifyOnEventPublish ?? true,
+ // NotifyOnEventStart = req.NotifyOnEventStart ?? true,
+ // NotifyOnEventUpdated = req.NotifyOnEventUpdated ?? true,
+ // NotifyOnTimeslotStart = req.NotifyOnTimeslotStart ?? true,
+ // SecondsToNotifyBeforeTimeslotStart = req.SecondsToNotifyBeforeTimeslotStart ?? 600
+ // };
+ // }
+ // else
+ // {
+ // user.Settings.EnableNotifications =
+ // req.EnableNotifications ?? user.Settings.EnableNotifications;
+ // user.Settings.NotifyOnEventPublish =
+ // req.NotifyOnEventPublish ?? user.Settings.NotifyOnEventPublish;
+ // user.Settings.NotifyOnEventStart =
+ // req.NotifyOnEventStart ?? user.Settings.NotifyOnEventStart;
+ // user.Settings.NotifyOnEventUpdated =
+ // req.NotifyOnEventUpdated ?? user.Settings.NotifyOnEventUpdated;
+ // user.Settings.NotifyOnTimeslotStart =
+ // req.NotifyOnTimeslotStart ?? user.Settings.NotifyOnTimeslotStart;
+ // user.Settings.SecondsToNotifyBeforeTimeslotStart =
+ // req.SecondsToNotifyBeforeTimeslotStart
+ // ?? user.Settings.SecondsToNotifyBeforeTimeslotStart;
+ // }
+
+ // await databaseContext.SaveChangesAsync(ct);
+ await SendOkAsync(ct);
+
+ // await realtimeEventsService.SendEventAsync(
+ // new RealtimeEvent.UserSettingsChanged(idService.User.Encode(userId)),
+ // ct
+ // );
+ }
+}
From 1820a18065fff19382b03e4e04fffd9a419b6590 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Schmidt?=
<9435005+AnSch1510@users.noreply.github.com>
Date: Wed, 3 Jul 2024 19:45:45 +0200
Subject: [PATCH 03/12] send notification request to server
---
.../user-settings/user-settings.component.ts | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/client/src/app/components/user-settings/user-settings.component.ts b/src/client/src/app/components/user-settings/user-settings.component.ts
index 14d1f16..28ab844 100644
--- a/src/client/src/app/components/user-settings/user-settings.component.ts
+++ b/src/client/src/app/components/user-settings/user-settings.component.ts
@@ -12,7 +12,7 @@ import { FloatLabelModule } from 'primeng/floatlabel';
import { InputSwitchModule } from 'primeng/inputswitch';
import { MessagesModule } from 'primeng/messages';
import { ProgressSpinnerModule } from 'primeng/progressspinner';
-import { filter, map, Subject, timer } from 'rxjs';
+import { filter, map, Subject } from 'rxjs';
import { SavedFadingMessageComponent } from '../+common/saved-fading-message.component';
import { isActionBusy, hasActionFailed } from '../../+state/action-state';
@@ -22,6 +22,7 @@ import {
updateUserSettingsAction,
} from '../../+state/user-settings';
import { keepUserSettingsLoaded } from '../../+state/user-settings/user-settings.utils';
+import { NotificationsService } from '../../api/services';
import { ResetNgModelDirective } from '../../directives/reset-ng-model.directive';
import { UserSettings } from '../../models/parsed-models';
import { AuthService } from '../../services/auth.service';
@@ -75,6 +76,7 @@ export class UserSettingsComponent {
private readonly _actions$ = inject(Actions);
private readonly _messageService = inject(MessageService);
private readonly _webPushService = inject(WebPushService);
+ private readonly _notificationService = inject(NotificationsService);
private readonly _loadActionState = selectSignal(selectUserSettingsActionState('load'));
private readonly _updateActionState = selectSignal(selectUserSettingsActionState('update'));
@@ -169,11 +171,11 @@ export class UserSettingsComponent {
}
protected async sendTestNotification() {
- console.log('test');
- this.sendPush.set(true);
- timer(1000).subscribe(() => {
- this.sendPush.set(false);
- });
+ this.sendPush.set(false);
+ const response = await this._notificationService.sendNotification({ body: {} });
+ if (response.ok) {
+ this.sendPush.set(true);
+ }
}
protected async toggleNotifications(enabled: boolean) {
From a91de6784bff7a8a83ed89d4de5c794fa2454b80 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Schmidt?=
<9435005+AnSch1510@users.noreply.github.com>
Date: Wed, 3 Jul 2024 20:00:35 +0200
Subject: [PATCH 04/12] translate send notification success bunner
---
.../components/+common/saved-fading-message.component.ts | 8 ++++++--
.../components/user-settings/user-settings.component.html | 4 +++-
src/client/src/app/i18n/de.json | 3 ++-
src/client/src/app/i18n/en.json | 5 +++--
4 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/src/client/src/app/components/+common/saved-fading-message.component.ts b/src/client/src/app/components/+common/saved-fading-message.component.ts
index 1d1de76..a8cded5 100644
--- a/src/client/src/app/components/+common/saved-fading-message.component.ts
+++ b/src/client/src/app/components/+common/saved-fading-message.component.ts
@@ -12,8 +12,12 @@ import { TranslateService } from '../../services/translate.service';
class="pointer-events-none absolute top-1/2 -translate-x-[calc(100%+8px)] -translate-y-1/2 truncate rounded bg-green-100 px-4 py-2 text-green-900 dark:bg-green-900 dark:text-green-200"
[showTrigger]="showTrigger()"
>
-
- {{ translations.shared_saved() }}
+
+
+
+ {{ translations.shared_saved() }}
+
+
`,
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/src/client/src/app/components/user-settings/user-settings.component.html b/src/client/src/app/components/user-settings/user-settings.component.html
index edff1ff..0398f23 100644
--- a/src/client/src/app/components/user-settings/user-settings.component.html
+++ b/src/client/src/app/components/user-settings/user-settings.component.html
@@ -115,7 +115,9 @@ {{ translations.nav_settings() }}
[severity]="'primary'"
(onClick)="sendTestNotification()"
/>
-
+ {{
+ translations.settings_notifications_testNotification_successText()
+ }}