From 1c0d2de9fec0f1594dea89412445693858d6e512 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 18 Jul 2019 13:11:31 -0400 Subject: [PATCH] Install Wizard --- Oqtane.Client/App.razor | 26 ++- Oqtane.Client/Modules/Admin/Login/Index.razor | 2 +- Oqtane.Client/Modules/HtmlText/Edit.razor | 4 +- Oqtane.Client/Modules/HtmlText/Index.razor | 3 +- .../HtmlText/Services/HtmlTextService.cs | 6 +- Oqtane.Client/Services/AliasService.cs | 6 +- .../Services/IInstallationService.cs | 12 ++ Oqtane.Client/Services/InstallationService.cs | 39 ++++ .../Services/ModuleDefinitionService.cs | 6 +- Oqtane.Client/Services/ModuleService.cs | 6 +- Oqtane.Client/Services/PageModuleService.cs | 6 +- Oqtane.Client/Services/PageService.cs | 6 +- Oqtane.Client/Services/ServiceBase.cs | 28 +-- Oqtane.Client/Services/SiteService.cs | 6 +- Oqtane.Client/Services/TenantService.cs | 6 +- Oqtane.Client/Services/ThemeService.cs | 6 +- Oqtane.Client/Services/UserService.cs | 2 +- Oqtane.Client/Shared/Installer.razor | 187 +++++++++++++++++ Oqtane.Client/Startup.cs | 1 + Oqtane.Client/wwwroot/css/site.css | 10 + Oqtane.Client/wwwroot/js/interop.js | 8 + Oqtane.Client/wwwroot/loading.gif | Bin 0 -> 8238 bytes .../Controllers/InstallationController.cs | 192 ++++++++++++++++++ Oqtane.Server/Controllers/UserController.cs | 13 +- Oqtane.Server/Filters/UpgradeFilter.cs | 117 ----------- Oqtane.Server/Program.cs | 26 +-- Oqtane.Server/Repository/AliasRepository.cs | 4 +- .../{HostContext.cs => MasterContext.cs} | 4 +- Oqtane.Server/Repository/TenantRepository.cs | 4 +- Oqtane.Server/Repository/TenantResolver.cs | 8 +- Oqtane.Server/Scripts/Master.sql | 4 +- Oqtane.Server/Startup.cs | 16 +- Oqtane.Server/appsettings.json | 16 +- Oqtane.Server/wwwroot/css/site.css | 10 + Oqtane.Server/wwwroot/loading.gif | Bin 0 -> 8238 bytes Oqtane.Shared/Models/GenericResponse.cs | 8 + 36 files changed, 580 insertions(+), 218 deletions(-) create mode 100644 Oqtane.Client/Services/IInstallationService.cs create mode 100644 Oqtane.Client/Services/InstallationService.cs create mode 100644 Oqtane.Client/Shared/Installer.razor create mode 100644 Oqtane.Client/wwwroot/loading.gif create mode 100644 Oqtane.Server/Controllers/InstallationController.cs delete mode 100644 Oqtane.Server/Filters/UpgradeFilter.cs rename Oqtane.Server/Repository/{HostContext.cs => MasterContext.cs} (62%) create mode 100644 Oqtane.Server/wwwroot/loading.gif create mode 100644 Oqtane.Shared/Models/GenericResponse.cs diff --git a/Oqtane.Client/App.razor b/Oqtane.Client/App.razor index 3446ccbf..6ad2be28 100644 --- a/Oqtane.Client/App.razor +++ b/Oqtane.Client/App.razor @@ -1,15 +1,31 @@ @using Oqtane.Shared @using Oqtane.Client.Shared +@using Oqtane.Services +@inject IInstallationService InstallationService - - - - - +@if (!Installed) +{ + +} +else +{ + + + + + +} @code { + private bool Installed = false; private PageState PageState { get; set; } + protected override async Task OnInitAsync() + { + var response = await InstallationService.IsInstalled(); + Installed = response.Success; + } + private void ChangeState(PageState pagestate) { PageState = pagestate; diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index 3071d436..c381e244 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -44,7 +44,7 @@ @code { public override SecurityAccessLevelEnum SecurityAccessLevel { get { return SecurityAccessLevelEnum.Anonymous; } } -public string Message { get; set; } = "
Use host/password For Demo Access
"; +public string Message { get; set; } = ""; public string Username { get; set; } = ""; public string Password { get; set; } = ""; public bool Remember { get; set; } = false; diff --git a/Oqtane.Client/Modules/HtmlText/Edit.razor b/Oqtane.Client/Modules/HtmlText/Edit.razor index 4bdd8aaf..63c4b2f3 100644 --- a/Oqtane.Client/Modules/HtmlText/Edit.razor +++ b/Oqtane.Client/Modules/HtmlText/Edit.razor @@ -33,7 +33,7 @@ protected override async Task OnInitAsync() { - HtmlTextService htmltextservice = new HtmlTextService(http, sitestate); + HtmlTextService htmltextservice = new HtmlTextService(http, sitestate, UriHelper); List htmltextlist = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId); if (htmltextlist != null) { @@ -44,7 +44,7 @@ private async Task SaveContent() { - HtmlTextService htmltextservice = new HtmlTextService(http, sitestate); + HtmlTextService htmltextservice = new HtmlTextService(http, sitestate, UriHelper); if (htmltext != null) { htmltext.Content = content; diff --git a/Oqtane.Client/Modules/HtmlText/Index.razor b/Oqtane.Client/Modules/HtmlText/Index.razor index cc679988..90d86153 100644 --- a/Oqtane.Client/Modules/HtmlText/Index.razor +++ b/Oqtane.Client/Modules/HtmlText/Index.razor @@ -5,6 +5,7 @@ @using Oqtane.Client.Modules.Controls @using Oqtane.Shared; @inherits ModuleBase +@inject IUriHelper UriHelper @inject HttpClient http @inject SiteState sitestate @@ -17,7 +18,7 @@ protected override async Task OnInitAsync() { - HtmlTextService htmltextservice = new HtmlTextService(http, sitestate); + HtmlTextService htmltextservice = new HtmlTextService(http, sitestate, UriHelper); List htmltext = await htmltextservice.GetHtmlTextAsync(ModuleState.ModuleId); if (htmltext != null) { diff --git a/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs b/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs index c6f2443b..e8257b00 100644 --- a/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs +++ b/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs @@ -13,16 +13,18 @@ namespace Oqtane.Client.Modules.HtmlText.Services { private readonly HttpClient http; private readonly SiteState sitestate; + private readonly IUriHelper urihelper; - public HtmlTextService(HttpClient http, SiteState sitestate) + public HtmlTextService(HttpClient http, SiteState sitestate, IUriHelper urihelper) { this.http = http; this.sitestate = sitestate; + this.urihelper = urihelper; } private string apiurl { - get { return CreateApiUrl(sitestate.Alias, "HtmlText"); } + get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "HtmlText"); } } public async Task> GetHtmlTextAsync(int ModuleId) diff --git a/Oqtane.Client/Services/AliasService.cs b/Oqtane.Client/Services/AliasService.cs index 88aa9683..1d29f68a 100644 --- a/Oqtane.Client/Services/AliasService.cs +++ b/Oqtane.Client/Services/AliasService.cs @@ -11,17 +11,19 @@ namespace Oqtane.Services public class AliasService : ServiceBase, IAliasService { private readonly HttpClient http; + private readonly SiteState sitestate; private readonly IUriHelper urihelper; - public AliasService(HttpClient http, IUriHelper urihelper) + public AliasService(HttpClient http, SiteState sitestate, IUriHelper urihelper) { this.http = http; + this.sitestate = sitestate; this.urihelper = urihelper; } private string apiurl { - get { return CreateApiUrl(urihelper.GetAbsoluteUri(), "Alias"); } + get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Alias"); } } public async Task> GetAliasesAsync() diff --git a/Oqtane.Client/Services/IInstallationService.cs b/Oqtane.Client/Services/IInstallationService.cs new file mode 100644 index 00000000..37f71fef --- /dev/null +++ b/Oqtane.Client/Services/IInstallationService.cs @@ -0,0 +1,12 @@ +using Oqtane.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Oqtane.Services +{ + public interface IInstallationService + { + Task IsInstalled(); + Task Install(string connectionstring); + } +} diff --git a/Oqtane.Client/Services/InstallationService.cs b/Oqtane.Client/Services/InstallationService.cs new file mode 100644 index 00000000..399140ef --- /dev/null +++ b/Oqtane.Client/Services/InstallationService.cs @@ -0,0 +1,39 @@ +using Oqtane.Models; +using System.Threading.Tasks; +using System.Net.Http; +using System.Linq; +using Microsoft.AspNetCore.Components; +using System.Collections.Generic; +using Oqtane.Shared; + +namespace Oqtane.Services +{ + public class InstallationService : ServiceBase, IInstallationService + { + private readonly HttpClient http; + private readonly SiteState sitestate; + private readonly IUriHelper urihelper; + + public InstallationService(HttpClient http, SiteState sitestate, IUriHelper urihelper) + { + this.http = http; + this.sitestate = sitestate; + this.urihelper = urihelper; + } + + private string apiurl + { + get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Installation"); } + } + + public async Task IsInstalled() + { + return await http.GetJsonAsync(apiurl + "/installed"); + } + + public async Task Install(string connectionstring) + { + return await http.PostJsonAsync(apiurl, connectionstring); + } + } +} diff --git a/Oqtane.Client/Services/ModuleDefinitionService.cs b/Oqtane.Client/Services/ModuleDefinitionService.cs index 75b06b2f..31fff1ba 100644 --- a/Oqtane.Client/Services/ModuleDefinitionService.cs +++ b/Oqtane.Client/Services/ModuleDefinitionService.cs @@ -14,16 +14,18 @@ namespace Oqtane.Services { private readonly HttpClient http; private readonly SiteState sitestate; + private readonly IUriHelper urihelper; - public ModuleDefinitionService(HttpClient http, SiteState sitestate) + public ModuleDefinitionService(HttpClient http, SiteState sitestate, IUriHelper urihelper) { this.http = http; this.sitestate = sitestate; + this.urihelper = urihelper; } private string apiurl { - get { return CreateApiUrl(sitestate.Alias, "ModuleDefinition"); } + get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "ModuleDefinition"); } } public async Task> GetModuleDefinitionsAsync() diff --git a/Oqtane.Client/Services/ModuleService.cs b/Oqtane.Client/Services/ModuleService.cs index ede01dc5..60f22eeb 100644 --- a/Oqtane.Client/Services/ModuleService.cs +++ b/Oqtane.Client/Services/ModuleService.cs @@ -12,16 +12,18 @@ namespace Oqtane.Services { private readonly HttpClient http; private readonly SiteState sitestate; + private readonly IUriHelper urihelper; - public ModuleService(HttpClient http, SiteState sitestate) + public ModuleService(HttpClient http, SiteState sitestate, IUriHelper urihelper) { this.http = http; this.sitestate = sitestate; + this.urihelper = urihelper; } private string apiurl { - get { return CreateApiUrl(sitestate.Alias, "Module"); } + get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Module"); } } public async Task> GetModulesAsync(int PageId) diff --git a/Oqtane.Client/Services/PageModuleService.cs b/Oqtane.Client/Services/PageModuleService.cs index 240b4c44..8a65de2d 100644 --- a/Oqtane.Client/Services/PageModuleService.cs +++ b/Oqtane.Client/Services/PageModuleService.cs @@ -12,16 +12,18 @@ namespace Oqtane.Services { private readonly HttpClient http; private readonly SiteState sitestate; + private readonly IUriHelper urihelper; - public PageModuleService(HttpClient http, SiteState sitestate) + public PageModuleService(HttpClient http, SiteState sitestate, IUriHelper urihelper) { this.http = http; this.sitestate = sitestate; + this.urihelper = urihelper; } private string apiurl { - get { return CreateApiUrl(sitestate.Alias, "PageModule"); } + get { return CreateApiUrl(sitestate.Alias, "PageModule", urihelper.GetAbsoluteUri()); } } public async Task> GetPageModulesAsync() diff --git a/Oqtane.Client/Services/PageService.cs b/Oqtane.Client/Services/PageService.cs index fb54c32c..d0124c79 100644 --- a/Oqtane.Client/Services/PageService.cs +++ b/Oqtane.Client/Services/PageService.cs @@ -12,16 +12,18 @@ namespace Oqtane.Services { private readonly HttpClient http; private readonly SiteState sitestate; + private readonly IUriHelper urihelper; - public PageService(HttpClient http, SiteState sitestate) + public PageService(HttpClient http, SiteState sitestate, IUriHelper urihelper) { this.http = http; this.sitestate = sitestate; + this.urihelper = urihelper; } private string apiurl { - get { return CreateApiUrl(sitestate.Alias, "Page"); } + get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Page"); } } public async Task> GetPagesAsync(int SiteId) diff --git a/Oqtane.Client/Services/ServiceBase.cs b/Oqtane.Client/Services/ServiceBase.cs index ca7c588f..b970c373 100644 --- a/Oqtane.Client/Services/ServiceBase.cs +++ b/Oqtane.Client/Services/ServiceBase.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.AspNetCore.Components; using Oqtane.Models; using Oqtane.Shared; @@ -6,21 +7,24 @@ namespace Oqtane.Services { public class ServiceBase { - // method for alias agnostic api call - public string CreateApiUrl(string absoluteUri, string serviceName) - { - Uri uri = new Uri(absoluteUri); - string apiurl = uri.Scheme + "://" + uri.Authority + "/~/api/" + serviceName; - return apiurl; - } - // method for alias specific api call - public string CreateApiUrl(Alias alias, string serviceName) + public string CreateApiUrl(Alias alias, string absoluteUri, string serviceName) { - string apiurl = alias.Url + "/"; - if (alias.Path == "") + string apiurl = ""; + if (alias != null) { - apiurl += "~/"; + // build a url which passes the alias that may include a subfolder for multi-tenancy + apiurl = alias.Url + "/"; + if (alias.Path == "") + { + apiurl += "~/"; + } + } + else + { + // build a url which ignores any subfolder for multi-tenancy + Uri uri = new Uri(absoluteUri); + apiurl = uri.Scheme + "://" + uri.Authority + "/~/"; } apiurl += "api/" + serviceName; return apiurl; diff --git a/Oqtane.Client/Services/SiteService.cs b/Oqtane.Client/Services/SiteService.cs index 519155a5..83cfad34 100644 --- a/Oqtane.Client/Services/SiteService.cs +++ b/Oqtane.Client/Services/SiteService.cs @@ -12,16 +12,18 @@ namespace Oqtane.Services { private readonly HttpClient http; private readonly SiteState sitestate; + private readonly IUriHelper urihelper; - public SiteService(HttpClient http, SiteState sitestate) + public SiteService(HttpClient http, SiteState sitestate, IUriHelper urihelper) { this.http = http; this.sitestate = sitestate; + this.urihelper = urihelper; } private string apiurl { - get { return CreateApiUrl(sitestate.Alias, "Site"); } + get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Site"); } } public async Task> GetSitesAsync() diff --git a/Oqtane.Client/Services/TenantService.cs b/Oqtane.Client/Services/TenantService.cs index 27054504..4da871fb 100644 --- a/Oqtane.Client/Services/TenantService.cs +++ b/Oqtane.Client/Services/TenantService.cs @@ -12,16 +12,18 @@ namespace Oqtane.Services { private readonly HttpClient http; private readonly SiteState sitestate; + private readonly IUriHelper urihelper; - public TenantService(HttpClient http, SiteState sitestate) + public TenantService(HttpClient http, SiteState sitestate, IUriHelper urihelper) { this.http = http; this.sitestate = sitestate; + this.urihelper = urihelper; } private string apiurl { - get { return CreateApiUrl(sitestate.Alias, "Tenant"); } + get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Tenant"); } } public async Task> GetTenantsAsync() diff --git a/Oqtane.Client/Services/ThemeService.cs b/Oqtane.Client/Services/ThemeService.cs index b6c2201e..a38a9e28 100644 --- a/Oqtane.Client/Services/ThemeService.cs +++ b/Oqtane.Client/Services/ThemeService.cs @@ -14,16 +14,18 @@ namespace Oqtane.Services { private readonly HttpClient http; private readonly SiteState sitestate; + private readonly IUriHelper urihelper; - public ThemeService(HttpClient http, SiteState sitestate) + public ThemeService(HttpClient http, SiteState sitestate, IUriHelper urihelper) { this.http = http; this.sitestate = sitestate; + this.urihelper = urihelper; } private string apiurl { - get { return CreateApiUrl(sitestate.Alias, "Theme"); } + get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "Theme"); } } public async Task> GetThemesAsync() diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs index d83dc260..eeb19433 100644 --- a/Oqtane.Client/Services/UserService.cs +++ b/Oqtane.Client/Services/UserService.cs @@ -24,7 +24,7 @@ namespace Oqtane.Services private string apiurl { - get { return CreateApiUrl(sitestate.Alias, "User"); } + get { return CreateApiUrl(sitestate.Alias, urihelper.GetAbsoluteUri(), "User"); } } public async Task> GetUsersAsync() diff --git a/Oqtane.Client/Shared/Installer.razor b/Oqtane.Client/Shared/Installer.razor new file mode 100644 index 00000000..321f8d3d --- /dev/null +++ b/Oqtane.Client/Shared/Installer.razor @@ -0,0 +1,187 @@ +@using Oqtane.Services +@using Oqtane.Models +@inject IUriHelper UriHelper +@inject IInstallationService InstallationService +@inject IUserService UserService + +
+
+
+ +
+
+
+

Database Configuration

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+
+
+
+

Application Administrator

+
+
+ + + + + + + + + + + +
+ + + +
+ + + +
+
+
+
+
+

+ @((MarkupString)@Message) +
+
+
+
+ +@code { + +private string DatabaseType = "LocalDB"; +private string ServerName = "(LocalDb)\\MSSQLLocalDB"; +private string DatabaseName = "Oqtane-" + DateTime.Now.ToString("yyyyMMddHHmm"); +private bool IntegratedSecurity = true; +private string Username = ""; +private string Password = ""; +private string HostUsername = "host"; +private string HostPassword = ""; +private string Message = ""; + +private string IntegratedSecurityDisplay = "display:none;"; +private string LoadingDisplay = "display:none;"; + +private void SetIntegratedSecurity(UIChangeEventArgs e) +{ + if (Convert.ToBoolean(e.Value)) + { + IntegratedSecurityDisplay = "display:none;"; + } + else + { + IntegratedSecurityDisplay = ""; + } +} + +private async Task Install() +{ + if (HostPassword.Length >= 6) + { + LoadingDisplay = ""; + StateHasChanged(); + + string connectionstring = ""; + if (DatabaseType == "LocalDB") + { + connectionstring = "Data Source=" + ServerName + ";AttachDbFilename=|DataDirectory|\\" + DatabaseName + ".mdf;Initial Catalog=" + DatabaseName + ";Integrated Security=SSPI;"; + } + else + { + connectionstring = "Data Source=" + ServerName + ";Initial Catalog=" + DatabaseName + ";"; + if (IntegratedSecurityDisplay == "display:none;") + { + connectionstring += "Integrated Security=SSPI;"; + } + else + { + connectionstring += "User ID=" + Username + ";Password=" + Password; + + } + } + GenericResponse response = await InstallationService.Install(connectionstring); + if (response.Success) + { + User user = new User(); + user.Username = HostUsername; + user.DisplayName = HostUsername; + user.Password = HostPassword; + user.IsSuperUser = true; + user.Roles = ""; + await UserService.AddUserAsync(user); + UriHelper.NavigateTo("", true); + } + else + { + Message = "
" + response.Message + "
"; + LoadingDisplay = "display:none;"; + } + } + else + { + Message = "
Password Must Be 6 Characters Or Greater
"; + } +} +} diff --git a/Oqtane.Client/Startup.cs b/Oqtane.Client/Startup.cs index b817e046..1c64ca52 100644 --- a/Oqtane.Client/Startup.cs +++ b/Oqtane.Client/Startup.cs @@ -36,6 +36,7 @@ namespace Oqtane.Client // register scoped core services services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Oqtane.Client/wwwroot/css/site.css b/Oqtane.Client/wwwroot/css/site.css index 6774c2e9..193ec6f0 100644 --- a/Oqtane.Client/wwwroot/css/site.css +++ b/Oqtane.Client/wwwroot/css/site.css @@ -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; +} \ No newline at end of file diff --git a/Oqtane.Client/wwwroot/js/interop.js b/Oqtane.Client/wwwroot/js/interop.js index a322ca40..db61ecbe 100644 --- a/Oqtane.Client/wwwroot/js/interop.js +++ b/Oqtane.Client/wwwroot/js/interop.js @@ -20,6 +20,14 @@ window.interop = { } return ""; }, + getElementByName: function (name) { + var elements = document.getElementsByName(name); + if (elements.length) { + return elements[0].value; + } else { + return ""; + } + }, addCSS: function (fileName) { var head = document.head; var link = document.createElement("link"); diff --git a/Oqtane.Client/wwwroot/loading.gif b/Oqtane.Client/wwwroot/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..cc70a7a8b3d426c30e76686fac70c0dcd4c70125 GIT binary patch literal 8238 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 literal 0 HcmV?d00001 diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs new file mode 100644 index 00000000..3297c572 --- /dev/null +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -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/ + [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//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; + } + } +} diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 759515ee..86f1fa8a 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -86,20 +86,9 @@ namespace Oqtane.Controllers [HttpPost("login")] public async Task 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); diff --git a/Oqtane.Server/Filters/UpgradeFilter.cs b/Oqtane.Server/Filters/UpgradeFilter.cs deleted file mode 100644 index b08203aa..00000000 --- a/Oqtane.Server/Filters/UpgradeFilter.cs +++ /dev/null @@ -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 Configure(Action 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; - } - } -} diff --git a/Oqtane.Server/Program.cs b/Oqtane.Server/Program.cs index 8e6d1632..180d79f3 100644 --- a/Oqtane.Server/Program.cs +++ b/Oqtane.Server/Program.cs @@ -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); - } - } - } } } diff --git a/Oqtane.Server/Repository/AliasRepository.cs b/Oqtane.Server/Repository/AliasRepository.cs index 0cc3b365..ca9f3059 100644 --- a/Oqtane.Server/Repository/AliasRepository.cs +++ b/Oqtane.Server/Repository/AliasRepository.cs @@ -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; diff --git a/Oqtane.Server/Repository/HostContext.cs b/Oqtane.Server/Repository/MasterContext.cs similarity index 62% rename from Oqtane.Server/Repository/HostContext.cs rename to Oqtane.Server/Repository/MasterContext.cs index f0f77023..21dca1dd 100644 --- a/Oqtane.Server/Repository/HostContext.cs +++ b/Oqtane.Server/Repository/MasterContext.cs @@ -3,9 +3,9 @@ using Oqtane.Models; namespace Oqtane.Repository { - public class HostContext : DbContext + public class MasterContext : DbContext { - public HostContext(DbContextOptions options) : base(options) { } + public MasterContext(DbContextOptions options) : base(options) { } public virtual DbSet Alias { get; set; } public virtual DbSet Tenant { get; set; } diff --git a/Oqtane.Server/Repository/TenantRepository.cs b/Oqtane.Server/Repository/TenantRepository.cs index 9ee2c188..da7c6be9 100644 --- a/Oqtane.Server/Repository/TenantRepository.cs +++ b/Oqtane.Server/Repository/TenantRepository.cs @@ -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; diff --git a/Oqtane.Server/Repository/TenantResolver.cs b/Oqtane.Server/Repository/TenantResolver.cs index 4685f9cd..bd78f450 100644 --- a/Oqtane.Server/Repository/TenantResolver.cs +++ b/Oqtane.Server/Repository/TenantResolver.cs @@ -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; diff --git a/Oqtane.Server/Scripts/Master.sql b/Oqtane.Server/Scripts/Master.sql index 1d76ad91..9c2cb0b4 100644 --- a/Oqtane.Server/Scripts/Master.sql +++ b/Oqtane.Server/Scripts/Master.sql @@ -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 diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index ab2de131..d071f537 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -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(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -101,7 +101,7 @@ namespace Oqtane.Server services.AddSingleton(); - services.AddDbContext(options => + services.AddDbContext(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(); - // register singleton scoped core services + services.AddSingleton(Configuration); services.AddSingleton(); services.AddSingleton(); @@ -237,7 +235,7 @@ namespace Oqtane.Server { services.AddSingleton(); - services.AddDbContext(options => + services.AddDbContext(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(); - // register singleton scoped core services + services.AddSingleton(Configuration); services.AddSingleton(); services.AddSingleton(); diff --git a/Oqtane.Server/appsettings.json b/Oqtane.Server/appsettings.json index 9e23f3b1..8138a005 100644 --- a/Oqtane.Server/appsettings.json +++ b/Oqtane.Server/appsettings.json @@ -1,5 +1,19 @@ { "ConnectionStrings": { - "DefaultConnection": "Data Source=(LocalDb)\\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\\Oqtane.mdf;Initial Catalog=Oqtane;Integrated Security=SSPI;" + "DefaultConnection": "" } } + + + + + + + + + + + + + + diff --git a/Oqtane.Server/wwwroot/css/site.css b/Oqtane.Server/wwwroot/css/site.css index 6774c2e9..193ec6f0 100644 --- a/Oqtane.Server/wwwroot/css/site.css +++ b/Oqtane.Server/wwwroot/css/site.css @@ -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; +} \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/loading.gif b/Oqtane.Server/wwwroot/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..cc70a7a8b3d426c30e76686fac70c0dcd4c70125 GIT binary patch literal 8238 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 literal 0 HcmV?d00001 diff --git a/Oqtane.Shared/Models/GenericResponse.cs b/Oqtane.Shared/Models/GenericResponse.cs new file mode 100644 index 00000000..2281d79f --- /dev/null +++ b/Oqtane.Shared/Models/GenericResponse.cs @@ -0,0 +1,8 @@ +namespace Oqtane.Models +{ + public class GenericResponse + { + public bool Success { get; set; } + public string Message { get; set; } + } +}