diff --git a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor index d4ac4947..8e19125f 100644 --- a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor +++ b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor @@ -52,6 +52,7 @@ +Access Framework API @code { public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; diff --git a/Oqtane.Client/Modules/HtmlText/ModuleInfo.cs b/Oqtane.Client/Modules/HtmlText/ModuleInfo.cs index 48b674d5..4be90a8b 100644 --- a/Oqtane.Client/Modules/HtmlText/ModuleInfo.cs +++ b/Oqtane.Client/Modules/HtmlText/ModuleInfo.cs @@ -9,7 +9,8 @@ namespace Oqtane.Modules.HtmlText Name = "HtmlText", Description = "Renders HTML or Text Content", Version = "1.0.0", - ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server" + ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server", + ReleaseVersions = "1.0.0" }; } } diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index b197e62d..74ff43b9 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -12,7 +12,7 @@ using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Security; using System; -using System.Runtime.InteropServices.ComTypes; +using Microsoft.Extensions.DependencyInjection; // ReSharper disable StringIndexOfIsCultureSpecific.1 namespace Oqtane.Controllers @@ -25,21 +25,17 @@ namespace Oqtane.Controllers private readonly IUserPermissions _userPermissions; private readonly IInstallationManager _installationManager; private readonly IWebHostEnvironment _environment; - private readonly ITenantResolver _resolver; - private readonly ITenantRepository _tenants; - private readonly ISqlRepository _sql; + private readonly IServiceProvider _serviceProvider; private readonly ILogManager _logger; - public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository modules, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantResolver resolver, ITenantRepository tenants, ISqlRepository sql, ILogManager logger) + public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository modules, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ILogManager logger) { _moduleDefinitions = moduleDefinitions; _modules = modules; _userPermissions = userPermissions; _installationManager = installationManager; _environment = environment; - _resolver = resolver; - _tenants = tenants; - _sql = sql; + _serviceProvider = serviceProvider; _logger = logger; } @@ -100,57 +96,40 @@ namespace Oqtane.Controllers [Authorize(Roles = Constants.HostRole)] public void Delete(int id, int siteid) { - List moduledefinitions = _moduleDefinitions.GetModuleDefinitions(siteid).ToList(); - ModuleDefinition moduledefinition = moduledefinitions.Where(item => item.ModuleDefinitionId == id).FirstOrDefault(); + ModuleDefinition moduledefinition = _moduleDefinitions.GetModuleDefinition(id, siteid); if (moduledefinition != null) { - // server assembly name should follow client naming convention - string assemblyname = Utilities.GetAssemblyName(moduledefinition.ModuleDefinitionName).Replace(".Client",".Server"); - - string uninstallScript = ""; - Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().SingleOrDefault(a => a.GetName().Name == assemblyname); - if (assembly != null) + if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType)) { - Stream resourceStream = assembly.GetManifestResourceStream(assemblyname + ".Scripts.Uninstall.sql"); - if (resourceStream != null) + Type moduletype = Type.GetType(moduledefinition.ServerManagerType); + if (moduletype != null && moduletype.GetInterface("IInstallable") != null) { - using (var reader = new StreamReader(resourceStream)) - { - uninstallScript = reader.ReadToEnd(); - } + var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype); + ((IInstallable)moduleobject).Uninstall(); } } - 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 {AssemblyName}", assemblyname); - } - // clean up module schema versions - _sql.ExecuteNonQuery(tenant, "DELETE FROM [dbo].[SchemaVersions] WHERE ScriptName LIKE '" + assemblyname + "%'"); - _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Schema Versions Removed For {AssemblyName}", assemblyname); - } - // format root assembly name - assemblyname = assemblyname.Replace(".Server", ""); - - // clean up module static resource folder - string folder = Path.Combine(_environment.WebRootPath, "Modules\\" + assemblyname); - if (Directory.Exists(folder)) + string assemblyname = Utilities.GetAssemblyName(moduledefinition.ModuleDefinitionName); + if (assemblyname != "Oqtane.Client") { - Directory.Delete(folder, true); - _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Static Resources Removed For {AssemblynName}", assemblyname); - } + assemblyname = assemblyname.Replace(".Client", ""); - // remove module assembly from /bin - string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - foreach (string file in Directory.EnumerateFiles(binfolder, assemblyname + "*.*")) - { - System.IO.File.Delete(file); - _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Assembly Removed {Filename}", file); + // clean up module static resource folder + string folder = Path.Combine(_environment.WebRootPath, "Modules\\" + assemblyname); + if (Directory.Exists(folder)) + { + Directory.Delete(folder, true); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Static Resources Removed For {AssemblynName}", assemblyname); + } + + // remove module assembly from /bin + string binfolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + foreach (string file in Directory.EnumerateFiles(binfolder, assemblyname + "*.*")) + { + System.IO.File.Delete(file); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Assembly Removed {Filename}", file); + } } // remove module definition @@ -247,12 +226,6 @@ namespace Oqtane.Controllers text = text.Replace("[File]", Path.GetFileName(filePath)); text = text.Replace("[FrameworkVersion]", Constants.Version); System.IO.File.WriteAllText(filePath, text); - - if (Path.GetExtension(filePath).ToLower() == ".sql" && !filePath.ToLower().Contains("uninstall")) - { - // execute installation script in curent tenant - _sql.ExecuteScript(_resolver.GetTenant(), text); - } } DirectoryInfo[] folders = current.GetDirectories(); diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 76e91390..f283e075 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -184,7 +184,7 @@ namespace Oqtane.Infrastructure .SqlDatabase(connectionString) .WithVariable("ConnectionString", connectionString) .WithVariable("Alias", alias) - .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => master || !s.Contains("Master.")); + .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Master.")); var dbUpgrade = dbUpgradeConfig.Build(); if (!dbUpgrade.IsUpgradeRequired()) diff --git a/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs b/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs new file mode 100644 index 00000000..c71de4f6 --- /dev/null +++ b/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs @@ -0,0 +1,8 @@ +namespace Oqtane.Infrastructure +{ + public interface IInstallable + { + bool Install(string version); + bool Uninstall(); + } +} diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs index f37ac523..4d38e250 100644 --- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs +++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs @@ -1,17 +1,31 @@ -using Oqtane.Models; +using Oqtane.Infrastructure; +using Oqtane.Models; +using Oqtane.Repository; using Oqtane.Modules.HtmlText.Models; using Oqtane.Modules.HtmlText.Repository; using System.Net; namespace Oqtane.Modules.HtmlText.Manager { - public class HtmlTextManager : IPortable + public class HtmlTextManager : IInstallable, IPortable { private IHtmlTextRepository _htmlTexts; + private ISqlRepository _sql; - public HtmlTextManager(IHtmlTextRepository htmltexts) + public HtmlTextManager(IHtmlTextRepository htmltexts, ISqlRepository sql) { _htmlTexts = htmltexts; + _sql = sql; + } + + public bool Install(string version) + { + return _sql.ExecuteEmbeddedScript(GetType().Assembly, "HtmlText." + version + ".sql"); + } + + public bool Uninstall() + { + return _sql.ExecuteEmbeddedScript(GetType().Assembly, "HtmlText.Uninstall.sql"); } public string ExportModule(Module module) diff --git a/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.1.0.0.sql b/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.1.0.0.sql new file mode 100644 index 00000000..5c54eae2 --- /dev/null +++ b/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.1.0.0.sql @@ -0,0 +1,19 @@ +CREATE TABLE [dbo].[HtmlText]( + [HtmlTextId] [int] IDENTITY(1,1) NOT NULL, + [ModuleId] [int] NOT NULL, + [Content] [nvarchar](max) NOT NULL, + [CreatedBy] [nvarchar](256) NOT NULL, + [CreatedOn] [datetime] NOT NULL, + [ModifiedBy] [nvarchar](256) NOT NULL, + [ModifiedOn] [datetime] NOT NULL, + CONSTRAINT [PK_HtmlText] PRIMARY KEY CLUSTERED + ( + [HtmlTextId] ASC + ) +) +GO + +ALTER TABLE [dbo].[HtmlText] WITH CHECK ADD CONSTRAINT [FK_HtmlText_Module] FOREIGN KEY([ModuleId]) +REFERENCES [dbo].[Module] ([ModuleId]) +ON DELETE CASCADE +GO diff --git a/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.Uninstall.sql b/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.Uninstall.sql new file mode 100644 index 00000000..b0831b67 --- /dev/null +++ b/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.Uninstall.sql @@ -0,0 +1,2 @@ +DROP TABLE [dbo].[HtmlText] +GO diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index fd6d457d..63634523 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -18,6 +18,13 @@ + + + + + + + diff --git a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs index 29dfe8ba..037e9416 100644 --- a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs @@ -1,10 +1,12 @@ using System.Data.SqlClient; +using System.Reflection; using Oqtane.Models; namespace Oqtane.Repository { public interface ISqlRepository { + bool ExecuteEmbeddedScript(Assembly assembly, string script); 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 4803ba9e..5bc8ee64 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -188,18 +188,15 @@ namespace Oqtane.Repository { Name = moduleType.Substring(moduleType.LastIndexOf(".") + 1), Description = "Manage " + moduleType.Substring(moduleType.LastIndexOf(".") + 1), - Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : ""), - Version = "1.0.0" + Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "") }; } // set internal properties moduledefinition.ModuleDefinitionName = qualifiedModuleType; + moduledefinition.Version = ""; // will be populated from database moduledefinition.ControlTypeTemplate = moduleType + "." + Constants.ActionToken + ", " + typename[1]; moduledefinition.AssemblyName = assembly.FullName.Split(",")[0]; - if (assembly.FullName.StartsWith("Oqtane.Client")) - { - moduledefinition.Version = Constants.Version; - } + if (string.IsNullOrEmpty(moduledefinition.Categories)) { moduledefinition.Categories = "Common"; diff --git a/Oqtane.Server/Repository/ModuleRepository.cs b/Oqtane.Server/Repository/ModuleRepository.cs index 7d88f0a1..1ec27d3f 100644 --- a/Oqtane.Server/Repository/ModuleRepository.cs +++ b/Oqtane.Server/Repository/ModuleRepository.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index f85294df..5861aa04 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -20,7 +19,6 @@ namespace Oqtane.Repository private readonly IRoleRepository _roleRepository; private readonly IProfileRepository _profileRepository; private readonly IFolderRepository _folderRepository; - private readonly IFileRepository _fileRepository; private readonly IPageRepository _pageRepository; private readonly IModuleRepository _moduleRepository; private readonly IPageModuleRepository _pageModuleRepository; @@ -30,15 +28,14 @@ namespace Oqtane.Repository private readonly IConfigurationRoot _config; - public SiteRepository(TenantDBContext context, IRoleRepository roleRepository, IProfileRepository profileRepository, IFolderRepository folderRepository, IFileRepository fileRepository, IPageRepository pageRepository, - IModuleRepository moduleRepository, IPageModuleRepository pageModuleRepository, IModuleDefinitionRepository moduleDefinitionRepository, IPermissionRepository permissionRepository, IServiceProvider serviceProvider, + public SiteRepository(TenantDBContext context, IRoleRepository roleRepository, IProfileRepository profileRepository, IFolderRepository folderRepository, IPageRepository pageRepository, + IModuleRepository moduleRepository, IPageModuleRepository pageModuleRepository, IModuleDefinitionRepository moduleDefinitionRepository, IServiceProvider serviceProvider, IConfigurationRoot config) { _db = context; _roleRepository = roleRepository; _profileRepository = profileRepository; _folderRepository = folderRepository; - _fileRepository = fileRepository; _pageRepository = pageRepository; _moduleRepository = moduleRepository; _pageModuleRepository = pageModuleRepository; @@ -787,7 +784,14 @@ namespace Oqtane.Repository if (moduletype != null && moduletype.GetInterface("IPortable") != null) { var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype); - ((IPortable) moduleobject).ImportModule(module, pagetemplatemodule.Content, moduledefinition.Version); + try + { + ((IPortable)moduleobject).ImportModule(module, pagetemplatemodule.Content, moduledefinition.Version); + } + catch + { + // error in module import + } } } diff --git a/Oqtane.Server/Repository/SqlRepository.cs b/Oqtane.Server/Repository/SqlRepository.cs index f20b28e6..0e5c2f3c 100644 --- a/Oqtane.Server/Repository/SqlRepository.cs +++ b/Oqtane.Server/Repository/SqlRepository.cs @@ -1,12 +1,61 @@ using System; using System.Data; using System.Data.SqlClient; +using System.IO; +using System.Linq; +using System.Reflection; using Oqtane.Models; namespace Oqtane.Repository { public class SqlRepository : ISqlRepository { + private readonly ITenantRepository _tenants; + + public SqlRepository(ITenantRepository tenants) + { + _tenants = tenants; + } + + public bool ExecuteEmbeddedScript(Assembly assembly, string filename) + { + // script must be included as an Embedded Resource within an assembly + bool success = true; + string uninstallScript = ""; + + if (assembly != null) + { + string name = assembly.GetManifestResourceNames().FirstOrDefault(item => item.EndsWith("." + filename)); + if (name != null) + { + Stream resourceStream = assembly.GetManifestResourceStream(name); + if (resourceStream != null) + { + using (var reader = new StreamReader(resourceStream)) + { + uninstallScript = reader.ReadToEnd(); + } + } + } + } + + if (!string.IsNullOrEmpty(uninstallScript)) + { + foreach (Tenant tenant in _tenants.GetTenants()) + { + try + { + ExecuteScript(tenant, uninstallScript); + } + catch + { + success = false; + } + } + } + + return success; + } public void ExecuteScript(Tenant tenant, string script) { diff --git a/Oqtane.Shared/Models/ModuleDefinition.cs b/Oqtane.Shared/Models/ModuleDefinition.cs index 82836f0c..504cc890 100644 --- a/Oqtane.Shared/Models/ModuleDefinition.cs +++ b/Oqtane.Shared/Models/ModuleDefinition.cs @@ -19,6 +19,7 @@ namespace Oqtane.Models PermissionNames = ""; ServerManagerType = ""; ControlTypeRoutes = ""; + ReleaseVersions = ""; Template = ""; } @@ -34,8 +35,7 @@ namespace Oqtane.Models public string ModifiedBy { get; set; } public DateTime ModifiedOn { get; set; } - [NotMapped] - public int SiteId { get; set; } + // additional IModule properties [NotMapped] public string Owner { get; set; } [NotMapped] @@ -53,12 +53,18 @@ namespace Oqtane.Models [NotMapped] public string ControlTypeRoutes { get; set; } [NotMapped] - public string Template { get; set; } + public string ReleaseVersions { get; set; } + + // internal properties + [NotMapped] + public int SiteId { get; set; } [NotMapped] public string ControlTypeTemplate { get; set; } [NotMapped] public string AssemblyName { get; set; } [NotMapped] public string Permissions { get; set; } + [NotMapped] + public string Template { get; set; } } }