Merge pull request #364 from sbwalker/master
added uninstall support for modules
This commit is contained in:
		| @ -1,5 +1,5 @@ | ||||
| /*   | ||||
| Create [Module] table | ||||
| Create [Owner][Module] table | ||||
| */ | ||||
|  | ||||
| CREATE TABLE [dbo].[[Owner][Module]]( | ||||
|  | ||||
							
								
								
									
										6
									
								
								Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Server/Scripts/Uninstall.sql
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								Oqtane.Client/Modules/Admin/ModuleCreator/Templates/External/Server/Scripts/Uninstall.sql
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| /*   | ||||
| Remove [Owner][Module] table | ||||
| */ | ||||
|  | ||||
| DROP TABLE [dbo].[[Owner][Module]] | ||||
| GO | ||||
| @ -1,5 +1,5 @@ | ||||
| /*   | ||||
| Create [Module] table | ||||
| Create [Owner][Module] table | ||||
| */ | ||||
|  | ||||
| CREATE TABLE [dbo].[[Owner][Module]]( | ||||
|  | ||||
| @ -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 | ||||
| @ -0,0 +1,6 @@ | ||||
| /*   | ||||
| Remove [Owner][Module] table | ||||
| */ | ||||
|  | ||||
| DROP TABLE [dbo].[[Owner][Module]] | ||||
| GO | ||||
| @ -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)) | ||||
| { | ||||
|     <div class="app-controlpanel" style="@_display"> | ||||
|  | ||||
| @ -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 | ||||
|         { | ||||
|  | ||||
| @ -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); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|  | ||||
| @ -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<IdentityUser> 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); | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
|  | ||||
| @ -15,6 +15,7 @@ namespace Oqtane.Repository | ||||
|         private MasterDBContext _db; | ||||
|         private readonly IMemoryCache _cache; | ||||
|         private readonly IPermissionRepository _permissions; | ||||
|         private List<ModuleDefinition> _moduleDefinitions; // lazy load | ||||
|  | ||||
|         public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions) | ||||
|         { | ||||
| @ -61,12 +62,12 @@ namespace Oqtane.Repository | ||||
|  | ||||
|         private List<ModuleDefinition> LoadSiteModuleDefinitions(int siteId) | ||||
|         { | ||||
|             // get module assemblies  | ||||
|             List<ModuleDefinition> moduleDefinitions = _cache.GetOrCreate("moduledefinitions", entry => | ||||
|             if (_moduleDefinitions == null) | ||||
|             { | ||||
|                 entry.SlidingExpiration = TimeSpan.FromMinutes(30); | ||||
|                 return LoadModuleDefinitionsFromAssemblies(); | ||||
|             }); | ||||
|                 // get module assemblies  | ||||
|                 _moduleDefinitions = LoadModuleDefinitionsFromAssemblies(); | ||||
|             } | ||||
|             List<ModuleDefinition> moduleDefinitions = _moduleDefinitions; | ||||
|  | ||||
|             // get module definition permissions for site | ||||
|             List<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList(); | ||||
| @ -210,14 +211,5 @@ namespace Oqtane.Repository | ||||
|             return moduledefinitions; | ||||
|         } | ||||
|  | ||||
|         private string GetProperty(Dictionary<string, string> properties, string key) | ||||
|         { | ||||
|             string value = ""; | ||||
|             if (properties.ContainsKey(key)) | ||||
|             { | ||||
|                 value = properties[key]; | ||||
|             } | ||||
|             return value; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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)); | ||||
|  | ||||
| @ -9,7 +9,24 @@ namespace Oqtane.Repository | ||||
| { | ||||
|     public class ThemeRepository : IThemeRepository | ||||
|     { | ||||
|         private List<Theme> _themes; // lazy load | ||||
|  | ||||
|         public IEnumerable<Theme> GetThemes() | ||||
|         { | ||||
|             return LoadThemes(); | ||||
|         } | ||||
|  | ||||
|         private List<Theme> LoadThemes() | ||||
|         { | ||||
|             if (_themes == null) | ||||
|             { | ||||
|                 // get themes | ||||
|                 _themes = LoadThemesFromAssemblies(); | ||||
|             } | ||||
|             return _themes; | ||||
|         } | ||||
|  | ||||
|         private List<Theme> LoadThemesFromAssemblies() | ||||
|         { | ||||
|             List<Theme> themes = new List<Theme>(); | ||||
|  | ||||
| @ -103,20 +120,5 @@ namespace Oqtane.Repository | ||||
|             } | ||||
|             return themes; | ||||
|         } | ||||
|  | ||||
|         private string GetProperty(Dictionary<string, string> properties, string key) | ||||
|         { | ||||
|             string value = ""; | ||||
|             if (properties.ContainsKey(key)) | ||||
|             { | ||||
|                 value = properties[key]; | ||||
|             } | ||||
|             return value; | ||||
|         } | ||||
|  | ||||
|         public IEnumerable<Theme> GetThemes() | ||||
|         { | ||||
|             return LoadThemes(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Shaun Walker
					Shaun Walker