-
+
@((MarkupString) _message)
@@ -146,7 +146,7 @@
private async Task Install()
{
- if (_hostUsername != "" && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && _hostEmail != "")
+ if (_serverName != "" && _databaseName != "" && _hostUsername != "" && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && _hostEmail != "")
{
_loadingDisplay = "";
StateHasChanged();
@@ -169,19 +169,24 @@
}
}
+ Uri uri = new Uri(NavigationManager.Uri);
+
var config = new InstallConfig
{
ConnectionString = connectionstring,
- HostUser = _hostUsername,
+ Aliases = uri.Authority,
HostEmail = _hostEmail,
- Password = _hostPassword,
- IsMaster = true,
+ HostPassword = _hostPassword,
+ HostName = Constants.HostUser,
+ TenantName = Constants.MasterTenant,
+ IsNewTenant = true,
+ SiteName = Constants.DefaultSite
};
var installation = await InstallationService.Install(config);
if (installation.Success)
{
- NavigationManager.NavigateTo("", true);
+ NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true);
}
else
{
diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs
index b57d1444..73aaf029 100644
--- a/Oqtane.Client/UI/Interop.cs
+++ b/Oqtane.Client/UI/Interop.cs
@@ -73,13 +73,13 @@ namespace Oqtane.UI
}
}
- public Task IncludeLink(string id, string rel, string url, string type)
+ public Task IncludeLink(string id, string rel, string url, string type, string integrity, string crossorigin)
{
try
{
_jsRuntime.InvokeAsync
(
"interop.includeLink",
- id, rel, url, type);
+ id, rel, url, type, integrity, crossorigin);
return Task.CompletedTask;
}
catch
@@ -88,13 +88,13 @@ namespace Oqtane.UI
}
}
- public Task IncludeScript(string id, string src, string content, string location)
+ public Task IncludeScript(string id, string src, string content, string location, string integrity, string crossorigin)
{
try
{
_jsRuntime.InvokeAsync(
"interop.includeScript",
- id, src, content, location);
+ id, src, content, location, integrity, crossorigin);
return Task.CompletedTask;
}
catch
diff --git a/Oqtane.Client/UI/ThemeBuilder.razor b/Oqtane.Client/UI/ThemeBuilder.razor
index 4f5d76f2..1649ac89 100644
--- a/Oqtane.Client/UI/ThemeBuilder.razor
+++ b/Oqtane.Client/UI/ThemeBuilder.razor
@@ -22,7 +22,7 @@
}
if (PageState.Site.FaviconFileId != null)
{
- await interop.IncludeLink("fav-icon", "shortcut icon", Utilities.ContentUrl(PageState.Alias.Path, PageState.Site.FaviconFileId.Value), "image/x-icon");
+ await interop.IncludeLink("fav-icon", "shortcut icon", Utilities.ContentUrl(PageState.Alias.Path, PageState.Site.FaviconFileId.Value), "image/x-icon", "", "");
}
if (PageState.Site.PwaIsEnabled)
{
@@ -74,7 +74,7 @@
"document.getElementById('pwa-manifest').setAttribute('href', url); " +
"} " +
", 1000);";
- await interop.IncludeScript("pwa-manifestscript", "", manifest, "body");
+ await interop.IncludeScript("pwa-manifestscript", "", manifest, "body", "", "");
// service worker must be in root of site
string serviceworker = "if ('serviceWorker' in navigator) { " +
@@ -84,6 +84,6 @@
"console.log('ServiceWorker Registration Failed ', err); " +
"}); " +
"}";
- await interop.IncludeScript("pwa-serviceworker", "", serviceworker, "body");
+ await interop.IncludeScript("pwa-serviceworker", "", serviceworker, "body", "", "");
}
}
diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs
index 713d065a..b930db48 100644
--- a/Oqtane.Server/Controllers/InstallationController.cs
+++ b/Oqtane.Server/Controllers/InstallationController.cs
@@ -5,8 +5,6 @@ using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Infrastructure;
-// ReSharper disable StringIndexOfIsCultureSpecific.1
-
namespace Oqtane.Controllers
{
[Route("{site}/api/[controller]")]
@@ -14,9 +12,9 @@ namespace Oqtane.Controllers
{
private readonly IConfigurationRoot _config;
private readonly IInstallationManager _installationManager;
- private readonly DatabaseManager _databaseManager;
+ private readonly IDatabaseManager _databaseManager;
- public InstallationController(IConfigurationRoot config, IInstallationManager installationManager, DatabaseManager databaseManager)
+ public InstallationController(IConfigurationRoot config, IInstallationManager installationManager, IDatabaseManager databaseManager)
{
_config = config;
_installationManager = installationManager;
@@ -27,33 +25,17 @@ namespace Oqtane.Controllers
[HttpPost]
public Installation Post([FromBody] InstallConfig config)
{
- //TODO Security ????
var installation = new Installation {Success = false, Message = ""};
- if (ModelState.IsValid && (!_databaseManager.IsInstalled || !config.IsMaster))
+ if (ModelState.IsValid && (User.IsInRole(Constants.HostRole) || string.IsNullOrEmpty(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
{
- bool master = config.IsMaster;
-
- config.Alias = config.Alias ?? HttpContext.Request.Host.Value;
- var result = DatabaseManager.InstallDatabase(config);
-
- if (result.Success)
- {
- if (master)
- {
- _config.Reload();
- }
-
- _databaseManager.BuildDefaultSite(config.Password, config.HostEmail);
- installation.Success = true;
- return installation;
- }
-
- installation.Message = result.Message;
- return installation;
+ installation = _databaseManager.Install(config);
+ }
+ else
+ {
+ installation.Message = "Installation Not Authorized";
}
- installation.Message = "Application Is Already Installed";
return installation;
}
@@ -61,12 +43,8 @@ namespace Oqtane.Controllers
[HttpGet("installed")]
public Installation IsInstalled()
{
- var installation = new Installation {Success = false, Message = ""};
-
- installation.Success = _databaseManager.IsInstalled;
- installation.Message = _databaseManager.Message;
-
- return installation;
+ bool isInstalled = _databaseManager.IsInstalled();
+ return new Installation {Success = isInstalled, Message = string.Empty};
}
[HttpGet("upgrade")]
diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs
index 4ba5d613..673e42b3 100644
--- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs
+++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs
@@ -22,16 +22,18 @@ namespace Oqtane.Controllers
{
private readonly IModuleDefinitionRepository _moduleDefinitions;
private readonly IModuleRepository _modules;
+ private readonly ITenantRepository _tenants;
private readonly IUserPermissions _userPermissions;
private readonly IInstallationManager _installationManager;
private readonly IWebHostEnvironment _environment;
private readonly IServiceProvider _serviceProvider;
private readonly ILogManager _logger;
- public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository modules, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ILogManager logger)
+ public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository modules,ITenantRepository tenants, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ILogManager logger)
{
_moduleDefinitions = moduleDefinitions;
_modules = modules;
+ _tenants = tenants;
_userPermissions = userPermissions;
_installationManager = installationManager;
_environment = environment;
@@ -104,8 +106,18 @@ namespace Oqtane.Controllers
Type moduletype = Type.GetType(moduledefinition.ServerManagerType);
if (moduletype != null && moduletype.GetInterface("IInstallable") != null)
{
- var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
- ((IInstallable)moduleobject).Uninstall();
+ foreach (Tenant tenant in _tenants.GetTenants())
+ {
+ try
+ {
+ var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
+ ((IInstallable)moduleobject).Uninstall(tenant);
+ }
+ catch
+ {
+ // an error occurred executing the uninstall
+ }
+ }
}
}
@@ -190,6 +202,11 @@ namespace Oqtane.Controllers
module.ModuleDefinitionName = moduleDefinition.ModuleDefinitionName;
_modules.UpdateModule(module);
+ if (moduleDefinition.Template == "internal")
+ {
+ // need logic to add embedded scripts to Oqtane.Server.csproj - also you need to remove them on uninstall
+ }
+
_installationManager.RestartApplication();
}
}
diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs
index a896a337..7fd44c66 100644
--- a/Oqtane.Server/Infrastructure/DatabaseManager.cs
+++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs
@@ -1,13 +1,13 @@
using System;
using System.Collections.Generic;
using System.Data;
-using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using DbUp;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
@@ -15,193 +15,172 @@ using Oqtane.Extensions;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
+using Oqtane.Enums;
using File = System.IO.File;
namespace Oqtane.Infrastructure
{
- public class DatabaseManager
+ public class DatabaseManager : IDatabaseManager
{
private readonly IConfigurationRoot _config;
private readonly IServiceScopeFactory _serviceScopeFactory;
- private bool _isInstalled;
+ private readonly IMemoryCache _cache;
- public DatabaseManager(IConfigurationRoot config, IServiceScopeFactory serviceScopeFactory)
+ public DatabaseManager(IConfigurationRoot config, IServiceScopeFactory serviceScopeFactory, IMemoryCache cache)
{
_config = config;
_serviceScopeFactory = serviceScopeFactory;
+ _cache = cache;
}
- public string Message { get; set; }
-
- public void StartupMigration()
+ public bool IsInstalled()
{
- var defaultConnectionString = _config.GetConnectionString(SettingKeys.ConnectionStringKey);
- var defaultAlias = GetInstallationConfig(SettingKeys.DefaultAliasKey, string.Empty);
- var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString();
-
- //create data directory if does not exists
- if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory);
-
- // if no values specified, fallback to IDE installer
- if (string.IsNullOrEmpty(defaultConnectionString))
- {
- IsInstalled = false;
- return;
- }
-
- var freshInstall = !IsMasterInstalled(defaultConnectionString);
- var password = GetInstallationConfig(SettingKeys.HostPasswordKey, String.Empty);
- var email = GetInstallationConfig(SettingKeys.HostEmailKey, String.Empty);
- if (freshInstall && (string.IsNullOrEmpty(password) || string.IsNullOrEmpty(email) || string.IsNullOrEmpty(defaultAlias)))
- {
- IsInstalled = false;
- Message = "Incomplete startup install configuration";
- return;
- }
-
- var result = MasterMigration(defaultConnectionString, defaultAlias, null, true);
- IsInstalled = result.Success;
-
- if (result.Success)
- {
- WriteVersionInfo(defaultConnectionString);
- TenantMigration(defaultConnectionString, dataDirectory);
- }
-
- if (_isInstalled && !IsDefaultSiteInstalled(defaultConnectionString))
- {
- BuildDefaultSite(password,email);
- }
- }
-
- public bool IsInstalled
- {
- get
- {
- if (!_isInstalled) _isInstalled = CheckInstallState();
-
- return _isInstalled;
- }
- set => _isInstalled = value;
- }
-
- private bool CheckInstallState()
- {
- var defaultConnectionString = _config.GetConnectionString(SettingKeys.ConnectionStringKey);
+ var defaultConnectionString = NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey));
var result = !string.IsNullOrEmpty(defaultConnectionString);
if (result)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
- var dbContext = scope.ServiceProvider.GetRequiredService();
- result = dbContext.Database.CanConnect();
+ var db = scope.ServiceProvider.GetRequiredService();
+ result = db.Database.CanConnect();
+ if (result)
+ {
+ try
+ {
+ result = db.Tenant.Any();
+ }
+ catch
+ {
+ result = false;
+ }
+ }
}
- if (result)
- {
- //I think this is obsolete now and not accurate, maybe check presence of some table, Version ???
- var dbUpgradeConfig = DeployChanges
- .To
- .SqlDatabase(defaultConnectionString)
- .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Master"));
+ }
+ return result;
+ }
- result = !dbUpgradeConfig.Build().IsUpgradeRequired();
- if (!result) Message = "Master Installation Scripts Have Not Been Executed";
+ public Installation Install()
+ {
+ return Install(null);
+ }
+
+ public Installation Install(InstallConfig install)
+ {
+ var result = new Installation { Success = false, Message = string.Empty };
+
+ // get configuration
+ if (install == null)
+ {
+ // startup or silent installation
+ install = new InstallConfig { ConnectionString = _config.GetConnectionString(SettingKeys.ConnectionStringKey), TenantName = Constants.MasterTenant, IsNewTenant = false };
+
+ if (!IsInstalled())
+ {
+ install.Aliases = GetInstallationConfig(SettingKeys.DefaultAliasKey, string.Empty);
+ install.HostPassword = GetInstallationConfig(SettingKeys.HostPasswordKey, string.Empty);
+ install.HostEmail = GetInstallationConfig(SettingKeys.HostEmailKey, string.Empty);
+
+ if (!string.IsNullOrEmpty(install.ConnectionString) && !string.IsNullOrEmpty(install.Aliases) && !string.IsNullOrEmpty(install.HostPassword) && !string.IsNullOrEmpty(install.HostEmail))
+ {
+ // silent install
+ install.HostName = Constants.HostUser;
+ install.SiteTemplate = GetInstallationConfig(SettingKeys.SiteTemplateKey, Constants.DefaultSiteTemplate);
+ install.DefaultTheme = GetInstallationConfig(SettingKeys.DefaultThemeKey, Constants.DefaultTheme);
+ install.DefaultLayout = GetInstallationConfig(SettingKeys.DefaultLayoutKey, Constants.DefaultLayout);
+ install.DefaultContainer = GetInstallationConfig(SettingKeys.DefaultContainerKey, Constants.DefaultContainer);
+ install.SiteName = Constants.DefaultSite;
+ install.IsNewTenant = true;
+ }
+ else
+ {
+ // silent installation is missing required information
+ install.ConnectionString = "";
+ }
+ }
+ }
+ else
+ {
+ // install wizard or add new site
+ if (!string.IsNullOrEmpty(install.Aliases))
+ {
+ if (string.IsNullOrEmpty(install.SiteTemplate))
+ {
+ install.SiteTemplate = GetInstallationConfig(SettingKeys.SiteTemplateKey, Constants.DefaultSiteTemplate);
+ }
+ if (string.IsNullOrEmpty(install.DefaultTheme))
+ {
+ install.DefaultTheme = GetInstallationConfig(SettingKeys.DefaultThemeKey, Constants.DefaultTheme);
+ if (string.IsNullOrEmpty(install.DefaultLayout))
+ {
+ install.DefaultLayout = GetInstallationConfig(SettingKeys.DefaultLayoutKey, Constants.DefaultLayout);
+ }
+ }
+ if (string.IsNullOrEmpty(install.DefaultContainer))
+ {
+ install.DefaultContainer = GetInstallationConfig(SettingKeys.DefaultContainerKey, Constants.DefaultContainer);
+ }
}
else
{
- Message = "Database is not available";
+ result.Message = "Invalid Installation Configuration";
+ install.ConnectionString = "";
}
}
- else
+
+ // proceed with installation/migration
+ if (!string.IsNullOrEmpty(install.ConnectionString))
{
- Message = "Connection string is empty";
+ result = CreateDatabase(install);
+ if (result.Success)
+ {
+ result = MigrateMaster(install);
+ if (result.Success)
+ {
+ result = CreateTenant(install);
+ if (result.Success)
+ {
+ result = MigrateTenants(install);
+ if (result.Success)
+ {
+ result = MigrateModules(install);
+ if (result.Success)
+ {
+ result = CreateSite(install);
+ }
+ }
+ }
+ }
+ }
}
+
return result;
}
-
- public static string NormalizeConnectionString(string connectionString, string dataDirectory)
+ private Installation CreateDatabase(InstallConfig install)
{
- connectionString = connectionString
- .Replace("|DataDirectory|", dataDirectory);
- return connectionString;
- }
+ var result = new Installation { Success = false, Message = string.Empty };
- public static Installation InstallDatabase([NotNull] InstallConfig installConfig)
- {
- var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString();
- var result = new Installation {Success = false, Message = ""};
-
- var alias = installConfig.Alias;
- var connectionString = NormalizeConnectionString(installConfig.ConnectionString, dataDirectory);
-
- if (!string.IsNullOrEmpty(connectionString) && !string.IsNullOrEmpty(alias))
+ if (install.IsNewTenant)
{
- result = MasterMigration(connectionString, alias, result, installConfig.IsMaster);
- if (installConfig.IsMaster && result.Success)
+ try
{
- WriteVersionInfo(connectionString);
- TenantMigration(connectionString, dataDirectory);
- UpdateConnectionStringSetting(connectionString);
+ //create data directory if does not exist
+ var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString();
+ if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory);
+
+ using (var dbc = new DbContext(new DbContextOptionsBuilder().UseSqlServer(NormalizeConnectionString(install.ConnectionString)).Options))
+ {
+ // create empty database if it does not exist
+ dbc.Database.EnsureCreated();
+ result.Success = true;
+ }
}
- return result;
- }
-
- result = new Installation
- {
- Success = false,
- Message = "Connection string is empty",
- };
- return result;
- }
-
- private static Installation MasterMigration(string connectionString, string alias, Installation result, bool master)
- {
- if (result == null) result = new Installation {Success = false, Message = string.Empty};
-
- bool firstInstall;
- try
- {
- // create empty database if does not exists
- // dbup database creation does not work correctly on localdb databases
- using (var dbc = new DbContext(new DbContextOptionsBuilder().UseSqlServer(connectionString).Options))
+ catch (Exception ex)
{
- dbc.Database.EnsureCreated();
- //check for vanilla db
- firstInstall = !TableExists(dbc, "SchemaVersions");
+ result.Message = ex.Message;
}
}
- catch (Exception e)
- {
- result.Message = e.Message;
- Console.WriteLine(e);
- return result;
- }
- // when alias is not specified on first install, fallback to ide
- if (firstInstall && string.IsNullOrEmpty(alias)) return result;
-
- var dbUpgradeConfig = DeployChanges
- .To
- .SqlDatabase(connectionString)
- .WithVariable("ConnectionString", connectionString)
- .WithVariable("Alias", alias)
- .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Master."));
-
- var dbUpgrade = dbUpgradeConfig.Build();
- if (!dbUpgrade.IsUpgradeRequired())
- {
- result.Success = true;
- result.Message = string.Empty;
- return result;
- }
-
- var upgradeResult = dbUpgrade.PerformUpgrade();
- if (!upgradeResult.Successful)
- {
- Console.WriteLine(upgradeResult.Error.Message);
- result.Message = upgradeResult.Error.Message;
- }
else
{
result.Success = true;
@@ -210,74 +189,326 @@ namespace Oqtane.Infrastructure
return result;
}
- private static void ModuleMigration(Assembly assembly, string connectionString)
+ private Installation MigrateMaster(InstallConfig install)
{
- Console.WriteLine($"Migrating assembly {assembly.FullName}");
- var dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionString)
- .WithScriptsEmbeddedInAssembly(assembly, s => !s.ToLower().Contains("uninstall.sql")); // scripts must be included as Embedded Resources
- var dbUpgrade = dbUpgradeConfig.Build();
- if (dbUpgrade.IsUpgradeRequired())
+ var result = new Installation { Success = false, Message = string.Empty };
+
+ if (install.TenantName == Constants.MasterTenant)
{
- var result = dbUpgrade.PerformUpgrade();
- if (!result.Successful)
+ var upgradeConfig = DeployChanges
+ .To
+ .SqlDatabase(NormalizeConnectionString(install.ConnectionString))
+ .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Master."));
+
+ var upgrade = upgradeConfig.Build();
+ if (upgrade.IsUpgradeRequired())
{
- // TODO: log result.Error.Message - problem is logger is not available here
+ var upgradeResult = upgrade.PerformUpgrade();
+ result.Success = upgradeResult.Successful;
+ if (!result.Success)
+ {
+ result.Message = upgradeResult.Error.Message;
+ }
+ }
+ else
+ {
+ result.Success = true;
+ }
+
+ if (result.Success)
+ {
+ CreateApplicationVersion(install.ConnectionString);
+ UpdateConnectionString(install.ConnectionString);
}
}
+ else
+ {
+ result.Success = true;
+ }
+
+ return result;
}
- private static void WriteVersionInfo(string connectionString)
+ private Installation CreateTenant(InstallConfig install)
{
- using (var db = new InstallationContext(connectionString))
+ var result = new Installation { Success = false, Message = string.Empty };
+
+ if (!string.IsNullOrEmpty(install.TenantName) && !string.IsNullOrEmpty(install.Aliases))
{
- var version = db.ApplicationVersion.ToList().LastOrDefault();
- if (version == null || version.Version != Constants.Version)
+ using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
{
- version = new ApplicationVersion {Version = Constants.Version, CreatedOn = DateTime.UtcNow};
+ Tenant tenant;
+ if (install.IsNewTenant)
+ {
+ tenant = new Tenant { Name = install.TenantName, DBConnectionString = DenormalizeConnectionString(install.ConnectionString), CreatedBy = "", CreatedOn = DateTime.UtcNow, ModifiedBy = "", ModifiedOn = DateTime.UtcNow };
+ db.Tenant.Add(tenant);
+ db.SaveChanges();
+ _cache.Remove("tenants");
+
+ if (install.TenantName == Constants.MasterTenant)
+ {
+ var job = new Job { Name = "Notification Job", JobType = "Oqtane.Infrastructure.NotificationJob, Oqtane.Server", Frequency = "m", Interval = 1, StartDate = null, EndDate = null, IsEnabled = false, IsStarted = false, IsExecuting = false, NextExecution = null, RetentionHistory = 10, CreatedBy = "", CreatedOn = DateTime.UtcNow, ModifiedBy = "", ModifiedOn = DateTime.UtcNow };
+ db.Job.Add(job);
+ db.SaveChanges();
+ _cache.Remove("jobs");
+ }
+ }
+ else
+ {
+ tenant = db.Tenant.FirstOrDefault(item => item.Name == install.TenantName);
+ }
+
+ foreach (string aliasname in install.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ var alias = new Alias { Name = aliasname, TenantId = tenant.TenantId, SiteId = -1, CreatedBy = "", CreatedOn = DateTime.UtcNow, ModifiedBy = "", ModifiedOn = DateTime.UtcNow };
+ db.Alias.Add(alias);
+ db.SaveChanges();
+ }
+ _cache.Remove("aliases");
+ }
+ }
+
+ result.Success = true;
+
+ return result;
+ }
+
+ private Installation MigrateTenants(InstallConfig install)
+ {
+ var result = new Installation { Success = false, Message = string.Empty };
+
+ using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
+ {
+ foreach (var tenant in db.Tenant.ToList())
+ {
+ var upgradeConfig = DeployChanges.To.SqlDatabase(NormalizeConnectionString(tenant.DBConnectionString))
+ .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Tenant"));
+
+ var upgrade = upgradeConfig.Build();
+ if (upgrade.IsUpgradeRequired())
+ {
+ var upgradeResult = upgrade.PerformUpgrade();
+ result.Success = upgradeResult.Successful;
+ if (!result.Success)
+ {
+ result.Message = upgradeResult.Error.Message;
+ }
+ }
+ }
+ }
+
+ if (string.IsNullOrEmpty(result.Message))
+ {
+ result.Success = true;
+ }
+
+ return result;
+ }
+
+ private Installation MigrateModules(InstallConfig install)
+ {
+ var result = new Installation { Success = false, Message = string.Empty };
+
+ using (var scope = _serviceScopeFactory.CreateScope())
+ {
+ var moduledefinitions = scope.ServiceProvider.GetRequiredService();
+ foreach (var moduledefinition in moduledefinitions.GetModuleDefinitions())
+ {
+ if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType) && !string.IsNullOrEmpty(moduledefinition.ReleaseVersions))
+ {
+ string[] versions = moduledefinition.ReleaseVersions.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
+ {
+ foreach (var tenant in db.Tenant.ToList())
+ {
+ int index = Array.FindIndex(versions, item => item == moduledefinition.Version);
+ if (tenant.Name == install.TenantName && install.TenantName != Constants.MasterTenant)
+ {
+ index = -1;
+ }
+ if (index != (versions.Length - 1))
+ {
+ if (index == -1) index = 0;
+ for (int i = index; i < versions.Length; i++)
+ {
+ Type moduletype = Type.GetType(moduledefinition.ServerManagerType);
+ if (moduletype != null && moduletype.GetInterface("IInstallable") != null)
+ {
+ try
+ {
+ var moduleobject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduletype);
+ ((IInstallable)moduleobject).Install(tenant, versions[i]);
+ }
+ catch (Exception ex)
+ {
+ result.Message = "An Error Occurred Installing " + moduledefinition.Name + " - " + ex.Message.ToString();
+ }
+ }
+ }
+ }
+ }
+ if (moduledefinition.Version != versions[versions.Length - 1])
+ {
+ moduledefinition.Version = versions[versions.Length - 1];
+ db.Entry(moduledefinition).State = EntityState.Modified;
+ db.SaveChanges();
+ }
+ }
+ }
+ }
+ }
+
+ if (string.IsNullOrEmpty(result.Message))
+ {
+ result.Success = true;
+ }
+
+ return result;
+ }
+
+ private Installation CreateSite(InstallConfig install)
+ {
+ var result = new Installation { Success = false, Message = string.Empty };
+
+ if (!string.IsNullOrEmpty(install.TenantName) && !string.IsNullOrEmpty(install.Aliases) && !string.IsNullOrEmpty(install.SiteName))
+ {
+ using (var scope = _serviceScopeFactory.CreateScope())
+ {
+ // use the SiteState to set the Alias explicitly so the tenant can be resolved
+ var aliases = scope.ServiceProvider.GetRequiredService();
+ string firstalias = install.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0];
+ var alias = aliases.GetAliases().FirstOrDefault(item => item.Name == firstalias);
+ var siteState = scope.ServiceProvider.GetRequiredService();
+ siteState.Alias = alias;
+
+ var sites = scope.ServiceProvider.GetRequiredService();
+ var site = sites.GetSites().FirstOrDefault(item => item.Name == install.SiteName);
+ if (site == null)
+ {
+ var tenants = scope.ServiceProvider.GetRequiredService();
+ var users = scope.ServiceProvider.GetRequiredService();
+ var roles = scope.ServiceProvider.GetRequiredService();
+ var userroles = scope.ServiceProvider.GetRequiredService();
+ var folders = scope.ServiceProvider.GetRequiredService();
+ var log = scope.ServiceProvider.GetRequiredService();
+ var identityUserManager = scope.ServiceProvider.GetRequiredService>();
+
+ var tenant = tenants.GetTenants().FirstOrDefault(item => item.Name == install.TenantName);
+
+ site = new Site
+ {
+ TenantId = tenant.TenantId,
+ Name = install.SiteName,
+ LogoFileId = null,
+ DefaultThemeType = install.DefaultTheme,
+ DefaultLayoutType = install.DefaultLayout,
+ DefaultContainerType = install.DefaultContainer,
+ SiteTemplateType = install.SiteTemplate
+ };
+ site = sites.AddSite(site);
+
+ IdentityUser identityUser = identityUserManager.FindByNameAsync(Constants.HostUser).GetAwaiter().GetResult();
+ if (identityUser == null)
+ {
+ identityUser = new IdentityUser { UserName = Constants.HostUser, Email = install.HostEmail, EmailConfirmed = true };
+ var create = identityUserManager.CreateAsync(identityUser, install.HostPassword).GetAwaiter().GetResult();
+ if (create.Succeeded)
+ {
+ var user = new User
+ {
+ SiteId = site.SiteId,
+ Username = Constants.HostUser,
+ Password = install.HostPassword,
+ Email = install.HostEmail,
+ DisplayName = install.HostName,
+ LastIPAddress = "",
+ LastLoginOn = null
+ };
+
+ user = users.AddUser(user);
+ var hostRoleId = roles.GetRoles(user.SiteId, true).FirstOrDefault(item => item.Name == Constants.HostRole)?.RoleId ?? 0;
+ var userRole = new UserRole { UserId = user.UserId, RoleId = hostRoleId, EffectiveDate = null, ExpiryDate = null };
+ userroles.AddUserRole(userRole);
+
+ // add user folder
+ var folder = folders.GetFolder(user.SiteId, Utilities.PathCombine("Users", "\\"));
+ if (folder != null)
+ {
+ folders.AddFolder(new Folder
+ {
+ SiteId = folder.SiteId,
+ ParentId = folder.FolderId,
+ Name = "My Folder",
+ Path = Utilities.PathCombine(folder.Path, user.UserId.ToString(), "\\"),
+ Order = 1,
+ IsSystem = true,
+ Permissions = new List
+ {
+ new Permission(PermissionNames.Browse, user.UserId, true),
+ new Permission(PermissionNames.View, Constants.AllUsersRole, true),
+ new Permission(PermissionNames.Edit, user.UserId, true),
+ }.EncodePermissions(),
+ });
+ }
+ }
+ }
+
+ foreach (string aliasname in install.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ alias = aliases.GetAliases().FirstOrDefault(item => item.Name == aliasname);
+ alias.SiteId = site.SiteId;
+ aliases.UpdateAlias(alias);
+ }
+
+ log.Log(site.SiteId, LogLevel.Trace, this, LogFunction.Create, "Site Created {Site}", site);
+ }
+ }
+ }
+
+ result.Success = true;
+
+ return result;
+ }
+
+ private void CreateApplicationVersion(string connectionString)
+ {
+ using (var db = new InstallationContext(NormalizeConnectionString(connectionString)))
+ {
+ var version = db.ApplicationVersion.FirstOrDefault(item => item.Version == Constants.Version);
+ if (version == null)
+ {
+ version = new ApplicationVersion { Version = Constants.Version, CreatedOn = DateTime.UtcNow };
db.ApplicationVersion.Add(version);
db.SaveChanges();
}
}
}
-
- private static void TenantMigration(string connectionString, string dataDirectory)
+
+ private string NormalizeConnectionString(string connectionString)
{
- Console.WriteLine("Tenant migration");
- var assemblies = AppDomain.CurrentDomain.GetAssemblies()
- .Where(item => item.FullName != null && item.FullName.ToLower().Contains(".module.")).ToArray();
+ var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString();
+ connectionString = connectionString.Replace("|DataDirectory|", dataDirectory);
+ return connectionString;
+ }
- // get tenants
- using (var db = new InstallationContext(connectionString))
+ private string DenormalizeConnectionString(string connectionString)
+ {
+ var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString();
+ connectionString = connectionString.Replace(dataDirectory, "|DataDirectory|");
+ return connectionString;
+ }
+
+ public void UpdateConnectionString(string connectionString)
+ {
+ connectionString = DenormalizeConnectionString(connectionString);
+ if (_config.GetConnectionString(SettingKeys.ConnectionStringKey) != connectionString)
{
- foreach (var tenant in db.Tenant.ToList())
- {
- Console.WriteLine($"Migrating tenant {tenant.Name}");
- connectionString = NormalizeConnectionString(tenant.DBConnectionString, dataDirectory);
- // upgrade framework
- var dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionString)
- .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Tenant"));
- var dbUpgrade = dbUpgradeConfig.Build();
- if (dbUpgrade.IsUpgradeRequired())
- {
- var result = dbUpgrade.PerformUpgrade();
- if (!result.Successful)
- {
- // TODO: log result.Error.Message - problem is logger is not available here
- }
- }
-
- // iterate through Oqtane module assemblies and execute any database scripts
- foreach (var assembly in assemblies) ModuleMigration(assembly, connectionString);
- }
+ AddOrUpdateAppSetting($"ConnectionStrings:{SettingKeys.ConnectionStringKey}", connectionString);
+ _config.Reload();
}
}
- public static void UpdateConnectionStringSetting(string connectionString)
- {
- AddOrUpdateAppSetting($"ConnectionStrings:{SettingKeys.ConnectionStringKey}", connectionString);
- }
-
- public static void AddOrUpdateAppSetting(string sectionPathKey, T value)
+ public void AddOrUpdateAppSetting(string sectionPathKey, T value)
{
try
{
@@ -296,7 +527,7 @@ namespace Oqtane.Infrastructure
}
}
- private static void SetValueRecursively(string sectionPathKey, dynamic jsonObj, T value)
+ private void SetValueRecursively(string sectionPathKey, dynamic jsonObj, T value)
{
// split the string at the first ':' character
var remainingSections = sectionPathKey.Split(":", 2);
@@ -315,51 +546,6 @@ namespace Oqtane.Infrastructure
}
}
- public void BuildDefaultSite(string password, string email)
- {
- using (var scope = _serviceScopeFactory.CreateScope())
- {
- //Gather required services
- var siteRepository = scope.ServiceProvider.GetRequiredService();
-
- // Build default site only if no site present
- if (siteRepository.GetSites().Any()) return;
-
- var users = scope.ServiceProvider.GetRequiredService();
- var roles = scope.ServiceProvider.GetRequiredService();
- var userRoles = scope.ServiceProvider.GetRequiredService();
- var folders = scope.ServiceProvider.GetRequiredService();
- var identityUserManager = scope.ServiceProvider.GetRequiredService>();
- var tenants = scope.ServiceProvider.GetRequiredService();
-
- var tenant = tenants.GetTenants().First();
-
- var site = new Site
- {
- TenantId = tenant.TenantId,
- Name = "Default Site",
- LogoFileId = null,
- DefaultThemeType = GetInstallationConfig(SettingKeys.DefaultThemeKey, Constants.DefaultTheme),
- DefaultLayoutType = GetInstallationConfig(SettingKeys.DefaultLayoutKey, Constants.DefaultLayout),
- DefaultContainerType = GetInstallationConfig(SettingKeys.DefaultContainerKey, Constants.DefaultContainer),
- SiteTemplateType = GetInstallationConfig(SettingKeys.SiteTemplateKey, Constants.DefaultSiteTemplate),
- };
- site = siteRepository.AddSite(site);
-
- var user = new User
- {
- SiteId = site.SiteId,
- Username = Constants.HostUser,
- Password = password,
- Email = email,
- DisplayName = Constants.HostUser
- };
- CreateHostUser(folders, userRoles, roles, users, identityUserManager, user);
- tenant.IsInitialized = true;
- tenants.UpdateTenant(tenant);
- }
- }
-
private string GetInstallationConfig(string key, string defaultValue)
{
var value = _config.GetSection(SettingKeys.InstallationSection).GetValue(key, defaultValue);
@@ -368,96 +554,5 @@ 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};
- var result = identityUserManager.CreateAsync(identityUser, user.Password).GetAwaiter().GetResult();
-
- if (result.Succeeded)
- {
- user.LastLoginOn = null;
- user.LastIPAddress = "";
- var newUser = userRepository.AddUser(user);
-
- // assign to host role if this is the host user ( initial installation )
- if (user.Username == Constants.HostUser)
- {
- var hostRoleId = roleRepository.GetRoles(user.SiteId, true).FirstOrDefault(item => item.Name == Constants.HostRole)?.RoleId ?? 0;
- var userRole = new UserRole {UserId = newUser.UserId, RoleId = hostRoleId, EffectiveDate = null, ExpiryDate = null};
- userRoleRepository.AddUserRole(userRole);
- }
-
- // add folder for user
- var folder = folderRepository.GetFolder(user.SiteId, Utilities.PathCombine("Users","\\"));
- if (folder != null)
- folderRepository.AddFolder(new Folder
- {
- SiteId = folder.SiteId,
- ParentId = folder.FolderId,
- Name = "My Folder",
- Path = Utilities.PathCombine(folder.Path, newUser.UserId.ToString(),"\\"),
- Order = 1,
- IsSystem = true,
- Permissions = new List
- {
- new Permission(PermissionNames.Browse, newUser.UserId, true),
- new Permission(PermissionNames.View, Constants.AllUsersRole, true),
- new Permission(PermissionNames.Edit, newUser.UserId, true),
- }.EncodePermissions(),
- });
- }
- }
-
- private static bool IsDefaultSiteInstalled(string connectionString)
- {
- using (var db = new InstallationContext(connectionString))
- {
- return db.Tenant.Any(t => t.IsInitialized);
- }
- }
-
- private static bool IsMasterInstalled(string connectionString)
- {
- using (var db = new InstallationContext(connectionString))
- {
-
- //check if DbUp was initialized
- return TableExists(db, "SchemaVersions");
- }
- }
-
- public static bool TableExists(DbContext context, string tableName)
- {
- return TableExists(context, "dbo", tableName);
- }
-
- public static bool TableExists(DbContext context, string schema, string tableName)
- {
- if (!context.Database.CanConnect()) return false;
- var connection = context.Database.GetDbConnection();
-
- if (connection.State.Equals(ConnectionState.Closed))
- connection.Open();
-
- using (var command = connection.CreateCommand())
- {
- command.CommandText = @"
- SELECT 1 FROM INFORMATION_SCHEMA.TABLES
- WHERE TABLE_SCHEMA = @Schema
- AND TABLE_NAME = @TableName";
-
- var schemaParam = command.CreateParameter();
- schemaParam.ParameterName = "@Schema";
- schemaParam.Value = schema;
- command.Parameters.Add(schemaParam);
-
- var tableNameParam = command.CreateParameter();
- tableNameParam.ParameterName = "@TableName";
- tableNameParam.Value = tableName;
- command.Parameters.Add(tableNameParam);
-
- return command.ExecuteScalar() != null;
- }
- }
}
}
diff --git a/Oqtane.Server/Infrastructure/Interfaces/IDatabaseManager.cs b/Oqtane.Server/Infrastructure/Interfaces/IDatabaseManager.cs
new file mode 100644
index 00000000..ffb0ff9c
--- /dev/null
+++ b/Oqtane.Server/Infrastructure/Interfaces/IDatabaseManager.cs
@@ -0,0 +1,12 @@
+using Oqtane.Models;
+using Oqtane.Shared;
+
+namespace Oqtane.Infrastructure
+{
+ public interface IDatabaseManager
+ {
+ bool IsInstalled();
+ Installation Install();
+ Installation Install(InstallConfig install);
+ }
+}
diff --git a/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs b/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs
index c71de4f6..baef7184 100644
--- a/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs
+++ b/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs
@@ -1,8 +1,10 @@
-namespace Oqtane.Infrastructure
+using Oqtane.Models;
+
+namespace Oqtane.Infrastructure
{
public interface IInstallable
{
- bool Install(string version);
- bool Uninstall();
+ bool Install(Tenant tenant, string version);
+ bool Uninstall(Tenant tenant);
}
}
diff --git a/Oqtane.Server/Infrastructure/LogManager.cs b/Oqtane.Server/Infrastructure/LogManager.cs
index b46bc8dc..52a4736a 100644
--- a/Oqtane.Server/Infrastructure/LogManager.cs
+++ b/Oqtane.Server/Infrastructure/LogManager.cs
@@ -69,10 +69,14 @@ namespace Oqtane.Infrastructure
{
log.UserId = user.UserId;
}
- HttpRequest request = _accessor.HttpContext.Request;
- if (request != null)
+ log.Url = "";
+ if (_accessor.HttpContext != null)
{
- log.Url = $"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}";
+ HttpRequest request = _accessor.HttpContext.Request;
+ if (request != null)
+ {
+ log.Url = $"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}";
+ }
}
Type type = Type.GetType(@class.ToString());
diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs
index 4d38e250..7e068076 100644
--- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs
+++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs
@@ -18,14 +18,14 @@ namespace Oqtane.Modules.HtmlText.Manager
_sql = sql;
}
- public bool Install(string version)
+ public bool Install(Tenant tenant, string version)
{
- return _sql.ExecuteEmbeddedScript(GetType().Assembly, "HtmlText." + version + ".sql");
+ return _sql.ExecuteScript(tenant, GetType().Assembly, "HtmlText." + version + ".sql");
}
- public bool Uninstall()
+ public bool Uninstall(Tenant tenant)
{
- return _sql.ExecuteEmbeddedScript(GetType().Assembly, "HtmlText.Uninstall.sql");
+ return _sql.ExecuteScript(tenant, GetType().Assembly, "HtmlText.Uninstall.sql");
}
public string ExportModule(Module module)
diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj
index 95471ca0..347e9fc0 100644
--- a/Oqtane.Server/Oqtane.Server.csproj
+++ b/Oqtane.Server/Oqtane.Server.csproj
@@ -17,11 +17,6 @@
Oqtane
-
-
-
-
-
diff --git a/Oqtane.Server/Program.cs b/Oqtane.Server/Program.cs
index 08d1fa06..c4b5492f 100644
--- a/Oqtane.Server/Program.cs
+++ b/Oqtane.Server/Program.cs
@@ -12,11 +12,10 @@ namespace Oqtane.Server
public static void Main(string[] args)
{
var host = BuildWebHost(args);
- // execute any database migrations for the framework or extensions
using (var serviceScope = host.Services.GetRequiredService().CreateScope())
{
- var databaseManager = serviceScope.ServiceProvider.GetService();
- databaseManager.StartupMigration();
+ var databaseManager = serviceScope.ServiceProvider.GetService();
+ databaseManager.Install();
}
host.Run();
}
diff --git a/Oqtane.Server/Repository/Context/InstallationContext.cs b/Oqtane.Server/Repository/Context/InstallationContext.cs
index 7b971a16..18694939 100644
--- a/Oqtane.Server/Repository/Context/InstallationContext.cs
+++ b/Oqtane.Server/Repository/Context/InstallationContext.cs
@@ -17,7 +17,11 @@ namespace Oqtane.Repository
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(_connectionString);
- public virtual DbSet ApplicationVersion { get; set; }
+ public virtual DbSet Alias { get; set; }
public virtual DbSet Tenant { get; set; }
+ public virtual DbSet ModuleDefinition { get; set; }
+ public virtual DbSet Job { get; set; }
+
+ public virtual DbSet ApplicationVersion { get; set; }
}
}
diff --git a/Oqtane.Server/Repository/Interfaces/IModuleDefinitionRepository.cs b/Oqtane.Server/Repository/Interfaces/IModuleDefinitionRepository.cs
index 32c59549..dd516461 100644
--- a/Oqtane.Server/Repository/Interfaces/IModuleDefinitionRepository.cs
+++ b/Oqtane.Server/Repository/Interfaces/IModuleDefinitionRepository.cs
@@ -5,6 +5,7 @@ namespace Oqtane.Repository
{
public interface IModuleDefinitionRepository
{
+ IEnumerable GetModuleDefinitions();
IEnumerable GetModuleDefinitions(int sideId);
ModuleDefinition GetModuleDefinition(int moduleDefinitionId, int sideId);
void UpdateModuleDefinition(ModuleDefinition moduleDefinition);
diff --git a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs
index 037e9416..a1402e3b 100644
--- a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs
+++ b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs
@@ -6,8 +6,8 @@ namespace Oqtane.Repository
{
public interface ISqlRepository
{
- bool ExecuteEmbeddedScript(Assembly assembly, string script);
void ExecuteScript(Tenant tenant, string script);
+ bool ExecuteScript(Tenant tenant, Assembly assembly, string filename);
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 6db44ac0..117c7ae7 100644
--- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs
+++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs
@@ -25,6 +25,11 @@ namespace Oqtane.Repository
_permissions = permissions;
}
+ public IEnumerable GetModuleDefinitions()
+ {
+ return LoadModuleDefinitions(-1); // used only during startup
+ }
+
public IEnumerable GetModuleDefinitions(int siteId)
{
return LoadModuleDefinitions(siteId);
@@ -72,8 +77,12 @@ namespace Oqtane.Repository
}
List moduleDefinitions = _moduleDefinitions;
- // get module definition permissions for site
- List permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList();
+ List permissions = new List();
+ if (siteId != -1)
+ {
+ // get module definition permissions for site
+ permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList();
+ }
// get module definitions in database
List moduledefs = _db.ModuleDefinition.ToList();
@@ -88,7 +97,10 @@ namespace Oqtane.Repository
moduledef = new ModuleDefinition { ModuleDefinitionName = moduledefinition.ModuleDefinitionName };
_db.ModuleDefinition.Add(moduledef);
_db.SaveChanges();
- _permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledef.ModuleDefinitionId, moduledefinition.Permissions);
+ if (siteId != -1)
+ {
+ _permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledef.ModuleDefinitionId, moduledefinition.Permissions);
+ }
}
else
{
@@ -109,13 +121,16 @@ namespace Oqtane.Repository
{
moduledefinition.Version = moduledef.Version;
}
- if (permissions.Count == 0)
+ if (siteId != -1)
{
- _permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledef.ModuleDefinitionId, moduledefinition.Permissions);
- }
- else
- {
- moduledefinition.Permissions = permissions.Where(item => item.EntityId == moduledef.ModuleDefinitionId).EncodePermissions();
+ if (permissions.Count == 0)
+ {
+ _permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledef.ModuleDefinitionId, moduledefinition.Permissions);
+ }
+ else
+ {
+ moduledefinition.Permissions = permissions.Where(item => item.EntityId == moduledef.ModuleDefinitionId).EncodePermissions();
+ }
}
// remove module definition from list as it is already synced
moduledefs.Remove(moduledef);
@@ -131,7 +146,10 @@ namespace Oqtane.Repository
// any remaining module definitions are orphans
foreach (ModuleDefinition moduledefinition in moduledefs)
{
- _permissions.DeletePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId);
+ if (siteId != -1)
+ {
+ _permissions.DeletePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId);
+ }
_db.ModuleDefinition.Remove(moduledefinition); // delete
_db.SaveChanges();
}
diff --git a/Oqtane.Server/Repository/ModuleRepository.cs b/Oqtane.Server/Repository/ModuleRepository.cs
index 7f38a1bf..a66517fc 100644
--- a/Oqtane.Server/Repository/ModuleRepository.cs
+++ b/Oqtane.Server/Repository/ModuleRepository.cs
@@ -87,8 +87,15 @@ namespace Oqtane.Repository
Type moduletype = Type.GetType(moduledefinition.ServerManagerType);
if (moduletype != null && moduletype.GetInterface("IPortable") != null)
{
- var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
- modulecontent.Content = ((IPortable) moduleobject).ExportModule(module);
+ try
+ {
+ var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
+ modulecontent.Content = ((IPortable)moduleobject).ExportModule(module);
+ }
+ catch
+ {
+ // error in IPortable implementation
+ }
}
}
@@ -124,9 +131,16 @@ namespace Oqtane.Repository
Type moduletype = Type.GetType(moduledefinition.ServerManagerType);
if (moduletype != null && moduletype.GetInterface("IPortable") != null)
{
- var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
- ((IPortable) moduleobject).ImportModule(module, modulecontent.Content, modulecontent.Version);
- success = true;
+ try
+ {
+ var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
+ ((IPortable)moduleobject).ImportModule(module, modulecontent.Content, modulecontent.Version);
+ success = true;
+ }
+ catch
+ {
+ // error in IPortable implementation
+ }
}
}
}
diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs
index 91cbccbf..0e39a6bf 100644
--- a/Oqtane.Server/Repository/SiteRepository.cs
+++ b/Oqtane.Server/Repository/SiteRepository.cs
@@ -784,14 +784,14 @@ namespace Oqtane.Repository
Type moduletype = Type.GetType(moduledefinition.ServerManagerType);
if (moduletype != null && moduletype.GetInterface("IPortable") != null)
{
- var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
try
{
+ var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
((IPortable)moduleobject).ImportModule(module, pagetemplatemodule.Content, moduledefinition.Version);
}
catch
{
- // error in module import
+ // error in IPortable implementation
}
}
}
diff --git a/Oqtane.Server/Repository/SqlRepository.cs b/Oqtane.Server/Repository/SqlRepository.cs
index 0e5c2f3c..fcc4caf3 100644
--- a/Oqtane.Server/Repository/SqlRepository.cs
+++ b/Oqtane.Server/Repository/SqlRepository.cs
@@ -10,18 +10,21 @@ namespace Oqtane.Repository
{
public class SqlRepository : ISqlRepository
{
- private readonly ITenantRepository _tenants;
- public SqlRepository(ITenantRepository tenants)
+ public void ExecuteScript(Tenant tenant, string script)
{
- _tenants = tenants;
+ // execute script in curent tenant
+ foreach (string query in script.Split("GO", StringSplitOptions.RemoveEmptyEntries))
+ {
+ ExecuteNonQuery(tenant, query);
+ }
}
- public bool ExecuteEmbeddedScript(Assembly assembly, string filename)
+ public bool ExecuteScript(Tenant tenant, Assembly assembly, string filename)
{
// script must be included as an Embedded Resource within an assembly
bool success = true;
- string uninstallScript = "";
+ string script = "";
if (assembly != null)
{
@@ -33,39 +36,27 @@ namespace Oqtane.Repository
{
using (var reader = new StreamReader(resourceStream))
{
- uninstallScript = reader.ReadToEnd();
+ script = reader.ReadToEnd();
}
}
}
}
- if (!string.IsNullOrEmpty(uninstallScript))
+ if (!string.IsNullOrEmpty(script))
{
- foreach (Tenant tenant in _tenants.GetTenants())
+ try
{
- try
- {
- ExecuteScript(tenant, uninstallScript);
- }
- catch
- {
- success = false;
- }
+ ExecuteScript(tenant, script);
+ }
+ catch
+ {
+ success = false;
}
}
return success;
}
- 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/TenantResolver.cs b/Oqtane.Server/Repository/TenantResolver.cs
index 8a5b85c4..f5035a65 100644
--- a/Oqtane.Server/Repository/TenantResolver.cs
+++ b/Oqtane.Server/Repository/TenantResolver.cs
@@ -17,47 +17,48 @@ namespace Oqtane.Repository
int aliasId = -1;
string aliasName = "";
- // get alias identifier based on request context
- if (accessor.HttpContext != null)
+ if (siteState != null && siteState.Alias != null)
{
- // check if an alias is passed as a querystring parameter ( for cross tenant access )
- if (accessor.HttpContext.Request.Query.ContainsKey("aliasid"))
- {
- aliasId = int.Parse(accessor.HttpContext.Request.Query["aliasid"]);
- }
- else // get the alias from the request url
- {
- aliasName = accessor.HttpContext.Request.Host.Value;
- string path = accessor.HttpContext.Request.Path.Value;
- string[] segments = path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
- if (segments.Length > 1 && segments[1] == "api" && segments[0] != "~")
- {
- aliasName += "/" + segments[0];
- }
-
- if (aliasName.EndsWith("/"))
- {
- aliasName = aliasName.Substring(0, aliasName.Length - 1);
- }
- }
- }
- else // background processes can pass in an alias using the SiteState service
- {
- aliasId = siteState?.Alias?.AliasId ?? -1;
- }
-
- // get the alias and tenant
- IEnumerable aliases = aliasRepository.GetAliases().ToList(); // cached
- if (aliasId != -1)
- {
- _alias = aliases.FirstOrDefault(item => item.AliasId == aliasId);
+ // background processes can pass in an alias using the SiteState service
+ _alias = siteState.Alias;
}
else
- {
-
- _alias = aliases.FirstOrDefault(item => item.Name == aliasName
- //if here is only one alias and other methods fail, take it (case of startup install)
- || aliases.Count() == 1);
+ {
+ // get alias identifier based on request context
+ if (accessor.HttpContext != null)
+ {
+ // check if an alias is passed as a querystring parameter ( for cross tenant access )
+ if (accessor.HttpContext.Request.Query.ContainsKey("aliasid"))
+ {
+ aliasId = int.Parse(accessor.HttpContext.Request.Query["aliasid"]);
+ }
+ else // get the alias from the request url
+ {
+ aliasName = accessor.HttpContext.Request.Host.Value;
+ string path = accessor.HttpContext.Request.Path.Value;
+ string[] segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
+ if (segments.Length > 1 && segments[1] == "api" && segments[0] != "~")
+ {
+ aliasName += "/" + segments[0];
+ }
+
+ if (aliasName.EndsWith("/"))
+ {
+ aliasName = aliasName.Substring(0, aliasName.Length - 1);
+ }
+ }
+ }
+
+ // get the alias
+ IEnumerable aliases = aliasRepository.GetAliases().ToList(); // cached
+ if (aliasId != -1)
+ {
+ _alias = aliases.FirstOrDefault(item => item.AliasId == aliasId);
+ }
+ else
+ {
+ _alias = aliases.FirstOrDefault(item => item.Name == aliasName || aliases.Count() == 1);
+ }
}
if (_alias != null)
diff --git a/Oqtane.Server/Scripts/Master.00.00.00.sql b/Oqtane.Server/Scripts/Master.00.00.00.sql
index 0b10fb01..fcfdd3ef 100644
--- a/Oqtane.Server/Scripts/Master.00.00.00.sql
+++ b/Oqtane.Server/Scripts/Master.00.00.00.sql
@@ -7,8 +7,6 @@ CREATE TABLE [dbo].[Tenant](
[TenantId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](100) NOT NULL,
[DBConnectionString] [nvarchar](1024) NOT NULL,
- [DBSchema] [nvarchar](50) NOT NULL,
- [IsInitialized] [bit] NOT NULL,
[CreatedBy] [nvarchar](256) NOT NULL,
[CreatedOn] [datetime] NOT NULL,
[ModifiedBy] [nvarchar](256) NOT NULL,
@@ -119,32 +117,4 @@ REFERENCES [dbo].[Job] ([JobId])
ON DELETE CASCADE
GO
-/*
-
-Create seed data
-
-*/
-SET IDENTITY_INSERT [dbo].[Tenant] ON
-GO
-INSERT [dbo].[Tenant] ([TenantId], [Name], [DBConnectionString], [DBSchema], [IsInitialized], [CreatedBy], [CreatedOn], [ModifiedBy], [ModifiedOn])
-VALUES (1, N'Master', N'$ConnectionString$', N'', 0, '', getdate(), '', getdate())
-GO
-SET IDENTITY_INSERT [dbo].[Tenant] OFF
-GO
-
-SET IDENTITY_INSERT [dbo].[Alias] ON
-GO
-INSERT [dbo].[Alias] ([AliasId], [Name], [TenantId], [SiteId], [CreatedBy], [CreatedOn], [ModifiedBy], [ModifiedOn])
-VALUES (1, N'$Alias$', 1, 1, '', getdate(), '', getdate())
-GO
-SET IDENTITY_INSERT [dbo].[Alias] OFF
-GO
-
-SET IDENTITY_INSERT [dbo].[Job] ON
-GO
-INSERT [dbo].[Job] ([JobId], [Name], [JobType], [Frequency], [Interval], [StartDate], [EndDate], [IsEnabled], [IsStarted], [IsExecuting], [NextExecution], [RetentionHistory], [CreatedBy], [CreatedOn], [ModifiedBy], [ModifiedOn])
-VALUES (1, N'Notification Job', N'Oqtane.Infrastructure.NotificationJob, Oqtane.Server', N'm', 1, null, null, 0, 0, 0, null, 10, '', getdate(), '', getdate())
-GO
-SET IDENTITY_INSERT [dbo].[Job] OFF
-GO
diff --git a/Oqtane.Server/Scripts/Master.00.00.01.sql b/Oqtane.Server/Scripts/Master.00.00.01.sql
index 02943e96..49ec882c 100644
--- a/Oqtane.Server/Scripts/Master.00.00.01.sql
+++ b/Oqtane.Server/Scripts/Master.00.00.01.sql
@@ -1,2 +1,5 @@
-alter table Tenant drop column DBSchema
-go
+/*
+
+schema updates
+
+*/
diff --git a/Oqtane.Server/Security/UserPermissions.cs b/Oqtane.Server/Security/UserPermissions.cs
index 14a13199..1e89c4cd 100644
--- a/Oqtane.Server/Security/UserPermissions.cs
+++ b/Oqtane.Server/Security/UserPermissions.cs
@@ -55,7 +55,14 @@ namespace Oqtane.Security
public User GetUser()
{
- return GetUser(_accessor.HttpContext.User);
+ if (_accessor.HttpContext != null)
+ {
+ return GetUser(_accessor.HttpContext.User);
+ }
+ else
+ {
+ return null;
+ }
}
}
}
diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs
index 52599d6e..9e24a6fa 100644
--- a/Oqtane.Server/Startup.cs
+++ b/Oqtane.Server/Startup.cs
@@ -155,7 +155,7 @@ namespace Oqtane
services.AddSingleton(Configuration);
services.AddSingleton();
services.AddSingleton();
- services.AddSingleton();
+ services.AddSingleton();
// install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain )
InstallationManager.UnpackPackages("Modules,Themes", _webRoot);
diff --git a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css
index 114c2584..c69fc61d 100644
--- a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css
+++ b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css
@@ -16,7 +16,6 @@
.main .top-row {
background-color: #e6e6e6;
border-bottom: 1px solid #d6d5d5;
- z-index: 9999;
}
.sidebar {
@@ -42,6 +41,10 @@
margin-bottom: 0;
}
+.app-controlpanel {
+ z-index: 9999;
+}
+
.app-menu .nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
@@ -97,19 +100,21 @@
position: fixed;
left: 275px;
top: 0;
- z-index: 1;
+ z-index: 3
}
-
+
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
+ z-index: 1
}
.main .top-row {
position: sticky;
top: 0;
+ z-index: 2
}
.main > div {
@@ -154,10 +159,23 @@
}
@media (max-width: 767px) {
+ .breadcrumbs {
+ position: fixed;
+ top: 150px;
+ width: 100%;
+ left: 0;
+ z-index: 1;
+ }
+
.sidebar {
margin-top: 3.5rem;
position: fixed;
width: 100%;
+ z-index: 2;
+ }
+
+ .main .top-row {
+ z-index: 2;
}
.main > .top-row.px-4 {
@@ -172,13 +190,6 @@
margin-left: auto;
}
- .breadcrumbs {
- position: fixed;
- top: 150px;
- width: 100%;
- left: 0;
- }
-
.main > .container {
margin-top: 200px;
}
diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js
index ec289bbe..aa0529a0 100644
--- a/Oqtane.Server/wwwroot/js/interop.js
+++ b/Oqtane.Server/wwwroot/js/interop.js
@@ -48,7 +48,7 @@ window.interop = {
}
}
},
- includeLink: function (id, rel, url, type) {
+ includeLink: function (id, rel, url, type, integrity, crossorigin) {
var link;
if (id !== "") {
link = document.getElementById(id);
@@ -66,6 +66,12 @@ window.interop = {
if (type !== "") {
link.type = type;
}
+ if (integrity !== "") {
+ link.integrity = integrity;
+ }
+ if (crossorigin !== "") {
+ link.crossorigin = crossorigin;
+ }
document.head.appendChild(link);
}
else {
@@ -78,9 +84,15 @@ window.interop = {
if (type !== "" && link.type !== type) {
link.setAttribute('type', type);
}
+ if (integrity !== "" && link.integrity !== integrity) {
+ link.setAttribute('integrity', integrity);
+ }
+ if (crossorigin !== "" && link.crossorigin !== crossorigin) {
+ link.setAttribute('crossorigin', crossorigin);
+ }
}
},
- includeScript: function (id, src, content, location) {
+ includeScript: function (id, src, content, location, integrity, crossorigin) {
var script;
if (id !== "") {
script = document.getElementById(id);
@@ -92,6 +104,12 @@ window.interop = {
}
if (src !== "") {
script.src = src;
+ if (integrity !== "") {
+ script.integrity = integrity;
+ }
+ if (crossorigin !== "") {
+ script.crossorigin = crossorigin;
+ }
}
else {
script.innerHTML = content;
@@ -108,6 +126,12 @@ window.interop = {
if (script.src !== src) {
script.src = src;
}
+ if (integrity !== "" && script.integrity !== integrity) {
+ script.setAttribute('integrity', integrity);
+ }
+ if (crossorigin !== "" && script.crossorigin !== crossorigin) {
+ script.setAttribute('crossorigin', crossorigin);
+ }
}
else {
if (script.innerHTML !== content) {
diff --git a/Oqtane.Shared/Models/Tenant.cs b/Oqtane.Shared/Models/Tenant.cs
index 3f0a518a..ed392544 100644
--- a/Oqtane.Shared/Models/Tenant.cs
+++ b/Oqtane.Shared/Models/Tenant.cs
@@ -7,7 +7,6 @@ namespace Oqtane.Models
public int TenantId { get; set; }
public string Name { get; set; }
public string DBConnectionString { get; set; }
- public bool IsInitialized { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }
public string ModifiedBy { get; set; }
diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs
index eacaa4c6..f4edd1ce 100644
--- a/Oqtane.Shared/Shared/Constants.cs
+++ b/Oqtane.Shared/Shared/Constants.cs
@@ -33,6 +33,7 @@
public const string HostUser = "host";
public const string MasterTenant = "Master";
+ public const string DefaultSite = "Default Site";
public const string AllUsersRole = "All Users";
public const string HostRole = "Host Users";
diff --git a/Oqtane.Shared/Shared/InstallConfig.cs b/Oqtane.Shared/Shared/InstallConfig.cs
index f7f065b4..2ab74c74 100644
--- a/Oqtane.Shared/Shared/InstallConfig.cs
+++ b/Oqtane.Shared/Shared/InstallConfig.cs
@@ -2,11 +2,17 @@
{
public class InstallConfig
{
- public string Alias { get; set; }
public string ConnectionString { get; set; }
- public string HostUser { get; set; }
- public string Password { get; set; }
+ public string Aliases { get; set; }
+ public string TenantName { get; set; }
+ public bool IsNewTenant { get; set; }
+ public string SiteName { get; set; }
+ public string HostPassword { get; set; }
public string HostEmail { get; set; }
- public bool IsMaster { get; set; }
+ public string HostName { get; set; }
+ public string SiteTemplate { get; set; }
+ public string DefaultTheme { get; set; }
+ public string DefaultLayout { get; set; }
+ public string DefaultContainer { get; set; }
}
}