diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 8bca59ac..589bd3ce 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -190,6 +190,10 @@ namespace Oqtane.Infrastructure if (result.Success) { result = CreateSite(install); + if (result.Success) + { + result = MigrateSites(); + } } } } @@ -665,6 +669,78 @@ namespace Oqtane.Infrastructure return result; } + private Installation MigrateSites() + { + var result = new Installation { Success = false, Message = string.Empty }; + + // find upgrade type + Type upgradetype = null; + var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); + foreach (Assembly assembly in assemblies) + { + var types = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IUpgradeable))); + if (types.Any()) + { + upgradetype = types.First(); + break; + } + } + + // execute upgrade + if (upgradetype != null) + { + var obj = Activator.CreateInstance(upgradetype) as IUpgradeable; + if (obj != null) + { + using (var scope = _serviceScopeFactory.CreateScope()) + { + var aliases = scope.ServiceProvider.GetRequiredService(); + var tenantManager = scope.ServiceProvider.GetRequiredService(); + var sites = scope.ServiceProvider.GetRequiredService(); + + foreach (var alias in aliases.GetAliases().ToList().Where(item => item.IsDefault)) + { + var versions = obj.GetVersions(alias); + if (!string.IsNullOrEmpty(versions)) + { + tenantManager.SetTenant(alias.TenantId); + var site = sites.GetSites().FirstOrDefault(item => item.SiteId == alias.SiteId); + if (site != null) + { + foreach (var version in versions.Split(',', StringSplitOptions.RemoveEmptyEntries)) + { + if (string.IsNullOrEmpty(site.Version) || Version.Parse(version) > Version.Parse(site.Version)) + { + if (obj.Upgrade(alias, version)) + { + site.Version = version; + sites.UpdateSite(site); + } + else + { + result.Message = "An Error Occurred Executing IUpgradeable Interface For " + alias.Name + " For Version " + version; + } + } + } + } + } + } + } + } + } + + if (string.IsNullOrEmpty(result.Message)) + { + result.Success = true; + } + else + { + _filelogger.LogError(Utilities.LogMessage(this, result.Message)); + } + + return result; + } + private string DenormalizeConnectionString(string connectionString) { var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); diff --git a/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs b/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs index 48ed9bc8..4d06406d 100644 --- a/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs +++ b/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs @@ -1,5 +1,3 @@ -using Microsoft.EntityFrameworkCore; -using Oqtane.Enums; using Oqtane.Models; namespace Oqtane.Infrastructure diff --git a/Oqtane.Server/Infrastructure/Interfaces/IUpgradeable.cs b/Oqtane.Server/Infrastructure/Interfaces/IUpgradeable.cs new file mode 100644 index 00000000..607fed92 --- /dev/null +++ b/Oqtane.Server/Infrastructure/Interfaces/IUpgradeable.cs @@ -0,0 +1,11 @@ +using Oqtane.Models; + +namespace Oqtane.Infrastructure +{ + public interface IUpgradeable + { + string GetVersions(Alias alias); + + bool Upgrade(Alias alias, string version); + } +} diff --git a/Oqtane.Server/Infrastructure/SiteUpgrade/ExampleUpgrade.cs b/Oqtane.Server/Infrastructure/SiteUpgrade/ExampleUpgrade.cs new file mode 100644 index 00000000..0ec00c00 --- /dev/null +++ b/Oqtane.Server/Infrastructure/SiteUpgrade/ExampleUpgrade.cs @@ -0,0 +1,40 @@ +using Oqtane.Models; +using Oqtane.Documentation; + +namespace Oqtane.Infrastructure +{ + [PrivateApi("Mark Site-Template classes as private, since it's not very useful in the public docs")] + public class ExampleUpgrade : IUpgradeable + { + string IUpgradeable.GetVersions(Alias alias) + { + var versions = ""; + switch (alias.Name) + { + case "localhost:44357": + // return the list of official release versions for the specific site + versions = "1.0.0"; + break; + } + return versions; + } + + bool IUpgradeable.Upgrade(Alias alias, string version) + { + bool success = true; + switch (alias.Name) + { + case "localhost:44357": + switch (version) + { + case "1.0.0": + // execute some version-specific upgrade logic for the site here such as adding pages, modules, content, etc... + success = true; + break; + } + break; + } + return success; + } + } +} diff --git a/Oqtane.Server/Migrations/Tenant/03010004_AddSiteVersion.cs b/Oqtane.Server/Migrations/Tenant/03010004_AddSiteVersion.cs new file mode 100644 index 00000000..3c1bf47e --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/03010004_AddSiteVersion.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.03.01.00.04")] + public class AddSiteVersion : MultiDatabaseMigration + { + public AddSiteVersion(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.AddStringColumn("Version", 50, true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.DropColumn("Version"); + } + } +} diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 4f899ca4..baafc119 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -20,6 +20,7 @@ using Microsoft.Extensions.Primitives; using Oqtane.Enums; using Oqtane.Security; using Oqtane.Extensions; +using Oqtane.Themes; namespace Oqtane.Pages { @@ -69,7 +70,6 @@ namespace Oqtane.Pages public string Meta = ""; public string FavIcon = "favicon.ico"; public string PWAScript = ""; - public string ThemeType = ""; public string Message = ""; public IActionResult OnGet() @@ -134,7 +134,7 @@ namespace Oqtane.Pages PWAScript = CreatePWAScript(alias, site, route); } Title = site.Name; - ThemeType = site.DefaultThemeType; + var ThemeType = site.DefaultThemeType; // get jwt token for downstream APIs if (User.Identity.IsAuthenticated) @@ -153,7 +153,7 @@ namespace Oqtane.Pages } var page = _pages.GetPage(route.PagePath, site.SiteId); - if (page != null) + if (page != null & !page.IsDeleted) { // set page title if (!string.IsNullOrEmpty(page.Title)) @@ -171,6 +171,7 @@ namespace Oqtane.Pages { ThemeType = page.ThemeType; } + ProcessThemeResources(ThemeType); } else // page not found { @@ -407,19 +408,43 @@ namespace Oqtane.Pages var obj = Activator.CreateInstance(type) as IHostResources; foreach (var resource in obj.Resources) { - ProcessResource(resource); + resource.Level = ResourceLevel.App; + ProcessResource(resource, 0); } } } - private void ProcessResource(Resource resource) + private void ProcessThemeResources(string ThemeType) + { + var type = Type.GetType(ThemeType); + if (type != null) + { + var obj = Activator.CreateInstance(type) as IThemeControl; + if (obj.Resources != null) + { + int count = 1; + foreach (var resource in obj.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) + { + resource.Level = ResourceLevel.Page; + ProcessResource(resource, count++); + } + } + } + } + + private void ProcessResource(Resource resource, int count) { switch (resource.ResourceType) { case ResourceType.Stylesheet: if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase)) { - HeadResources += "" + Environment.NewLine; + string id = ""; + if (resource.Level == ResourceLevel.Page) + { + id = "id=\"app-stylesheet-" + resource.Level.ToString().ToLower() + "-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + "-" + count.ToString("00") + "\" "; + } + HeadResources += "" + Environment.NewLine; } break; case ResourceType.Script: diff --git a/Oqtane.Shared/Enums/ResourceLevel.cs b/Oqtane.Shared/Enums/ResourceLevel.cs index cc5aafe9..2c839c85 100644 --- a/Oqtane.Shared/Enums/ResourceLevel.cs +++ b/Oqtane.Shared/Enums/ResourceLevel.cs @@ -2,6 +2,7 @@ namespace Oqtane.Shared { public enum ResourceLevel { + App, Page, Module } diff --git a/Oqtane.Shared/Models/Site.cs b/Oqtane.Shared/Models/Site.cs index eb7682a2..d66f2a18 100644 --- a/Oqtane.Shared/Models/Site.cs +++ b/Oqtane.Shared/Models/Site.cs @@ -78,6 +78,11 @@ namespace Oqtane.Models /// public string RenderMode { get; set; } + /// + /// Keeps track of site configuration changes and is used by the IUpgradeable interface + /// + public string Version { get; set; } + #region IAuditable Properties ///