From a6f4921055af95e8695c1c82b0ea4f1acb3d7a28 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 18 Sep 2025 13:46:18 -0400 Subject: [PATCH 1/8] changes to release.cmd --- Oqtane.Package/release.cmd | 49 ++++++++++---------------------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/Oqtane.Package/release.cmd b/Oqtane.Package/release.cmd index 43ac96ed..4c08591e 100644 --- a/Oqtane.Package/release.cmd +++ b/Oqtane.Package/release.cmd @@ -4,42 +4,19 @@ nuget.exe pack Oqtane.Server.nuspec nuget.exe pack Oqtane.Shared.nuspec nuget.exe pack Oqtane.Framework.nuspec dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\Content" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-arm" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-arm64" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-x64" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-x86" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\ios-arm" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\ios-arm64" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-arm64" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-x64" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-x86" -setlocal ENABLEDELAYEDEXPANSION -set retain=Radzen.Blazor -for /D %%i in ("..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\_content\*") do ( -set /A found=0 -for %%j in (%retain%) do ( -if "%%~nxi" == "%%j" set /A found=1 -) -if not !found! == 1 rmdir /Q/S "%%i" -) -set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText -for /D %%i in ("..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Modules\*") do ( -set /A found=0 -for %%j in (%retain%) do ( -if "%%~nxi" == "%%j" set /A found=1 -) -if not !found! == 1 rmdir /Q/S "%%i" -) -set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme -for /D %%i in ("..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Themes\*") do ( -set /A found=0 -for %%j in (%retain%) do ( -if "%%~nxi" == "%%j" set /A found=1 -) -if not !found! == 1 rmdir /Q/S "%%i" -) +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\Content" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-arm" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-arm64" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-x64" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-x86" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\ios-arm" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\ios-arm64" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-arm64" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-x64" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-x86" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Modules\Templates" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Themes\Templates" del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json" ren "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.release.json" "appsettings.json" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1" From 1995a96a984aed1b922e835e569e6b4704576abb Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 18 Sep 2025 17:19:30 -0400 Subject: [PATCH 2/8] fix issues with NotificationJob related to MailKit behavior --- .../Infrastructure/Jobs/NotificationJob.cs | 96 ++++++++++--------- .../Repository/NotificationRepository.cs | 4 +- 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index 65b53cc0..e6c018da 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -143,64 +143,68 @@ namespace Oqtane.Infrastructure List notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList(); foreach (Notification notification in notifications) { - // get sender and receiver information from user object if not provided - if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null) + var fromEmail = notification.FromEmail ?? ""; + var fromName = notification.FromDisplayName ?? ""; + var toEmail = notification.ToEmail ?? ""; + var toName = notification.ToDisplayName ?? ""; + + // get sender and receiver information from user information if available + if ((string.IsNullOrEmpty(fromEmail) || string.IsNullOrEmpty(fromName)) && notification.FromUserId != null) { var user = userRepository.GetUser(notification.FromUserId.Value); if (user != null) { - notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail; - notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName; + fromEmail = string.IsNullOrEmpty(fromEmail) ? user.Email ?? "" : fromEmail; + fromName = string.IsNullOrEmpty(fromName) ? user.DisplayName ?? "" : fromName; } } - if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null) + if ((string.IsNullOrEmpty(toEmail) || string.IsNullOrEmpty(toName)) && notification.ToUserId != null) { var user = userRepository.GetUser(notification.ToUserId.Value); if (user != null) { - notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail; - notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName; + toEmail = string.IsNullOrEmpty(toEmail) ? user.Email ?? "" : toEmail; + toName = string.IsNullOrEmpty(toName) ? user.DisplayName ?? "" : toName; } } - // validate recipient - if (string.IsNullOrEmpty(notification.ToEmail) || !MailboxAddress.TryParse(notification.ToEmail, out _)) + // create mailbox addresses + MailboxAddress to = null; + MailboxAddress from = null; + var error = ""; + + // sender + if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") != "True") { - log += $"NotificationId: {notification.NotificationId} - Has Missing Or Invalid Recipient {notification.ToEmail}
"; - notification.IsDeleted = true; - notificationRepository.UpdateNotification(notification); + fromEmail = settingRepository.GetSettingValue(settings, "SMTPSender", ""); + fromName = !string.IsNullOrEmpty(fromName) ? fromName : site.Name; } - else + try + { + from = new MailboxAddress(fromName, fromEmail); + } + catch + { + // parse error creating sender mailbox address + error += $" Invalid Sender: {fromName} <{fromEmail}>"; + } + + // recipient + try + { + to = new MailboxAddress(toName, toEmail); + } + catch + { + // parse error creating recipient mailbox address + error += $" Invalid Recipient: {toName} <{toEmail}>"; + } + + if (from != null && to != null) { MimeMessage mailMessage = new MimeMessage(); - - // sender - if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") == "True" && !string.IsNullOrEmpty(notification.FromEmail)) - { - if (!string.IsNullOrEmpty(notification.FromDisplayName)) - { - mailMessage.From.Add(new MailboxAddress(notification.FromDisplayName, notification.FromEmail)); - } - else - { - mailMessage.From.Add(new MailboxAddress("", notification.FromEmail)); - } - } - else - { - mailMessage.From.Add(new MailboxAddress((!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name, - settingRepository.GetSettingValue(settings, "SMTPSender", ""))); - } - - // recipient - if (!string.IsNullOrEmpty(notification.ToDisplayName)) - { - mailMessage.To.Add(new MailboxAddress(notification.ToDisplayName, notification.ToEmail)); - } - else - { - mailMessage.To.Add(new MailboxAddress("", notification.ToEmail)); - } + mailMessage.From.Add(from); + mailMessage.To.Add(to); // subject mailMessage.Subject = notification.Subject; @@ -231,13 +235,19 @@ namespace Oqtane.Infrastructure catch (Exception ex) { // error - log += $"NotificationId: {notification.NotificationId} - {ex.Message}
"; + log += $"Error Sending Notification Id: {notification.NotificationId} - {ex.Message}
"; } } + else + { + log += $"Notification Id: {notification.NotificationId} - Has An {error} And Has Been Deleted
"; + notification.IsDeleted = true; + notificationRepository.UpdateNotification(notification); + } } - await client.DisconnectAsync(true); log += "Notifications Delivered: " + sent + "
"; } + await client.DisconnectAsync(true); } } else diff --git a/Oqtane.Server/Repository/NotificationRepository.cs b/Oqtane.Server/Repository/NotificationRepository.cs index 1c20faee..92e8fc74 100644 --- a/Oqtane.Server/Repository/NotificationRepository.cs +++ b/Oqtane.Server/Repository/NotificationRepository.cs @@ -144,14 +144,14 @@ namespace Oqtane.Repository // delete notifications in batches of 100 records var count = 0; var purgedate = DateTime.UtcNow.AddDays(-age); - var notifications = db.Notification.Where(item => item.SiteId == siteId && item.FromUserId == null && item.IsDelivered && item.DeliveredOn < purgedate) + var notifications = db.Notification.Where(item => item.SiteId == siteId && item.FromUserId == null && (item.IsDeleted || item.IsDelivered && item.DeliveredOn < purgedate)) .OrderBy(item => item.DeliveredOn).Take(100).ToList(); while (notifications.Count > 0) { count += notifications.Count; db.Notification.RemoveRange(notifications); db.SaveChanges(); - notifications = db.Notification.Where(item => item.SiteId == siteId && item.FromUserId == null && item.IsDelivered && item.DeliveredOn < purgedate) + notifications = db.Notification.Where(item => item.SiteId == siteId && item.FromUserId == null && (item.IsDeleted || item.IsDelivered && item.DeliveredOn < purgedate)) .OrderBy(item => item.DeliveredOn).Take(100).ToList(); } return count; From 70551f9d2739f298e711f25f8247b81bf4307b0c Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 19 Sep 2025 08:55:36 -0400 Subject: [PATCH 3/8] synchronize Application Template project settings with Oqtane Framework --- Oqtane.Application/Client/Oqtane.Application.Client.csproj | 6 ++++-- Oqtane.Application/Server/Oqtane.Application.Server.csproj | 1 + Oqtane.Application/Shared/Oqtane.Application.Shared.csproj | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Oqtane.Application/Client/Oqtane.Application.Client.csproj b/Oqtane.Application/Client/Oqtane.Application.Client.csproj index 9e9aee26..6dc0740c 100644 --- a/Oqtane.Application/Client/Oqtane.Application.Client.csproj +++ b/Oqtane.Application/Client/Oqtane.Application.Client.csproj @@ -2,12 +2,14 @@ net9.0 + Exe 1.0.0 Oqtane.Application.Client.Oqtane - true + true Default - false true + false + false diff --git a/Oqtane.Application/Server/Oqtane.Application.Server.csproj b/Oqtane.Application/Server/Oqtane.Application.Server.csproj index 19d69f21..71dbe6a0 100644 --- a/Oqtane.Application/Server/Oqtane.Application.Server.csproj +++ b/Oqtane.Application/Server/Oqtane.Application.Server.csproj @@ -4,6 +4,7 @@ net9.0 1.0.0 Oqtane.Application.Server.Oqtane + true true none false diff --git a/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj b/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj index ebdd7c07..02dee1e3 100644 --- a/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj +++ b/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj @@ -4,6 +4,7 @@ net9.0 1.0.0 Oqtane.Application.Shared.Oqtane + true From 442ec291a1b64a9081a78cf85cfbef9258206fc2 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 19 Sep 2025 09:05:35 -0400 Subject: [PATCH 4/8] optimizations to NotificationJob --- Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index e6c018da..85e16ff9 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -103,7 +103,7 @@ namespace Oqtane.Infrastructure break; } - await client.ConnectAsync(settingRepository.GetSettingValue(settings, "SMTPHost", ""), + await client.ConnectAsync(settingRepository.GetSettingValue(settings, "SMTPHost", ""), int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")), secureSocketOptions); @@ -177,7 +177,7 @@ namespace Oqtane.Infrastructure if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") != "True") { fromEmail = settingRepository.GetSettingValue(settings, "SMTPSender", ""); - fromName = !string.IsNullOrEmpty(fromName) ? fromName : site.Name; + fromName = string.IsNullOrEmpty(fromName) ? site.Name : fromName; } try { @@ -202,6 +202,7 @@ namespace Oqtane.Infrastructure if (from != null && to != null) { + // create mail message MimeMessage mailMessage = new MimeMessage(); mailMessage.From.Add(from); mailMessage.To.Add(to); @@ -240,13 +241,14 @@ namespace Oqtane.Infrastructure } else { - log += $"Notification Id: {notification.NotificationId} - Has An {error} And Has Been Deleted
"; + log += $"Notification Id: {notification.NotificationId} Has An {error} And Has Been Deleted
"; notification.IsDeleted = true; notificationRepository.UpdateNotification(notification); } } log += "Notifications Delivered: " + sent + "
"; } + await client.DisconnectAsync(true); } } From 05b37080c1ae6ed321321124453288c42eeec451 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 19 Sep 2025 12:45:55 -0400 Subject: [PATCH 5/8] improve NotificationJob validation logic --- .../Infrastructure/Jobs/NotificationJob.cs | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index 85e16ff9..c85663e8 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -171,7 +171,7 @@ namespace Oqtane.Infrastructure // create mailbox addresses MailboxAddress to = null; MailboxAddress from = null; - var error = ""; + var mailboxAddressValidationError = ""; // sender if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") != "True") @@ -181,25 +181,41 @@ namespace Oqtane.Infrastructure } try { - from = new MailboxAddress(fromName, fromEmail); + // exception handler is necessary because of https://github.com/jstedfast/MimeKit/issues/1186 + if (MailboxAddress.TryParse(fromEmail, out _)) + { + from = new MailboxAddress(fromName, fromEmail); + } } catch { // parse error creating sender mailbox address - error += $" Invalid Sender: {fromName} <{fromEmail}>"; + } + if (from == null) + { + + mailboxAddressValidationError += $" Invalid Sender: {fromName} <{fromEmail}>"; } // recipient try { - to = new MailboxAddress(toName, toEmail); + // exception handler is necessary because of https://github.com/jstedfast/MimeKit/issues/1186 + if (MailboxAddress.TryParse(toEmail, out _)) + { + to = new MailboxAddress(toName, toEmail); + } } catch { // parse error creating recipient mailbox address - error += $" Invalid Recipient: {toName} <{toEmail}>"; + } + if (to == null) + { + mailboxAddressValidationError += $" Invalid Recipient: {toName} <{toEmail}>"; } + // if mailbox addresses are valid if (from != null && to != null) { // create mail message @@ -210,7 +226,7 @@ namespace Oqtane.Infrastructure // subject mailMessage.Subject = notification.Subject; - //body + // body var bodyText = notification.Body; if (!bodyText.Contains('<') || !bodyText.Contains('>')) @@ -235,17 +251,18 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - // error log += $"Error Sending Notification Id: {notification.NotificationId} - {ex.Message}
"; } } else { - log += $"Notification Id: {notification.NotificationId} Has An {error} And Has Been Deleted
"; + // invalid mailbox address + log += $"Notification Id: {notification.NotificationId} Has An {mailboxAddressValidationError} And Has Been Deleted
"; notification.IsDeleted = true; notificationRepository.UpdateNotification(notification); } } + log += "Notifications Delivered: " + sent + "
"; } From beb4919d97718e59cb338f62da4e942aba144cad Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 19 Sep 2025 14:59:58 -0400 Subject: [PATCH 6/8] add ability to view Migration History --- .../OqtaneServiceCollectionExtensions.cs | 1 + .../Modules/Admin/SystemInfo/Index.razor | 434 ++++++++++-------- .../Modules/Admin/SystemInfo/Index.resx | 9 + .../Services/MigrationHistoryService.cs | 34 ++ .../Controllers/MigrationHistoryController.cs | 28 ++ .../OqtaneServiceCollectionExtensions.cs | 2 + .../Repository/Context/MasterDBContext.cs | 1 + .../Repository/MigrationHistoryRepository.cs | 25 + Oqtane.Shared/Models/MigrationHistory.cs | 16 + 9 files changed, 346 insertions(+), 204 deletions(-) create mode 100644 Oqtane.Client/Services/MigrationHistoryService.cs create mode 100644 Oqtane.Server/Controllers/MigrationHistoryController.cs create mode 100644 Oqtane.Server/Repository/MigrationHistoryRepository.cs create mode 100644 Oqtane.Shared/Models/MigrationHistory.cs diff --git a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs index 96d68d37..82186385 100644 --- a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs @@ -56,6 +56,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); // providers diff --git a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor index f4d347e9..ca32bf86 100644 --- a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor +++ b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor @@ -2,241 +2,267 @@ @inherits ModuleBase @inject ISystemService SystemService @inject IInstallationService InstallationService +@inject IMigrationHistoryService MigrationHistoryService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer - - -
-
- -
- +@if (_initialized) +{ + + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
-
- -
- +

+ + + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
-
- -
- +

+   + +

+ @Localizer["Swagger"]  + @Localizer["Endpoints"] + + +
+
+ +
+