[Admin] Notification channel toggles (web/SMS/push active-deactive)
CI/CD / CI · dotnet build (push) Successful in 50s
CI/CD / Deploy · hamkadr (push) Successful in 1m1s

Add a 'notification channels' card at the top of admin Settings with three master on/off checkboxes: web/in-app (new WebNotificationsEnabled, default true), SMS (existing SmsEnabled), and Web Push (existing PushEnabled). Removed the duplicate enable checkboxes from the SMS and Push sections so each binds once. NotificationService now gates the in-app + live SSE channel on WebNotificationsEnabled; push self-gates on PushEnabled. Migration defaults the new column to true so existing installs keep web notifications on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 15:56:40 +03:30
parent 91c953ff5d
commit 8fad9c1bb6
8 changed files with 1154 additions and 22 deletions
@@ -78,18 +78,26 @@ public class NotificationService
private async Task AddAsync(List<int> userIds, string title, string? body, string url)
{
if (userIds.Count == 0) return;
foreach (var uid in userIds)
_db.Notifications.Add(new Notification { UserId = uid, Title = title, Body = body, Url = url });
await _db.SaveChangesAsync();
_log.LogInformation("Notified {Count} users: {Title}", userIds.Count, title);
// Live: stream to any open tab/PWA over SSE (our own origin — works in Iran).
// The browser updates the bell instantly + shows a local toast/OS notification.
var notice = new LiveNotice(title, body, url);
foreach (var uid in userIds) _hub.Publish(uid, notice);
var settings = await _db.AppSettings.FindAsync(1);
var webOn = settings?.WebNotificationsEnabled ?? true; // master toggle for the in-app/web channel
// Also push to the lock screen for users who subscribed (best-effort; Web Push
// depends on the browser's push service, which is filtered in Iran for Chromium).
// Web / in-app notifications channel (bell list + live SSE). Admin can turn it off.
if (webOn)
{
foreach (var uid in userIds)
_db.Notifications.Add(new Notification { UserId = uid, Title = title, Body = body, Url = url });
await _db.SaveChangesAsync();
_log.LogInformation("Notified {Count} users: {Title}", userIds.Count, title);
// Live: stream to any open tab/PWA over SSE (our own origin — works in Iran).
// The browser updates the bell instantly + shows a local toast/OS notification.
var notice = new LiveNotice(title, body, url);
foreach (var uid in userIds) _hub.Publish(uid, notice);
}
// Web Push channel (lock screen). Self-gates on PushEnabled + VAPID keys; best-effort
// since Web Push depends on the browser's push service, which is filtered in Iran.
try { await _push.PushToUsersAsync(userIds, title, body, url); }
catch (Exception ex) { _log.LogWarning(ex, "Web push fan-out failed"); }
}
@@ -54,6 +54,7 @@ public class SettingsService
s.SmsTemplate = incoming.SmsTemplate?.Trim();
s.SmsSender = incoming.SmsSender?.Trim();
s.NeshanMapKey = incoming.NeshanMapKey?.Trim();
s.WebNotificationsEnabled = incoming.WebNotificationsEnabled;
s.PushEnabled = incoming.PushEnabled;
s.VapidPublicKey = incoming.VapidPublicKey?.Trim();
s.VapidPrivateKey = incoming.VapidPrivateKey?.Trim();