From b7de4b81a625921c9e7a3308befc0e71f4abc84e Mon Sep 17 00:00:00 2001 From: vnetonline Date: Wed, 5 Jul 2023 15:58:35 +1000 Subject: [PATCH 1/3] [ENHANCE] - Added IsRead property to Notifications Fixed Version to Tenant.04.00.01.01 and reverted the Program.cs back to the way it was This reverts commit 82fef82c4f29115a3c9f2ffefbae355ce24605e6. [ENHANCE] - Added API to get Count of New Notifications based on IsRead Fixed Typo in Notification Controller [ENHANCE] - Added API to get Notifications by Count and IsRead --- .../Modules/Admin/UserProfile/Index.razor | 84 ++++++++++++++----- .../Modules/Admin/UserProfile/View.razor | 3 + .../Interfaces/INotificationService.cs | 21 +++++ Oqtane.Client/Services/NotificationService.cs | 14 ++++ .../Controllers/NotificationController.cs | 69 +++++++++++++++ .../Tenant/04000101_AddNotificationIsRead.cs | 35 ++++++++ .../Interfaces/INotificationRepository.cs | 2 + .../Repository/NotificationRepository.cs | 46 ++++++++++ Oqtane.Shared/Models/Notification.cs | 5 ++ 9 files changed, 257 insertions(+), 22 deletions(-) create mode 100644 Oqtane.Server/Migrations/Tenant/04000101_AddNotificationIsRead.cs diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index e1eabc2c..76002925 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -159,22 +159,41 @@ else - @context.FromDisplayName - @context.Subject - @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) + + @if (context.IsRead) + { + @context.FromDisplayName + @context.Subject + @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) + } + else + { + @context.FromDisplayName + @context.Subject + @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) + } @{ - string input = "___"; - if (context.Body.Contains(input)) - { - context.Body = context.Body.Split(input)[0]; - context.Body = context.Body.Replace("\n", ""); - context.Body = context.Body.Replace("\r", ""); - } } - @(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body) + string input = "___"; + if (context.Body.Contains(input)) + { + context.Body = context.Body.Split(input)[0]; + context.Body = context.Body.Replace("\n", ""); + context.Body = context.Body.Replace("\r", ""); + } + notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; + } + @if (context.IsRead) + { + @notificationSummary + } + else + { + @notificationSummary + } @@ -192,22 +211,42 @@ else - @context.ToDisplayName - @context.Subject - @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) + + @if (context.IsRead) + { + @context.ToDisplayName + @context.Subject + @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) + } + else + { + @context.ToDisplayName + @context.Subject + @string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn) + } + @{ - string input = "___"; - if (context.Body.Contains(input)) - { - context.Body = context.Body.Split(input)[0]; - context.Body = context.Body.Replace("\n", ""); - context.Body = context.Body.Replace("\r", ""); - } } - @(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body) + string input = "___"; + if (context.Body.Contains(input)) + { + context.Body = context.Body.Split(input)[0]; + context.Body = context.Body.Replace("\n", ""); + context.Body = context.Body.Replace("\r", ""); + } + notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; + } + @if (context.IsRead) + { + @notificationSummary + } + else + { + @notificationSummary + } @@ -246,6 +285,7 @@ else private string category = string.Empty; private string filter = "to"; private List notifications; + private string notificationSummary = string.Empty; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; diff --git a/Oqtane.Client/Modules/Admin/UserProfile/View.razor b/Oqtane.Client/Modules/Admin/UserProfile/View.razor index 947a6c7b..58a3d211 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/View.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/View.razor @@ -118,6 +118,9 @@ Notification notification = await NotificationService.GetNotificationAsync(notificationid); if (notification != null) { + notification.IsRead = true; + notification = await NotificationService.UpdateNotificationAsync(notification); + int userid = -1; if (notification.ToUserId == PageState.User.UserId) { diff --git a/Oqtane.Client/Services/Interfaces/INotificationService.cs b/Oqtane.Client/Services/Interfaces/INotificationService.cs index d1be32a2..a831dd5d 100644 --- a/Oqtane.Client/Services/Interfaces/INotificationService.cs +++ b/Oqtane.Client/Services/Interfaces/INotificationService.cs @@ -18,6 +18,27 @@ namespace Oqtane.Services /// Task> GetNotificationsAsync(int siteId, string direction, int userId); + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task> GetNotificationsAsync(int siteId, string direction, int userId, int count, bool isRead); + + /// + /// + /// + /// + /// + /// + /// + /// + Task GetNotificationCountAsync(int siteId, string direction, int userId, bool isRead); + /// /// Returns a specific notifications /// diff --git a/Oqtane.Client/Services/NotificationService.cs b/Oqtane.Client/Services/NotificationService.cs index 3368ff0b..f6fd1585 100644 --- a/Oqtane.Client/Services/NotificationService.cs +++ b/Oqtane.Client/Services/NotificationService.cs @@ -22,6 +22,20 @@ namespace Oqtane.Services return notifications.OrderByDescending(item => item.CreatedOn).ToList(); } + public async Task> GetNotificationsAsync(int siteId, string direction, int userId, int count, bool isRead) + { + var notifications = await GetJsonAsync>($"{Apiurl}/read?siteid={siteId}&direction={direction.ToLower()}&userid={userId}&count={count}&isread={isRead}"); + + return notifications.OrderByDescending(item => item.CreatedOn).ToList(); + } + + public async Task GetNotificationCountAsync(int siteId, string direction, int userId, bool isRead) + { + var notificationCount = await GetJsonAsync($"{Apiurl}/read-count?siteid={siteId}&direction={direction.ToLower()}&userid={userId}&isread={isRead}"); + + return notificationCount; + } + public async Task GetNotificationAsync(int notificationId) { return await GetJsonAsync($"{Apiurl}/{notificationId}"); diff --git a/Oqtane.Server/Controllers/NotificationController.cs b/Oqtane.Server/Controllers/NotificationController.cs index dfdba5f1..95621a47 100644 --- a/Oqtane.Server/Controllers/NotificationController.cs +++ b/Oqtane.Server/Controllers/NotificationController.cs @@ -9,6 +9,9 @@ using Oqtane.Repository; using Oqtane.Security; using System.Net; using System.Reflection.Metadata; +using Microsoft.Extensions.Localization; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using System.Linq; namespace Oqtane.Controllers { @@ -30,6 +33,72 @@ namespace Oqtane.Controllers _alias = tenantManager.GetAlias(); } + // GET: api//read?siteid=x&direction=to&userid=1&count=5&isread=false + [HttpGet("read")] + [Authorize(Roles = RoleNames.Registered)] + public IEnumerable Get(string siteid, string direction, string userid, string count, string isread) + { + IEnumerable notifications = null; + + int SiteId; + int UserId; + int Count; + bool IsRead; + if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && int.TryParse(userid, out UserId) && int.TryParse(count, out Count) && bool.TryParse(isread, out IsRead) && IsAuthorized(UserId)) + { + if (direction == "to") + { + notifications = _notifications.GetNotifications(SiteId, -1, UserId, Count, IsRead); + } + else + { + notifications = _notifications.GetNotifications(SiteId, UserId, -1, Count, IsRead); + } + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Notification Get Attempt {SiteId} {Direction} {UserId} {Count} {isRead}", siteid, direction, userid, count, isread); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + notifications = null; + } + + + return notifications; + } + + // GET: api//read?siteid=x&direction=to&userid=1&count=5&isread=false + [HttpGet("read-count")] + [Authorize(Roles = RoleNames.Registered)] + public int Get(string siteid, string direction, string userid, string isread) + { + int notificationsCount = 0; + + int SiteId; + int UserId; + bool IsRead; + if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && int.TryParse(userid, out UserId) && bool.TryParse(isread, out IsRead) && IsAuthorized(UserId)) + { + if (direction == "to") + { + notificationsCount = _notifications.GetNotificationCount(SiteId, -1, UserId, IsRead); + } + else + { + notificationsCount = _notifications.GetNotificationCount(SiteId, UserId, -1, IsRead); + } + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Notification Get Attempt {SiteId} {Direction} {UserId} {isRead}", siteid, direction, userid, isread); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + notificationsCount = 0; + } + + + return notificationsCount; + } + + // GET: api/?siteid=x&type=y&userid=z [HttpGet] [Authorize(Roles = RoleNames.Registered)] diff --git a/Oqtane.Server/Migrations/Tenant/04000101_AddNotificationIsRead.cs b/Oqtane.Server/Migrations/Tenant/04000101_AddNotificationIsRead.cs new file mode 100644 index 00000000..8b97308c --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/04000101_AddNotificationIsRead.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; +using Oqtane.Shared; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.04.00.01.01")] + public class AddNotificationIsRead : MultiDatabaseMigration + { + + public AddNotificationIsRead(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var notificationEntityBuilder = new NotificationEntityBuilder(migrationBuilder, ActiveDatabase); + notificationEntityBuilder.AddBooleanColumn("IsRead", false); + notificationEntityBuilder.UpdateColumn("IsRead", "1", "bool", ""); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var notificationEntityBuilder = new NotificationEntityBuilder(migrationBuilder, ActiveDatabase); + notificationEntityBuilder.DropColumn("IsPublic"); + } + + } + + +} diff --git a/Oqtane.Server/Repository/Interfaces/INotificationRepository.cs b/Oqtane.Server/Repository/Interfaces/INotificationRepository.cs index 34fb58be..948d7b53 100644 --- a/Oqtane.Server/Repository/Interfaces/INotificationRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/INotificationRepository.cs @@ -6,6 +6,8 @@ namespace Oqtane.Repository public interface INotificationRepository { IEnumerable GetNotifications(int siteId, int fromUserId, int toUserId); + IEnumerable GetNotifications(int siteId, int fromUserId, int toUserId, int count, bool isRead); + int GetNotificationCount(int siteId, int fromUserId, int toUserId, bool isRead); Notification AddNotification(Notification notification); Notification UpdateNotification(Notification notification); Notification GetNotification(int notificationId); diff --git a/Oqtane.Server/Repository/NotificationRepository.cs b/Oqtane.Server/Repository/NotificationRepository.cs index 7596ee94..42eff7cd 100644 --- a/Oqtane.Server/Repository/NotificationRepository.cs +++ b/Oqtane.Server/Repository/NotificationRepository.cs @@ -33,6 +33,52 @@ namespace Oqtane.Repository .ToList(); } + public IEnumerable GetNotifications(int siteId, int fromUserId, int toUserId, int count, bool isRead) + { + if (toUserId == -1 && fromUserId == -1) + { + return _db.Notification + .Where(item => item.SiteId == siteId) + .Where(item => item.IsDelivered == false && item.IsDeleted == false) + .Where(item => item.SendOn == null || item.SendOn < System.DateTime.UtcNow) + .Where(item => item.IsRead == isRead) + .ToList() + .Take(count); + } + + return _db.Notification + .Where(item => item.SiteId == siteId) + .Where(item => item.ToUserId == toUserId || toUserId == -1) + .Where(item => item.FromUserId == fromUserId || fromUserId == -1) + .Where(item => item.IsRead == isRead) + .ToList() + .Take(count); + } + + public int GetNotificationCount(int siteId, int fromUserId, int toUserId, bool isRead) + { + if (toUserId == -1 && fromUserId == -1) + { + return _db.Notification + .Where(item => item.SiteId == siteId) + .Where(item => item.IsDelivered == false && item.IsDeleted == false) + .Where(item => item.SendOn == null || item.SendOn < System.DateTime.UtcNow) + .Where(item => item.IsRead == isRead) + .ToList() + .Count(); + + } + + return _db.Notification + .Where(item => item.SiteId == siteId) + .Where(item => item.ToUserId == toUserId || toUserId == -1) + .Where(item => item.FromUserId == fromUserId || fromUserId == -1) + .Where(item => item.IsRead == isRead) + .ToList() + .Count(); + } + + public Notification AddNotification(Notification notification) { _db.Notification.Add(notification); diff --git a/Oqtane.Shared/Models/Notification.cs b/Oqtane.Shared/Models/Notification.cs index 975b28c8..d7e08c01 100644 --- a/Oqtane.Shared/Models/Notification.cs +++ b/Oqtane.Shared/Models/Notification.cs @@ -94,6 +94,10 @@ namespace Oqtane.Models /// public DateTime? SendOn { get; set; } + /// + /// If it has been read. See also + /// + public bool IsRead { get; set; } // constructors public Notification() {} @@ -174,6 +178,7 @@ namespace Oqtane.Models } IsDelivered = false; DeliveredOn = null; + IsRead = false; } } From 40459defa4687ba4d8f256d6d3db968994d021bb Mon Sep 17 00:00:00 2001 From: vnetonline Date: Thu, 6 Jul 2023 15:28:11 +1000 Subject: [PATCH 2/3] Cosmetic change to more the filter drop down to top of the notifications tab --- Oqtane.Client/Modules/Admin/UserProfile/Index.razor | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index 76002925..2dd1303d 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -144,6 +144,11 @@ else @if (notifications != null) { + +


@if (filter == "to") @@ -256,11 +261,6 @@ else
} -

- }
From f7338bf00e928fc66945d790bada4780c099bb74 Mon Sep 17 00:00:00 2001 From: vnetonline Date: Fri, 7 Jul 2023 10:21:22 +1000 Subject: [PATCH 3/3] Update GetNotifications (read) to retrieve descending oder by CreatedOn at repository level --- Oqtane.Server/Repository/NotificationRepository.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Oqtane.Server/Repository/NotificationRepository.cs b/Oqtane.Server/Repository/NotificationRepository.cs index 42eff7cd..43f9b386 100644 --- a/Oqtane.Server/Repository/NotificationRepository.cs +++ b/Oqtane.Server/Repository/NotificationRepository.cs @@ -42,6 +42,7 @@ namespace Oqtane.Repository .Where(item => item.IsDelivered == false && item.IsDeleted == false) .Where(item => item.SendOn == null || item.SendOn < System.DateTime.UtcNow) .Where(item => item.IsRead == isRead) + .OrderByDescending(item => item.CreatedOn) .ToList() .Take(count); } @@ -51,6 +52,7 @@ namespace Oqtane.Repository .Where(item => item.ToUserId == toUserId || toUserId == -1) .Where(item => item.FromUserId == fromUserId || fromUserId == -1) .Where(item => item.IsRead == isRead) + .OrderByDescending(item => item.CreatedOn) .ToList() .Take(count); }