Install Wizard
This commit is contained in:
192
Oqtane.Server/Controllers/InstallationController.cs
Normal file
192
Oqtane.Server/Controllers/InstallationController.cs
Normal file
@ -0,0 +1,192 @@
|
||||
using DbUp;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Oqtane.Models;
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
[Route("{site}/api/[controller]")]
|
||||
public class InstallationController : Controller
|
||||
{
|
||||
private readonly IConfigurationRoot _config;
|
||||
|
||||
public InstallationController(IConfigurationRoot config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
// POST api/<controller>
|
||||
[HttpPost]
|
||||
public GenericResponse Post([FromBody] string connectionString)
|
||||
{
|
||||
var response = new GenericResponse { Success = false, Message = "" };
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
bool exists = IsInstalled().Success;
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
string datadirectory = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
|
||||
connectionString = connectionString.Replace("|DataDirectory|", datadirectory);
|
||||
|
||||
SqlConnection connection = new SqlConnection(connectionString);
|
||||
try
|
||||
{
|
||||
using (connection)
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
exists = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// database does not exist
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
response.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 = "";
|
||||
using (StreamReader reader = new StreamReader(Directory.GetCurrentDirectory() + "\\Scripts\\Master.sql"))
|
||||
{
|
||||
initializationScript = reader.ReadToEnd();
|
||||
}
|
||||
initializationScript = initializationScript.Replace("{ConnectionString}", connectionString);
|
||||
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)
|
||||
{
|
||||
response.Message = result.Error.Message;
|
||||
}
|
||||
else
|
||||
{
|
||||
// update appsettings
|
||||
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();
|
||||
response.Success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response.Message = "Application Is Already Installed";
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// GET api/<controller>/installed
|
||||
[HttpGet("installed")]
|
||||
public GenericResponse IsInstalled()
|
||||
{
|
||||
var response = new GenericResponse { Success = false, Message = "" };
|
||||
|
||||
string datadirectory = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
|
||||
string connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
connectionString = connectionString.Replace("|DataDirectory|", datadirectory);
|
||||
|
||||
SqlConnection connection = new SqlConnection(connectionString);
|
||||
try
|
||||
{
|
||||
using (connection)
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
response.Success = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// database does not exist
|
||||
response.Message = "Database Does Not Exist";
|
||||
}
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
var dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionString)
|
||||
.WithScript(new DbUp.Engine.SqlScript("Master.sql", ""));
|
||||
var dbUpgrade = dbUpgradeConfig.Build();
|
||||
response.Success = !dbUpgrade.IsUpgradeRequired();
|
||||
if (!response.Success)
|
||||
{
|
||||
response.Message = "Scripts Have Not Been Run";
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
@ -86,20 +86,9 @@ namespace Oqtane.Controllers
|
||||
[HttpPost("login")]
|
||||
public async Task<User> Login([FromBody] User user)
|
||||
{
|
||||
// TODO: seed host user - this logic should be moved to installation
|
||||
IdentityUser identityuser = await identityUserManager.FindByNameAsync("host");
|
||||
if (identityuser == null)
|
||||
{
|
||||
var result = await identityUserManager.CreateAsync(new IdentityUser { UserName = "host", Email = "host" }, "password");
|
||||
if (result.Succeeded)
|
||||
{
|
||||
users.AddUser(new Models.User { Username = "host", DisplayName = "host", IsSuperUser = true, Roles = "" });
|
||||
}
|
||||
}
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
identityuser = await identityUserManager.FindByNameAsync(user.Username);
|
||||
IdentityUser identityuser = await identityUserManager.FindByNameAsync(user.Username);
|
||||
if (identityuser != null)
|
||||
{
|
||||
var result = await identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false);
|
||||
|
@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.Reflection;
|
||||
using DbUp;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Oqtane.Filters
|
||||
{
|
||||
public class UpgradeFilter : IStartupFilter
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
public UpgradeFilter(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
|
||||
{
|
||||
string datadirectory = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
|
||||
string connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
connectionString = connectionString.Replace("|DataDirectory|", datadirectory);
|
||||
|
||||
// check if database exists
|
||||
SqlConnection connection = new SqlConnection(connectionString);
|
||||
bool databaseExists;
|
||||
try
|
||||
{
|
||||
using (connection)
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
databaseExists = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
databaseExists = false;
|
||||
}
|
||||
|
||||
// create database if it does not exist
|
||||
if (!databaseExists)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// sleep to allow SQL server to attach new database
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
|
||||
// get master initialization script and update connectionstring in seed data
|
||||
string initializationScript = "";
|
||||
using (StreamReader reader = new StreamReader(Directory.GetCurrentDirectory() + "\\Scripts\\Master.sql"))
|
||||
{
|
||||
initializationScript = reader.ReadToEnd();
|
||||
}
|
||||
initializationScript = initializationScript.Replace("{ConnectionString}", connectionString);
|
||||
|
||||
// handle upgrade scripts
|
||||
var dbUpgradeConfig = DeployChanges.To.SqlDatabase(connectionString)
|
||||
.WithScript(new DbUp.Engine.SqlScript("Master.sql", initializationScript))
|
||||
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly()); // upgrade scripts should be added to /Scripts folder as Embedded Resources
|
||||
var dbUpgrade = dbUpgradeConfig.Build();
|
||||
if (dbUpgrade.IsUpgradeRequired())
|
||||
{
|
||||
var result = dbUpgrade.PerformUpgrade();
|
||||
if (!result.Successful)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,8 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.AspNetCore.Blazor.Hosting;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System;
|
||||
using Microsoft.AspNetCore;
|
||||
|
||||
namespace Oqtane.Server
|
||||
{
|
||||
@ -14,7 +11,6 @@ namespace Oqtane.Server
|
||||
#if DEBUG || RELEASE
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
PrepareConfiguration();
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
@ -29,7 +25,6 @@ namespace Oqtane.Server
|
||||
#if WASM
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
PrepareConfiguration();
|
||||
BuildWebHost(args).Run();
|
||||
}
|
||||
|
||||
@ -42,24 +37,5 @@ namespace Oqtane.Server
|
||||
.Build();
|
||||
#endif
|
||||
|
||||
private static void PrepareConfiguration()
|
||||
{
|
||||
string config = "";
|
||||
using (StreamReader reader = new StreamReader(Directory.GetCurrentDirectory() + "\\appsettings.json"))
|
||||
{
|
||||
config = reader.ReadToEnd();
|
||||
}
|
||||
// if using LocalDB create a unique database name
|
||||
if (config.Contains("AttachDbFilename=|DataDirectory|\\\\Oqtane.mdf"))
|
||||
{
|
||||
string timestamp = DateTime.Now.ToString("yyyyMMddHHmm");
|
||||
config = config.Replace("Initial Catalog=Oqtane", "Initial Catalog=Oqtane-" + timestamp)
|
||||
.Replace("AttachDbFilename=|DataDirectory|\\\\Oqtane.mdf", "AttachDbFilename=|DataDirectory|\\\\Oqtane-" + timestamp + ".mdf");
|
||||
using (StreamWriter writer = new StreamWriter(Directory.GetCurrentDirectory() + "\\appsettings.json"))
|
||||
{
|
||||
writer.WriteLine(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ namespace Oqtane.Repository
|
||||
{
|
||||
public class AliasRepository : IAliasRepository
|
||||
{
|
||||
private HostContext db;
|
||||
private MasterContext db;
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
public AliasRepository(HostContext context, IMemoryCache cache)
|
||||
public AliasRepository(MasterContext context, IMemoryCache cache)
|
||||
{
|
||||
db = context;
|
||||
_cache = cache;
|
||||
|
@ -3,9 +3,9 @@ using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
public class HostContext : DbContext
|
||||
public class MasterContext : DbContext
|
||||
{
|
||||
public HostContext(DbContextOptions<HostContext> options) : base(options) { }
|
||||
public MasterContext(DbContextOptions<MasterContext> options) : base(options) { }
|
||||
|
||||
public virtual DbSet<Alias> Alias { get; set; }
|
||||
public virtual DbSet<Tenant> Tenant { get; set; }
|
@ -10,10 +10,10 @@ namespace Oqtane.Repository
|
||||
{
|
||||
public class TenantRepository : ITenantRepository
|
||||
{
|
||||
private HostContext db;
|
||||
private MasterContext db;
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
public TenantRepository(HostContext context, IMemoryCache cache)
|
||||
public TenantRepository(MasterContext context, IMemoryCache cache)
|
||||
{
|
||||
db = context;
|
||||
_cache = cache;
|
||||
|
@ -1,20 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Oqtane.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
public class TenantResolver : ITenantResolver
|
||||
{
|
||||
private HostContext db;
|
||||
private MasterContext db;
|
||||
private readonly string aliasname;
|
||||
private readonly IAliasRepository _aliasrepository;
|
||||
private readonly ITenantRepository _tenantrepository;
|
||||
|
||||
public TenantResolver(HostContext context, IHttpContextAccessor accessor, IAliasRepository aliasrepository, ITenantRepository tenantrepository)
|
||||
public TenantResolver(MasterContext context, IHttpContextAccessor accessor, IAliasRepository aliasrepository, ITenantRepository tenantrepository)
|
||||
{
|
||||
db = context;
|
||||
_aliasrepository = aliasrepository;
|
||||
|
@ -54,10 +54,10 @@ GO
|
||||
SET IDENTITY_INSERT [dbo].[Alias] ON
|
||||
GO
|
||||
INSERT [dbo].[Alias] ([AliasId], [Name], [TenantId], [SiteId])
|
||||
VALUES (1, N'localhost:44357', 1, 1)
|
||||
VALUES (1, N'{Alias}', 1, 1)
|
||||
GO
|
||||
INSERT [dbo].[Alias] ([AliasId], [Name], [TenantId], [SiteId])
|
||||
VALUES (2, N'localhost:44357/site2', 1, 2)
|
||||
VALUES (2, N'{Alias}/site2', 1, 2)
|
||||
GO
|
||||
SET IDENTITY_INSERT [dbo].[Alias] OFF
|
||||
GO
|
||||
|
@ -11,7 +11,6 @@ using System.Reflection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Filters;
|
||||
using System.IO;
|
||||
using System.Runtime.Loader;
|
||||
using Oqtane.Services;
|
||||
@ -25,7 +24,7 @@ namespace Oqtane.Server
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public IConfiguration Configuration { get; }
|
||||
public IConfigurationRoot Configuration { get; }
|
||||
public Startup(IWebHostEnvironment env)
|
||||
{
|
||||
var builder = new ConfigurationBuilder()
|
||||
@ -68,6 +67,7 @@ namespace Oqtane.Server
|
||||
|
||||
// register scoped core services
|
||||
services.AddScoped<SiteState>();
|
||||
services.AddScoped<IInstallationService, InstallationService>();
|
||||
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
|
||||
services.AddScoped<IThemeService, ThemeService>();
|
||||
services.AddScoped<IAliasService, AliasService>();
|
||||
@ -101,7 +101,7 @@ namespace Oqtane.Server
|
||||
|
||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
|
||||
services.AddDbContext<HostContext>(options =>
|
||||
services.AddDbContext<MasterContext>(options =>
|
||||
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
|
||||
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString())
|
||||
));
|
||||
@ -143,10 +143,8 @@ namespace Oqtane.Server
|
||||
|
||||
services.AddMvc().AddNewtonsoftJson();
|
||||
|
||||
// register database install/upgrade filter
|
||||
services.AddTransient<IStartupFilter, UpgradeFilter>();
|
||||
|
||||
// register singleton scoped core services
|
||||
services.AddSingleton<IConfigurationRoot>(Configuration);
|
||||
services.AddSingleton<IModuleDefinitionRepository, ModuleDefinitionRepository>();
|
||||
services.AddSingleton<IThemeRepository, ThemeRepository>();
|
||||
|
||||
@ -237,7 +235,7 @@ namespace Oqtane.Server
|
||||
{
|
||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
|
||||
services.AddDbContext<HostContext>(options =>
|
||||
services.AddDbContext<MasterContext>(options =>
|
||||
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
|
||||
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString())
|
||||
));
|
||||
@ -279,10 +277,8 @@ namespace Oqtane.Server
|
||||
|
||||
services.AddMvc().AddNewtonsoftJson();
|
||||
|
||||
// register database install/upgrade filter
|
||||
services.AddTransient<IStartupFilter, UpgradeFilter>();
|
||||
|
||||
// register singleton scoped core services
|
||||
services.AddSingleton<IConfigurationRoot>(Configuration);
|
||||
services.AddSingleton<IModuleDefinitionRepository, ModuleDefinitionRepository>();
|
||||
services.AddSingleton<IThemeRepository, ThemeRepository>();
|
||||
|
||||
|
@ -1,5 +1,19 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Data Source=(LocalDb)\\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\\Oqtane.mdf;Initial Catalog=Oqtane;Integrated Security=SSPI;"
|
||||
"DefaultConnection": ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -246,3 +246,13 @@ app {
|
||||
text-align: center;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.loading {
|
||||
background: rgba(0,0,0,0.2) url('../loading.gif') no-repeat 50% 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
}
|
BIN
Oqtane.Server/wwwroot/loading.gif
Normal file
BIN
Oqtane.Server/wwwroot/loading.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
Reference in New Issue
Block a user