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:
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user