install/upgrade refactoring to consolidate all use cases and implement IInstallable interface for modules, moved tenant creation to site management UI, fixed z-order issues in Blazor theme, enhanced JS Interop methods to support integrity and crossorigin
This commit is contained in:
		@ -62,7 +62,8 @@ else
 | 
			
		||||
- Repository\I[Module]Repository.cs - interface for defining repository methods<br />
 | 
			
		||||
- Repository\[Module]Respository.cs - implements repository interface methods for data access using EF Core<br />
 | 
			
		||||
- Repository\[Module]Context.cs - provides a DB Context for data access<br />
 | 
			
		||||
- Scripts\01.00.00.sql - database schema definition<br /><br />
 | 
			
		||||
- Scripts\[Module].1.0.0.sql - database schema definition script<br /><br />
 | 
			
		||||
- Scripts\[Module].Uninstall.sql - database uninstall script<br /><br />
 | 
			
		||||
[RootPath]Shared\<br />
 | 
			
		||||
- [Owner].[Module]s.Module.Shared.csproj - shared project<br />
 | 
			
		||||
- Models\[Module].cs - model definition<br /><br />
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,8 @@ namespace [Owner].[Module]s.Modules
 | 
			
		||||
            Description = "[Module]",
 | 
			
		||||
            Version = "1.0.0",
 | 
			
		||||
            Dependencies = "[Owner].[Module]s.Module.Shared",
 | 
			
		||||
            ServerManagerType = "[ServerManagerType]"
 | 
			
		||||
            ServerManagerType = "[ServerManagerType]",
 | 
			
		||||
            ReleaseVersions = "1.0.0"
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,18 +3,32 @@ using System.Linq;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using Oqtane.Modules;
 | 
			
		||||
using Oqtane.Models;
 | 
			
		||||
using Oqtane.Infrastructure;
 | 
			
		||||
using Oqtane.Repository;
 | 
			
		||||
using [Owner].[Module]s.Models;
 | 
			
		||||
using [Owner].[Module]s.Repository;
 | 
			
		||||
 | 
			
		||||
namespace [Owner].[Module]s.Manager
 | 
			
		||||
{
 | 
			
		||||
    public class [Module]Manager : IPortable
 | 
			
		||||
    public class [Module]Manager : IInstallable, IPortable
 | 
			
		||||
    {
 | 
			
		||||
        private I[Module]Repository _[Module]s;
 | 
			
		||||
        private ISqlRepository _sql;
 | 
			
		||||
 | 
			
		||||
        public [Module]Manager(I[Module]Repository [Module]s)
 | 
			
		||||
        public [Module]Manager(I[Module]Repository [Module]s, ISqlRepository sql)
 | 
			
		||||
        {
 | 
			
		||||
            _[Module]s = [Module]s;
 | 
			
		||||
            _sql = sql;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Install(Tenant tenant, string version)
 | 
			
		||||
        {
 | 
			
		||||
            return _sql.ExecuteScript(tenant, GetType().Assembly, "[Owner].[Module]." + version + ".sql");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Uninstall(Tenant tenant)
 | 
			
		||||
        {
 | 
			
		||||
            return _sql.ExecuteScript(tenant, GetType().Assembly, "[Owner].[Module].Uninstall.sql");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string ExportModule(Module module)
 | 
			
		||||
 | 
			
		||||
@ -13,13 +13,13 @@
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <None Remove="Scripts\01.00.00.sql" />
 | 
			
		||||
    <None Remove="Scripts\Uninstall.sql" />
 | 
			
		||||
    <None Remove="Scripts\[Module].1.0.0.sql" />
 | 
			
		||||
    <None Remove="Scripts\[Module].Uninstall.sql" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <EmbeddedResource Include="Scripts\01.00.00.sql" />
 | 
			
		||||
    <EmbeddedResource Include="Scripts\Uninstall.sql" />
 | 
			
		||||
    <EmbeddedResource Include="Scripts\[Module].1.0.0.sql" />
 | 
			
		||||
    <EmbeddedResource Include="Scripts\[Module].Uninstall.sql" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,8 @@ else
 | 
			
		||||
- Repository\I[Module]Repository.cs - interface for defining repository methods<br />
 | 
			
		||||
- Repository\[Module]Respository.cs - implements repository interface methods for data access using EF Core<br />
 | 
			
		||||
- Repository\[Module]Context.cs - provides a DB Context for data access<br />
 | 
			
		||||
- Scripts\01.00.00.sql - database schema definition<br /><br />
 | 
			
		||||
- Scripts\[Module].1.0.0.sql - database schema definition script<br /><br />
 | 
			
		||||
- Scripts\[Module].Uninstall.sql - database uninstall script<br /><br />
 | 
			
		||||
[RootPath]Oqtane.Shared\Modules\[Module]\<br />
 | 
			
		||||
- Models\[Module].cs - model definition<br /><br />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,8 @@ namespace [Owner].[Module]s.Modules
 | 
			
		||||
            Description = "[Module]",
 | 
			
		||||
            Version = "1.0.0",
 | 
			
		||||
            Dependencies = "[Owner].[Module]s.Module.Shared",
 | 
			
		||||
            ServerManagerType = "[ServerManagerType]"
 | 
			
		||||
            ServerManagerType = "[ServerManagerType]",
 | 
			
		||||
            ReleaseVersions = "1.0.0"
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,18 +3,32 @@ using System.Linq;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using Oqtane.Modules;
 | 
			
		||||
using Oqtane.Models;
 | 
			
		||||
using Oqtane.Infrastructure;
 | 
			
		||||
using Oqtane.Repository;
 | 
			
		||||
using [Owner].[Module]s.Models;
 | 
			
		||||
using [Owner].[Module]s.Repository;
 | 
			
		||||
 | 
			
		||||
namespace [Owner].[Module]s.Manager
 | 
			
		||||
{
 | 
			
		||||
    public class [Module]Manager : IPortable
 | 
			
		||||
    public class [Module]Manager : IInstallable, IPortable
 | 
			
		||||
    {
 | 
			
		||||
        private I[Module]Repository _[Module]s;
 | 
			
		||||
        private ISqlRepository _sql;
 | 
			
		||||
 | 
			
		||||
        public [Module]Manager(I[Module]Repository [Module]s)
 | 
			
		||||
        public [Module]Manager(I[Module]Repository [Module]s, ISqlRepository sql)
 | 
			
		||||
        {
 | 
			
		||||
            _[Module]s = [Module]s;
 | 
			
		||||
            _sql = sql;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Install(Tenant tenant, string version)
 | 
			
		||||
        {
 | 
			
		||||
            return _sql.ExecuteScript(tenant, GetType().Assembly, "[Owner].[Module]." + version + ".sql");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool Uninstall(Tenant tenant)
 | 
			
		||||
        {
 | 
			
		||||
            return _sql.ExecuteScript(tenant, GetType().Assembly, "[Owner].[Module].Uninstall.sql");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string ExportModule(Module module)
 | 
			
		||||
 | 
			
		||||
@ -5,8 +5,9 @@
 | 
			
		||||
@inject IAliasService AliasService
 | 
			
		||||
@inject ISiteService SiteService
 | 
			
		||||
@inject IThemeService  ThemeService
 | 
			
		||||
@inject ISiteTemplateService SiteTemplateService 
 | 
			
		||||
@inject ISiteTemplateService SiteTemplateService
 | 
			
		||||
@inject IUserService UserService
 | 
			
		||||
@inject IInstallationService InstallationService
 | 
			
		||||
 | 
			
		||||
@if (_tenants == null)
 | 
			
		||||
{
 | 
			
		||||
@ -17,21 +18,7 @@ else
 | 
			
		||||
<table class="table table-borderless">
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
            <Label For="tenant" HelpText="Select the tenant for the site">Tenant: </Label>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <select id="tenant" class="form-control" @onchange="(e => TenantChanged(e))">
 | 
			
		||||
                <option value="-1"><Select Tenant></option>
 | 
			
		||||
                @foreach (Tenant tenant in _tenants)
 | 
			
		||||
                {
 | 
			
		||||
                    <option value="@tenant.TenantId">@tenant.Name</option>
 | 
			
		||||
                }
 | 
			
		||||
            </select>
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
            <Label For="name" HelpText="Enter the name of the site">Name: </Label>
 | 
			
		||||
            <Label For="name" HelpText="Enter the name of the site">Site Name: </Label>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <input id="name" class="form-control" @bind="@_name" />
 | 
			
		||||
@ -101,14 +88,99 @@ else
 | 
			
		||||
            </select>
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    @if (!_isinitialized)
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
            <Label For="tenant" HelpText="Select the tenant for the site">Tenant: </Label>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <select id="tenant" class="form-control" @onchange="(e => TenantChanged(e))">
 | 
			
		||||
                <option value="-"><Select Tenant></option>
 | 
			
		||||
                <option value="+"><Create New Tenant></option>
 | 
			
		||||
                @foreach (Tenant tenant in _tenants)
 | 
			
		||||
                {
 | 
			
		||||
                    <option value="@tenant.TenantId">@tenant.Name</option>
 | 
			
		||||
                }
 | 
			
		||||
            </select>
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    @if (_tenantid == "+")
 | 
			
		||||
    {
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td colspan="2">
 | 
			
		||||
                <hr class="app-rule" />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="name" HelpText="Enter the name for the tenant">Tenant Name: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <input id="name" class="form-control" @bind="@_tenantname" />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="databaseType" HelpText="Select the database type for the tenant">Database Type: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <select id="databaseType" class="custom-select" @bind="@_databasetype">
 | 
			
		||||
                    <option value="LocalDB">Local Database</option>
 | 
			
		||||
                    <option value="SQLServer">SQL Server</option>
 | 
			
		||||
                </select>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="server" HelpText="Enter the server for the tenant">Server: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <input id="server" type="text" class="form-control" @bind="@_server" />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="database" HelpText="Enter the database for the tenant">Database: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <input id="database" type="text" class="form-control" @bind="@_database" />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="integratedSecurity" HelpText="Select if you want integrated security or not">Integrated Security: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <select id="integratedSecurity" class="custom-select" @onchange="SetIntegratedSecurity">
 | 
			
		||||
                    <option value="true" selected>True</option>
 | 
			
		||||
                    <option value="false">False</option>
 | 
			
		||||
                </select>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        @if (!_integratedsecurity)
 | 
			
		||||
        {
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <Label For="username" HelpText="Enter the username for the integrated security">Database Username: </Label>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <input id="username" type="text" class="form-control" @bind="@_username" />
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <Label For="password" HelpText="Enter the password for the integrated security">Database Password: </Label>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <input id="password" type="password" class="form-control" @bind="@_password" />
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
        }
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="hostUsername" HelpText="Enter the username of the host for this site">Host Username:</Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <input id="hostUsername" class="form-control" @bind="@_username" readonly />
 | 
			
		||||
                <input id="hostUsername" class="form-control" @bind="@_hostusername" readonly />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
@ -116,7 +188,7 @@ else
 | 
			
		||||
                <Label For="hostPassword" HelpText="Enter the password for the host of this site">Host Password:</Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <input id="hostPassword" type="password" class="form-control" @bind="@_password" />
 | 
			
		||||
                <input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    }
 | 
			
		||||
@ -132,16 +204,24 @@ else
 | 
			
		||||
    private List<SiteTemplate> _siteTemplates;
 | 
			
		||||
    private List<Theme> _themeList;
 | 
			
		||||
    private List<Tenant> _tenants;
 | 
			
		||||
    private string _tenantid = "-1";
 | 
			
		||||
    private string _tenantid = "-";
 | 
			
		||||
 | 
			
		||||
    private string _tenantname = string.Empty;
 | 
			
		||||
    private string _databasetype = "LocalDB";
 | 
			
		||||
    private string _server = "(LocalDb)\\MSSQLLocalDB";
 | 
			
		||||
    private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
 | 
			
		||||
    private string _username = string.Empty;
 | 
			
		||||
    private string _password = string.Empty;
 | 
			
		||||
    private bool _integratedsecurity = true;
 | 
			
		||||
    private string _hostusername = Constants.HostUser;
 | 
			
		||||
    private string _hostpassword = string.Empty;
 | 
			
		||||
 | 
			
		||||
    private string _name = string.Empty;
 | 
			
		||||
    private string _urls = string.Empty;
 | 
			
		||||
    private string _themetype = string.Empty;
 | 
			
		||||
    private string _layouttype = string.Empty;
 | 
			
		||||
    private string _containertype = string.Empty;
 | 
			
		||||
    private string _sitetemplatetype = string.Empty;
 | 
			
		||||
    private bool _isinitialized = true;
 | 
			
		||||
    private string _username = string.Empty;
 | 
			
		||||
    private string _password = string.Empty;
 | 
			
		||||
 | 
			
		||||
    public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
 | 
			
		||||
 | 
			
		||||
@ -153,29 +233,29 @@ else
 | 
			
		||||
        _themes = ThemeService.GetThemeTypes(_themeList);
 | 
			
		||||
        _containers = ThemeService.GetContainerTypes(_themeList);
 | 
			
		||||
        _siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
 | 
			
		||||
        _username = Constants.HostUser;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async void TenantChanged(ChangeEventArgs e)
 | 
			
		||||
    private void TenantChanged(ChangeEventArgs e)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        _tenantid = (string)e.Value;
 | 
			
		||||
        if (string.IsNullOrEmpty(_tenantname))
 | 
			
		||||
        {
 | 
			
		||||
            _tenantid = (string)e.Value;
 | 
			
		||||
            if (_tenantid != "-1")
 | 
			
		||||
            {
 | 
			
		||||
                var tenant = _tenants.FirstOrDefault(item => item.TenantId == int.Parse(_tenantid));
 | 
			
		||||
                if (tenant != null)
 | 
			
		||||
                {
 | 
			
		||||
                    _isinitialized = tenant.IsInitialized;
 | 
			
		||||
                    StateHasChanged();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _tenantname = _name;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        StateHasChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void SetIntegratedSecurity(ChangeEventArgs e)
 | 
			
		||||
    {
 | 
			
		||||
        if (Convert.ToBoolean((string)e.Value))
 | 
			
		||||
        {
 | 
			
		||||
            await logger.LogError(ex, "Error Loading Tenant {TenantId} {Error}", _tenantid, ex.Message);
 | 
			
		||||
            AddModuleMessage("Error Loading Tenant", MessageType.Error);
 | 
			
		||||
            _integratedsecurity = true;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            _integratedsecurity = false;
 | 
			
		||||
        }
 | 
			
		||||
        StateHasChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async void ThemeChanged(ChangeEventArgs e)
 | 
			
		||||
@ -191,7 +271,7 @@ else
 | 
			
		||||
            {
 | 
			
		||||
                _panelayouts = new Dictionary<string, string>();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            StateHasChanged();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
@ -203,105 +283,116 @@ else
 | 
			
		||||
 | 
			
		||||
    private async Task SaveSite()
 | 
			
		||||
    {
 | 
			
		||||
        if (_tenantid != "-1" && _name != string.Empty && _urls != string.Empty && !string.IsNullOrEmpty(_themetype) && (_panelayouts.Count == 0 || !string.IsNullOrEmpty(_layouttype)) && !string.IsNullOrEmpty(_containertype) && !string.IsNullOrEmpty(_sitetemplatetype))
 | 
			
		||||
        if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && !string.IsNullOrEmpty(_themetype) && (_panelayouts.Count == 0 || !string.IsNullOrEmpty(_layouttype)) && !string.IsNullOrEmpty(_containertype) && !string.IsNullOrEmpty(_sitetemplatetype))
 | 
			
		||||
        {
 | 
			
		||||
            var unique = true;
 | 
			
		||||
            var duplicates = new List<string>();
 | 
			
		||||
            var aliases = await AliasService.GetAliasesAsync();
 | 
			
		||||
            foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
 | 
			
		||||
            {
 | 
			
		||||
                if (aliases.Exists(item => item.Name == name))
 | 
			
		||||
                {
 | 
			
		||||
                    unique = false;
 | 
			
		||||
                    duplicates.Add(name);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (unique)
 | 
			
		||||
 | 
			
		||||
            if (duplicates.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                var isvalid = true;
 | 
			
		||||
                InstallConfig config = new InstallConfig();
 | 
			
		||||
 | 
			
		||||
                if (!_isinitialized)
 | 
			
		||||
                if (_tenantid == "+")
 | 
			
		||||
                {
 | 
			
		||||
                    var user = new User();
 | 
			
		||||
                    user.SiteId = PageState.Site.SiteId;
 | 
			
		||||
                    user.Username = _username;
 | 
			
		||||
                    user.Password = _password;
 | 
			
		||||
                    user = await UserService.LoginUserAsync(user, false, false);
 | 
			
		||||
                    isvalid = user.IsAuthenticated;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (isvalid)
 | 
			
		||||
                {
 | 
			
		||||
                    ShowProgressIndicator();
 | 
			
		||||
 | 
			
		||||
                    aliases = new List<Alias>();
 | 
			
		||||
                    _urls = _urls.Replace("\n", ",");
 | 
			
		||||
                    
 | 
			
		||||
                    foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
 | 
			
		||||
                    {
 | 
			
		||||
                        var alias = new Alias();
 | 
			
		||||
                        alias.Name = name;
 | 
			
		||||
                        alias.TenantId = int.Parse(_tenantid);
 | 
			
		||||
                        alias.SiteId = -1;
 | 
			
		||||
                        alias = await AliasService.AddAliasAsync(alias);
 | 
			
		||||
                        aliases.Add(alias);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var site = new Site();
 | 
			
		||||
                    site.TenantId = int.Parse(_tenantid);
 | 
			
		||||
                    site.Name = _name;
 | 
			
		||||
                    site.LogoFileId = null;
 | 
			
		||||
                    site.FaviconFileId = null;
 | 
			
		||||
                    site.DefaultThemeType = _themetype;
 | 
			
		||||
                    site.DefaultLayoutType = (_layouttype == null ? string.Empty : _layouttype);
 | 
			
		||||
                    site.DefaultContainerType = _containertype;
 | 
			
		||||
                    site.PwaIsEnabled = false;
 | 
			
		||||
                    site.PwaAppIconFileId = null;
 | 
			
		||||
                    site.PwaSplashIconFileId = null;
 | 
			
		||||
                    site.AllowRegistration = false;
 | 
			
		||||
                    site.SiteTemplateType = _sitetemplatetype;
 | 
			
		||||
                    site = await SiteService.AddSiteAsync(site, aliases[0]);
 | 
			
		||||
 | 
			
		||||
                    foreach (Alias alias in aliases)
 | 
			
		||||
                    {
 | 
			
		||||
                        alias.SiteId = site.SiteId;
 | 
			
		||||
                        await AliasService.UpdateAliasAsync(alias);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (!_isinitialized)
 | 
			
		||||
                    if (!string.IsNullOrEmpty(_tenantname) && _tenants.FirstOrDefault(item => item.Name == _tenantname) == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        // validate host credentials
 | 
			
		||||
                        var user = new User();
 | 
			
		||||
                        user.SiteId = site.SiteId;
 | 
			
		||||
                        user.Username = _username;
 | 
			
		||||
                        user.Password = _password;
 | 
			
		||||
                        user.Email = PageState.User.Email;
 | 
			
		||||
                        user.DisplayName = PageState.User.DisplayName;
 | 
			
		||||
                        user = await UserService.AddUserAsync(user, aliases[0]);
 | 
			
		||||
 | 
			
		||||
                        if (user != null)
 | 
			
		||||
                        user.SiteId = PageState.Site.SiteId;
 | 
			
		||||
                        user.Username = Constants.HostUser;
 | 
			
		||||
                        user.Password = _hostpassword;
 | 
			
		||||
                        user = await UserService.LoginUserAsync(user, false, false);
 | 
			
		||||
                        if (user.IsAuthenticated)
 | 
			
		||||
                        {
 | 
			
		||||
                            var tenant = _tenants.FirstOrDefault(item => item.TenantId == int.Parse(_tenantid));
 | 
			
		||||
                            if (tenant != null)
 | 
			
		||||
                            if (!string.IsNullOrEmpty(_server) && !string.IsNullOrEmpty(_database))
 | 
			
		||||
                            {
 | 
			
		||||
                                tenant.IsInitialized = true;
 | 
			
		||||
                                await TenantService.UpdateTenantAsync(tenant);
 | 
			
		||||
                                var connectionString = string.Empty;
 | 
			
		||||
                                if (_databasetype == "LocalDB")
 | 
			
		||||
                                {
 | 
			
		||||
                                    connectionString = "Data Source=" + _server + ";AttachDbFilename=|DataDirectory|\\" + _database + ".mdf;Initial Catalog=" + _database + ";Integrated Security=SSPI;";
 | 
			
		||||
                                }
 | 
			
		||||
                                else
 | 
			
		||||
                                {
 | 
			
		||||
                                    connectionString = "Data Source=" + _server + ";Initial Catalog=" + _database + ";";
 | 
			
		||||
 | 
			
		||||
                                    if (_integratedsecurity)
 | 
			
		||||
                                    {
 | 
			
		||||
                                        connectionString += "Integrated Security=SSPI;";
 | 
			
		||||
                                    }
 | 
			
		||||
                                    else
 | 
			
		||||
                                    {
 | 
			
		||||
                                        connectionString += "User ID=" + _username + ";Password=" + _password;
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                config.ConnectionString = connectionString;
 | 
			
		||||
                                config.HostPassword = _hostpassword;
 | 
			
		||||
                                config.HostEmail = user.Email;
 | 
			
		||||
                                config.HostName = user.DisplayName;
 | 
			
		||||
                                config.TenantName = _tenantname;
 | 
			
		||||
                                config.IsNewTenant = true;
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                AddModuleMessage("You Must Specify A Server And Database", MessageType.Error);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            AddModuleMessage("Invalid Host Password", MessageType.Error);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        AddModuleMessage("Tenant Name Is Missing Or Already Exists", MessageType.Error);
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    await Log(aliases[0], LogLevel.Information, string.Empty, null, "Site Created {Site}", site);
 | 
			
		||||
 | 
			
		||||
                    var uri = new Uri(NavigationManager.Uri);
 | 
			
		||||
                    NavigationManager.NavigateTo(uri.Scheme + "://" + aliases[0].Name, true);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    await logger.LogError("Invalid Password Entered For Host {Username}", _username);
 | 
			
		||||
                    AddModuleMessage("Invalid Host Password", MessageType.Error);
 | 
			
		||||
                    var tenant = _tenants.FirstOrDefault(item => item.TenantId == int.Parse(_tenantid));
 | 
			
		||||
                    if (tenant != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        config.TenantName = tenant.Name;
 | 
			
		||||
                        config.ConnectionString= tenant.DBConnectionString;
 | 
			
		||||
                        config.IsNewTenant = false;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if  (!string.IsNullOrEmpty(config.TenantName))
 | 
			
		||||
                {
 | 
			
		||||
                    config.SiteName = _name;
 | 
			
		||||
                    config.Aliases = _urls.Replace("\n", ",");
 | 
			
		||||
                    config.DefaultTheme = _themetype;
 | 
			
		||||
                    config.DefaultLayout = _layouttype;
 | 
			
		||||
                    config.DefaultContainer = _containertype;
 | 
			
		||||
                    config.SiteTemplate = _sitetemplatetype;
 | 
			
		||||
 | 
			
		||||
                    ShowProgressIndicator();
 | 
			
		||||
 | 
			
		||||
                    var installation = await InstallationService.Install(config);
 | 
			
		||||
                    if (installation.Success)
 | 
			
		||||
                    {
 | 
			
		||||
                        var aliasname = config.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0];
 | 
			
		||||
                        var uri = new Uri(NavigationManager.Uri);
 | 
			
		||||
                        NavigationManager.NavigateTo(uri.Scheme + "://" + aliasname, true);
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        await logger.LogError("Error Creating Site {Error}", installation.Message);
 | 
			
		||||
                        AddModuleMessage(installation.Message, MessageType.Error);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                AddModuleMessage("An Alias Specified Has Already Been Used For Another Site", MessageType.Warning);
 | 
			
		||||
                AddModuleMessage(string.Join(", ", duplicates.ToArray()) + " Already Used For Another Site", MessageType.Warning);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
 | 
			
		||||
@ -1,156 +0,0 @@
 | 
			
		||||
@namespace Oqtane.Modules.Admin.Tenants
 | 
			
		||||
@inherits ModuleBase
 | 
			
		||||
@inject NavigationManager NavigationManager
 | 
			
		||||
@inject ITenantService TenantService
 | 
			
		||||
@inject IInstallationService InstallationService
 | 
			
		||||
 | 
			
		||||
    <table class="table table-borderless">
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="name" HelpText="Enter the name for the tenant">Name: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <input id="name" class="form-control" @bind="@name" />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="databaseType" HelpText="Select the database type for the tenant">Database Type: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <select id="databaseType" class="custom-select" @bind="@type">
 | 
			
		||||
                    <option value="LocalDB">Local Database</option>
 | 
			
		||||
                    <option value="SQLServer">SQL Server</option>
 | 
			
		||||
                </select>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="server" HelpText="Enter the server for the tenant">Server: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <input id="server" type="text" class="form-control" @bind="@server" />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="database" HelpText="Enter the database for the tenant">Database: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <input id="database" type="text" class="form-control" @bind="@database" />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="integratedSecurity" HelpText="Select if you want integrated security or not">Integrated Security: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <select id="integratedSecurity" class="custom-select" @onchange="SetIntegratedSecurity">
 | 
			
		||||
                    <option value="true" selected>True</option>
 | 
			
		||||
                    <option value="false">False</option>
 | 
			
		||||
                </select>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr style="@integratedsecurity">
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="username" HelpText="Enter the username for the integrated security">Username: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <input id="username" type="text" class="form-control" @bind="@username" />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr style="@integratedsecurity">
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="password" HelpText="Enter the password for the integrated security">Password: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <input id="password" type="password" class="form-control" @bind="@password" />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    </table>
 | 
			
		||||
<button type="button" class="btn btn-success" @onclick="SaveTenant">Save</button>
 | 
			
		||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
 | 
			
		||||
 | 
			
		||||
@code {
 | 
			
		||||
    private string name = string.Empty;
 | 
			
		||||
    private string type = "LocalDB";
 | 
			
		||||
    private string server = "(LocalDb)\\MSSQLLocalDB";
 | 
			
		||||
    private string database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
 | 
			
		||||
    private string username = string.Empty;
 | 
			
		||||
    private string password = string.Empty;
 | 
			
		||||
    private string schema = string.Empty;
 | 
			
		||||
    private string integratedsecurity = "display: none;";
 | 
			
		||||
 | 
			
		||||
    public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
 | 
			
		||||
 | 
			
		||||
    private void SetIntegratedSecurity(ChangeEventArgs e)
 | 
			
		||||
    {
 | 
			
		||||
        if (Convert.ToBoolean((string)e.Value))
 | 
			
		||||
        {
 | 
			
		||||
            integratedsecurity = "display: none;";
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            integratedsecurity = string.Empty;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task SaveTenant()
 | 
			
		||||
    {
 | 
			
		||||
        if (!string.IsNullOrEmpty(name))
 | 
			
		||||
        {
 | 
			
		||||
            ShowProgressIndicator();
 | 
			
		||||
 | 
			
		||||
            var connectionString = string.Empty;
 | 
			
		||||
            if (type == "LocalDB")
 | 
			
		||||
            {
 | 
			
		||||
                connectionString = "Data Source=" + server + ";AttachDbFilename=|DataDirectory|\\" + database + ".mdf;Initial Catalog=" + database + ";Integrated Security=SSPI;";
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                connectionString = "Data Source=" + server + ";Initial Catalog=" + database + ";";
 | 
			
		||||
                
 | 
			
		||||
                if (integratedsecurity == "display: none;")
 | 
			
		||||
                {
 | 
			
		||||
                    connectionString += "Integrated Security=SSPI;";
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    connectionString += "User ID=" + username + ";Password=" + password;
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var config = new InstallConfig
 | 
			
		||||
            {
 | 
			
		||||
                IsMaster = false,
 | 
			
		||||
                ConnectionString = connectionString,
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
            var installation = await InstallationService.Install(config);
 | 
			
		||||
            if (installation.Success)
 | 
			
		||||
            {
 | 
			
		||||
                //TODO : Move to Database Manager
 | 
			
		||||
                var tenant = new Tenant
 | 
			
		||||
                {
 | 
			
		||||
                    Name = name,
 | 
			
		||||
                    DBConnectionString = connectionString,
 | 
			
		||||
                    IsInitialized = false
 | 
			
		||||
                };
 | 
			
		||||
                await TenantService.AddTenantAsync(tenant);
 | 
			
		||||
                await logger.LogInformation("Tenant Created {Tenant}", tenant);
 | 
			
		||||
 | 
			
		||||
                NavigationManager.NavigateTo(NavigateUrl());
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                await logger.LogError("Error Creating Tenant {Error}", installation.Message);
 | 
			
		||||
                AddModuleMessage(installation.Message, MessageType.Error);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            AddModuleMessage("You Must Provide A Name For The Tenant", MessageType.Warning);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -6,7 +6,7 @@
 | 
			
		||||
<table class="table table-borderless">
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
            <Label For="name" HelpText="Enter the nameof the tenant">Name: </Label>
 | 
			
		||||
            <Label For="name" HelpText="The name of the tenant">Name: </Label>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            @if (name == Constants.MasterTenant)
 | 
			
		||||
@ -21,10 +21,10 @@
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
            <Label For="connectionString" HelpText="Enter the connection string for the tenant">Connection String: </Label>
 | 
			
		||||
            <Label For="connectionstring" HelpText="The database connection string">Connection String: </Label>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <input id="integratedSecurity" class="form-control" @bind="@connectionstring" />
 | 
			
		||||
            <textarea id="connectionstring" class="form-control" @bind="@connectionstring" rows="3" readonly></textarea>
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
@namespace Oqtane.Modules.Admin.Tenants
 | 
			
		||||
@inherits ModuleBase
 | 
			
		||||
@inject ITenantService TenantService
 | 
			
		||||
@inject IAliasService AliasService
 | 
			
		||||
 | 
			
		||||
@if (tenants == null)
 | 
			
		||||
{
 | 
			
		||||
@ -8,8 +9,6 @@
 | 
			
		||||
}
 | 
			
		||||
else
 | 
			
		||||
{
 | 
			
		||||
    <ActionLink Action="Add" Text="Add Tenant" />
 | 
			
		||||
 | 
			
		||||
    <Pager Items="@tenants">
 | 
			
		||||
        <Header>
 | 
			
		||||
            <th> </th>
 | 
			
		||||
@ -39,9 +38,25 @@ else
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await TenantService.DeleteTenantAsync(Tenant.TenantId);
 | 
			
		||||
            await logger.LogInformation("Tenant Deleted {Tenant}", Tenant);
 | 
			
		||||
            StateHasChanged();
 | 
			
		||||
            string message = string.Empty;
 | 
			
		||||
            var aliases = await AliasService.GetAliasesAsync();
 | 
			
		||||
            foreach (var alias in aliases)
 | 
			
		||||
            {
 | 
			
		||||
                if (alias.TenantId == Tenant.TenantId)
 | 
			
		||||
                {
 | 
			
		||||
                    message += ", " + alias.Name;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (string.IsNullOrEmpty(message))
 | 
			
		||||
            {
 | 
			
		||||
                await TenantService.DeleteTenantAsync(Tenant.TenantId);
 | 
			
		||||
                await logger.LogInformation("Tenant Deleted {Tenant}", Tenant);
 | 
			
		||||
                StateHasChanged();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                AddModuleMessage("Tenant Cannot Be Deleted Until The Following Sites Are Deleted: " + message.Substring(2), MessageType.Warning);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -7,116 +7,116 @@
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="mx-auto text-center">
 | 
			
		||||
            <img src="oqtane.png"/>
 | 
			
		||||
            <img src="oqtane.png" />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr class="app-rule"/>
 | 
			
		||||
    <hr class="app-rule" />
 | 
			
		||||
    <div class="row justify-content-center">
 | 
			
		||||
        <div class="col text-center">
 | 
			
		||||
            <h2>Database Configuration</h2><br/>
 | 
			
		||||
            <h2>Database Configuration</h2><br />
 | 
			
		||||
            <table class="form-group" cellpadding="4" cellspacing="4" style="margin: auto;">
 | 
			
		||||
                <tbody>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="control-label" style="font-weight: bold">Database Type: </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <select class="custom-select" @bind="@_databaseType">
 | 
			
		||||
                            <option value="LocalDB">Local Database</option>
 | 
			
		||||
                            <option value="SQLServer">SQL Server</option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="control-label" style="font-weight: bold">Server: </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <input type="text" class="form-control" @bind="@_serverName"/>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="control-label" style="font-weight: bold">Database: </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <input type="text" class="form-control" @bind="@_databaseName"/>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="control-label" style="font-weight: bold">Integrated Security: </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <select class="custom-select" @onchange="SetIntegratedSecurity">
 | 
			
		||||
                            <option value="true" selected>True</option>
 | 
			
		||||
                            <option value="false">False</option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr style="@_integratedSecurityDisplay">
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="control-label" style="font-weight: bold">Username: </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <input type="text" class="form-control" @bind="@_username"/>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr style="@_integratedSecurityDisplay">
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="control-label" style="font-weight: bold">Password: </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <input type="password" class="form-control" @bind="@_password"/>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <label class="control-label" style="font-weight: bold">Database Type: </label>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <select class="custom-select" @bind="@_databaseType">
 | 
			
		||||
                                <option value="LocalDB">Local Database</option>
 | 
			
		||||
                                <option value="SQLServer">SQL Server</option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <label class="control-label" style="font-weight: bold">Server: </label>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <input type="text" class="form-control" @bind="@_serverName" />
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <label class="control-label" style="font-weight: bold">Database: </label>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <input type="text" class="form-control" @bind="@_databaseName" />
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <label class="control-label" style="font-weight: bold">Integrated Security: </label>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <select class="custom-select" @onchange="SetIntegratedSecurity">
 | 
			
		||||
                                <option value="true" selected>True</option>
 | 
			
		||||
                                <option value="false">False</option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr style="@_integratedSecurityDisplay">
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <label class="control-label" style="font-weight: bold">Username: </label>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <input type="text" class="form-control" @bind="@_username" />
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr style="@_integratedSecurityDisplay">
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <label class="control-label" style="font-weight: bold">Password: </label>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <input type="password" class="form-control" @bind="@_password" />
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col text-center">
 | 
			
		||||
            <h2>Application Administrator</h2><br/>
 | 
			
		||||
            <h2>Application Administrator</h2><br />
 | 
			
		||||
            <table class="form-group" cellpadding="4" cellspacing="4" style="margin: auto;">
 | 
			
		||||
                <tbody>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="control-label" style="font-weight: bold">Username: </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <input type="text" class="form-control" @bind="@_hostUsername" readonly/>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="control-label" style="font-weight: bold">Password: </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <input type="password" class="form-control" @bind="@_hostPassword"/>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="control-label" style="font-weight: bold">Confirm: </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <input type="password" class="form-control" @bind="@_confirmPassword"/>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <label class="control-label" style="font-weight: bold">Email: </label>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <input type="text" class="form-control" @bind="@_hostEmail"/>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <label class="control-label" style="font-weight: bold">Username: </label>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <input type="text" class="form-control" @bind="@_hostUsername" readonly />
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <label class="control-label" style="font-weight: bold">Password: </label>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <input type="password" class="form-control" @bind="@_hostPassword" />
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <label class="control-label" style="font-weight: bold">Confirm: </label>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <input type="password" class="form-control" @bind="@_confirmPassword" />
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <label class="control-label" style="font-weight: bold">Email: </label>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <input type="text" class="form-control" @bind="@_hostEmail" />
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr class="app-rule"/>
 | 
			
		||||
    <hr class="app-rule" />
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="mx-auto text-center">
 | 
			
		||||
            <button type="button" class="btn btn-success" @onclick="Install">Install Now</button><br/><br/>
 | 
			
		||||
            <button type="button" class="btn btn-success" @onclick="Install">Install Now</button><br /><br />
 | 
			
		||||
            @((MarkupString) _message)
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="app-progress-indicator" style="@_loadingDisplay"></div>
 | 
			
		||||
@ -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
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
@ -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<string>(
 | 
			
		||||
                    "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<string>(
 | 
			
		||||
                    "interop.includeScript",
 | 
			
		||||
                    id, src, content, location);
 | 
			
		||||
                    id, src, content, location, integrity, crossorigin);
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
 | 
			
		||||
@ -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", "", "");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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")]
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -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<MasterDBContext>();
 | 
			
		||||
                    result = dbContext.Database.CanConnect();
 | 
			
		||||
                    var db = scope.ServiceProvider.GetRequiredService<MasterDBContext>();
 | 
			
		||||
                    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<IModuleDefinitionRepository>();
 | 
			
		||||
                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<IAliasRepository>();
 | 
			
		||||
                    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>();
 | 
			
		||||
                    siteState.Alias = alias;
 | 
			
		||||
 | 
			
		||||
                    var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
 | 
			
		||||
                    var site = sites.GetSites().FirstOrDefault(item => item.Name == install.SiteName);
 | 
			
		||||
                    if (site == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        var tenants = scope.ServiceProvider.GetRequiredService<ITenantRepository>();
 | 
			
		||||
                        var users = scope.ServiceProvider.GetRequiredService<IUserRepository>();
 | 
			
		||||
                        var roles = scope.ServiceProvider.GetRequiredService<IRoleRepository>();
 | 
			
		||||
                        var userroles = scope.ServiceProvider.GetRequiredService<IUserRoleRepository>();
 | 
			
		||||
                        var folders = scope.ServiceProvider.GetRequiredService<IFolderRepository>();
 | 
			
		||||
                        var log = scope.ServiceProvider.GetRequiredService<ILogManager>();
 | 
			
		||||
                        var identityUserManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>();
 | 
			
		||||
 | 
			
		||||
                        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<Permission>
 | 
			
		||||
                                        {
 | 
			
		||||
                                            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<T>(string sectionPathKey, T value)
 | 
			
		||||
        public void AddOrUpdateAppSetting<T>(string sectionPathKey, T value)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@ -296,7 +527,7 @@ namespace Oqtane.Infrastructure
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void SetValueRecursively<T>(string sectionPathKey, dynamic jsonObj, T value)
 | 
			
		||||
        private void SetValueRecursively<T>(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<ISiteRepository>();
 | 
			
		||||
 | 
			
		||||
                // Build default site only if no site present
 | 
			
		||||
                if (siteRepository.GetSites().Any()) return;
 | 
			
		||||
 | 
			
		||||
                var users = scope.ServiceProvider.GetRequiredService<IUserRepository>();
 | 
			
		||||
                var roles = scope.ServiceProvider.GetRequiredService<IRoleRepository>();
 | 
			
		||||
                var userRoles = scope.ServiceProvider.GetRequiredService<IUserRoleRepository>();
 | 
			
		||||
                var folders = scope.ServiceProvider.GetRequiredService<IFolderRepository>();
 | 
			
		||||
                var identityUserManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>();
 | 
			
		||||
                var tenants = scope.ServiceProvider.GetRequiredService<ITenantRepository>();
 | 
			
		||||
 | 
			
		||||
                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<IdentityUser> 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<Permission>
 | 
			
		||||
                        {
 | 
			
		||||
                            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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								Oqtane.Server/Infrastructure/Interfaces/IDatabaseManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								Oqtane.Server/Infrastructure/Interfaces/IDatabaseManager.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
using Oqtane.Models;
 | 
			
		||||
using Oqtane.Shared;
 | 
			
		||||
 | 
			
		||||
namespace Oqtane.Infrastructure
 | 
			
		||||
{
 | 
			
		||||
    public interface IDatabaseManager
 | 
			
		||||
    {
 | 
			
		||||
        bool IsInstalled();
 | 
			
		||||
        Installation Install();
 | 
			
		||||
        Installation Install(InstallConfig install);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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());
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -17,11 +17,6 @@
 | 
			
		||||
    <RootNamespace>Oqtane</RootNamespace>
 | 
			
		||||
  </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <None Remove="Modules\HtmlText\Scripts\HtmlText.1.0.0.sql" />
 | 
			
		||||
    <None Remove="Modules\HtmlText\Scripts\HtmlText.Uninstall.sql" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.1.0.0.sql" />
 | 
			
		||||
    <EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.Uninstall.sql" />
 | 
			
		||||
 | 
			
		||||
@ -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<IServiceScopeFactory>().CreateScope())
 | 
			
		||||
            {
 | 
			
		||||
                var databaseManager = serviceScope.ServiceProvider.GetService<DatabaseManager>();
 | 
			
		||||
                databaseManager.StartupMigration();
 | 
			
		||||
                var databaseManager = serviceScope.ServiceProvider.GetService<IDatabaseManager>();
 | 
			
		||||
                databaseManager.Install();
 | 
			
		||||
            }
 | 
			
		||||
            host.Run();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,11 @@ namespace Oqtane.Repository
 | 
			
		||||
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
 | 
			
		||||
            => optionsBuilder.UseSqlServer(_connectionString);
 | 
			
		||||
 | 
			
		||||
        public virtual DbSet<ApplicationVersion> ApplicationVersion { get; set; }
 | 
			
		||||
        public virtual DbSet<Alias> Alias { get; set; }
 | 
			
		||||
        public virtual DbSet<Tenant> Tenant { get; set; }
 | 
			
		||||
        public virtual DbSet<ModuleDefinition> ModuleDefinition { get; set; }
 | 
			
		||||
        public virtual DbSet<Job> Job { get; set; }
 | 
			
		||||
 | 
			
		||||
        public virtual DbSet<ApplicationVersion> ApplicationVersion { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ namespace Oqtane.Repository
 | 
			
		||||
{
 | 
			
		||||
    public interface IModuleDefinitionRepository
 | 
			
		||||
    {
 | 
			
		||||
        IEnumerable<ModuleDefinition> GetModuleDefinitions();
 | 
			
		||||
        IEnumerable<ModuleDefinition> GetModuleDefinitions(int sideId);
 | 
			
		||||
        ModuleDefinition GetModuleDefinition(int moduleDefinitionId, int sideId);
 | 
			
		||||
        void UpdateModuleDefinition(ModuleDefinition moduleDefinition);
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,11 @@ namespace Oqtane.Repository
 | 
			
		||||
            _permissions = permissions;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerable<ModuleDefinition> GetModuleDefinitions()
 | 
			
		||||
        {
 | 
			
		||||
            return LoadModuleDefinitions(-1); // used only during startup
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerable<ModuleDefinition> GetModuleDefinitions(int siteId)
 | 
			
		||||
        {
 | 
			
		||||
            return LoadModuleDefinitions(siteId);
 | 
			
		||||
@ -72,8 +77,12 @@ namespace Oqtane.Repository
 | 
			
		||||
            }
 | 
			
		||||
            List<ModuleDefinition> moduleDefinitions = _moduleDefinitions;
 | 
			
		||||
 | 
			
		||||
            // get module definition permissions for site
 | 
			
		||||
            List<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList();
 | 
			
		||||
            List<Permission> permissions = new List<Permission>();
 | 
			
		||||
            if (siteId != -1)
 | 
			
		||||
            {
 | 
			
		||||
                // get module definition permissions for site
 | 
			
		||||
                permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // get module definitions in database
 | 
			
		||||
            List<ModuleDefinition> 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();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
@ -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));
 | 
			
		||||
 | 
			
		||||
@ -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<Alias> 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<Alias> 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)
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,2 +1,5 @@
 | 
			
		||||
alter table Tenant drop column DBSchema
 | 
			
		||||
go
 | 
			
		||||
/*  
 | 
			
		||||
 | 
			
		||||
schema updates
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -155,7 +155,7 @@ namespace Oqtane
 | 
			
		||||
            services.AddSingleton(Configuration);
 | 
			
		||||
            services.AddSingleton<IInstallationManager, InstallationManager>();
 | 
			
		||||
            services.AddSingleton<ISyncManager, SyncManager>();
 | 
			
		||||
            services.AddSingleton<DatabaseManager>();
 | 
			
		||||
            services.AddSingleton<IDatabaseManager, DatabaseManager>();
 | 
			
		||||
 | 
			
		||||
            // install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain )
 | 
			
		||||
            InstallationManager.UnpackPackages("Modules,Themes", _webRoot);
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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; }
 | 
			
		||||
 | 
			
		||||
@ -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";
 | 
			
		||||
 | 
			
		||||
@ -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; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user