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 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..6f7c4b02 100644 --- a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor +++ b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor @@ -2,241 +2,280 @@ @inherits ModuleBase @inject ISystemService SystemService @inject IInstallationService InstallationService +@inject IMigrationHistoryService MigrationHistoryService +@inject ITenantService TenantService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer - - - - - Framework Version: - - +@if (_initialized) +{ + + + + + Framework Version: + + + + + + CLR Version: + + + + + + OS Version: + + + + + + Process: + + + + + + Machine Name: + + + + + + IP Address: + + + + + + Environment: + + + + + + Root Path: + + + + + + Web Path: + + + + + + Server Date/Time: + + + + + + Memory Allocation: + + + + + + Installation ID: + + + - - CLR Version: - - + + + + + + + Detailed Errors? + + + @SharedLocalizer["True"] + @SharedLocalizer["False"] + + + + + Logging Level: + + + @Localizer["Trace"] + @Localizer["Debug"] + @Localizer["Information"] + @Localizer["Warning"] + @Localizer["Error"] + @Localizer["Critical"] + @Localizer["None"] + + + + + Notification Level: + + + @Localizer["Trace"] + @Localizer["Debug"] + @Localizer["Information"] + @Localizer["Warning"] + @Localizer["Error"] + @Localizer["Critical"] + @Localizer["None"] + + + + + Swagger Enabled? + + + @SharedLocalizer["True"] + @SharedLocalizer["False"] + + + + + Static Asset Caching: + + + + + + Package Manager Url: + + + + + + Package Manager Email: + + + - - OS Version: - - + + @SharedLocalizer["Save"] + + + @Localizer["Swagger"] + @Localizer["Endpoints"] + + + + + Log: + + + - - Process: - - + + @Localizer["Clear"] + + + + + Database: + + + - - Machine Name: - - - - - - IP Address: - - - - - - Environment: - - - - - - Root Path: - - - - - - Web Path: - - - - - - Server Date/Time: - - - - - - Memory Allocation: - - - - - - Installation ID: - - - - - - - - - - - - Detailed Errors? - - - @SharedLocalizer["True"] - @SharedLocalizer["False"] - - - - - Logging Level: - - - @Localizer["Trace"] - @Localizer["Debug"] - @Localizer["Information"] - @Localizer["Warning"] - @Localizer["Error"] - @Localizer["Critical"] - @Localizer["None"] - - - - - Notification Level: - - - @Localizer["Trace"] - @Localizer["Debug"] - @Localizer["Information"] - @Localizer["Warning"] - @Localizer["Error"] - @Localizer["Critical"] - @Localizer["None"] - - - - - Swagger Enabled? - - - @SharedLocalizer["True"] - @SharedLocalizer["False"] - - - - - Static Asset Caching: - - - - - - Package Manager Url: - - - - - - Package Manager Email: - - - - - - - @SharedLocalizer["Save"] - - - @Localizer["Swagger"] - @Localizer["Endpoints"] - - - - - Log: - - - - - - - @Localizer["Clear"] - - - + + + + @Localizer["Migration"] + @Localizer["Date"] + @Localizer["Version"] + + + @context.MigrationId + @UtcToLocal(context.AppliedDate) + @context.AppliedVersion + + + + + +} @code { - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; - private string _version = string.Empty; - private string _clrversion = string.Empty; - private string _osversion = string.Empty; + private bool _initialized = false; + + private string _version = string.Empty; + private string _clrversion = string.Empty; + private string _osversion = string.Empty; private string _process = string.Empty; private string _machinename = string.Empty; - private string _ipaddress = string.Empty; - private string _environment = string.Empty; - private string _contentrootpath = string.Empty; - private string _webrootpath = string.Empty; - private string _servertime = string.Empty; - private string _workingset = string.Empty; - private string _installationid = string.Empty; + private string _ipaddress = string.Empty; + private string _environment = string.Empty; + private string _contentrootpath = string.Empty; + private string _webrootpath = string.Empty; + private string _servertime = string.Empty; + private string _workingset = string.Empty; + private string _installationid = string.Empty; - private string _detailederrors = string.Empty; - private string _logginglevel = string.Empty; - private string _notificationlevel = string.Empty; - private string _swagger = string.Empty; + private string _detailederrors = string.Empty; + private string _logginglevel = string.Empty; + private string _notificationlevel = string.Empty; + private string _swagger = string.Empty; private string _cachecontrol = string.Empty; private string _packageregistryurl = string.Empty; private string _packageregistryemail = string.Empty; private string _log = string.Empty; - protected override async Task OnInitializedAsync() - { - _version = Constants.Version; + private string _tenant = string.Empty; + private List _history; - var systeminfo = await SystemService.GetSystemInfoAsync("environment"); - if (systeminfo != null) - { - _clrversion = systeminfo["CLRVersion"].ToString(); - _osversion = systeminfo["OSVersion"].ToString(); + protected override async Task OnInitializedAsync() + { + _version = Constants.Version; + + var systeminfo = await SystemService.GetSystemInfoAsync("environment"); + if (systeminfo != null) + { + _clrversion = systeminfo["CLRVersion"].ToString(); + _osversion = systeminfo["OSVersion"].ToString(); _process = systeminfo["Process"].ToString(); _machinename = systeminfo["MachineName"].ToString(); - _ipaddress = systeminfo["IPAddress"].ToString(); - _environment = systeminfo["Environment"].ToString(); - _contentrootpath = systeminfo["ContentRootPath"].ToString(); - _webrootpath = systeminfo["WebRootPath"].ToString(); - _servertime = systeminfo["ServerTime"].ToString() + " UTC"; - _workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB"; - } + _ipaddress = systeminfo["IPAddress"].ToString(); + _environment = systeminfo["Environment"].ToString(); + _contentrootpath = systeminfo["ContentRootPath"].ToString(); + _webrootpath = systeminfo["WebRootPath"].ToString(); + _servertime = systeminfo["ServerTime"].ToString() + " UTC"; + _workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB"; + } - systeminfo = await SystemService.GetSystemInfoAsync("configuration"); - if (systeminfo != null) - { - _installationid = systeminfo["InstallationId"].ToString(); - _detailederrors = systeminfo["DetailedErrors"].ToString(); - _logginglevel = systeminfo["Logging:LogLevel:Default"].ToString(); - _notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString(); + systeminfo = await SystemService.GetSystemInfoAsync("configuration"); + if (systeminfo != null) + { + _installationid = systeminfo["InstallationId"].ToString(); + _detailederrors = systeminfo["DetailedErrors"].ToString(); + _logginglevel = systeminfo["Logging:LogLevel:Default"].ToString(); + _notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString(); _swagger = systeminfo["UseSwagger"].ToString(); _cachecontrol = systeminfo["CacheControl"].ToString(); _packageregistryurl = systeminfo["PackageRegistryUrl"].ToString(); _packageregistryemail = systeminfo["PackageRegistryEmail"].ToString(); } - systeminfo = await SystemService.GetSystemInfoAsync("log"); - if (systeminfo != null) - { - _log = systeminfo["Log"].ToString(); - } - } + systeminfo = await SystemService.GetSystemInfoAsync("log"); + if (systeminfo != null) + { + _log = systeminfo["Log"].ToString(); + } + + var tenants = await TenantService.GetTenantsAsync(); + _tenant = tenants.Find(item => item.TenantId == PageState.Site.TenantId).Name; + _history = await MigrationHistoryService.GetMigrationHistoryAsync(); + + _initialized = true; + } private async Task SaveConfig() { diff --git a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx index 19bdf93a..a4a292d4 100644 --- a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx @@ -309,4 +309,19 @@ API Endpoints + + Migration + + + Date + + + Framework Version + + + Database: + + + The name of the current database. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database. + \ No newline at end of file diff --git a/Oqtane.Client/Services/MigrationHistoryService.cs b/Oqtane.Client/Services/MigrationHistoryService.cs new file mode 100644 index 00000000..be3ed815 --- /dev/null +++ b/Oqtane.Client/Services/MigrationHistoryService.cs @@ -0,0 +1,34 @@ +using Oqtane.Models; +using System.Net.Http; +using System.Threading.Tasks; +using System.Collections.Generic; +using Oqtane.Documentation; +using Oqtane.Shared; + +namespace Oqtane.Services +{ + /// + /// Service to manage s + /// + /// + Task> GetMigrationHistoryAsync(); + } + + [PrivateApi("Don't show in the documentation, as everything should use the Interface")] + public class MigrationHistoryService : ServiceBase, IMigrationHistoryService + { + public MigrationHistoryService(HttpClient http, SiteState siteState) : base(http, siteState) { } + + private string Apiurl => CreateApiUrl("MigrationHistory"); + + public async Task> GetMigrationHistoryAsync() + { + return await GetJsonAsync>(Apiurl); + } + } +} 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" diff --git a/Oqtane.Server/Controllers/MigrationHistoryController.cs b/Oqtane.Server/Controllers/MigrationHistoryController.cs new file mode 100644 index 00000000..b2ba5f5c --- /dev/null +++ b/Oqtane.Server/Controllers/MigrationHistoryController.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Oqtane.Models; +using System.Collections.Generic; +using Oqtane.Shared; +using Oqtane.Repository; + +namespace Oqtane.Controllers +{ + [Route(ControllerRoutes.ApiRoute)] + public class MigrationHistoryController : Controller + { + private readonly IMigrationHistoryRepository _history; + + public MigrationHistoryController(IMigrationHistoryRepository history) + { + _history = history; + } + + // GET: api/ + [HttpGet] + [Authorize(Roles = RoleNames.Host)] + public IEnumerable Get() + { + return _history.GetMigrationHistory(); + } + } +} diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index dbafdab7..115abee0 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -228,6 +228,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // providers services.AddScoped(); @@ -276,6 +277,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // managers services.AddTransient(); diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index 65b53cc0..c85663e8 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); @@ -143,69 +143,90 @@ 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 mailboxAddressValidationError = ""; + + // 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) ? site.Name : fromName; } - else + try { + // 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 + } + if (from == null) + { + + mailboxAddressValidationError += $" Invalid Sender: {fromName} <{fromEmail}>"; + } + + // recipient + try + { + // 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 + } + if (to == null) + { + mailboxAddressValidationError += $" Invalid Recipient: {toName} <{toEmail}>"; + } + + // if mailbox addresses are valid + if (from != null && to != null) + { + // create mail message 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; - //body + // body var bodyText = notification.Body; if (!bodyText.Contains('<') || !bodyText.Contains('>')) @@ -230,14 +251,22 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - // error - log += $"NotificationId: {notification.NotificationId} - {ex.Message}"; + log += $"Error Sending Notification Id: {notification.NotificationId} - {ex.Message}"; } } + else + { + // invalid mailbox address + log += $"Notification Id: {notification.NotificationId} Has An {mailboxAddressValidationError} 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/Context/TenantDBContext.cs b/Oqtane.Server/Repository/Context/TenantDBContext.cs index 599485f7..bb0e1c72 100644 --- a/Oqtane.Server/Repository/Context/TenantDBContext.cs +++ b/Oqtane.Server/Repository/Context/TenantDBContext.cs @@ -33,5 +33,6 @@ namespace Oqtane.Repository public virtual DbSet SearchContentProperty { get; set; } public virtual DbSet SearchContentWord { get; set; } public virtual DbSet SearchWord { get; set; } + public virtual DbSet MigrationHistory { get; set; } } } diff --git a/Oqtane.Server/Repository/MigrationHistoryRepository.cs b/Oqtane.Server/Repository/MigrationHistoryRepository.cs new file mode 100644 index 00000000..5465dc08 --- /dev/null +++ b/Oqtane.Server/Repository/MigrationHistoryRepository.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Oqtane.Models; + +namespace Oqtane.Repository +{ + public interface IMigrationHistoryRepository + { + IEnumerable GetMigrationHistory(); + } + public class MigrationHistoryRepository : IMigrationHistoryRepository + { + private readonly IDbContextFactory _dbContextFactory; + + public MigrationHistoryRepository(IDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + public IEnumerable GetMigrationHistory() + { + using var db = _dbContextFactory.CreateDbContext(); + return db.MigrationHistory.ToList(); + } + } +} 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; diff --git a/Oqtane.Shared/Models/MigrationHistory.cs b/Oqtane.Shared/Models/MigrationHistory.cs new file mode 100644 index 00000000..e35a8be3 --- /dev/null +++ b/Oqtane.Shared/Models/MigrationHistory.cs @@ -0,0 +1,16 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Oqtane.Models +{ + [Table("__EFMigrationsHistory")] + [Keyless] + public class MigrationHistory + { + public string MigrationId { get; set; } + public string ProductVersion { get; set; } + public DateTime AppliedDate { get; set; } + public string AppliedVersion { get; set; } + } +}