From 482747627ee1a2eba7768be298127323855391a0 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Sun, 12 Apr 2020 20:08:19 -0400 Subject: [PATCH] added uninstall support for modules --- .../External/Server/Scripts/01.00.00.sql | 2 +- .../External/Server/Scripts/Uninstall.sql | 6 ++ .../Modules/[Module]/Scripts/01.00.00.sql | 2 +- .../[Module]/Scripts/Tenant.01.00.00.sql | 26 -------- .../Modules/[Module]/Scripts/Uninstall.sql | 6 ++ .../Themes/Controls/ControlPanel.razor | 60 +++++++++---------- .../Controllers/ModuleDefinitionController.cs | 49 ++++++++++++--- .../Infrastructure/DatabaseManager.cs | 8 +-- .../Repository/Interfaces/ISqlRepository.cs | 1 + .../Repository/ModuleDefinitionRepository.cs | 20 ++----- Oqtane.Server/Repository/SqlRepository.cs | 9 +++ Oqtane.Server/Repository/ThemeRepository.cs | 32 +++++----- 12 files changed, 121 insertions(+), 100 deletions(-) create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Server/Scripts/Uninstall.sql delete mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Server/Modules/[Module]/Scripts/Tenant.01.00.00.sql create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Server/Modules/[Module]/Scripts/Uninstall.sql diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Server/Scripts/01.00.00.sql b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Server/Scripts/01.00.00.sql index 0e7266e6..7a1b99ea 100644 --- a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Server/Scripts/01.00.00.sql +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Server/Scripts/01.00.00.sql @@ -1,5 +1,5 @@ /* -Create [Module] table +Create [Owner][Module] table */ CREATE TABLE [dbo].[[Owner][Module]]( diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Server/Scripts/Uninstall.sql b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Server/Scripts/Uninstall.sql new file mode 100644 index 00000000..47baecc9 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Server/Scripts/Uninstall.sql @@ -0,0 +1,6 @@ +/* +Remove [Owner][Module] table +*/ + +DROP TABLE [dbo].[[Owner][Module]] +GO diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Server/Modules/[Module]/Scripts/01.00.00.sql b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Server/Modules/[Module]/Scripts/01.00.00.sql index 0e7266e6..7a1b99ea 100644 --- a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Server/Modules/[Module]/Scripts/01.00.00.sql +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Server/Modules/[Module]/Scripts/01.00.00.sql @@ -1,5 +1,5 @@ /* -Create [Module] table +Create [Owner][Module] table */ CREATE TABLE [dbo].[[Owner][Module]]( diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Server/Modules/[Module]/Scripts/Tenant.01.00.00.sql b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Server/Modules/[Module]/Scripts/Tenant.01.00.00.sql deleted file mode 100644 index f97f4daf..00000000 --- a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Server/Modules/[Module]/Scripts/Tenant.01.00.00.sql +++ /dev/null @@ -1,26 +0,0 @@ -/* -Create [Module] table -*/ - -CREATE TABLE [dbo].[[Module]]( - [[Module]Id] [int] IDENTITY(1,1) NOT NULL, - [ModuleId] [int] NOT NULL, - [Name] [nvarchar](256) NOT NULL, - [CreatedBy] [nvarchar](256) NOT NULL, - [CreatedOn] [datetime] NOT NULL, - [ModifiedBy] [nvarchar](256) NOT NULL, - [ModifiedOn] [datetime] NOT NULL, - CONSTRAINT [PK_[Module]] PRIMARY KEY CLUSTERED - ( - [[Module]Id] ASC - ) -) -GO - -/* -Create foreign key relationships -*/ -ALTER TABLE [dbo].[[Module]] WITH CHECK ADD CONSTRAINT [FK_[Module]_Module] FOREIGN KEY([ModuleId]) -REFERENCES [dbo].Module ([ModuleId]) -ON DELETE CASCADE -GO \ No newline at end of file diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Server/Modules/[Module]/Scripts/Uninstall.sql b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Server/Modules/[Module]/Scripts/Uninstall.sql new file mode 100644 index 00000000..47baecc9 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Internal/Oqtane.Server/Modules/[Module]/Scripts/Uninstall.sql @@ -0,0 +1,6 @@ +/* +Remove [Owner][Module] table +*/ + +DROP TABLE [dbo].[[Owner][Module]] +GO diff --git a/Oqtane.Client/Themes/Controls/ControlPanel.razor b/Oqtane.Client/Themes/Controls/ControlPanel.razor index 2a352fd9..f2cf43e7 100644 --- a/Oqtane.Client/Themes/Controls/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/ControlPanel.razor @@ -10,7 +10,7 @@ @inject IPageModuleService PageModuleService @inject ILogService logger -@if (UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, PageState.Page.Permissions)) +@if (_moduleDefinitions != null && UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, PageState.Page.Permissions)) {
@@ -231,17 +231,17 @@ { ButtonClass = "btn-outline-primary"; } - + if (string.IsNullOrEmpty(CardClass)) { CardClass = "card bg-secondary mb-3"; } - + if (string.IsNullOrEmpty(HeaderClass)) { HeaderClass = "card-header text-white"; } - + if (string.IsNullOrEmpty(BodyClass)) { BodyClass = "card-body"; @@ -251,8 +251,22 @@ { _pages?.Clear(); + foreach (Page p in PageState.Pages) + { + if (UserSecurity.IsAuthorized(PageState.User,PermissionNames.View, p.Permissions)) + { + _pages.Add(p); + } + } + + var panes = PageState.Page.Panes.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries); + _pane = panes.Count() == 1 ? panes.SingleOrDefault() : ""; + var themes = await ThemeService.GetThemesAsync(); + _containers = ThemeService.GetContainerTypes(themes); + _containerType = PageState.Site.DefaultContainerType; + _allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); - + foreach (ModuleDefinition moduledefinition in _allModuleDefinitions) { if (moduledefinition.Categories != "") @@ -266,22 +280,8 @@ } } } - + _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories == "").ToList(); - - foreach (Page p in PageState.Pages) - { - if (UserSecurity.IsAuthorized(PageState.User,PermissionNames.View, p.Permissions)) - { - _pages.Add(p); - } - } - - var panes = PageState.Page.Panes.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries); - _pane = panes.Count() == 1 ? panes.SingleOrDefault() : ""; - var themes = await ThemeService.GetThemesAsync(); - _containers = ThemeService.GetContainerTypes(themes); - _containerType = PageState.Site.DefaultContainerType; } } @@ -296,7 +296,7 @@ { _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(category)).ToList(); } - + _moduleDefinitionName = "-"; StateHasChanged(); } @@ -305,7 +305,7 @@ { _pageId = (string) e.Value; _modules?.Clear(); - + if (_pageId != "-") { foreach (Module module in PageState.Modules.Where(item => item.PageId == int.Parse(_pageId) && !item.IsDeleted)) @@ -316,7 +316,7 @@ } } } - + _moduleId = "-"; StateHasChanged(); } @@ -355,7 +355,7 @@ pageModule.Title = _modules.FirstOrDefault(item => item.ModuleId == int.Parse(_moduleId))?.Title; } } - + pageModule.Pane = _pane; pageModule.Order = int.MaxValue; pageModule.ContainerType = _containerType; @@ -438,19 +438,19 @@ case "Admin": // get admin dashboard moduleid module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule); - + if (module != null) { NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, module.ModuleId, "Index", "")); } - + break; case "Add": case "Edit": string url = ""; // get page management moduleid module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.PageManagementModule); - + if (module != null) { switch (location) @@ -463,12 +463,12 @@ break; } } - + if (url != "") { NavigationManager.NavigateTo(url); } - + break; } } @@ -482,7 +482,7 @@ private async Task DeletePage() { ConfirmDelete(); - + var page = PageState.Page; try { diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index 348673e1..21f8614d 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -26,10 +26,11 @@ namespace Oqtane.Controllers private readonly IInstallationManager _installationManager; private readonly IWebHostEnvironment _environment; private readonly ITenantResolver _resolver; + private readonly ITenantRepository _tenants; private readonly ISqlRepository _sql; private readonly ILogManager _logger; - public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository modules, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantResolver resolver, ISqlRepository sql, ILogManager logger) + public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository modules, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantResolver resolver, ITenantRepository tenants, ISqlRepository sql, ILogManager logger) { _moduleDefinitions = moduleDefinitions; _modules = modules; @@ -37,6 +38,7 @@ namespace Oqtane.Controllers _installationManager = installationManager; _environment = environment; _resolver = resolver; + _tenants = tenants; _sql = sql; _logger = logger; } @@ -102,23 +104,59 @@ namespace Oqtane.Controllers ModuleDefinition moduledefinition = moduledefinitions.Where(item => item.ModuleDefinitionId == id).FirstOrDefault(); if (moduledefinition != null) { + if (!string.IsNullOrEmpty(moduledefinition.ServerAssemblyName)) + { + string uninstallScript = ""; + Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().SingleOrDefault(a => a.GetName().Name == moduledefinition.ServerAssemblyName); + if (assembly != null) + { + Stream resourceStream = assembly.GetManifestResourceStream(moduledefinition.ServerAssemblyName + ".Scripts.Uninstall.sql"); + if (resourceStream != null) + { + using (var reader = new StreamReader(resourceStream)) + { + uninstallScript = reader.ReadToEnd(); + } + } + } + + foreach (Tenant tenant in _tenants.GetTenants()) + { + // uninstall module database schema + if (!string.IsNullOrEmpty(uninstallScript)) + { + _sql.ExecuteScript(tenant, uninstallScript); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Uninstall Script Executed For {ServerAssemblyName}", moduledefinition.ServerAssemblyName); + } + // clean up module schema versions + _sql.ExecuteNonQuery(tenant, "DELETE FROM [dbo].[SchemaVersions] WHERE ScriptName LIKE '" + moduledefinition.ServerAssemblyName + "%'"); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Schema Versions Removed For {ServerAssemblyName}", moduledefinition.ServerAssemblyName); + } + } + string moduledefinitionname = moduledefinition.ModuleDefinitionName.Substring(0, moduledefinition.ModuleDefinitionName.IndexOf(",")); + // clean up module static resource folder string folder = Path.Combine(_environment.WebRootPath, "Modules\\" + moduledefinitionname); if (Directory.Exists(folder)) { Directory.Delete(folder, true); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Static Resources Removed For {ModuleDefinitionName}", moduledefinitionname); } + // remove module assembly from /bin string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - foreach (string file in Directory.EnumerateFiles(binfolder, moduledefinitionname + "*.dll")) + foreach (string file in Directory.EnumerateFiles(binfolder, moduledefinitionname + "*.*")) { System.IO.File.Delete(file); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Assemblies Removed For {ModuleDefinitionName}", moduledefinitionname); } + // remove module definition _moduleDefinitions.DeleteModuleDefinition(id, siteid); - _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Deleted {ModuleDefinitionId}", id); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition Deleted {ModuleDefinitionName}", moduledefinition.Name); + // restart application _installationManager.RestartApplication(); } } @@ -209,10 +247,7 @@ namespace Oqtane.Controllers if (Path.GetExtension(filePath) == ".sql") { // execute script in curent tenant - foreach (string query in text.Split("GO", StringSplitOptions.RemoveEmptyEntries)) - { - _sql.ExecuteNonQuery(_resolver.GetTenant(), query); - } + _sql.ExecuteScript(_resolver.GetTenant(), text); } } diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index d058d2fe..dabe8c78 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -11,7 +11,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; -using Oqtane.Controllers; using Oqtane.Extensions; using Oqtane.Models; using Oqtane.Repository; @@ -109,7 +108,7 @@ namespace Oqtane.Infrastructure } else { - Message = "Database is not avaiable"; + Message = "Database is not available"; } } else @@ -214,7 +213,7 @@ namespace Oqtane.Infrastructure Console.WriteLine($"Migrating assembly {assembly.FullName}"); var dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionString) - .WithScriptsEmbeddedInAssembly(assembly); // scripts must be included as Embedded Resources + .WithScriptsEmbeddedInAssembly(assembly, s => !s.ToLower().Contains("uninstall.sql")); // scripts must be included as Embedded Resources var dbUpgrade = dbUpgradeConfig.Build(); if (dbUpgrade.IsUpgradeRequired()) { @@ -368,8 +367,6 @@ namespace Oqtane.Infrastructure return value; } - - private static void CreateHostUser(IFolderRepository folderRepository, IUserRoleRepository userRoleRepository, IRoleRepository roleRepository, IUserRepository userRepository, UserManager identityUserManager, User user) { var identityUser = new IdentityUser {UserName = user.Username, Email = user.Email, EmailConfirmed = true}; @@ -427,7 +424,6 @@ namespace Oqtane.Infrastructure } } - public static bool TableExists(DbContext context, string tableName) { return TableExists(context, "dbo", tableName); diff --git a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs index 4bd04e91..29dfe8ba 100644 --- a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs @@ -5,6 +5,7 @@ namespace Oqtane.Repository { public interface ISqlRepository { + void ExecuteScript(Tenant tenant, string script); int ExecuteNonQuery(Tenant tenant, string query); SqlDataReader ExecuteReader(Tenant tenant, string query); } diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index 8d711245..0023c2e8 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -15,6 +15,7 @@ namespace Oqtane.Repository private MasterDBContext _db; private readonly IMemoryCache _cache; private readonly IPermissionRepository _permissions; + private List _moduleDefinitions; // lazy load public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions) { @@ -61,12 +62,12 @@ namespace Oqtane.Repository private List LoadSiteModuleDefinitions(int siteId) { - // get module assemblies - List moduleDefinitions = _cache.GetOrCreate("moduledefinitions", entry => + if (_moduleDefinitions == null) { - entry.SlidingExpiration = TimeSpan.FromMinutes(30); - return LoadModuleDefinitionsFromAssemblies(); - }); + // get module assemblies + _moduleDefinitions = LoadModuleDefinitionsFromAssemblies(); + } + List moduleDefinitions = _moduleDefinitions; // get module definition permissions for site List permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList(); @@ -210,14 +211,5 @@ namespace Oqtane.Repository return moduledefinitions; } - private string GetProperty(Dictionary properties, string key) - { - string value = ""; - if (properties.ContainsKey(key)) - { - value = properties[key]; - } - return value; - } } } diff --git a/Oqtane.Server/Repository/SqlRepository.cs b/Oqtane.Server/Repository/SqlRepository.cs index 2952752e..f20b28e6 100644 --- a/Oqtane.Server/Repository/SqlRepository.cs +++ b/Oqtane.Server/Repository/SqlRepository.cs @@ -8,6 +8,15 @@ namespace Oqtane.Repository public class SqlRepository : ISqlRepository { + public void ExecuteScript(Tenant tenant, string script) + { + // execute script in curent tenant + foreach (string query in script.Split("GO", StringSplitOptions.RemoveEmptyEntries)) + { + ExecuteNonQuery(tenant, query); + } + } + public int ExecuteNonQuery(Tenant tenant, string query) { SqlConnection conn = new SqlConnection(FormatConnectionString(tenant.DBConnectionString)); diff --git a/Oqtane.Server/Repository/ThemeRepository.cs b/Oqtane.Server/Repository/ThemeRepository.cs index 1fa7c813..943f42a8 100644 --- a/Oqtane.Server/Repository/ThemeRepository.cs +++ b/Oqtane.Server/Repository/ThemeRepository.cs @@ -9,7 +9,24 @@ namespace Oqtane.Repository { public class ThemeRepository : IThemeRepository { + private List _themes; // lazy load + + public IEnumerable GetThemes() + { + return LoadThemes(); + } + private List LoadThemes() + { + if (_themes == null) + { + // get themes + _themes = LoadThemesFromAssemblies(); + } + return _themes; + } + + private List LoadThemesFromAssemblies() { List themes = new List(); @@ -103,20 +120,5 @@ namespace Oqtane.Repository } return themes; } - - private string GetProperty(Dictionary properties, string key) - { - string value = ""; - if (properties.ContainsKey(key)) - { - value = properties[key]; - } - return value; - } - - public IEnumerable GetThemes() - { - return LoadThemes(); - } } }