Database Manager

done:
+ master.sql as resource
+ implemented incremental database changes also for Master
+ dbUp sql script variables implemented
+ improved database handling and creation code
+ simpified database creation
+ almost all Database and Tenant creation moved to DatabaseManager.cs (rest code marked with TODO)
+ Unattended install of master can be performed by settings in appsettings.json
+ Improved IsInstalled checking
+ Removed DBSchema field from Tenant
+ Default database and site creation moved to Program.Main
This commit is contained in:
Pavel Vesely 2020-03-25 15:30:16 +01:00
parent 744782df7a
commit 940cdcb349
26 changed files with 726 additions and 525 deletions

View File

@ -164,7 +164,7 @@ else
_tenantid = (string)e.Value;
if (_tenantid != "-1")
{
Tenant tenant = _tenants.Where(item => item.TenantId == int.Parse(_tenantid)).FirstOrDefault();
Tenant tenant = _tenants.FirstOrDefault(item => item.TenantId == int.Parse(_tenantid));
if (tenant != null)
{
_isinitialized = tenant.IsInitialized;
@ -273,8 +273,11 @@ else
if (user != null)
{
Tenant tenant = _tenants.FirstOrDefault(item => item.TenantId == int.Parse(_tenantid));
tenant.IsInitialized = true;
await TenantService.UpdateTenantAsync(tenant);
if (tenant != null)
{
tenant.IsInitialized = true;
await TenantService.UpdateTenantAsync(tenant);
}
}
}
await Log(aliases[0], LogLevel.Information, "", null, "Site Created {Site}", site);

View File

@ -67,14 +67,6 @@
<input type="password" class="form-control" @bind="@password" />
</td>
</tr>
<tr>
<td>
<label class="control-label">Schema: </label>
</td>
<td>
<input class="form-control" @bind="@schema" />
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveTenant">Save</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
@ -88,7 +80,6 @@
string database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
string username = "";
string password = "";
string schema = "";
string integratedsecurity = "display: none;";
private void SetIntegratedSecurity(ChangeEventArgs e)
@ -109,32 +100,41 @@
{
ShowProgressIndicator();
string connectionstring = "";
string connectionString = "";
if (type == "LocalDB")
{
connectionstring = "Data Source=" + server + ";AttachDbFilename=|DataDirectory|\\" + database + ".mdf;Initial Catalog=" + database + ";Integrated Security=SSPI;";
connectionString = "Data Source=" + server + ";AttachDbFilename=|DataDirectory|\\" + database + ".mdf;Initial Catalog=" + database + ";Integrated Security=SSPI;";
}
else
{
connectionstring = "Data Source=" + server + ";Initial Catalog=" + database + ";";
connectionString = "Data Source=" + server + ";Initial Catalog=" + database + ";";
if (integratedsecurity == "display: none;")
{
connectionstring += "Integrated Security=SSPI;";
connectionString += "Integrated Security=SSPI;";
}
else
{
connectionstring += "User ID=" + username + ";Password=" + password;
connectionString += "User ID=" + username + ";Password=" + password;
}
}
Installation installation = await InstallationService.Install(connectionstring);
var config = new InstallConfig
{
IsMaster = false,
ConnectionString = connectionString,
};
Installation installation = await InstallationService.Install(config);
if (installation.Success)
{
Tenant tenant = new Tenant();
tenant.Name = name;
tenant.DBConnectionString = connectionstring;
tenant.DBSchema = schema;
tenant.IsInitialized = false;
//TODO : Move to Database Manager
Tenant tenant = new Tenant
{
Name = name,
DBConnectionString = connectionString,
IsInitialized = false
};
await TenantService.AddTenantAsync(tenant);
await logger.LogInformation("Tenant Created {Tenant}", tenant);

View File

@ -20,14 +20,7 @@
<input class="form-control" @bind="@connectionstring" />
</td>
</tr>
<tr>
<td>
<label class="control-label">Schema: </label>
</td>
<td>
<input class="form-control" @bind="@schema" />
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveTenant">Save</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
@ -38,7 +31,6 @@
int tenantid;
string name = "";
string connectionstring = "";
string schema = "";
protected override async Task OnInitializedAsync()
{
@ -50,7 +42,6 @@
{
name = tenant.Name;
connectionstring = tenant.DBConnectionString;
schema = tenant.DBSchema;
}
}
catch (Exception ex)
@ -70,7 +61,6 @@
{
tenant.Name = name;
tenant.DBConnectionString = connectionstring;
tenant.DBSchema = schema;
await TenantService.UpdateTenantAsync(tenant);
await logger.LogInformation("Tenant Saved {TenantId}", tenantid);

View File

@ -19,24 +19,21 @@ namespace Oqtane.Services
_navigationManager = navigationManager;
}
private string Apiurl
{
get { return CreateApiUrl(_siteState.Alias, _navigationManager.Uri, "Installation"); }
}
private string ApiUrl => CreateApiUrl(_siteState.Alias, _navigationManager.Uri, "Installation");
public async Task<Installation> IsInstalled()
{
return await _http.GetJsonAsync<Installation>(Apiurl + "/installed");
return await _http.GetJsonAsync<Installation>(ApiUrl + "/installed");
}
public async Task<Installation> Install(string connectionstring)
public async Task<Installation> Install(InstallConfig config)
{
return await _http.PostJsonAsync<Installation>(Apiurl, connectionstring);
return await _http.PostJsonAsync<Installation>(ApiUrl, config);
}
public async Task<Installation> Upgrade()
{
return await _http.GetJsonAsync<Installation>(Apiurl + "/upgrade");
return await _http.GetJsonAsync<Installation>(ApiUrl + "/upgrade");
}
}
}

View File

@ -1,12 +1,13 @@
using Oqtane.Models;
using System.Threading.Tasks;
using Oqtane.Shared;
namespace Oqtane.Services
{
public interface IInstallationService
{
Task<Installation> IsInstalled();
Task<Installation> Install(string connectionstring);
Task<Installation> Install(InstallConfig config);
Task<Installation> Upgrade();
}
}

View File

@ -7,117 +7,117 @@
<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 />
@((MarkupString)_message)
<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>
</div>
@ -140,7 +140,7 @@
private void SetIntegratedSecurity(ChangeEventArgs e)
{
if (Convert.ToBoolean((string)e.Value))
if (Convert.ToBoolean((string) e.Value))
{
_integratedSecurityDisplay = "display: none;";
}
@ -172,10 +172,20 @@
else
{
connectionstring += "User ID=" + _username + ";Password=" + _password;
}
}
Installation installation = await InstallationService.Install(connectionstring);
var config = new InstallConfig
{
ConnectionString = connectionstring,
HostUser = _hostUsername,
HostEmail = _hostEmail,
Password = _hostPassword,
IsMaster = true,
};
Installation installation = await InstallationService.Install(config);
//TODO: Should be moved to Database manager
if (installation.Success)
{
Site site = new Site();
@ -208,4 +218,5 @@
_message = "<div class=\"alert alert-danger\" role=\"alert\">Please Enter All Fields And Ensure Passwords Match And Are Greater Than 5 Characters In Length</div>";
}
}
}

View File

@ -1,16 +1,9 @@
using DbUp;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Oqtane.Models;
using Oqtane.Shared;
using System;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using Oqtane.Infrastructure;
using Oqtane.Infrastructure.Interfaces;
// ReSharper disable StringIndexOfIsCultureSpecific.1
@ -22,155 +15,45 @@ namespace Oqtane.Controllers
{
private readonly IConfigurationRoot _config;
private readonly IInstallationManager _installationManager;
private readonly DatabaseManager _databaseManager;
public InstallationController(IConfigurationRoot config, IInstallationManager installationManager)
public InstallationController(IConfigurationRoot config, IInstallationManager installationManager, DatabaseManager databaseManager)
{
_config = config;
_installationManager = installationManager;
_databaseManager = databaseManager;
}
// POST api/<controller>
[HttpPost]
public Installation Post([FromBody] string connectionString)
public Installation Post([FromBody] InstallConfig config)
{
var installation = new Installation { Success = false, Message = "" };
//TODO Security ????
var installation = new Installation {Success = false, Message = ""};
if (ModelState.IsValid)
if (ModelState.IsValid && (!_databaseManager.IsInstalled || !config.IsMaster))
{
bool master = false;
string defaultconnectionstring = _config.GetConnectionString("DefaultConnection");
if (string.IsNullOrEmpty(defaultconnectionstring) || connectionString == defaultconnectionstring)
{
master = true;
}
bool master = config.IsMaster;
bool exists = false;
if (master)
{
exists = IsInstalled().Success;
}
config.Alias = config.Alias ?? HttpContext.Request.Host.Value;
var result = DatabaseManager.InstallDatabase(config);
if (!exists)
if (result.Success)
{
string datadirectory = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
connectionString = connectionString.Replace("|DataDirectory|", datadirectory);
SqlConnection connection = new SqlConnection(connectionString);
try
if (master)
{
using (connection)
{
connection.Open();
}
exists = true;
}
catch
{
// database does not exist
_config.Reload();
}
// try to create database if it does not exist
if (!exists)
{
string masterConnectionString = "";
string databaseName = "";
string[] fragments = connectionString.Split(';', StringSplitOptions.RemoveEmptyEntries);
foreach (string fragment in fragments)
{
if (fragment.ToLower().Contains("initial catalog=") || fragment.ToLower().Contains("database="))
{
databaseName = fragment.Substring(fragment.IndexOf("=") + 1);
}
else
{
if (!fragment.ToLower().Contains("attachdbfilename="))
{
masterConnectionString += fragment + ";";
}
}
}
connection = new SqlConnection(masterConnectionString);
try
{
using (connection)
{
connection.Open();
SqlCommand command;
if (connectionString.ToLower().Contains("attachdbfilename=")) // LocalDB
{
command = new SqlCommand("CREATE DATABASE [" + databaseName + "] ON ( NAME = '" + databaseName + "', FILENAME = '" + datadirectory + "\\" + databaseName + ".mdf')", connection);
}
else
{
command = new SqlCommand("CREATE DATABASE [" + databaseName + "]", connection);
}
command.ExecuteNonQuery();
exists = true;
}
}
catch (Exception ex)
{
installation.Message = "Can Not Create Database - " + ex.Message;
}
// sleep to allow SQL server to attach new database
Thread.Sleep(5000);
}
if (exists)
{
// get master initialization script and update connectionstring and alias in seed data
string initializationScript = "";
if (master)
{
using (StreamReader reader = new StreamReader(Directory.GetCurrentDirectory() + "\\Scripts\\Master.sql"))
{
initializationScript = reader.ReadToEnd();
}
initializationScript = initializationScript.Replace("{ConnectionString}", connectionString.Replace(datadirectory, "|DataDirectory|"));
initializationScript = initializationScript.Replace("{Alias}", HttpContext.Request.Host.Value);
}
var dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionString)
.WithScript(new DbUp.Engine.SqlScript("Master.sql", initializationScript))
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly()); // tenant scripts should be added to /Scripts folder as Embedded Resources
var dbUpgrade = dbUpgradeConfig.Build();
if (dbUpgrade.IsUpgradeRequired())
{
var result = dbUpgrade.PerformUpgrade();
if (!result.Successful)
{
installation.Message = result.Error.Message;
}
else
{
// update appsettings
if (master)
{
string config = "";
using (StreamReader reader = new StreamReader(Directory.GetCurrentDirectory() + "\\appsettings.json"))
{
config = reader.ReadToEnd();
}
connectionString = connectionString.Replace(datadirectory, "|DataDirectory|");
connectionString = connectionString.Replace(@"\", @"\\");
config = config.Replace("DefaultConnection\": \"", "DefaultConnection\": \"" + connectionString);
using (StreamWriter writer = new StreamWriter(Directory.GetCurrentDirectory() + "\\appsettings.json"))
{
writer.WriteLine(config);
}
_config.Reload();
}
installation.Success = true;
}
}
}
}
else
{
installation.Message = "Application Is Already Installed";
installation.Success = true;
return installation;
}
installation.Message = result.Message;
return installation;
}
installation.Message = "Application Is Already Installed";
return installation;
}
@ -178,133 +61,21 @@ namespace Oqtane.Controllers
[HttpGet("installed")]
public Installation IsInstalled()
{
var installation = new Installation { Success = false, Message = "" };
var installation = new Installation {Success = false, Message = ""};
string datadirectory = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
string connectionString = _config.GetConnectionString("DefaultConnection");
connectionString = connectionString.Replace("|DataDirectory|", datadirectory);
if (!string.IsNullOrEmpty(connectionString))
{
SqlConnection connection = new SqlConnection(connectionString);
try
{
using (connection)
{
connection.Open();
}
installation.Success = true;
}
catch
{
// database does not exist
installation.Message = "Database Does Not Exist";
}
}
else
{
installation.Message = "Connection String Has Not Been Specified In Oqtane.Server\\appsettings.json";
}
if (installation.Success)
{
var dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionString)
.WithScript(new DbUp.Engine.SqlScript("Master.sql", ""));
var dbUpgrade = dbUpgradeConfig.Build();
installation.Success = !dbUpgrade.IsUpgradeRequired();
if (!installation.Success)
{
installation.Message = "Master Installation Scripts Have Not Been Executed";
}
else
{
using (var db = new InstallationContext(connectionString))
{
ApplicationVersion version = db.ApplicationVersion.ToList().LastOrDefault();
if (version == null || version.Version != Constants.Version)
{
version = new ApplicationVersion();
version.Version = Constants.Version;
version.CreatedOn = DateTime.UtcNow;
db.ApplicationVersion.Add(version);
db.SaveChanges();
}
}
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(item => item.FullName.Contains(".Module.")).ToArray();
// get tenants
using (var db = new InstallationContext(connectionString))
{
foreach (Tenant tenant in db.Tenant.ToList())
{
connectionString = tenant.DBConnectionString;
connectionString = connectionString.Replace("|DataDirectory|", datadirectory);
// upgrade framework
dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly());
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 (Assembly assembly in assemblies)
{
InstallModule(assembly, connectionString);
}
}
}
}
}
installation.Success = _databaseManager.IsInstalled;
installation.Message = _databaseManager.Message;
return installation;
}
private void InstallModule(Assembly assembly, string connectionstring)
{
var dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionstring)
.WithScriptsEmbeddedInAssembly(assembly); // scripts must be included as Embedded Resources
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
}
}
}
[HttpGet("upgrade")]
[Authorize(Roles = Constants.HostRole)]
public Installation Upgrade()
{
var installation = new Installation { Success = true, Message = "" };
var installation = new Installation {Success = true, Message = ""};
_installationManager.UpgradeFramework();
return installation;
}
}
public class InstallationContext : DbContext
{
private readonly string _connectionString;
public InstallationContext(string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(_connectionString);
public virtual DbSet<ApplicationVersion> ApplicationVersion { get; set; }
public virtual DbSet<Tenant> Tenant { get; set; }
}
}

View File

@ -75,95 +75,107 @@ namespace Oqtane.Controllers
[HttpPost]
public async Task<User> Post([FromBody] User user)
{
User newUser = null;
if (ModelState.IsValid)
{
// users created by non-administrators must be verified
bool verified = !(!User.IsInRole(Constants.AdminRole) && user.Username != Constants.HostUser);
var newUser = await CreateUser(user);
return newUser;
}
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser == null)
return null;
}
//TODO shoud be moved to another layer
private async Task<User> CreateUser(User user)
{
User newUser = null;
// users created by non-administrators must be verified
bool verified = !(!User.IsInRole(Constants.AdminRole) && user.Username != Constants.HostUser);
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser == null)
{
identityuser = new IdentityUser();
identityuser.UserName = user.Username;
identityuser.Email = user.Email;
identityuser.EmailConfirmed = verified;
var result = await _identityUserManager.CreateAsync(identityuser, user.Password);
if (result.Succeeded)
{
identityuser = new IdentityUser();
identityuser.UserName = user.Username;
identityuser.Email = user.Email;
identityuser.EmailConfirmed = verified;
var result = await _identityUserManager.CreateAsync(identityuser, user.Password);
if (result.Succeeded)
user.LastLoginOn = null;
user.LastIPAddress = "";
newUser = _users.AddUser(user);
if (!verified)
{
user.LastLoginOn = null;
user.LastIPAddress = "";
newUser = _users.AddUser(user);
if (!verified)
{
Notification notification = new Notification();
notification.SiteId = user.SiteId;
notification.FromUserId = null;
notification.ToUserId = newUser.UserId;
notification.ToEmail = "";
notification.Subject = "User Account Verification";
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = HttpContext.Request.Scheme + "://" + _tenants.GetAlias().Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
notification.Body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
notification.ParentId = null;
notification.CreatedOn = DateTime.UtcNow;
notification.IsDelivered = false;
notification.DeliveredOn = null;
_notifications.AddNotification(notification);
}
// assign to host role if this is the host user ( initial installation )
if (user.Username == Constants.HostUser)
{
int hostroleid = _roles.GetRoles(user.SiteId, true).Where(item => item.Name == Constants.HostRole).FirstOrDefault().RoleId;
UserRole userrole = new UserRole();
userrole.UserId = newUser.UserId;
userrole.RoleId = hostroleid;
userrole.EffectiveDate = null;
userrole.ExpiryDate = null;
_userRoles.AddUserRole(userrole);
}
// add folder for user
Folder folder = _folders.GetFolder(user.SiteId, "Users\\");
if (folder != null)
{
_folders.AddFolder(new Folder { SiteId = folder.SiteId, ParentId = folder.FolderId, Name = "My Folder", Path = folder.Path + newUser.UserId.ToString() + "\\", Order = 1, IsSystem = true,
Permissions = "[{\"PermissionName\":\"Browse\",\"Permissions\":\"[" + newUser.UserId.ToString() + "]\"},{\"PermissionName\":\"View\",\"Permissions\":\"All Users\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"[" + newUser.UserId.ToString() + "]\"}]" });
}
Notification notification = new Notification();
notification.SiteId = user.SiteId;
notification.FromUserId = null;
notification.ToUserId = newUser.UserId;
notification.ToEmail = "";
notification.Subject = "User Account Verification";
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = HttpContext.Request.Scheme + "://" + _tenants.GetAlias().Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
notification.Body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
notification.ParentId = null;
notification.CreatedOn = DateTime.UtcNow;
notification.IsDelivered = false;
notification.DeliveredOn = null;
_notifications.AddNotification(notification);
}
}
else
{
var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false);
if (result.Succeeded)
{
newUser = _users.GetUser(user.Username);
}
}
if (newUser != null && user.Username != Constants.HostUser)
{
// add auto assigned roles to user for site
List<Role> roles = _roles.GetRoles(user.SiteId).Where(item => item.IsAutoAssigned).ToList();
foreach (Role role in roles)
// assign to host role if this is the host user ( initial installation )
if (user.Username == Constants.HostUser)
{
int hostroleid = _roles.GetRoles(user.SiteId, true).Where(item => item.Name == Constants.HostRole).FirstOrDefault().RoleId;
UserRole userrole = new UserRole();
userrole.UserId = newUser.UserId;
userrole.RoleId = role.RoleId;
userrole.RoleId = hostroleid;
userrole.EffectiveDate = null;
userrole.ExpiryDate = null;
_userRoles.AddUserRole(userrole);
}
}
if (newUser != null)
{
newUser.Password = ""; // remove sensitive information
_logger.Log(user.SiteId, LogLevel.Information, this, LogFunction.Create, "User Added {User}", newUser);
// add folder for user
Folder folder = _folders.GetFolder(user.SiteId, "Users\\");
if (folder != null)
{
_folders.AddFolder(new Folder
{
SiteId = folder.SiteId, ParentId = folder.FolderId, Name = "My Folder", Path = folder.Path + newUser.UserId.ToString() + "\\", Order = 1, IsSystem = true,
Permissions = "[{\"PermissionName\":\"Browse\",\"Permissions\":\"[" + newUser.UserId.ToString() + "]\"},{\"PermissionName\":\"View\",\"Permissions\":\"All Users\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"[" +
newUser.UserId.ToString() + "]\"}]"
});
}
}
}
else
{
var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false);
if (result.Succeeded)
{
newUser = _users.GetUser(user.Username);
}
}
if (newUser != null && user.Username != Constants.HostUser)
{
// add auto assigned roles to user for site
List<Role> roles = _roles.GetRoles(user.SiteId).Where(item => item.IsAutoAssigned).ToList();
foreach (Role role in roles)
{
UserRole userrole = new UserRole();
userrole.UserId = newUser.UserId;
userrole.RoleId = role.RoleId;
userrole.EffectiveDate = null;
userrole.ExpiryDate = null;
_userRoles.AddUserRole(userrole);
}
}
if (newUser != null)
{
newUser.Password = ""; // remove sensitive information
_logger.Log(user.SiteId, LogLevel.Information, this, LogFunction.Create, "User Added {User}", newUser);
}
return newUser;
}

View File

@ -115,7 +115,7 @@ namespace Microsoft.Extensions.DependencyInjection
foreach (var file in assembliesFolder.EnumerateFiles($"*.{pattern}.*.dll"))
{
// check if assembly is already loaded
var assembly = Assemblies.FirstOrDefault(a => a.Location == file.FullName);
var assembly = Assemblies.FirstOrDefault(a =>!a.IsDynamic && a.Location == file.FullName);
if (assembly == null)
{
// load assembly from stream to prevent locking file ( as long as dependencies are in /bin they will load as well )

View File

@ -0,0 +1,367 @@
using System;
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.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Oqtane.Controllers;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
using File = System.IO.File;
namespace Oqtane.Infrastructure
{
public class DatabaseManager
{
private readonly IConfigurationRoot _config;
private readonly IServiceScopeFactory _serviceScopeFactory;
private bool _isInstalled;
public DatabaseManager(IConfigurationRoot config, IServiceScopeFactory serviceScopeFactory)
{
_config = config;
_serviceScopeFactory = serviceScopeFactory;
}
public string Message { get; set; }
public bool IsInstalled
{
get
{
if (!_isInstalled) _isInstalled = CheckInstallState();
return _isInstalled;
}
set => _isInstalled = value;
}
private bool CheckInstallState()
{
var defaultConnectionString = _config.GetConnectionString("DefaultConnection");
var result = !string.IsNullOrEmpty(defaultConnectionString);
if (result)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<MasterDBContext>();
result = dbContext.Database.CanConnect();
}
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"));
result = !dbUpgradeConfig.Build().IsUpgradeRequired();
if (!result) Message = "Master Installation Scripts Have Not Been Executed";
}
else
{
Message = "Database is not avaiable";
}
}
else
{
Message = "Connection string is empty";
}
return result;
}
public static string NormalizeConnectionString(string connectionString, string dataDirectory)
{
connectionString = connectionString
.Replace("|DataDirectory|", dataDirectory);
//.Replace(@"\", @"\\");
return connectionString;
}
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))
{
result = new Installation
{
Success = false,
Message = "Connection string is empty",
};
return result;
}
result = MasterMigration(connectionString, alias, result, installConfig.IsMaster);
if (installConfig.IsMaster && result.Success)
{
WriteVersionInfo(connectionString);
TenantMigration(connectionString, dataDirectory);
UpdateOqtaneSettings(connectionString);
AddOrUpdateAppSetting("Oqtane:DefaultAlias", alias);
}
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};
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))
{
dbc.Database.EnsureCreated();
}
}
catch (Exception e)
{
result = new Installation
{
Success = false,
Message = e.Message,
};
Console.WriteLine(e);
return result;
}
var dbUpgradeConfig = DeployChanges
.To
.SqlDatabase(connectionString)
.WithVariable("ConnectionString", connectionString)
.WithVariable("Alias", alias)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Master") && master || s.Contains("Tenant"))
;
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;
}
return result;
}
private static void ModuleMigration(Assembly assembly, string connectionString)
{
var dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(assembly); // scripts must be included as Embedded Resources
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
}
}
}
private static void WriteVersionInfo(string connectionString)
{
using (var db = new InstallationContext(connectionString))
{
var version = db.ApplicationVersion.ToList().LastOrDefault();
if (version == null || version.Version != Constants.Version)
{
version = new ApplicationVersion {Version = Constants.Version, CreatedOn = DateTime.UtcNow};
db.ApplicationVersion.Add(version);
db.SaveChanges();
}
}
}
private static void TenantMigration(string connectionString, string dataDirectory)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(item => item.FullName != null && item.FullName.Contains(".Module.")).ToArray();
// get tenants
using (var db = new InstallationContext(connectionString))
{
foreach (var tenant in db.Tenant.ToList())
{
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);
}
}
}
public static void UpdateOqtaneSettings(string connectionString)
{
AddOrUpdateAppSetting("ConnectionStrings:DefaultConnection", connectionString);
//AddOrUpdateAppSetting("Oqtane:DefaultAlias", connectionString);
}
public static void AddOrUpdateAppSetting<T>(string sectionPathKey, T value)
{
try
{
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json");
var json = File.ReadAllText(filePath);
dynamic jsonObj = JsonConvert.DeserializeObject(json);
SetValueRecursively(sectionPathKey, jsonObj, value);
string output = JsonConvert.SerializeObject(jsonObj, Formatting.Indented);
File.WriteAllText(filePath, output);
}
catch (Exception ex)
{
Console.WriteLine("Error writing app settings | {0}", ex);
}
}
private static void SetValueRecursively<T>(string sectionPathKey, dynamic jsonObj, T value)
{
// split the string at the first ':' character
var remainingSections = sectionPathKey.Split(":", 2);
var currentSection = remainingSections[0];
if (remainingSections.Length > 1)
{
// continue with the procress, moving down the tree
var nextSection = remainingSections[1];
SetValueRecursively(nextSection, jsonObj[currentSection], value);
}
else
{
// we've got to the end of the tree, set the value
jsonObj[currentSection] = value;
}
}
public void StartupMigration()
{
var defaultConnectionString = _config.GetConnectionString("DefaultConnection");
var defaultAlias = _config.GetSection("Oqtane").GetValue("DefaultAlias", string.Empty);
// if no values specified, fallback to IDE installer
if (string.IsNullOrEmpty(defaultConnectionString) || string.IsNullOrEmpty(defaultAlias))
{
IsInstalled = false;
return;
}
var result = MasterMigration(defaultConnectionString, defaultAlias, null, true);
IsInstalled = result.Success;
if (_isInstalled)
BuildDefaultSite();
}
public void BuildDefaultSite()
{
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 site = new Site
{
TenantId = -1,
Name = "Default Site",
LogoFileId = null,
DefaultThemeType = Constants.DefaultTheme,
DefaultLayoutType = Constants.DefaultLayout,
DefaultContainerType = Constants.DefaultContainer,
};
site = siteRepository.AddSite(site);
var user = new User
{
SiteId = site.SiteId,
Username = Constants.HostUser,
//TODO Decide default password or throw exception ??
Password = _config.GetSection("Oqtane").GetValue("DefaultPassword", "oQtane123"),
Email = _config.GetSection("Oqtane").GetValue("DefaultEmail", "nobody@cortonso.com"),
DisplayName = Constants.HostUser,
};
CreateHostUser(folders, userRoles, roles, users, identityUserManager, user);
}
}
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, "Users\\");
if (folder != null)
folderRepository.AddFolder(new Folder
{
SiteId = folder.SiteId, ParentId = folder.FolderId, Name = "My Folder", Path = folder.Path + newUser.UserId + "\\", Order = 1, IsSystem = true,
Permissions = "[{\"PermissionName\":\"Browse\",\"Permissions\":\"[" + newUser.UserId + "]\"},{\"PermissionName\":\"View\",\"Permissions\":\"All Users\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"[" +
newUser.UserId + "]\"}]",
});
}
}
}
}

View File

@ -27,14 +27,10 @@
</PropertyGroup>
<ItemGroup>
<None Remove="Scripts\00.00.01.sql" />
<None Remove="Scripts\Master.sql" />
</ItemGroup>
<ItemGroup>
<Content Include="Scripts\Master.sql" />
<EmbeddedResource Include="Scripts\00.00.01.sql" />
<EmbeddedResource Include="Scripts\00.00.00.sql" />
<EmbeddedResource Include="Scripts\Master.00.00.00.sql" />
<EmbeddedResource Include="Scripts\Master.00.00.01.sql" />
<EmbeddedResource Include="Scripts\Tenant.00.00.00.sql" />
<EmbeddedResource Include="Scripts\Tenant.00.00.01.sql" />
</ItemGroup>
<ItemGroup>

View File

@ -4,6 +4,8 @@ using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Blazor.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Infrastructure;
namespace Oqtane.Server
{
@ -12,7 +14,14 @@ namespace Oqtane.Server
#if DEBUG || RELEASE
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
var host = CreateHostBuilder(args).Build();
using (var serviceScope = host.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var manager = serviceScope.ServiceProvider.GetService<DatabaseManager>();
manager.StartupMigration();
}
//DatabaseManager.StartupMigration();
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>

View File

@ -22,21 +22,11 @@ namespace Oqtane.Repository
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_tenant.DBConnectionString
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString())
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString())
);
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
if (_tenant.DBSchema != "")
{
modelBuilder.HasDefaultSchema(_tenant.DBSchema);
}
}
public override int SaveChanges()
{
ChangeTracker.DetectChanges();

View File

@ -0,0 +1,23 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
namespace Oqtane.Repository
{
public class InstallationContext : DbContext
{
private readonly string _connectionString;
public InstallationContext(string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(_connectionString);
public virtual DbSet<ApplicationVersion> ApplicationVersion { get; set; }
public virtual DbSet<Tenant> Tenant { get; set; }
}
}

View File

@ -41,7 +41,7 @@ namespace Oqtane.Repository
var siteTemplateObject = ActivatorUtilities.CreateInstance(_serviceProvider, siteTemplateType);
siteTemplate = new SiteTemplate
{
Name = (string)siteTemplateType.GetProperty("Name").GetValue(siteTemplateObject),
Name = (string)siteTemplateType.GetProperty("Name")?.GetValue(siteTemplateObject),
TypeName = siteTemplateType.AssemblyQualifiedName
};
siteTemplates.Add(siteTemplate);

View File

@ -49,10 +49,14 @@ namespace Oqtane.Repository
}
public void DeleteTenant(int tenantId)
{
{
Tenant tenant = _db.Tenant.Find(tenantId);
_db.Tenant.Remove(tenant);
_db.SaveChanges();
if (tenant != null)
{
_db.Tenant.Remove(tenant);
_db.SaveChanges();
}
_cache.Remove("tenants");
}
}

View File

@ -29,43 +29,41 @@ namespace Oqtane.Repository
{
aliasName = accessor.HttpContext.Request.Host.Value;
string path = accessor.HttpContext.Request.Path.Value;
string[] segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
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
else // background processes can pass in an alias using the SiteState service
{
if (siteState != null)
{
aliasId = siteState.Alias.AliasId;
}
aliasId = siteState?.Alias?.AliasId ?? -1;
}
// get the alias and tenant
if (aliasId != -1 || aliasName != "")
IEnumerable<Alias> aliases = aliasRepository.GetAliases().ToList(); // cached
if (aliasId != -1)
{
IEnumerable<Alias> aliases = aliasRepository.GetAliases(); // cached
IEnumerable<Tenant> tenants = tenantRepository.GetTenants(); // cached
_alias = aliases.FirstOrDefault(item => item.AliasId == aliasId);
}
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);
}
if (aliasId != -1)
{
_alias = aliases.FirstOrDefault(item => item.AliasId == aliasId);
}
else
{
_alias = aliases.FirstOrDefault(item => item.Name == aliasName);
}
if (_alias != null)
{
_tenant = tenants.FirstOrDefault(item => item.TenantId == _alias.TenantId);
}
if (_alias != null)
{
IEnumerable<Tenant> tenants = tenantRepository.GetTenants(); // cached
_tenant = tenants.FirstOrDefault(item => item.TenantId == _alias.TenantId);
}
}

View File

@ -123,7 +123,7 @@ 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'', 1, '', getdate(), '', getdate())
VALUES (1, N'Master', N'$ConnectionString$', N'', 1, '', getdate(), '', getdate())
GO
SET IDENTITY_INSERT [dbo].[Tenant] OFF
GO
@ -131,7 +131,7 @@ 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())
VALUES (1, N'$Alias$', 1, 1, '', getdate(), '', getdate())
GO
SET IDENTITY_INSERT [dbo].[Alias] OFF
GO

View File

@ -0,0 +1,2 @@
alter table Tenant drop column DBSchema
go

View File

@ -404,7 +404,7 @@ Create indexes
*/
CREATE UNIQUE NONCLUSTERED INDEX IX_Setting ON dbo.Setting
CREATE UNIQUE NONCLUSTERED INDEX IX_Setting ON [dbo].Setting
(
EntityName,
EntityId,
@ -412,13 +412,13 @@ CREATE UNIQUE NONCLUSTERED INDEX IX_Setting ON dbo.Setting
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX IX_User ON dbo.[User]
CREATE UNIQUE NONCLUSTERED INDEX IX_User ON [dbo].[User]
(
Username
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX IX_Permission ON dbo.Permission
CREATE UNIQUE NONCLUSTERED INDEX IX_Permission ON [dbo].Permission
(
SiteId,
EntityName,
@ -429,7 +429,7 @@ CREATE UNIQUE NONCLUSTERED INDEX IX_Permission ON dbo.Permission
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX IX_Page ON dbo.Page
CREATE UNIQUE NONCLUSTERED INDEX IX_Page ON [dbo].Page
(
SiteId,
[Path],
@ -437,14 +437,14 @@ CREATE UNIQUE NONCLUSTERED INDEX IX_Page ON dbo.Page
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX IX_UserRole ON dbo.UserRole
CREATE UNIQUE NONCLUSTERED INDEX IX_UserRole ON [dbo].UserRole
(
RoleId,
UserId
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX IX_Folder ON dbo.Folder
CREATE UNIQUE NONCLUSTERED INDEX IX_Folder ON [dbo].Folder
(
SiteId,
[Path]

View File

@ -0,0 +1,7 @@
{
"Alias" : "",
"DefaultConnection" : "",
"HostUser" : "host",
"Password" : "",
"HostEmail" : ""
}

View File

@ -19,9 +19,12 @@ using Oqtane.Infrastructure.Interfaces;
using Oqtane.Repository;
using Oqtane.Security;
using Oqtane.Services;
// DO NOT REMOVE - needed for client-side Blazor
using Oqtane.Shared;
#if WASM
// DO NOT REMOVE - needed for client-side Blazor
using Microsoft.AspNetCore.ResponseCompression;
#endif
namespace Oqtane
{
@ -113,7 +116,7 @@ namespace Oqtane
services.AddDbContext<MasterDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString())
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString())
));
services.AddDbContext<TenantDBContext>(options => { });
@ -160,7 +163,8 @@ namespace Oqtane
services.AddSingleton(Configuration);
services.AddSingleton<IInstallationManager, InstallationManager>();
services.AddSingleton<ISyncManager, SyncManager>();
services.AddSingleton<DatabaseManager>();
// register transient scoped core services
services.AddTransient<IModuleDefinitionRepository, ModuleDefinitionRepository>();
services.AddTransient<IThemeRepository, ThemeRepository>();
@ -203,6 +207,8 @@ namespace Oqtane
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Oqtane", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -1,5 +1,9 @@
{
"ConnectionStrings": {
"DefaultConnection": ""
},
"Oqtane": {
"DefaultAlias": "",
"DefaultPassword": ""
}
}

View File

@ -7,9 +7,7 @@ namespace Oqtane.Models
public int TenantId { get; set; }
public string Name { get; set; }
public string DBConnectionString { get; set; }
public string DBSchema { get; set; }
public bool IsInitialized { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }
public string ModifiedBy { get; set; }

View File

@ -0,0 +1,12 @@
namespace Oqtane.Shared
{
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 HostEmail { get; set; }
public bool IsMaster { get; set; }
}
}