diff --git a/.gitignore b/.gitignore index 98408e86..29b72c2a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ msbuild.binlog Oqtane.Server/appsettings.json Oqtane.Server/Data/*.mdf Oqtane.Server/Data/*.ldf +Oqtane.Server/Data/*.db /Oqtane.Server/Properties/PublishProfiles/FolderProfile.pubxml Oqtane.Server/Content diff --git a/Oqtane.Client/Modules/Admin/Sites/Add.razor b/Oqtane.Client/Modules/Admin/Sites/Add.razor index 5e3ac770..c77bdca9 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Add.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Add.razor @@ -1,4 +1,5 @@ @namespace Oqtane.Modules.Admin.Sites +@using Oqtane.Interfaces @inherits ModuleBase @inject NavigationManager NavigationManager @inject ITenantService TenantService @@ -9,6 +10,7 @@ @inject IUserService UserService @inject IInstallationService InstallationService @inject IStringLocalizer Localizer +@inject IEnumerable Databases @if (_tenants == null) { @@ -117,7 +119,7 @@ else - + @@ -125,64 +127,67 @@ else - + @{ + foreach (var database in Databases) + { + + } + } - - - - - - - - - - - - - - - - - - - - - - - - - @if (!_integratedsecurity) { - - - - - - - - - - - - - - - - + _selectedDatabase = Databases.Single(d => d.Name == _databaseType); + foreach (var field in _selectedDatabase.ConnectionStringFields) + { + var fieldId = field.Name.ToLowerInvariant(); + if (field.Name != "IntegratedSecurity") + { + var isVisible = ""; + var fieldType = (field.Name == "Pwd") ? "password" : "text"; + if ((field.Name == "Uid" || field.Name == "Pwd") && _selectedDatabase.Name != "MySQL" ) + { + var intSecurityField = _selectedDatabase.ConnectionStringFields.Single(f => f.Name == "IntegratedSecurity"); + if (intSecurityField != null) + { + isVisible = (Convert.ToBoolean(intSecurityField.Value)) ? "display: none;" : ""; + } + } + + field.Value = field.Value.Replace("{{Date}}", DateTime.UtcNow.ToString("yyyyMMddHHmm")); + + + + + + + + + + } + else + { + + + + + + + + + } + } } - + @@ -207,14 +212,11 @@ else private List _tenants; private string _tenantid = "-"; - private string _tenantname = string.Empty; - private string _databasetype = "LocalDB"; - private string _server = "(LocalDb)\\MSSQLLocalDB"; - private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm"); - private string _username = string.Empty; - private string _password = string.Empty; - private bool _integratedsecurity = true; - private string _hostusername = UserNames.Host; + private string _tenantName = string.Empty; + private IOqtaneDatabase _selectedDatabase; + private string _databaseType = "LocalDB"; + + private string _hostUserName = UserNames.Host; private string _hostpassword = string.Empty; private string _name = string.Empty; @@ -238,22 +240,9 @@ else private void TenantChanged(ChangeEventArgs e) { _tenantid = (string)e.Value; - if (string.IsNullOrEmpty(_tenantname)) + if (string.IsNullOrEmpty(_tenantName)) { - _tenantname = _name; - } - StateHasChanged(); - } - - private void SetIntegratedSecurity(ChangeEventArgs e) - { - if (Convert.ToBoolean((string)e.Value)) - { - _integratedsecurity = true; - } - else - { - _integratedsecurity = false; + _tenantName = _name; } StateHasChanged(); } @@ -302,7 +291,7 @@ else if (_tenantid == "+") { - if (!string.IsNullOrEmpty(_tenantname) && _tenants.FirstOrDefault(item => item.Name == _tenantname) == null) + if (!string.IsNullOrEmpty(_tenantName) && _tenants.FirstOrDefault(item => item.Name == _tenantName) == null) { // validate host credentials var user = new User(); @@ -312,32 +301,15 @@ else user = await UserService.LoginUserAsync(user, false, false); if (user.IsAuthenticated) { - if (!string.IsNullOrEmpty(_server) && !string.IsNullOrEmpty(_database)) + var connectionString = _selectedDatabase.BuildConnectionString(); + if (connectionString != "") { - var connectionString = string.Empty; - if (_databasetype == "LocalDB") - { - connectionString = "Data Source=" + _server + ";AttachDbFilename=|DataDirectory|\\" + _database + ".mdf;Initial Catalog=" + _database + ";Integrated Security=SSPI;"; - } - else - { - connectionString = "Data Source=" + _server + ";Initial Catalog=" + _database + ";"; - - if (_integratedsecurity) - { - connectionString += "Integrated Security=SSPI;"; - } - else - { - connectionString += "User ID=" + _username + ";Password=" + _password; - } - } - + config.DatabaseType = _databaseType; config.ConnectionString = connectionString; config.HostPassword = _hostpassword; config.HostEmail = user.Email; config.HostName = user.DisplayName; - config.TenantName = _tenantname; + config.TenantName = _tenantName; config.IsNewTenant = true; } else diff --git a/Oqtane.Client/Modules/HtmlText/Edit.razor b/Oqtane.Client/Modules/HtmlText/Edit.razor index ee60134e..f2e1b154 100644 --- a/Oqtane.Client/Modules/HtmlText/Edit.razor +++ b/Oqtane.Client/Modules/HtmlText/Edit.razor @@ -84,7 +84,7 @@ } else { - htmltext = new HtmlTextInfo(); + htmltext = new HtmlText(); htmltext.ModuleId = ModuleState.ModuleId; htmltext.Content = content; await HtmlTextService.AddHtmlTextAsync(htmltext); diff --git a/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs b/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs index 1e9d42ab..af0063bb 100644 --- a/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs +++ b/Oqtane.Client/Modules/HtmlText/Services/HtmlTextService.cs @@ -19,18 +19,18 @@ namespace Oqtane.Modules.HtmlText.Services private string ApiUrl => CreateApiUrl(_siteState.Alias, "HtmlText"); - public async Task GetHtmlTextAsync(int moduleId) + public async Task GetHtmlTextAsync(int moduleId) { - var htmltext = await GetJsonAsync>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", moduleId)); + var htmltext = await GetJsonAsync>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", moduleId)); return htmltext.FirstOrDefault(); } - public async Task AddHtmlTextAsync(HtmlTextInfo htmlText) + public async Task AddHtmlTextAsync(Models.HtmlText htmlText) { await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", htmlText.ModuleId), htmlText); } - public async Task UpdateHtmlTextAsync(HtmlTextInfo htmlText) + public async Task UpdateHtmlTextAsync(Models.HtmlText htmlText) { await PutJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlText.HtmlTextId}", htmlText.ModuleId), htmlText); } diff --git a/Oqtane.Client/Modules/HtmlText/Services/IHtmlTextService.cs b/Oqtane.Client/Modules/HtmlText/Services/IHtmlTextService.cs index 6ce1d646..c663882a 100644 --- a/Oqtane.Client/Modules/HtmlText/Services/IHtmlTextService.cs +++ b/Oqtane.Client/Modules/HtmlText/Services/IHtmlTextService.cs @@ -6,11 +6,11 @@ namespace Oqtane.Modules.HtmlText.Services { public interface IHtmlTextService { - Task GetHtmlTextAsync(int ModuleId); + Task GetHtmlTextAsync(int ModuleId); - Task AddHtmlTextAsync(HtmlTextInfo htmltext); + Task AddHtmlTextAsync(Models.HtmlText htmltext); - Task UpdateHtmlTextAsync(HtmlTextInfo htmltext); + Task UpdateHtmlTextAsync(Models.HtmlText htmltext); Task DeleteHtmlTextAsync(int ModuleId); } diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index ac2485f3..e315a1f6 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -21,11 +21,11 @@ - - - + + + - + diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index ca60b063..fdc5145f 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.DependencyInjection; using Microsoft.JSInterop; +using Oqtane.Interfaces; using Oqtane.Modules; using Oqtane.Providers; using Oqtane.Services; @@ -74,8 +75,8 @@ namespace Oqtane.Client var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); foreach (var assembly in assemblies) { - // dynamically register module services - var implementationTypes = assembly.GetInterfaces(); + // dynamically register module services + var implementationTypes = assembly.GetInterfaces(); foreach (var implementationType in implementationTypes) { if (implementationType.AssemblyQualifiedName != null) @@ -85,6 +86,17 @@ namespace Oqtane.Client } } + // dynamically register database providers + var databaseTypes = assembly.GetInterfaces(); + foreach (var databaseType in databaseTypes) + { + if (databaseType.AssemblyQualifiedName != null) + { + var serviceType = Type.GetType("Oqtane.Interfaces.IDatabase, Oqtane.Shared"); + builder.Services.AddScoped(serviceType ?? databaseType, databaseType); + } + } + // register client startup services var startUps = assembly.GetInstances(); foreach (var startup in startUps) @@ -115,7 +127,7 @@ namespace Oqtane.Client private static async Task LoadClientAssemblies(HttpClient http) { - // get list of loaded assemblies on the client + // get list of loaded assemblies on the client var assemblies = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToList(); // get assemblies from server and load into client app domain diff --git a/Oqtane.Client/UI/Installer.razor b/Oqtane.Client/UI/Installer.razor index 9231e092..3c013711 100644 --- a/Oqtane.Client/UI/Installer.razor +++ b/Oqtane.Client/UI/Installer.razor @@ -1,3 +1,5 @@ +@using Oqtane.Interfaces +@using System.Reflection @namespace Oqtane.UI @inject NavigationManager NavigationManager @inject IInstallationService InstallationService @@ -5,6 +7,7 @@ @inject IUserService UserService @inject IJSRuntime JSRuntime @inject IStringLocalizer Localizer +@inject IEnumerable Databases
@@ -25,54 +28,59 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @{ + _selectedDatabase = Databases.Single(d => d.Name == _databaseType); + foreach (var field in _selectedDatabase.ConnectionStringFields) + { + if (field.Name != "IntegratedSecurity") + { + var isVisible = ""; + var fieldType = (field.Name == "Pwd") ? "password" : "text"; + if ((field.Name == "Uid" || field.Name == "Pwd") && _selectedDatabase.Name != "MySQL" ) + { + var intSecurityField = _selectedDatabase.ConnectionStringFields.Single(f => f.Name == "IntegratedSecurity"); + if (intSecurityField != null) + { + isVisible = (Convert.ToBoolean(intSecurityField.Value)) ? "display: none;" : ""; + } + } + + field.Value = field.Value.Replace("{{Date}}", DateTime.UtcNow.ToString("yyyyMMddHHmm")); + + + + + + + + + + } + else + { + + + + + + + + + } + } + }
@@ -127,17 +135,13 @@
@code { + private IOqtaneDatabase _selectedDatabase; private string _databaseType = "LocalDB"; - private string _serverName = "(LocalDb)\\MSSQLLocalDB"; - private string _databaseName = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm"); - private string _username = string.Empty; - private string _password = string.Empty; private string _hostUsername = UserNames.Host; private string _hostPassword = string.Empty; private string _confirmPassword = string.Empty; private string _hostEmail = string.Empty; private string _message = string.Empty; - private string _integratedSecurityDisplay = "display: none;"; private string _loadingDisplay = "display: none;"; protected override async Task OnAfterRenderAsync(bool firstRender) @@ -149,43 +153,21 @@ } } - private void SetIntegratedSecurity(ChangeEventArgs e) - { - _integratedSecurityDisplay = Convert.ToBoolean((string)e.Value) - ? "display: none;" - : string.Empty; - } - private async Task Install() { - if (_serverName != "" && _databaseName != "" && _hostUsername != "" && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && _hostEmail != "") + var connectionString = _selectedDatabase.BuildConnectionString(); + + if (connectionString != "" && _hostUsername != "" && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && _hostEmail != "") { _loadingDisplay = ""; StateHasChanged(); - var 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; - } - } - Uri uri = new Uri(NavigationManager.Uri); var config = new InstallConfig { - ConnectionString = connectionstring, + DatabaseType = _databaseType, + ConnectionString = connectionString, Aliases = uri.Authority, HostEmail = _hostEmail, HostPassword = _hostPassword, diff --git a/Oqtane.Database.MySQL/MySQLDatabase.cs b/Oqtane.Database.MySQL/MySQLDatabase.cs new file mode 100644 index 00000000..3e988aab --- /dev/null +++ b/Oqtane.Database.MySQL/MySQLDatabase.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using MySql.EntityFrameworkCore.Metadata; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Database.MySQL +{ + public class MySQLDatabase : OqtaneDatabaseBase + { + private static string _friendlyName => "MySQL"; + + private static string _name => "MySQL"; + + private static readonly List _connectionStringFields = new() + { + new() {Name = "Server", FriendlyName = "Server", Value = "127.0.0.1", HelpText="Enter the database server"}, + new() {Name = "Port", FriendlyName = "Port", Value = "3306", HelpText="Enter the port used to connect to the server"}, + new() {Name = "Database", FriendlyName = "Database", Value = "Oqtane-{{Date}}", HelpText="Enter the name of the database"}, + new() {Name = "Uid", FriendlyName = "User Id", Value = "", HelpText="Enter the username to use for the database"}, + new() {Name = "Pwd", FriendlyName = "Password", Value = "", HelpText="Enter the password to use for the database"} + }; + + public MySQLDatabase() :base(_name, _friendlyName, _connectionStringFields) { } + + public override string Provider => "MySql.EntityFrameworkCore"; + + public override OperationBuilder AddAutoIncrementColumn(ColumnsBuilder table, string name) + { + return table.Column(name: name, nullable: false).Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn); + } + + public override string BuildConnectionString() + { + var connectionString = String.Empty; + + var server = ConnectionStringFields[0].Value; + var port = ConnectionStringFields[1].Value; + var database = ConnectionStringFields[2].Value; + var userId = ConnectionStringFields[3].Value; + var password = ConnectionStringFields[4].Value; + + if (!String.IsNullOrEmpty(server) && !String.IsNullOrEmpty(database) && !String.IsNullOrEmpty(userId) && !String.IsNullOrEmpty(password)) + { + connectionString = $"Server={server};Database={database};Uid={userId};Pwd={password};"; + } + + if (!String.IsNullOrEmpty(port)) + { + connectionString += $"Port={port};"; + } + return connectionString; + } + + public override string ConcatenateSql(params string[] values) + { + var returnValue = "CONCAT("; + for (var i = 0; i < values.Length; i++) + { + if (i > 0) + { + returnValue += ","; + } + returnValue += values[i]; + } + + returnValue += ")"; + + return returnValue; + } + + + public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString) + { + return optionsBuilder.UseMySQL(connectionString); + } + } +} diff --git a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj new file mode 100644 index 00000000..cc1cd306 --- /dev/null +++ b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj @@ -0,0 +1,16 @@ + + + + net5.0 + 9 + + + + + + + + + + + diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj new file mode 100644 index 00000000..6f1c3507 --- /dev/null +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj @@ -0,0 +1,18 @@ + + + + 9 + net5.0 + + + + + + + + + + + + + diff --git a/Oqtane.Database.PostgreSQL/PostgreSQLDatabase.cs b/Oqtane.Database.PostgreSQL/PostgreSQLDatabase.cs new file mode 100644 index 00000000..8d50debd --- /dev/null +++ b/Oqtane.Database.PostgreSQL/PostgreSQLDatabase.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using EFCore.NamingConventions.Internal; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Models; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Oqtane.Shared; + +namespace Oqtane.Database.PostgreSQL +{ + public class PostgreSQLDatabase : OqtaneDatabaseBase + { + private static string _friendlyName => "PostgreSQL"; + + private static string _name => "PostgreSQL"; + + private readonly INameRewriter _rewriter; + + private static readonly List _connectionStringFields = new() + { + new() {Name = "Server", FriendlyName = "Server", Value = "127.0.0.1", HelpText="Enter the database server"}, + new() {Name = "Port", FriendlyName = "Port", Value = "5432", HelpText="Enter the port used to connect to the server"}, + new() {Name = "Database", FriendlyName = "Database", Value = "Oqtane-{{Date}}", HelpText="Enter the name of the database"}, + new() {Name = "IntegratedSecurity", FriendlyName = "Integrated Security", Value = "true", HelpText="Select if you want integrated security or not"}, + new() {Name = "Uid", FriendlyName = "User Id", Value = "", HelpText="Enter the username to use for the database"}, + new() {Name = "Pwd", FriendlyName = "Password", Value = "", HelpText="Enter the password to use for the database"} + }; + + public PostgreSQLDatabase() : base(_name, _friendlyName, _connectionStringFields) + { + _rewriter = new SnakeCaseNameRewriter(CultureInfo.InvariantCulture); + } + + public override string Provider => "Npgsql.EntityFrameworkCore.PostgreSQL"; + + public override OperationBuilder AddAutoIncrementColumn(ColumnsBuilder table, string name) + { + return table.Column(name: name, nullable: false).Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityAlwaysColumn); + } + + public override string BuildConnectionString() + { + var connectionString = String.Empty; + + var server = ConnectionStringFields[0].Value; + var port = ConnectionStringFields[1].Value; + var database = ConnectionStringFields[2].Value; + var integratedSecurity = Boolean.Parse(ConnectionStringFields[3].Value); + var userId = ConnectionStringFields[4].Value; + var password = ConnectionStringFields[5].Value; + + if (!String.IsNullOrEmpty(server) && !String.IsNullOrEmpty(database) && !String.IsNullOrEmpty(port)) + { + connectionString = $"Server={server};Port={port};Database={database};"; + } + + if (integratedSecurity) + { + connectionString += "Integrated Security=true;"; + } + else + { + if (!String.IsNullOrEmpty(userId) && !String.IsNullOrEmpty(password)) + { + connectionString += $"User ID={userId};Password={password};"; + } + else + { + connectionString = String.Empty; + } + } + + return connectionString; + } + + public override string ConcatenateSql(params string[] values) + { + var returnValue = String.Empty; + for (var i = 0; i < values.Length; i++) + { + if (i > 0) + { + returnValue += " || "; + } + returnValue += values[i]; + } + + return returnValue; + } + + public override string RewriteName(string name) + { + return _rewriter.RewriteName(name); + } + + public override void UpdateIdentityStoreTableNames(ModelBuilder builder) + { + foreach(var entity in builder.Model.GetEntityTypes()) + { + var tableName = entity.GetTableName(); + if (tableName.StartsWith("AspNetUser")) + { + // Replace table names + entity.SetTableName(RewriteName(entity.GetTableName())); + + // Replace column names + foreach(var property in entity.GetProperties()) + { + property.SetColumnName(RewriteName(property.GetColumnName())); + } + + foreach(var key in entity.GetKeys()) + { + key.SetName(RewriteName(key.GetName())); + } + + foreach(var index in entity.GetIndexes()) + { + index.SetName(RewriteName(index.GetName())); + } + } + } + } + + public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString) + { + return optionsBuilder.UseNpgsql(connectionString).UseSnakeCaseNamingConvention(); + } + } +} diff --git a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj new file mode 100644 index 00000000..75f13dad --- /dev/null +++ b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj @@ -0,0 +1,15 @@ + + + + net5.0 + + + + + + + + + + + diff --git a/Oqtane.Database.Sqlite/SqliteDatabase.cs b/Oqtane.Database.Sqlite/SqliteDatabase.cs new file mode 100644 index 00000000..50b901d0 --- /dev/null +++ b/Oqtane.Database.Sqlite/SqliteDatabase.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Repository.Databases +{ + public class SqliteDatabase : OqtaneDatabaseBase + { + private static string _friendlyName => "Sqlite"; + + private static string _name => "Sqlite"; + + private static readonly List _connectionStringFields = new() + { + new() {Name = "Server", FriendlyName = "File Name", Value = "Oqtane-{{Date}}.db", HelpText="Enter the file name to use for the database"} + }; + + public SqliteDatabase() :base(_name, _friendlyName, _connectionStringFields) { } + + public override string Provider => "Microsoft.EntityFrameworkCore.Sqlite"; + + public override OperationBuilder AddAutoIncrementColumn(ColumnsBuilder table, string name) + { + return table.Column(name: name, nullable: false).Annotation("Sqlite:Autoincrement", true); + } + + public override string BuildConnectionString() + { + var connectionstring = String.Empty; + + var server = ConnectionStringFields[0].Value; + + if (!String.IsNullOrEmpty(server)) + { + connectionstring = $"Data Source={server};"; + } + + return connectionstring; + } + + public override string ConcatenateSql(params string[] values) + { + var returnValue = String.Empty; + for (var i = 0; i < values.Length; i++) + { + if (i > 0) + { + returnValue += " || "; + } + returnValue += values[i]; + } + + return returnValue; + } + + public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString) + { + return optionsBuilder.UseSqlite(connectionString); + } + } +} diff --git a/Oqtane.Server/Controllers/SqlController.cs b/Oqtane.Server/Controllers/SqlController.cs index 7e7bedcc..d2a13078 100644 --- a/Oqtane.Server/Controllers/SqlController.cs +++ b/Oqtane.Server/Controllers/SqlController.cs @@ -6,11 +6,8 @@ using Oqtane.Shared; using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Enums; -using System.Data.SqlClient; -using System.Data; -using System.Dynamic; -using Newtonsoft.Json; using System; +using Microsoft.Data.SqlClient; namespace Oqtane.Controllers { diff --git a/Oqtane.Server/Databases/Interfaces/IMultiDatabase.cs b/Oqtane.Server/Databases/Interfaces/IMultiDatabase.cs new file mode 100644 index 00000000..103cd76b --- /dev/null +++ b/Oqtane.Server/Databases/Interfaces/IMultiDatabase.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Oqtane.Interfaces; + +namespace Oqtane.Repository.Databases.Interfaces +{ + public interface IMultiDatabase + { + public IEnumerable Databases { get; } + } +} diff --git a/Oqtane.Server/Databases/LocalDbDatabase.cs b/Oqtane.Server/Databases/LocalDbDatabase.cs new file mode 100644 index 00000000..77be3035 --- /dev/null +++ b/Oqtane.Server/Databases/LocalDbDatabase.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using Oqtane.Models; +using Oqtane.Repository.Databases; + +namespace Oqtane.Databases +{ + public class LocalDbDatabase : SqlServerDatabaseBase + { + private static string _friendlyName => "Local Database"; + private static string _name => "LocalDB"; + + private static readonly List _connectionStringFields = new() + { + new() {Name = "Server", FriendlyName = "Server", Value = "(LocalDb)\\MSSQLLocalDB", HelpText="Enter the database server"}, + new() {Name = "Database", FriendlyName = "Database", Value = "Oqtane-{{Date}}", HelpText="Enter the name of the database"} + }; + + public LocalDbDatabase() :base(_name, _friendlyName, _connectionStringFields) { } + + public override string BuildConnectionString() + { + var connectionString = String.Empty; + + var server = ConnectionStringFields[0].Value; + var database = ConnectionStringFields[1].Value; + + if (!String.IsNullOrEmpty(server) && !String.IsNullOrEmpty(database)) + { + connectionString = $"Data Source={server};AttachDbFilename=|DataDirectory|\\{database}.mdf;Initial Catalog={database};Integrated Security=SSPI;"; + } + + return connectionString; + } + } +} diff --git a/Oqtane.Server/Databases/SqlServerDatabase.cs b/Oqtane.Server/Databases/SqlServerDatabase.cs new file mode 100644 index 00000000..cfb852c3 --- /dev/null +++ b/Oqtane.Server/Databases/SqlServerDatabase.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using Oqtane.Models; +using Oqtane.Repository.Databases; + +// ReSharper disable ArrangeObjectCreationWhenTypeNotEvident + +namespace Oqtane.Databases +{ + public class SqlServerDatabase : SqlServerDatabaseBase + { + private static string _friendlyName => "SQL Server"; + + private static string _name => "SqlServer"; + + private static readonly List _connectionStringFields = new() + { + new() {Name = "Server", FriendlyName = "Server", Value = ".", HelpText="Enter the database server"}, + new() {Name = "Database", FriendlyName = "Database", Value = "Oqtane-{{Date}}", HelpText="Enter the name of the database"}, + new() {Name = "IntegratedSecurity", FriendlyName = "Integrated Security", Value = "true", HelpText="Select if you want integrated security or not"}, + new() {Name = "Uid", FriendlyName = "User Id", Value = "", HelpText="Enter the username to use for the database"}, + new() {Name = "Pwd", FriendlyName = "Password", Value = "", HelpText="Enter the password to use for the database"} + }; + + public SqlServerDatabase() :base(_name, _friendlyName, _connectionStringFields) { } + + public override string BuildConnectionString() + { + var connectionString = String.Empty; + + var server = ConnectionStringFields[0].Value; + var database = ConnectionStringFields[1].Value; + var integratedSecurity = Boolean.Parse(ConnectionStringFields[2].Value); + var userId = ConnectionStringFields[3].Value; + var password = ConnectionStringFields[4].Value; + + if (!String.IsNullOrEmpty(server) && !String.IsNullOrEmpty(database)) + { + connectionString = $"Data Source={server};Initial Catalog={database};"; + } + + if (integratedSecurity) + { + connectionString += "Integrated Security=SSPI;"; + } + else + { + if (!String.IsNullOrEmpty(userId) && !String.IsNullOrEmpty(password)) + { + connectionString += $"User ID={userId};Password={password};"; + } + else + { + connectionString = String.Empty; + } + } + + return connectionString; + + } + } +} diff --git a/Oqtane.Server/Databases/SqlServerDatabaseBase.cs b/Oqtane.Server/Databases/SqlServerDatabaseBase.cs new file mode 100644 index 00000000..70d3de3a --- /dev/null +++ b/Oqtane.Server/Databases/SqlServerDatabaseBase.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Databases; +using Oqtane.Interfaces; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Repository.Databases +{ + public abstract class SqlServerDatabaseBase : OqtaneDatabaseBase + { + protected SqlServerDatabaseBase(string name, string friendlyName, List connectionStringFields) : base(name, friendlyName, connectionStringFields) + { + } + + public override string Provider => "Microsoft.EntityFrameworkCore.SqlServer"; + + public override OperationBuilder AddAutoIncrementColumn(ColumnsBuilder table, string name) + { + return table.Column(name: name, nullable: false).Annotation("SqlServer:Identity", "1, 1"); + } + + public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString) + { + return optionsBuilder.UseSqlServer(connectionString); + } + } +} diff --git a/Oqtane.Server/Extensions/DbContextOptionsBuilderExtensions.cs b/Oqtane.Server/Extensions/DbContextOptionsBuilderExtensions.cs index e34d1563..41aa1de8 100644 --- a/Oqtane.Server/Extensions/DbContextOptionsBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/DbContextOptionsBuilderExtensions.cs @@ -1,15 +1,29 @@ +using System; using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore; +using Oqtane.Interfaces; +// ReSharper disable ConvertToUsingDeclaration namespace Oqtane.Extensions { public static class DbContextOptionsBuilderExtensions { - public static DbContextOptionsBuilder UseOqtaneDatabase([NotNull] this DbContextOptionsBuilder optionsBuilder, string connectionString) + public static DbContextOptionsBuilder UseOqtaneDatabase([NotNull] this DbContextOptionsBuilder optionsBuilder, IOqtaneDatabase database, string connectionString) { - optionsBuilder.UseSqlServer(connectionString); + database.UseDatabase(optionsBuilder, connectionString); return optionsBuilder; } + + public static DbContextOptionsBuilder UseOqtaneDatabase([NotNull] this DbContextOptionsBuilder optionsBuilder, string databaseType, string connectionString) + { + var type = Type.GetType(databaseType); + var database = Activator.CreateInstance(type) as IOqtaneDatabase; + + database.UseDatabase(optionsBuilder, connectionString); + + return optionsBuilder; + } + } } diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 36c41601..68306de5 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Runtime.Loader; using Microsoft.Extensions.Hosting; using Oqtane.Infrastructure; +using Oqtane.Interfaces; using Oqtane.Modules; using Oqtane.Services; using Oqtane.Shared; @@ -46,6 +47,17 @@ namespace Microsoft.Extensions.DependencyInjection } } + // dynamically register database providers + var databaseTypes = assembly.GetInterfaces(); + foreach (var databaseType in databaseTypes) + { + if (databaseType.AssemblyQualifiedName != null) + { + var serviceType = Type.GetType("Oqtane.Interfaces.IOqtaneDatabase, Oqtane.Shared"); + services.AddScoped(serviceType ?? databaseType, databaseType); + } + } + // dynamically register hosted services var serviceTypes = assembly.GetTypes(hostedServiceType); foreach (var serviceType in serviceTypes) diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index bb1028a0..e1f5606e 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; -using System.Data; using System.IO; using System.Linq; using System.Reflection; -using DbUp; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; @@ -16,8 +14,14 @@ using Oqtane.Models; using Oqtane.Repository; using Oqtane.Shared; using Oqtane.Enums; +using Oqtane.Interfaces; using File = System.IO.File; +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable ConvertToUsingDeclaration +// ReSharper disable BuiltInTypeReferenceStyleForMemberAccess +// ReSharper disable UseIndexFromEndExpression + namespace Oqtane.Infrastructure { public class DatabaseManager : IDatabaseManager @@ -75,7 +79,13 @@ namespace Oqtane.Infrastructure if (install == null) { // startup or silent installation - install = new InstallConfig { ConnectionString = _config.GetConnectionString(SettingKeys.ConnectionStringKey), TenantName = TenantNames.Master, IsNewTenant = false }; + install = new InstallConfig + { + ConnectionString = _config.GetConnectionString(SettingKeys.ConnectionStringKey), + TenantName = TenantNames.Master, + DatabaseType = _config.GetSection(SettingKeys.DatabaseSection)[SettingKeys.DatabaseTypeKey], + IsNewTenant = false + }; var installation = IsInstalled(); if (!installation.Success) @@ -173,14 +183,20 @@ namespace Oqtane.Infrastructure { //create data directory if does not exist var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); - if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory); + if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory ?? String.Empty); var connectionString = NormalizeConnectionString(install.ConnectionString); - using (var dbc = new DbContext(new DbContextOptionsBuilder().UseOqtaneDatabase(connectionString).Options)) + var databaseType = install.DatabaseType; + using (var scope = _serviceScopeFactory.CreateScope()) { - // create empty database if it does not exist - dbc.Database.EnsureCreated(); - result.Success = true; + var databases = scope.ServiceProvider.GetServices(); + + using (var dbc = new DbContext(new DbContextOptionsBuilder().UseOqtaneDatabase(databases.Single(d => d.Name == databaseType), connectionString).Options)) + { + // create empty database if it does not exist + dbc.Database.EnsureCreated(); + result.Success = true; + } } } catch (Exception ex) @@ -202,31 +218,37 @@ namespace Oqtane.Infrastructure if (install.TenantName == TenantNames.Master) { - MigrateScriptNamingConvention("Master", install.ConnectionString); - - var upgradeConfig = DeployChanges - .To - .SqlDatabase(NormalizeConnectionString(install.ConnectionString)) - .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Master.") && s.EndsWith(".sql",StringComparison.OrdinalIgnoreCase)); - - var upgrade = upgradeConfig.Build(); - if (upgrade.IsUpgradeRequired()) + using (var scope = _serviceScopeFactory.CreateScope()) { - var upgradeResult = upgrade.PerformUpgrade(); - result.Success = upgradeResult.Successful; - if (!result.Success) + var databases = scope.ServiceProvider.GetServices(); + var sql = scope.ServiceProvider.GetRequiredService(); + + try { - result.Message = upgradeResult.Error.Message; - } - } - else - { - result.Success = true; - } + var dbConfig = new DbConfig(null, null, databases) {ConnectionString = install.ConnectionString, DatabaseType = install.DatabaseType}; - if (result.Success) - { - UpdateConnectionString(install.ConnectionString); + using (var masterDbContext = new MasterDBContext(new DbContextOptions(), dbConfig)) + { + var installation = IsInstalled(); + if (installation.Success && (install.DatabaseType == "SqlServer" || install.DatabaseType == "LocalDB")) + { + UpgradeSqlServer(sql, install.ConnectionString, true); + } + // Push latest model into database + masterDbContext.Database.Migrate(); + result.Success = true; + } + } + catch (Exception ex) + { + result.Message = ex.Message; + } + + if (result.Success) + { + UpdateConnectionString(install.ConnectionString); + UpdateDatabaseType(install.DatabaseType); + } } } else @@ -243,28 +265,56 @@ namespace Oqtane.Infrastructure if (!string.IsNullOrEmpty(install.TenantName) && !string.IsNullOrEmpty(install.Aliases)) { - using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey)))) + using (var scope = _serviceScopeFactory.CreateScope()) { - Tenant tenant; - if (install.IsNewTenant) - { - tenant = new Tenant { Name = install.TenantName, DBConnectionString = DenormalizeConnectionString(install.ConnectionString), CreatedBy = "", CreatedOn = DateTime.UtcNow, ModifiedBy = "", ModifiedOn = DateTime.UtcNow }; - db.Tenant.Add(tenant); - db.SaveChanges(); - _cache.Remove("tenants"); - } - else - { - tenant = db.Tenant.FirstOrDefault(item => item.Name == install.TenantName); - } + var databases = scope.ServiceProvider.GetServices(); - foreach (string aliasname in install.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + using (var db = GetInstallationContext(databases)) { - var alias = new Alias { Name = aliasname, TenantId = tenant.TenantId, SiteId = -1, CreatedBy = "", CreatedOn = DateTime.UtcNow, ModifiedBy = "", ModifiedOn = DateTime.UtcNow }; - db.Alias.Add(alias); - db.SaveChanges(); + Tenant tenant; + if (install.IsNewTenant) + { + tenant = new Tenant + { + Name = install.TenantName, + DBConnectionString = DenormalizeConnectionString(install.ConnectionString), + DBType = install.DatabaseType, + CreatedBy = "", + CreatedOn = DateTime.UtcNow, + ModifiedBy = "", + ModifiedOn = DateTime.UtcNow + }; + db.Tenant.Add(tenant); + db.SaveChanges(); + _cache.Remove("tenants"); + } + else + { + tenant = db.Tenant.FirstOrDefault(item => item.Name == install.TenantName); + } + + foreach (var aliasName in install.Aliases.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)) + { + if (tenant != null) + { + var alias = new Alias + { + Name = aliasName, + TenantId = tenant.TenantId, + SiteId = -1, + CreatedBy = "", + CreatedOn = DateTime.UtcNow, + ModifiedBy = "", + ModifiedOn = DateTime.UtcNow + }; + db.Alias.Add(alias); + } + + db.SaveChanges(); + } + + _cache.Remove("aliases"); } - _cache.Remove("aliases"); } } @@ -277,39 +327,45 @@ namespace Oqtane.Infrastructure { var result = new Installation { Success = false, Message = string.Empty }; - string[] versions = Constants.ReleaseVersions.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var versions = Constants.ReleaseVersions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); using (var scope = _serviceScopeFactory.CreateScope()) { var upgrades = scope.ServiceProvider.GetRequiredService(); + var databases = scope.ServiceProvider.GetServices(); + var sql = scope.ServiceProvider.GetRequiredService(); - using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey)))) + using (var db = GetInstallationContext(databases)) { foreach (var tenant in db.Tenant.ToList()) { - MigrateScriptNamingConvention("Tenant", tenant.DBConnectionString); - - var upgradeConfig = DeployChanges.To.SqlDatabase(NormalizeConnectionString(tenant.DBConnectionString)) - .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains("Tenant.") && s.EndsWith(".sql", StringComparison.OrdinalIgnoreCase)); - - var upgrade = upgradeConfig.Build(); - if (upgrade.IsUpgradeRequired()) + try { - var upgradeResult = upgrade.PerformUpgrade(); - result.Success = upgradeResult.Successful; - if (!result.Success) + var dbConfig = new DbConfig(null, null, databases) {ConnectionString = tenant.DBConnectionString, DatabaseType = tenant.DBType}; + using (var tenantDbContext = new TenantDBContext(dbConfig, null)) { - result.Message = upgradeResult.Error.Message; + if (install.DatabaseType == "SqlServer" || install.DatabaseType == "LocalDB") + { + UpgradeSqlServer(sql, tenant.DBConnectionString, false); + } + + // Push latest model into database + tenantDbContext.Database.Migrate(); + result.Success = true; } } + catch (Exception ex) + { + result.Message = ex.Message; + } // execute any version specific upgrade logic - string version = tenant.Version; - int index = Array.FindIndex(versions, item => item == version); + var version = tenant.Version; + var index = Array.FindIndex(versions, item => item == version); if (index != (versions.Length - 1)) { if (index == -1) index = 0; - for (int i = index; i < versions.Length; i++) + for (var i = index; i < versions.Length; i++) { upgrades.Upgrade(tenant, versions[i]); } @@ -335,21 +391,23 @@ namespace Oqtane.Infrastructure using (var scope = _serviceScopeFactory.CreateScope()) { - var moduledefinitions = scope.ServiceProvider.GetRequiredService(); + var moduleDefinitions = scope.ServiceProvider.GetRequiredService(); var sql = scope.ServiceProvider.GetRequiredService(); - foreach (var moduledefinition in moduledefinitions.GetModuleDefinitions()) + var databases = scope.ServiceProvider.GetServices(); + + foreach (var moduleDefinition in moduleDefinitions.GetModuleDefinitions()) { - if (!string.IsNullOrEmpty(moduledefinition.ReleaseVersions) && !string.IsNullOrEmpty(moduledefinition.ServerManagerType)) + if (!string.IsNullOrEmpty(moduleDefinition.ReleaseVersions) && !string.IsNullOrEmpty(moduleDefinition.ServerManagerType)) { - Type moduletype = Type.GetType(moduledefinition.ServerManagerType); - if (moduletype != null) + var moduleType = Type.GetType(moduleDefinition.ServerManagerType); + if (moduleType != null) { - string[] versions = moduledefinition.ReleaseVersions.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey)))) + var versions = moduleDefinition.ReleaseVersions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + using (var db = GetInstallationContext(databases)) { foreach (var tenant in db.Tenant.ToList()) { - int index = Array.FindIndex(versions, item => item == moduledefinition.Version); + var index = Array.FindIndex(versions, item => item == moduleDefinition.Version); if (tenant.Name == install.TenantName && install.TenantName != TenantNames.Master) { index = -1; @@ -357,31 +415,31 @@ namespace Oqtane.Infrastructure if (index != (versions.Length - 1)) { if (index == -1) index = 0; - for (int i = index; i < versions.Length; i++) + for (var i = index; i < versions.Length; i++) { try { - if (moduletype.GetInterface("IInstallable") != null) + if (moduleType.GetInterface("IInstallable") != null) { - var moduleobject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduletype); - ((IInstallable)moduleobject).Install(tenant, versions[i]); + var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType) as IInstallable; + moduleObject?.Install(tenant, versions[i]); } else { - sql.ExecuteScript(tenant, moduletype.Assembly, Utilities.GetTypeName(moduledefinition.ModuleDefinitionName) + "." + versions[i] + ".sql"); + sql.ExecuteScript(tenant, moduleType.Assembly, Utilities.GetTypeName(moduleDefinition.ModuleDefinitionName) + "." + versions[i] + ".sql"); } } catch (Exception ex) { - result.Message = "An Error Occurred Installing " + moduledefinition.Name + " Version " + versions[i] + " - " + ex.Message.ToString(); + result.Message = "An Error Occurred Installing " + moduleDefinition.Name + " Version " + versions[i] + " - " + ex.Message; } } } } - if (string.IsNullOrEmpty(result.Message) && moduledefinition.Version != versions[versions.Length - 1]) + if (string.IsNullOrEmpty(result.Message) && moduleDefinition.Version != versions[versions.Length - 1]) { - moduledefinition.Version = versions[versions.Length - 1]; - db.Entry(moduledefinition).State = EntityState.Modified; + moduleDefinition.Version = versions[versions.Length - 1]; + db.Entry(moduleDefinition).State = EntityState.Modified; db.SaveChanges(); } } @@ -408,8 +466,8 @@ namespace Oqtane.Infrastructure { // use the SiteState to set the Alias explicitly so the tenant can be resolved var aliases = scope.ServiceProvider.GetRequiredService(); - string firstalias = install.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0]; - var alias = aliases.GetAliases().FirstOrDefault(item => item.Name == firstalias); + var firstAlias = install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0]; + var alias = aliases.GetAliases().FirstOrDefault(item => item.Name == firstAlias); var siteState = scope.ServiceProvider.GetRequiredService(); siteState.Alias = alias; @@ -420,7 +478,7 @@ namespace Oqtane.Infrastructure var tenants = scope.ServiceProvider.GetRequiredService(); var users = scope.ServiceProvider.GetRequiredService(); var roles = scope.ServiceProvider.GetRequiredService(); - var userroles = scope.ServiceProvider.GetRequiredService(); + var userRoles = scope.ServiceProvider.GetRequiredService(); var folders = scope.ServiceProvider.GetRequiredService(); var log = scope.ServiceProvider.GetRequiredService(); var identityUserManager = scope.ServiceProvider.GetRequiredService>(); @@ -438,10 +496,10 @@ namespace Oqtane.Infrastructure }; site = sites.AddSite(site); - IdentityUser identityUser = identityUserManager.FindByNameAsync(UserNames.Host).GetAwaiter().GetResult(); + var identityUser = identityUserManager.FindByNameAsync(UserNames.Host).GetAwaiter().GetResult(); if (identityUser == null) { - identityUser = new IdentityUser { UserName = UserNames.Host, Email = install.HostEmail, EmailConfirmed = true }; + identityUser = new IdentityUser {UserName = UserNames.Host, Email = install.HostEmail, EmailConfirmed = true}; var create = identityUserManager.CreateAsync(identityUser, install.HostPassword).GetAwaiter().GetResult(); if (create.Succeeded) { @@ -458,8 +516,8 @@ namespace Oqtane.Infrastructure user = users.AddUser(user); var hostRoleId = roles.GetRoles(user.SiteId, true).FirstOrDefault(item => item.Name == RoleNames.Host)?.RoleId ?? 0; - var userRole = new UserRole { UserId = user.UserId, RoleId = hostRoleId, EffectiveDate = null, ExpiryDate = null }; - userroles.AddUserRole(userRole); + var userRole = new UserRole {UserId = user.UserId, RoleId = hostRoleId, EffectiveDate = null, ExpiryDate = null}; + userRoles.AddUserRole(userRole); // add user folder var folder = folders.GetFolder(user.SiteId, Utilities.PathCombine("Users", Path.DirectorySeparatorChar.ToString())); @@ -484,17 +542,20 @@ namespace Oqtane.Infrastructure } } - foreach (string aliasname in install.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var aliasName in install.Aliases.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)) { - alias = aliases.GetAliases().FirstOrDefault(item => item.Name == aliasname); - alias.SiteId = site.SiteId; - aliases.UpdateAlias(alias); + alias = aliases.GetAliases().FirstOrDefault(item => item.Name == aliasName); + if (alias != null) + { + alias.SiteId = site.SiteId; + aliases.UpdateAlias(alias); + } } tenant.Version = Constants.Version; tenants.UpdateTenant(tenant); - log.Log(site.SiteId, LogLevel.Trace, this, LogFunction.Create, "Site Created {Site}", site); + if (site != null) log.Log(site.SiteId, LogLevel.Trace, this, LogFunction.Create, "Site Created {Site}", site); } } } @@ -504,30 +565,6 @@ namespace Oqtane.Infrastructure return result; } - private string NormalizeConnectionString(string connectionString) - { - var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); - connectionString = connectionString.Replace("|DataDirectory|", dataDirectory); - return connectionString; - } - - private string DenormalizeConnectionString(string connectionString) - { - var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); - connectionString = connectionString.Replace(dataDirectory, "|DataDirectory|"); - return connectionString; - } - - public void UpdateConnectionString(string connectionString) - { - connectionString = DenormalizeConnectionString(connectionString); - if (_config.GetConnectionString(SettingKeys.ConnectionStringKey) != connectionString) - { - AddOrUpdateAppSetting($"ConnectionStrings:{SettingKeys.ConnectionStringKey}", connectionString); - _config.Reload(); - } - } - public void AddOrUpdateAppSetting(string sectionPathKey, T value) { try @@ -547,6 +584,36 @@ namespace Oqtane.Infrastructure } } + private string DenormalizeConnectionString(string connectionString) + { + var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); + connectionString = connectionString.Replace(dataDirectory ?? String.Empty, "|DataDirectory|"); + return connectionString; + } + + private InstallationContext GetInstallationContext(IEnumerable databases) + { + var databaseType = _config.GetSection(SettingKeys.DatabaseSection)[SettingKeys.DatabaseTypeKey]; + var connectionString = NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey)); + + return new InstallationContext(databases.Single(d => d.Name == databaseType), connectionString); + } + + private string GetInstallationConfig(string key, string defaultValue) + { + var value = _config.GetSection(SettingKeys.InstallationSection).GetValue(key, defaultValue); + // double fallback to default value - allow hold sample keys in config + if (string.IsNullOrEmpty(value)) value = defaultValue; + return value; + } + + private string NormalizeConnectionString(string connectionString) + { + var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); + connectionString = connectionString.Replace("|DataDirectory|", dataDirectory); + return connectionString; + } + private void SetValueRecursively(string sectionPathKey, dynamic jsonObj, T value) { // split the string at the first ':' character @@ -566,25 +633,27 @@ namespace Oqtane.Infrastructure } } - private string GetInstallationConfig(string key, string defaultValue) + public void UpdateConnectionString(string connectionString) { - var value = _config.GetSection(SettingKeys.InstallationSection).GetValue(key, defaultValue); - // double fallback to default value - allow hold sample keys in config - if (string.IsNullOrEmpty(value)) value = defaultValue; - return value; - } - - private void MigrateScriptNamingConvention(string scriptType, string connectionString) - { - // migrate to new naming convention for scripts - var migrateConfig = DeployChanges.To.SqlDatabase(NormalizeConnectionString(connectionString)) - .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s == scriptType + ".00.00.00.00.sql"); - var migrate = migrateConfig.Build(); - if (migrate.IsUpgradeRequired()) + connectionString = DenormalizeConnectionString(connectionString); + if (_config.GetConnectionString(SettingKeys.ConnectionStringKey) != connectionString) { - migrate.PerformUpgrade(); + AddOrUpdateAppSetting($"{SettingKeys.ConnectionStringsSection}:{SettingKeys.ConnectionStringKey}", connectionString); + _config.Reload(); } } + public void UpdateDatabaseType(string databaseType) + { + AddOrUpdateAppSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", databaseType); + _config.Reload(); + } + + public void UpgradeSqlServer(ISqlRepository sql, string connectionString, bool isMaster) + { + var script = (isMaster) ? "MigrateMaster.sql" : "MigrateTenant.sql"; + + sql.ExecuteScript(connectionString, Assembly.GetExecutingAssembly(), script); + } } } diff --git a/Oqtane.Server/Infrastructure/InstallationManager.cs b/Oqtane.Server/Infrastructure/InstallationManager.cs index 3b627c70..3962baf5 100644 --- a/Oqtane.Server/Infrastructure/InstallationManager.cs +++ b/Oqtane.Server/Infrastructure/InstallationManager.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Hosting; using Oqtane.Shared; +// ReSharper disable AssignNullToNotNullAttribute namespace Oqtane.Infrastructure { diff --git a/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs b/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs index baef7184..48ed9bc8 100644 --- a/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs +++ b/Oqtane.Server/Infrastructure/Interfaces/IInstallable.cs @@ -1,10 +1,13 @@ -using Oqtane.Models; +using Microsoft.EntityFrameworkCore; +using Oqtane.Enums; +using Oqtane.Models; namespace Oqtane.Infrastructure { public interface IInstallable { bool Install(Tenant tenant, string version); + bool Uninstall(Tenant tenant); } } diff --git a/Oqtane.Server/Migrations/01000000_InitializeMaster.cs b/Oqtane.Server/Migrations/01000000_InitializeMaster.cs new file mode 100644 index 00000000..1338686a --- /dev/null +++ b/Oqtane.Server/Migrations/01000000_InitializeMaster.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(MasterDBContext))] + [Migration("Master.01.00.00.00")] + public class InitializeMaster : MultiDatabaseMigration + { + public InitializeMaster(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Create Tenant table + var tenantEntityBuilder = new TenantEntityBuilder(migrationBuilder, ActiveDatabase); + tenantEntityBuilder.Create(); + + //Create Alias table + var aliasEntityBuilder = new AliasEntityBuilder(migrationBuilder, ActiveDatabase); + aliasEntityBuilder.Create(); + + //Create ModuleDefinitions Table + var moduleDefinitionsEntityBuilder = new ModuleDefinitionsEntityBuilder(migrationBuilder, ActiveDatabase); + moduleDefinitionsEntityBuilder.Create(); + + //Create Job Table + var jobEntityBuilder = new JobEntityBuilder(migrationBuilder, ActiveDatabase); + jobEntityBuilder.Create(); + + //Create JobLog Table + var jobLogEntityBuilder = new JobLogEntityBuilder(migrationBuilder, ActiveDatabase); + jobLogEntityBuilder.Create(); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + //Drop Alias table + var aliasEntityBuilder = new AliasEntityBuilder(migrationBuilder, ActiveDatabase); + aliasEntityBuilder.Drop(); + + //Drop JobLog Table + var jobLogEntityBuilder = new JobLogEntityBuilder(migrationBuilder, ActiveDatabase); + jobLogEntityBuilder.Drop(); + + //Drop Tenant table + var tenantEntityBuilder = new TenantEntityBuilder(migrationBuilder, ActiveDatabase); + tenantEntityBuilder.Drop(); + + //Drop ModuleDefinitions Table + var moduleDefinitionsEntityBuilder = new ModuleDefinitionsEntityBuilder(migrationBuilder, ActiveDatabase); + moduleDefinitionsEntityBuilder.Drop(); + + //Drop Job Table + var jobEntityBuilder = new JobEntityBuilder(migrationBuilder, ActiveDatabase); + jobEntityBuilder.Drop(); + } + + } +} diff --git a/Oqtane.Server/Migrations/01000000_InitializeTenant.cs b/Oqtane.Server/Migrations/01000000_InitializeTenant.cs new file mode 100644 index 00000000..4d8817fa --- /dev/null +++ b/Oqtane.Server/Migrations/01000000_InitializeTenant.cs @@ -0,0 +1,169 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + + +namespace Oqtane.Migrations +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.01.00.00.00")] + public class InitializeTenant : MultiDatabaseMigration + { + public InitializeTenant(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Create Site table + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.Create(); + + //Create Page table + var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase); + pageEntityBuilder.Create(); + pageEntityBuilder.AddIndex("IX_Page", new [] {"SiteId", "Path", "UserId"}, true); + + //Add Column to Page table (for Sql Server only) we will drop it later for Sql Server only + if (ActiveDatabase.Name == "SqlServer" || ActiveDatabase.Name == "LocalDB") + { + pageEntityBuilder.AddBooleanColumn("EditMode"); + } + + //Create Module table + var moduleEntityBuilder = new ModuleEntityBuilder(migrationBuilder, ActiveDatabase); + moduleEntityBuilder.Create(); + + //Create PageModule table + var pageModuleEntityBuilder = new PageModuleEntityBuilder(migrationBuilder, ActiveDatabase); + pageModuleEntityBuilder.Create(); + + //Create User table + var userEntityBuilder = new UserEntityBuilder(migrationBuilder, ActiveDatabase); + userEntityBuilder.Create(); + userEntityBuilder.AddIndex("IX_User", "Username", true); + + //Create Role table + var roleEntityBuilder = new RoleEntityBuilder(migrationBuilder, ActiveDatabase); + roleEntityBuilder.Create(); + + //Create UserRole table + var userRoleEntityBuilder = new UserRoleEntityBuilder(migrationBuilder, ActiveDatabase); + userRoleEntityBuilder.Create(); + userRoleEntityBuilder.AddIndex("IX_UserRole", new [] {"RoleId", "UserId"}, true); + + //Create Permission table + var permissionEntityBuilder = new PermissionEntityBuilder(migrationBuilder, ActiveDatabase); + permissionEntityBuilder.Create(); + permissionEntityBuilder.AddIndex("IX_Permission", new [] {"SiteId", "EntityName", "EntityId", "PermissionName", "RoleId", "UserId"}, true); + + //Create Setting table + var settingEntityBuilder = new SettingEntityBuilder(migrationBuilder, ActiveDatabase); + settingEntityBuilder.Create(); + settingEntityBuilder.AddIndex("IX_Setting", new [] {"EntityName", "EntityId", "SettingName"}, true); + + //Create Profile table + var profileEntityBuilder = new ProfileEntityBuilder(migrationBuilder, ActiveDatabase); + profileEntityBuilder.Create(); + + //Create Log table + var logEntityBuilder = new LogEntityBuilder(migrationBuilder, ActiveDatabase); + logEntityBuilder.Create(); + + //Create Notification table + var notificationEntityBuilder = new NotificationEntityBuilder(migrationBuilder, ActiveDatabase); + notificationEntityBuilder.Create(); + + //Create Folder table + var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); + folderEntityBuilder.Create(); + folderEntityBuilder.AddIndex("IX_Folder", new [] {"SiteId", "Path"}, true); + + //Create File table + var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); + fileEntityBuilder.Create(); + + //Create AspNetUsers table + var aspNetUsersEntityBuilder = new AspNetUsersEntityBuilder(migrationBuilder, ActiveDatabase); + aspNetUsersEntityBuilder.Create(); + aspNetUsersEntityBuilder.AddIndex("EmailIndex", "NormalizedEmail", true); + aspNetUsersEntityBuilder.AddIndex("UserNameIndex", "NormalizedUserName", true); + + //Create AspNetUserClaims table + var aspNetUserClaimsEntityBuilder = new AspNetUserClaimsEntityBuilder(migrationBuilder, ActiveDatabase); + aspNetUserClaimsEntityBuilder.Create(); + aspNetUserClaimsEntityBuilder.AddIndex("IX_AspNetUserClaims_UserId", "UserId", true); + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + //Drop AspNetUserClaims table + var aspNetUserClaimsEntityBuilder = new AspNetUserClaimsEntityBuilder(migrationBuilder, ActiveDatabase); + aspNetUserClaimsEntityBuilder.Drop(); + + //Drop AspNetUsers table + var aspNetUsersEntityBuilder = new AspNetUsersEntityBuilder(migrationBuilder, ActiveDatabase); + aspNetUsersEntityBuilder.Drop(); + + //Drop File table + var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); + fileEntityBuilder.Drop(); + + //Drop Folder table + var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); + folderEntityBuilder.Drop(); + + //Drop Notification table + var notificationEntityBuilder = new NotificationEntityBuilder(migrationBuilder, ActiveDatabase); + notificationEntityBuilder.Drop(); + + //Drop Log table + var logEntityBuilder = new LogEntityBuilder(migrationBuilder, ActiveDatabase); + logEntityBuilder.Drop(); + + //Drop Profile table + var profileEntityBuilder = new ProfileEntityBuilder(migrationBuilder, ActiveDatabase); + profileEntityBuilder.Drop(); + + //Drop Setting table + var settingEntityBuilder = new SettingEntityBuilder(migrationBuilder, ActiveDatabase); + settingEntityBuilder.Drop(); + + //Drop Permission table + var permissionEntityBuilder = new PermissionEntityBuilder(migrationBuilder, ActiveDatabase); + permissionEntityBuilder.Drop(); + + //Drop UserRole table + var userRoleEntityBuilder = new UserRoleEntityBuilder(migrationBuilder, ActiveDatabase); + userRoleEntityBuilder.Drop(); + + //Drop Role table + var roleEntityBuilder = new RoleEntityBuilder(migrationBuilder, ActiveDatabase); + roleEntityBuilder.Drop(); + + //Drop User table + var userEntityBuilder = new UserEntityBuilder(migrationBuilder, ActiveDatabase); + userEntityBuilder.Drop(); + + //Drop PageModule table + var pageModuleEntityBuilder = new PageModuleEntityBuilder(migrationBuilder, ActiveDatabase); + pageModuleEntityBuilder.Drop(); + + //Drop Module table + var moduleEntityBuilder = new ModuleEntityBuilder(migrationBuilder, ActiveDatabase); + moduleEntityBuilder.Drop(); + + //Drop Page table + var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase); + pageEntityBuilder.Drop(); + + //Drop Site table + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.Drop(); + } + } +} diff --git a/Oqtane.Server/Migrations/01000100_AddAdditionalIndexesInMaster.cs b/Oqtane.Server/Migrations/01000100_AddAdditionalIndexesInMaster.cs new file mode 100644 index 00000000..a1ab6525 --- /dev/null +++ b/Oqtane.Server/Migrations/01000100_AddAdditionalIndexesInMaster.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(MasterDBContext))] + [Migration("Master.01.00.01.00")] + public class AddAdditionalIndexesInMaster : MultiDatabaseMigration + { + public AddAdditionalIndexesInMaster(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Update Tenant table + var tenantEntityBuilder = new TenantEntityBuilder(migrationBuilder, ActiveDatabase); + tenantEntityBuilder.AddIndex("IX_Tenant", "Name"); + + //Update Alias table + var aliasEntityBuilder = new AliasEntityBuilder(migrationBuilder, ActiveDatabase); + aliasEntityBuilder.AddIndex("IX_Alias", "Name"); + + //Update ModuleDefinitions Table + var moduleDefinitionsEntityBuilder = new ModuleDefinitionsEntityBuilder(migrationBuilder, ActiveDatabase); + moduleDefinitionsEntityBuilder.AddIndex("IX_ModuleDefinition", "ModuleDefinitionName"); + + //Update Job Table + var jobEntityBuilder = new JobEntityBuilder(migrationBuilder, ActiveDatabase); + jobEntityBuilder.AddIndex("IX_Job", "JobType"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + //Update Tenant table + var tenantEntityBuilder = new TenantEntityBuilder(migrationBuilder, ActiveDatabase); + tenantEntityBuilder.DropIndex("IX_Tenant"); + + //Update Alias table + var aliasEntityBuilder = new AliasEntityBuilder(migrationBuilder, ActiveDatabase); + aliasEntityBuilder.DropIndex("IX_Alias"); + + //Update ModuleDefinitions Table + var moduleDefinitionsEntityBuilder = new ModuleDefinitionsEntityBuilder(migrationBuilder, ActiveDatabase); + moduleDefinitionsEntityBuilder.DropIndex("IX_ModuleDefinition"); + + //Update Job Table + var jobEntityBuilder = new JobEntityBuilder(migrationBuilder, ActiveDatabase); + jobEntityBuilder.DropIndex("IX_Job"); + } + } +} diff --git a/Oqtane.Server/Migrations/01000100_AddAdditionalIndexesInTenant.cs b/Oqtane.Server/Migrations/01000100_AddAdditionalIndexesInTenant.cs new file mode 100644 index 00000000..6afdf29f --- /dev/null +++ b/Oqtane.Server/Migrations/01000100_AddAdditionalIndexesInTenant.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.01.00.01.00")] + public class AddAdditionalIndexesInTenant : MultiDatabaseMigration + { + public AddAdditionalIndexesInTenant(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Create Index on Site + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.AddIndex("IX_Site", new [] {"TenantId", "Name"}, true); + + //Create Index on Role table + var roleEntityBuilder = new RoleEntityBuilder(migrationBuilder, ActiveDatabase); + roleEntityBuilder.AddIndex("IX_Role", new [] {"SiteId", "Name"}, true); + + //Create Index on Profile table + var profileEntityBuilder = new ProfileEntityBuilder(migrationBuilder, ActiveDatabase); + profileEntityBuilder.AddIndex("IX_Profile", new [] {"SiteId", "Name"}, true); + + //Create Index on File table + var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); + fileEntityBuilder.AddIndex("IX_File", new [] {"FolderId", "Name"}, true); + + //Add Columns to Notification table + var notificationEntityBuilder = new NotificationEntityBuilder(migrationBuilder, ActiveDatabase); + notificationEntityBuilder.AddStringColumn("FromDisplayName", 50, true); + notificationEntityBuilder.AddStringColumn("FromEmail", 256, true); + notificationEntityBuilder.AddStringColumn("ToDisplayName", 50, true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + //Drop Index on Site table + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.DropIndex("IX_Site"); + + //Drop Index on Role table + var roleEntityBuilder = new RoleEntityBuilder(migrationBuilder, ActiveDatabase); + roleEntityBuilder.DropIndex("IX_Role"); + + //Drop Index on Profile table + var profileEntityBuilder = new ProfileEntityBuilder(migrationBuilder, ActiveDatabase); + profileEntityBuilder.DropIndex("IX_Profile"); + + //Drop Index on File table + var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); + fileEntityBuilder.DropIndex("IX_File"); + + //Drop Columns from Notification table + var notificationEntityBuilder = new NotificationEntityBuilder(migrationBuilder, ActiveDatabase); + notificationEntityBuilder.DropColumn("FromDisplayName"); + notificationEntityBuilder.DropColumn("FromEmail"); + notificationEntityBuilder.DropColumn("ToDisplayName"); + } + } +} diff --git a/Oqtane.Server/Migrations/01000101_AddAdditionColumnToNotifications.cs b/Oqtane.Server/Migrations/01000101_AddAdditionColumnToNotifications.cs new file mode 100644 index 00000000..f41cb26e --- /dev/null +++ b/Oqtane.Server/Migrations/01000101_AddAdditionColumnToNotifications.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.01.00.01.01")] + public class AddAdditionColumnToNotifications : MultiDatabaseMigration + { + public AddAdditionColumnToNotifications(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Add Column to Notification table + var notificationEntityBuilder = new NotificationEntityBuilder(migrationBuilder, ActiveDatabase); + notificationEntityBuilder.AddDateTimeColumn("SendOn", true); + + //Update new Column + notificationEntityBuilder.UpdateColumn("SendOn", $"{ActiveDatabase.RewriteName("CreatedOn")}", $"{ActiveDatabase.RewriteName("SendOn")} IS NULL"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + //Drop Column from Notification table + var notificationEntityBuilder = new NotificationEntityBuilder(migrationBuilder, ActiveDatabase); + notificationEntityBuilder.DropColumn("SendOn"); + } + } +} diff --git a/Oqtane.Server/Migrations/01000201_DropColumnFromPage.cs b/Oqtane.Server/Migrations/01000201_DropColumnFromPage.cs new file mode 100644 index 00000000..5f5fee65 --- /dev/null +++ b/Oqtane.Server/Migrations/01000201_DropColumnFromPage.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.01.00.02.01")] + public class DropColumnFromPage : MultiDatabaseMigration + { + public DropColumnFromPage(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Drop Column from Page table + if (ActiveDatabase.Name == "SqlServer" || ActiveDatabase.Name == "LocalDB") + { + var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase); + pageEntityBuilder.DropColumn("EditMode"); + } + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + //Add Column to Page table + var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase); + pageEntityBuilder.AddBooleanColumn("EditMode"); + } + } +} diff --git a/Oqtane.Server/Migrations/02000001_AddColumnToProfileAndUpdatePage.cs b/Oqtane.Server/Migrations/02000001_AddColumnToProfileAndUpdatePage.cs new file mode 100644 index 00000000..d7a13fb2 --- /dev/null +++ b/Oqtane.Server/Migrations/02000001_AddColumnToProfileAndUpdatePage.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.02.00.00.01")] + public class AddColumnToProfileAndUpdatePage : MultiDatabaseMigration + { + public AddColumnToProfileAndUpdatePage(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Add Column to Profile table + var profileEntityBuilder = new ProfileEntityBuilder(migrationBuilder, ActiveDatabase); + profileEntityBuilder.AddStringColumn("Options", 2000, true); + + //Update new column + profileEntityBuilder.UpdateColumn("Options", "''"); + + //Alter Column in Page table for Sql Server + if (ActiveDatabase.Name == "SqlServer" || ActiveDatabase.Name == "LocalDB") + { + var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase); + pageEntityBuilder.DropIndex("IX_Page"); + pageEntityBuilder.AlterStringColumn("Path", 256); + pageEntityBuilder.AddIndex("IX_Page", new [] {"SiteId", "Path", "UserId"}, true); + } + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + //Drop Column from Profile table + var profileEntityBuilder = new ProfileEntityBuilder(migrationBuilder, ActiveDatabase); + profileEntityBuilder.DropColumn("Options"); + + //Alter Column in Page table + if (ActiveDatabase.Name == "SqlServer" || ActiveDatabase.Name == "LocalDB") + { + var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase); + pageEntityBuilder.DropIndex("IX_Page"); + pageEntityBuilder.AlterStringColumn("Path", 50); + pageEntityBuilder.AddIndex("IX_Page", new [] {"SiteId", "Path", "UserId"}, true); + } + } + } +} diff --git a/Oqtane.Server/Migrations/02000101_UpdateIconColumnInPage.cs b/Oqtane.Server/Migrations/02000101_UpdateIconColumnInPage.cs new file mode 100644 index 00000000..f0dfd972 --- /dev/null +++ b/Oqtane.Server/Migrations/02000101_UpdateIconColumnInPage.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.02.00.01.01")] + public class UpdateIconColumnInPage : MultiDatabaseMigration + { + public UpdateIconColumnInPage(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + ///Update Icon Field in Page + var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase); + var updateSql = ActiveDatabase.ConcatenateSql("'oi oi-'", $"{ActiveDatabase.RewriteName("Icon")}"); + pageEntityBuilder.UpdateColumn("Icon", updateSql, $"{ActiveDatabase.RewriteName("Icon")} <> ''" ); + } + } +} diff --git a/Oqtane.Server/Migrations/02000102_AddLanguageTable.cs b/Oqtane.Server/Migrations/02000102_AddLanguageTable.cs new file mode 100644 index 00000000..0fa1b490 --- /dev/null +++ b/Oqtane.Server/Migrations/02000102_AddLanguageTable.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.02.00.01.02")] + public class AddLanguageTable : MultiDatabaseMigration + { + public AddLanguageTable(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Create Language table + var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase); + languageEntityBuilder.Create(); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + //Drop Language table + var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase); + languageEntityBuilder.Drop(); + } + } +} diff --git a/Oqtane.Server/Migrations/02000103_UpdatePageAndAddColumnToSite.cs b/Oqtane.Server/Migrations/02000103_UpdatePageAndAddColumnToSite.cs new file mode 100644 index 00000000..740b671a --- /dev/null +++ b/Oqtane.Server/Migrations/02000103_UpdatePageAndAddColumnToSite.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.02.00.01.03")] + public class UpdatePageAndAddColumnToSite : MultiDatabaseMigration + { + public UpdatePageAndAddColumnToSite(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Add Column to Site table + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.AddStringColumn("AdminContainerType", 200, true); + + //Update new column + siteEntityBuilder.UpdateColumn("AdminContainerType", "''"); + + + //Delete records from Page + var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase); + pageEntityBuilder.DeleteFromTable($"{ActiveDatabase.RewriteName("Path")} = 'admin/tenants'"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + //Drop Column from Site table + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.DropColumn("AdminContainerType"); + } + } +} diff --git a/Oqtane.Server/Migrations/02000201_AddSiteGuidToSite.cs b/Oqtane.Server/Migrations/02000201_AddSiteGuidToSite.cs new file mode 100644 index 00000000..45484d92 --- /dev/null +++ b/Oqtane.Server/Migrations/02000201_AddSiteGuidToSite.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.02.00.02.01")] + + public class AddSiteGuidToSite : MultiDatabaseMigration + { + public AddSiteGuidToSite(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Add Column to Site table + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.AddStringColumn("SiteGuid", 36, true, false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + //Drop Column from Site table + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.DropColumn("SiteGuid"); + } + + } +} diff --git a/Oqtane.Server/Migrations/02010000_AddAppVersionsTableInTenant.cs b/Oqtane.Server/Migrations/02010000_AddAppVersionsTableInTenant.cs new file mode 100644 index 00000000..73a15750 --- /dev/null +++ b/Oqtane.Server/Migrations/02010000_AddAppVersionsTableInTenant.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.02.01.00.00")] + + public class AddAppVersionsTable : MultiDatabaseMigration + { + public AddAppVersionsTable(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Create AppVersions table + var appVersionsEntityBuilder = new AppVersionsEntityBuilder(migrationBuilder, ActiveDatabase); + appVersionsEntityBuilder.Create(); + + //Finish SqlServer Migration from DbUp + if (ActiveDatabase.Name == "SqlServer" || ActiveDatabase.Name == "LocalDB") + { + //Version 1.0.0 + InsertVersion(migrationBuilder, "01.00.00", "Oqtane.Scripts.Master.00.09.00.00.sql"); + + //Version 1.0.1 + InsertVersion(migrationBuilder, "01.00.01", "Oqtane.Scripts.Master.01.00.01.00.sql"); + + //Version 1.0.2 + InsertVersion(migrationBuilder, "01.00.02", "Oqtane.Scripts.Tenant.01.00.02.01.sql"); + + //Version 2.0.0 + InsertVersion(migrationBuilder, "02.00.00", "Oqtane.Scripts.Tenant.02.00.00.01.sql"); + + //Version 2.0.1 + InsertVersion(migrationBuilder, "02.00.01", "Oqtane.Scripts.Tenant.02.00.01.03.sql"); + + //Version 2.0.2 + InsertVersion(migrationBuilder, "02.00.02", "Oqtane.Scripts.Tenant.02.00.02.01.sql"); + + //Drop SchemaVersions + migrationBuilder.Sql(@" + IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'dbo.SchemaVersions') AND OBJECTPROPERTY(id, N'IsTable') = 1) + BEGIN + DROP TABLE SchemaVersions + END"); + } + + //Version 2.1.0 + var appVersions = RewriteName("AppVersions"); + var version = RewriteName("Version"); + var appledDate = RewriteName("AppliedDate"); + + migrationBuilder.Sql($@" + INSERT INTO {appVersions}({version}, {appledDate}) + VALUES('02.01.00', '{DateTime.UtcNow:u}') + "); + } + + private void InsertVersion(MigrationBuilder migrationBuilder, string version, string scriptName) + { + migrationBuilder.Sql($@" + IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'dbo.SchemaVersions') AND OBJECTPROPERTY(id, N'IsTable') = 1) + BEGIN + INSERT INTO AppVersions(Version, AppliedDate) + SELECT Version = '{version}', Applied As AppliedDate + FROM SchemaVersions + WHERE ScriptName = '{scriptName}' + END + "); + } + } +} diff --git a/Oqtane.Server/Migrations/02010000_AddIndexesForForeignKeyInMaster.cs b/Oqtane.Server/Migrations/02010000_AddIndexesForForeignKeyInMaster.cs new file mode 100644 index 00000000..5828f962 --- /dev/null +++ b/Oqtane.Server/Migrations/02010000_AddIndexesForForeignKeyInMaster.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(MasterDBContext))] + [Migration("Master.02.01.00.00")] + public class AddIndexesForForeignKeyInMaster : MultiDatabaseMigration + { + public AddIndexesForForeignKeyInMaster(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Update JobLog table + var jobLogEntityBuilder = new JobLogEntityBuilder(migrationBuilder, ActiveDatabase); + jobLogEntityBuilder.AddIndex("IX_JobLog_JobId", "JobId"); + + //Update Alias table + var aliasEntityBuilder = new AliasEntityBuilder(migrationBuilder, ActiveDatabase); + aliasEntityBuilder.AddIndex("IX_Alias_TenantId", "TenantId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + //Update JobLog table + var jobLogEntityBuilder = new JobLogEntityBuilder(migrationBuilder, ActiveDatabase); + jobLogEntityBuilder.DropIndex("IX_JobLog_JobId"); + + //Update Alias table + var aliasEntityBuilder = new AliasEntityBuilder(migrationBuilder, ActiveDatabase); + aliasEntityBuilder.DropIndex("IX_Alias_TenantId"); + } + } +} diff --git a/Oqtane.Server/Migrations/02010001_AddDatabaseTypeColumnToTenant.cs b/Oqtane.Server/Migrations/02010001_AddDatabaseTypeColumnToTenant.cs new file mode 100644 index 00000000..f57b302f --- /dev/null +++ b/Oqtane.Server/Migrations/02010001_AddDatabaseTypeColumnToTenant.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations +{ + [DbContext(typeof(MasterDBContext))] + [Migration("Master.02.01.00.01")] + public class AddDatabaseTypeColumnToTenant : MultiDatabaseMigration + { + public AddDatabaseTypeColumnToTenant(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Add Column to Site table + var tenantEntityBuilder = new TenantEntityBuilder(migrationBuilder, ActiveDatabase); + tenantEntityBuilder.AddStringColumn("DBType", 200, true); + + //Update new column if SqlServer (Other Databases will not have any records yet) + if (ActiveDatabase.Name == "SqlServer" || ActiveDatabase.Name == "LocalDB") + { + tenantEntityBuilder.UpdateColumn("DBType", "'SqlServer'"); + } + } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/AliasEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/AliasEntityBuilder.cs new file mode 100644 index 00000000..2e05d58a --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/AliasEntityBuilder.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class AliasEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "Alias"; + private readonly PrimaryKey _primaryKey = new("PK_Alias", x => x.AliasId); + private readonly ForeignKey _tenantForeignKey = new("FK_Alias_Tenant", x => x.TenantId, "Tenant", "TenantId", ReferentialAction.Cascade); + + public AliasEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_tenantForeignKey); + } + + protected override AliasEntityBuilder BuildTable(ColumnsBuilder table) + { + AliasId = AddAutoIncrementColumn(table,"AliasId"); + Name = AddStringColumn(table, "Name", 200); + TenantId = AddIntegerColumn(table, "TenantId"); + SiteId = AddIntegerColumn(table, "SiteId"); + + AddAuditableColumns(table); + + return this; + } + + public OperationBuilder AliasId { get; private set; } + + public OperationBuilder Name { get; private set; } + + public OperationBuilder SiteId { get; private set; } + + public OperationBuilder TenantId { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/AppVersionsEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/AppVersionsEntityBuilder.cs new file mode 100644 index 00000000..7c5581ff --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/AppVersionsEntityBuilder.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +namespace Oqtane.Migrations.EntityBuilders +{ + public class AppVersionsEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "AppVersions"; + private readonly PrimaryKey _primaryKey = new("PK_AppVersions", x => x.Id); + + public AppVersionsEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + } + + protected override AppVersionsEntityBuilder BuildTable(ColumnsBuilder table) + { + Id = AddAutoIncrementColumn(table,"Id"); + Version = AddStringColumn(table,"Version", 10); + AppliedDate = AddDateTimeColumn(table,"AppliedDate"); + + return this; + } + + public OperationBuilder Id { get; set; } + + public OperationBuilder Version { get; set; } + + public OperationBuilder AppliedDate { get; set; } + + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/AspNetUserClaimsEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/AspNetUserClaimsEntityBuilder.cs new file mode 100644 index 00000000..bf53614c --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/AspNetUserClaimsEntityBuilder.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class AspNetUserClaimsEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "AspNetUserClaims"; + private readonly PrimaryKey _primaryKey = new("PK_AspNetUserClaims", x => x.Id); + private readonly ForeignKey _aspNetUsersForeignKey = new("FK_AspNetUserClaims_AspNetUsers_UserId", x => x.UserId, "AspNetUsers", "Id", ReferentialAction.Cascade); + + public AspNetUserClaimsEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_aspNetUsersForeignKey); + } + + protected override AspNetUserClaimsEntityBuilder BuildTable(ColumnsBuilder table) + { + Id = AddAutoIncrementColumn(table,"Id"); + UserId = AddStringColumn(table,"UserId", 450); + ClaimType = AddMaxStringColumn(table,"ClaimType", true); + ClaimValue = AddMaxStringColumn(table,"ClaimValue", true); + + return this; + } + + public OperationBuilder Id { get; set; } + + public OperationBuilder UserId { get; set; } + + public OperationBuilder ClaimType { get; set; } + + public OperationBuilder ClaimValue { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/AspNetUsersEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/AspNetUsersEntityBuilder.cs new file mode 100644 index 00000000..dec56d2d --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/AspNetUsersEntityBuilder.cs @@ -0,0 +1,73 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class AspNetUsersEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "AspNetUsers"; + private readonly PrimaryKey _primaryKey = new("PK_AspNetUsers", x => x.Id); + + public AspNetUsersEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + } + + protected override AspNetUsersEntityBuilder BuildTable(ColumnsBuilder table) + { + Id = AddStringColumn(table,"Id", 450); + UserName = AddStringColumn(table,"UserName", 256, true); + NormalizedUserName = AddStringColumn(table,"NormalizedUserName", 256, true); + Email = AddStringColumn(table,"Email", 256, true); + NormalizedEmail = AddStringColumn(table,"NormalizedEmail", 256, true); + EmailConfirmed = AddBooleanColumn(table,"EmailConfirmed"); + PasswordHash = AddMaxStringColumn(table,"PasswordHash", true); + SecurityStamp = AddMaxStringColumn(table,"SecurityStamp", true); + ConcurrencyStamp = AddMaxStringColumn(table,"ConcurrencyStamp", true); + PhoneNumber = AddMaxStringColumn(table,"PhoneNumber", true); + PhoneNumberConfirmed = AddBooleanColumn(table,"PhoneNumberConfirmed"); + TwoFactorEnabled = AddBooleanColumn(table,"TwoFactorEnabled"); + LockoutEnd = AddDateTimeOffsetColumn(table,"LockoutEnd", true); + LockoutEnabled = AddBooleanColumn(table,"LockoutEnabled"); + AccessFailedCount = AddIntegerColumn(table,"AccessFailedCount"); + + return this; + } + + public OperationBuilder Id { get; set; } + + public OperationBuilder UserName { get; set; } + + public OperationBuilder NormalizedUserName { get; set; } + + public OperationBuilder Email { get; set; } + + public OperationBuilder NormalizedEmail { get; set; } + + public OperationBuilder EmailConfirmed { get; set; } + + public OperationBuilder PasswordHash { get; set; } + + public OperationBuilder SecurityStamp { get; set; } + + public OperationBuilder ConcurrencyStamp { get; set; } + + public OperationBuilder PhoneNumber { get; set; } + + public OperationBuilder PhoneNumberConfirmed { get; set; } + + public OperationBuilder TwoFactorEnabled { get; set; } + + public OperationBuilder LockoutEnd { get; set; } + + public OperationBuilder LockoutEnabled { get; set; } + + public OperationBuilder AccessFailedCount { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/AuditableBaseEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/AuditableBaseEntityBuilder.cs new file mode 100644 index 00000000..cad1a993 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/AuditableBaseEntityBuilder.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable MemberCanBePrivate.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public abstract class AuditableBaseEntityBuilder : BaseEntityBuilder where TEntityBuilder : BaseEntityBuilder + { + protected AuditableBaseEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base (migrationBuilder, database) + { + } + + protected void AddAuditableColumns(ColumnsBuilder table) + { + CreatedBy = AddStringColumn(table,"CreatedBy", 256); + CreatedOn = AddDateTimeColumn(table,"CreatedOn"); + ModifiedBy = AddStringColumn(table,"ModifiedBy", 256); + ModifiedOn = AddDateTimeColumn(table,"ModifiedOn"); + } + + + public OperationBuilder CreatedBy { get; private set; } + + public OperationBuilder CreatedOn { get; private set; } + + public OperationBuilder ModifiedBy { get; private set; } + + public OperationBuilder ModifiedOn { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs new file mode 100644 index 00000000..77022a9f --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; +// ReSharper disable BuiltInTypeReferenceStyleForMemberAccess + +namespace Oqtane.Migrations.EntityBuilders +{ + public abstract class BaseEntityBuilder where TEntityBuilder : BaseEntityBuilder + { + private readonly MigrationBuilder _migrationBuilder; + + protected BaseEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) + { + _migrationBuilder = migrationBuilder; + ActiveDatabase = database; + ForeignKeys = new List>(); + } + + protected IOqtaneDatabase ActiveDatabase { get; } + + protected abstract TEntityBuilder BuildTable(ColumnsBuilder table); + + protected string EntityTableName { get; init; } + + protected PrimaryKey PrimaryKey { get; init; } + + protected List> ForeignKeys { get; } + + private string RewriteName(string name) + { + return ActiveDatabase.RewriteName(name); + } + + + // Column Operations + + protected OperationBuilder AddAutoIncrementColumn(ColumnsBuilder table, string name) + { + return ActiveDatabase.AddAutoIncrementColumn(table, RewriteName(name)); + } + + public void AddBooleanColumn(string name) + { + _migrationBuilder.AddColumn(RewriteName(name), RewriteName(EntityTableName)); + } + + protected OperationBuilder AddBooleanColumn(ColumnsBuilder table, string name, bool nullable = false) + { + return table.Column(name: RewriteName(name), nullable: nullable); + } + + public void AddDateTimeColumn(string name, bool nullable = false) + { + _migrationBuilder.AddColumn(RewriteName(name), RewriteName(EntityTableName), nullable: nullable); + } + + protected OperationBuilder AddDateTimeColumn(ColumnsBuilder table, string name, bool nullable = false) + { + return table.Column(name: RewriteName(name), nullable: nullable); + } + + public void AddDateTimeOffsetColumn(string name, bool nullable = false) + { + _migrationBuilder.AddColumn(RewriteName(name), RewriteName(EntityTableName), nullable: nullable); + } + + protected OperationBuilder AddDateTimeOffsetColumn(ColumnsBuilder table, string name, bool nullable = false) + { + return table.Column(name: RewriteName(name), nullable: nullable); + } + + public void AddIntegerColumn(string name, bool nullable = false) + { + _migrationBuilder.AddColumn(RewriteName(name), RewriteName(EntityTableName), nullable: nullable); + } + + protected OperationBuilder AddIntegerColumn(ColumnsBuilder table, string name, bool nullable = false) + { + return table.Column(name: RewriteName(name), nullable: nullable); + } + + public void AddMaxStringColumn(string name, int length, bool nullable = false, bool unicode = true) + { + _migrationBuilder.AddColumn(RewriteName(name), RewriteName(EntityTableName), nullable: nullable, unicode: unicode); + } + + protected OperationBuilder AddMaxStringColumn(ColumnsBuilder table, string name, bool nullable = false, bool unicode = true) + { + return table.Column(name: RewriteName(name), nullable: nullable, unicode: unicode); + } + + public void AddStringColumn(string name, int length, bool nullable = false, bool unicode = true) + { + _migrationBuilder.AddColumn(RewriteName(name), RewriteName(EntityTableName), maxLength: length, nullable: nullable, unicode: unicode); + } + + protected OperationBuilder AddStringColumn(ColumnsBuilder table, string name, int length, bool nullable = false, bool unicode = true) + { + return table.Column(name: RewriteName(name), maxLength: length, nullable: nullable, unicode: unicode); + } + + public void AlterStringColumn(string name, int length, bool nullable = false, bool unicode = true) + { + _migrationBuilder.AlterColumn(RewriteName(name), RewriteName(EntityTableName), maxLength: length, nullable: nullable, unicode: unicode); + } + + public void DropColumn(string name) + { + _migrationBuilder.DropColumn(RewriteName(name), RewriteName(EntityTableName)); + } + + + //Index Operations + + /// + /// Creates a Migration to add an Index to the Entity (table) + /// + /// The name of the Index to create + /// The name of the column to add to the index + /// A flag that determines if the Index should be Unique + public virtual void AddIndex(string indexName, string columnName, bool isUnique = false) + { + _migrationBuilder.CreateIndex( + name: RewriteName(indexName), + table: RewriteName(EntityTableName), + column: RewriteName(columnName), + unique: isUnique); + } + + /// + /// Creates a Migration to add an Index to the Entity (table) + /// + /// The name of the Index to create + /// The names of the columns to add to the index + /// A flag that determines if the Index should be Unique + public virtual void AddIndex(string indexName, string[] columnNames, bool isUnique = false) + { + _migrationBuilder.CreateIndex( + name: RewriteName(indexName), + table: RewriteName(EntityTableName), + columns: columnNames.Select(RewriteName).ToArray(), + unique: isUnique); + } + + /// + /// Creates a Migration to drop an Index from the Entity (table) + /// + /// The name of the Index to drop + public virtual void DropIndex(string indexName) + { + _migrationBuilder.DropIndex(RewriteName(indexName), RewriteName(EntityTableName)); + } + + + // Key Operations + + private void AddKeys(CreateTableBuilder table) + { + AddPrimaryKey(table, PrimaryKey); + foreach (var foreignKey in ForeignKeys) + { + AddForeignKey(table, foreignKey); + } + } + + public void AddPrimaryKey(CreateTableBuilder table, PrimaryKey primaryKey) + { + table.PrimaryKey(RewriteName(primaryKey.Name), primaryKey.Columns); + } + + public void AddForeignKey(CreateTableBuilder table, ForeignKey foreignKey) + { + table.ForeignKey( + name: RewriteName(foreignKey.Name), + column: foreignKey.Column, + principalTable: RewriteName(foreignKey.PrincipalTable), + principalColumn: RewriteName(foreignKey.PrincipalColumn), + onDelete: foreignKey.OnDeleteAction); + } + + public void DropForeignKey(ForeignKey foreignKey) + { + DropForeignKey(RewriteName(foreignKey.Name)); + } + + public void DropForeignKey(string keyName) + { + _migrationBuilder.DropForeignKey(RewriteName(keyName), RewriteName(EntityTableName)); + } + + + // Table Operations + + /// + /// Creates a Migration to Create the Entity (table) + /// + public void Create() + { + _migrationBuilder.CreateTable(RewriteName(EntityTableName), BuildTable, null, AddKeys); + } + + /// + /// Creates a Migration to Drop the Entity (table) + /// + public void Drop() + { + _migrationBuilder.DropTable(RewriteName(EntityTableName)); + } + + + //Sql Operations + + public void DeleteFromTable(string condition = "") + { + var deleteSql = $"DELETE FROM {RewriteName(EntityTableName)} "; + + if(!string.IsNullOrEmpty(condition)) + { + deleteSql += $"WHERE {condition}"; + } + _migrationBuilder.Sql(deleteSql); + } + + public void UpdateColumn(string columnName, string value, string condition = "") + { + var updateValue = value; + + var updateSql = $"UPDATE {RewriteName(EntityTableName)} SET {RewriteName(columnName)} = {value} "; + + if(!string.IsNullOrEmpty(condition)) + { + updateSql += $"WHERE {condition}"; + } + _migrationBuilder.Sql(updateSql); + } + } +} + diff --git a/Oqtane.Server/Migrations/EntityBuilders/DeletableAuditableBaseEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/DeletableAuditableBaseEntityBuilder.cs new file mode 100644 index 00000000..afe9627e --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/DeletableAuditableBaseEntityBuilder.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable MemberCanBePrivate.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public abstract class DeletableAuditableBaseEntityBuilder : AuditableBaseEntityBuilder where TEntityBuilder : BaseEntityBuilder + { + protected DeletableAuditableBaseEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + } + + protected void AddDeletableColumns(ColumnsBuilder table) + { + DeletedBy = AddStringColumn(table,"DeletedBy", 256, true); + DeletedOn = AddDateTimeColumn(table,"DeletedOn", true); + IsDeleted = AddBooleanColumn(table,"IsDeleted"); + } + + public OperationBuilder DeletedBy { get; private set; } + + public OperationBuilder DeletedOn { get; private set; } + + public OperationBuilder IsDeleted { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/DeletableBaseEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/DeletableBaseEntityBuilder.cs new file mode 100644 index 00000000..3ff98429 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/DeletableBaseEntityBuilder.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable MemberCanBePrivate.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public abstract class DeletableBaseEntityBuilder : BaseEntityBuilder where TEntityBuilder : BaseEntityBuilder + { + protected DeletableBaseEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + } + + protected void AddDeletableColumns(ColumnsBuilder table) + { + DeletedBy = AddStringColumn(table,"DeletedBy", 256, true); + DeletedOn = AddDateTimeColumn(table,"DeletedOn", true); + IsDeleted = AddBooleanColumn(table,"IsDeleted"); + } + + public OperationBuilder DeletedBy { get; private set; } + + public OperationBuilder DeletedOn { get; private set; } + + public OperationBuilder IsDeleted { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/FileEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/FileEntityBuilder.cs new file mode 100644 index 00000000..f875fb0b --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/FileEntityBuilder.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class FileEntityBuilder : DeletableAuditableBaseEntityBuilder + { + private const string _entityTableName = "File"; + private readonly PrimaryKey _primaryKey = new("PK_File", x => x.FileId); + private readonly ForeignKey _folderForeignKey = new("FK_File_Folder", x => x.FolderId, "Folder", "FolderId", ReferentialAction.Cascade); + + public FileEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_folderForeignKey); + } + + protected override FileEntityBuilder BuildTable(ColumnsBuilder table) + { + FileId = AddAutoIncrementColumn(table,"FileId"); + FolderId = AddIntegerColumn(table,"FolderId"); + Name = AddStringColumn(table,"Name", 50); + Extension = AddStringColumn(table,"Extension", 50); + Size = AddIntegerColumn(table,"Size"); + ImageHeight = AddIntegerColumn(table,"ImageHeight"); + ImageWidth = AddIntegerColumn(table,"ImageWidth"); + + AddAuditableColumns(table); + AddDeletableColumns(table); + + return this; + } + + public OperationBuilder FileId { get; set; } + + public OperationBuilder FolderId { get; set; } + + public OperationBuilder Name { get; set; } + + public OperationBuilder Extension { get; set; } + + public OperationBuilder Size { get; set; } + + public OperationBuilder ImageHeight { get; set; } + + public OperationBuilder ImageWidth { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/FolderEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/FolderEntityBuilder.cs new file mode 100644 index 00000000..e8da5831 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/FolderEntityBuilder.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class FolderEntityBuilder : DeletableAuditableBaseEntityBuilder + { + private const string _entityTableName = "Folder"; + private readonly PrimaryKey _primaryKey = new("PK_Folder", x => x.FolderId); + private readonly ForeignKey _siteForeignKey = new("FK_Folder_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade); + + public FolderEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_siteForeignKey); + } + + protected override FolderEntityBuilder BuildTable(ColumnsBuilder table) + { + FolderId = AddAutoIncrementColumn(table,"FolderId"); + SiteId = AddIntegerColumn(table,"SiteId"); + ParentId = AddIntegerColumn(table,"ParentId", true); + Name = AddStringColumn(table,"Name", 50); + Path = AddStringColumn(table,"Path", 50); + Order = AddIntegerColumn(table,"Order"); + IsSystem = AddBooleanColumn(table,"IsSystem"); + + AddAuditableColumns(table); + AddDeletableColumns(table); + + return this; + } + + public OperationBuilder FolderId { get; set; } + + public OperationBuilder SiteId { get; set; } + + public OperationBuilder ParentId { get; set; } + + public OperationBuilder Name { get; set; } + + public OperationBuilder Path { get; set; } + + public OperationBuilder Order { get; set; } + + public OperationBuilder IsSystem { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/JobEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/JobEntityBuilder.cs new file mode 100644 index 00000000..34cffd74 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/JobEntityBuilder.cs @@ -0,0 +1,66 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class JobEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "Job"; + private readonly PrimaryKey _primaryKey = new("PK_Job", x => x.JobId); + + public JobEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + } + + protected override JobEntityBuilder BuildTable(ColumnsBuilder table) + { + JobId = AddAutoIncrementColumn(table,"JobId"); + Name = AddStringColumn(table,"Name", 200); + JobType = AddStringColumn(table,"JobType", 200); + Frequency = AddStringColumn(table,"Frequency", 1); + Interval = AddIntegerColumn(table,"Interval"); + StartDate = AddDateTimeColumn(table,"StartDate", true); + EndDate = AddDateTimeColumn(table,"EndDate", true); + IsEnabled = AddBooleanColumn(table,"IsEnabled"); + IsStarted = AddBooleanColumn(table,"IsStarted"); + IsExecuting = AddBooleanColumn(table,"IsExecuting"); + NextExecution = AddDateTimeColumn(table,"NextExecution", true); + RetentionHistory = AddIntegerColumn(table,"RetentionHistory"); + + AddAuditableColumns(table); + + return this; + } + + public OperationBuilder JobId { get; set; } + + public OperationBuilder Name { get; set; } + + public OperationBuilder JobType { get; set; } + + public OperationBuilder Frequency { get; set; } + + public OperationBuilder Interval { get; set; } + + public OperationBuilder StartDate { get; set; } + + public OperationBuilder EndDate { get; set; } + + public OperationBuilder IsEnabled { get; set; } + + public OperationBuilder IsStarted { get; set; } + + public OperationBuilder IsExecuting { get; set; } + + public OperationBuilder NextExecution { get; set; } + + public OperationBuilder RetentionHistory { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/JobLogEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/JobLogEntityBuilder.cs new file mode 100644 index 00000000..f73f8aa0 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/JobLogEntityBuilder.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class JobLogEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "JobLog"; + private readonly PrimaryKey _primaryKey = new("PK_JobLog", x => x.JobLogId); + private readonly ForeignKey _jobLogForeignKey = new("FK_JobLog_Job", x => x.JobId, "Job", "JobId", ReferentialAction.Cascade); + + public JobLogEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_jobLogForeignKey); + } + + protected override JobLogEntityBuilder BuildTable(ColumnsBuilder table) + { + JobLogId = AddAutoIncrementColumn(table,"JobLogId"); + JobId = AddIntegerColumn(table,"JobId"); + StartDate = AddDateTimeColumn(table,"StartDate"); + FinishDate = AddDateTimeColumn(table,"FinishDate", true); + Succeeded = AddBooleanColumn(table,"Succeeded", true); + Notes = AddMaxStringColumn(table,"Notes", true); + + return this; + } + + public OperationBuilder JobLogId { get; private set; } + + public OperationBuilder JobId { get; private set; } + + public OperationBuilder StartDate { get; private set; } + + public OperationBuilder FinishDate { get; private set; } + + public OperationBuilder Succeeded { get; private set; } + + public OperationBuilder Notes { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/LanguageEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/LanguageEntityBuilder.cs new file mode 100644 index 00000000..44342abd --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/LanguageEntityBuilder.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class LanguageEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "Language"; + private readonly PrimaryKey _primaryKey = new("PK_Language", x => x.LanguageId); + private readonly ForeignKey _siteForeignKey = new("FK_Language_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade); + + public LanguageEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_siteForeignKey); + } + + protected override LanguageEntityBuilder BuildTable(ColumnsBuilder table) + { + LanguageId = AddAutoIncrementColumn(table,"LanguageId"); + SiteId = AddIntegerColumn(table,"SiteId"); + Name = AddStringColumn(table,"Name", 100); + Code = AddStringColumn(table,"Code", 10); + IsDefault = AddBooleanColumn(table,"IsDefault"); + + AddAuditableColumns(table); + + return this; + } + + public OperationBuilder LanguageId { get; set; } + + public OperationBuilder SiteId { get; set; } + + public OperationBuilder Name { get; set; } + + public OperationBuilder Code { get; set; } + + public OperationBuilder IsDefault { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/LogEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/LogEntityBuilder.cs new file mode 100644 index 00000000..f06feace --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/LogEntityBuilder.cs @@ -0,0 +1,78 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class LogEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "Log"; + private readonly PrimaryKey _primaryKey = new("PK_Log", x => x.LogId); + private readonly ForeignKey _siteForeignKey = new("FK_Log_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade); + + public LogEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_siteForeignKey); + } + + protected override LogEntityBuilder BuildTable(ColumnsBuilder table) + { + LogId = AddAutoIncrementColumn(table,"LogId"); + SiteId = AddIntegerColumn(table,"SiteId", true); + LogDate = AddDateTimeColumn(table,"LogDate"); + PageId = AddIntegerColumn(table,"PageId", true); + ModuleId = AddIntegerColumn(table,"ModuleId", true); + UserId = AddIntegerColumn(table,"UserId", true); + Url = AddStringColumn(table,"Url", 2048); + Server = AddStringColumn(table,"Server", 200); + Category = AddStringColumn(table,"Category", 200); + Feature = AddStringColumn(table,"Feature", 200); + Function = AddStringColumn(table,"Function", 20); + Level = AddStringColumn(table,"Level", 20); + Message = AddMaxStringColumn(table,"Message"); + MessageTemplate = AddMaxStringColumn(table,"MessageTemplate"); + Exception = AddMaxStringColumn(table,"Exception", true); + Properties = AddMaxStringColumn(table,"Properties", true); + + return this; + } + + public OperationBuilder LogId { get; set; } + + public OperationBuilder SiteId { get; set; } + + public OperationBuilder LogDate { get; set; } + + public OperationBuilder PageId { get; set; } + + public OperationBuilder ModuleId { get; set; } + + public OperationBuilder UserId { get; set; } + + public OperationBuilder Url { get; set; } + + public OperationBuilder Server { get; set; } + + public OperationBuilder Category { get; set; } + + public OperationBuilder Feature { get; set; } + + public OperationBuilder Function { get; set; } + + public OperationBuilder Level { get; set; } + + public OperationBuilder Message { get; set; } + + public OperationBuilder MessageTemplate { get; set; } + + public OperationBuilder Exception { get; set; } + + public OperationBuilder Properties { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/ModuleDefinitionsEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/ModuleDefinitionsEntityBuilder.cs new file mode 100644 index 00000000..7dad6645 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/ModuleDefinitionsEntityBuilder.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class ModuleDefinitionsEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "ModuleDefinition"; + private readonly PrimaryKey _primaryKey = new("PK_ModuleDefinition", x => x.ModuleDefinitionId); + + public ModuleDefinitionsEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + } + + protected override ModuleDefinitionsEntityBuilder BuildTable(ColumnsBuilder table) + { + ModuleDefinitionId = AddAutoIncrementColumn(table,"ModuleDefinitionId"); + ModuleDefinitionName = AddStringColumn(table,"ModuleDefinitionName", 200); + Name = AddStringColumn(table,"Name", 200, true); + Description = AddStringColumn(table,"Description", 2000, true); + Categories = AddStringColumn(table,"Categories", 200, true); + Version = AddStringColumn(table,"Version", 50, true); + + AddAuditableColumns(table); + + return this; + } + + public OperationBuilder ModuleDefinitionId { get; private set; } + + public OperationBuilder ModuleDefinitionName { get; private set; } + + public OperationBuilder Name { get; private set; } + + public OperationBuilder Description { get; private set; } + + public OperationBuilder Categories { get; private set; } + + public OperationBuilder Version { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/ModuleEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/ModuleEntityBuilder.cs new file mode 100644 index 00000000..98ac7577 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/ModuleEntityBuilder.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class ModuleEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "Module"; + private readonly PrimaryKey _primaryKey = new("PK_Module", x => x.ModuleId); + private readonly ForeignKey _siteForeignKey = new("FK_Module_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade); + + public ModuleEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_siteForeignKey); + } + + protected override ModuleEntityBuilder BuildTable(ColumnsBuilder table) + { + ModuleId = AddAutoIncrementColumn(table,"ModuleId"); + SiteId = AddIntegerColumn(table,"SiteId"); + ModuleDefinitionName = AddStringColumn(table,"ModuleDefinitionName", 200); + AllPages = AddBooleanColumn(table,"AllPages"); + + AddAuditableColumns(table); + + return this; + } + + public OperationBuilder ModuleId { get; private set; } + + public OperationBuilder SiteId { get; private set; } + + public OperationBuilder ModuleDefinitionName { get; private set; } + + public OperationBuilder AllPages { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/NotificationEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/NotificationEntityBuilder.cs new file mode 100644 index 00000000..1a38a07c --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/NotificationEntityBuilder.cs @@ -0,0 +1,65 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class NotificationEntityBuilder : DeletableBaseEntityBuilder + { + private const string _entityTableName = "Notification"; + private readonly PrimaryKey _primaryKey = new("PK_Notification", x => x.NotificationId); + private readonly ForeignKey _siteForeignKey = new("FK_Notification_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade); + + public NotificationEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_siteForeignKey); + } + + protected override NotificationEntityBuilder BuildTable(ColumnsBuilder table) + { + NotificationId = AddAutoIncrementColumn(table,"NotificationId"); + SiteId = AddIntegerColumn(table,"SiteId"); + FromUserId = AddIntegerColumn(table,"FromUserId", true); + ToUserId = AddIntegerColumn(table,"ToUserId", true); + ToEmail = AddStringColumn(table,"ToEmail", 256); + ParentId = AddIntegerColumn(table,"ParentId", true); + Subject = AddStringColumn(table,"Subject", 256); + Body = AddMaxStringColumn(table,"Body"); + CreatedOn = AddDateTimeColumn(table,"CreatedOn"); + IsDelivered = AddBooleanColumn(table,"IsDelivered"); + DeliveredOn = AddDateTimeColumn(table,"DeliveredOn", true); + + AddDeletableColumns(table); + + return this; + } + + public OperationBuilder NotificationId { get; set; } + + public OperationBuilder SiteId { get; set; } + + public OperationBuilder FromUserId { get; set; } + + public OperationBuilder ToUserId { get; set; } + + public OperationBuilder ToEmail { get; set; } + + public OperationBuilder ParentId { get; set; } + + public OperationBuilder Subject { get; set; } + + public OperationBuilder Body { get; set; } + + public OperationBuilder CreatedOn { get; set; } + + public OperationBuilder IsDelivered { get; set; } + + public OperationBuilder DeliveredOn { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/PageEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/PageEntityBuilder.cs new file mode 100644 index 00000000..b1cee2e9 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/PageEntityBuilder.cs @@ -0,0 +1,84 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class PageEntityBuilder : DeletableAuditableBaseEntityBuilder + { + private const string _entityTableName = "Page"; + private readonly PrimaryKey _primaryKey = new("PK_Page", x => x.PageId); + private readonly ForeignKey _siteForeignKey = new("FK_Page_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade); + + public PageEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_siteForeignKey); + } + + protected override PageEntityBuilder BuildTable(ColumnsBuilder table) + { + PageId = AddAutoIncrementColumn(table,"PageId"); + SiteId = AddIntegerColumn(table,"SiteId"); + if (ActiveDatabase.Name == "SqlServer" || ActiveDatabase.Name == "LocalDB") + { + Path = AddStringColumn(table,"Path", 50); + } + else + { + Path = AddStringColumn(table,"Path", 256); + } + Name = AddStringColumn(table,"Name", 50); + Title = AddStringColumn(table,"Title", 200, true); + ThemeType = AddStringColumn(table,"ThemeType", 200, true); + Icon = AddStringColumn(table,"Icon", 50); + ParentId = AddIntegerColumn(table,"ParentId", true); + Order = AddIntegerColumn(table,"Order"); + IsNavigation = AddBooleanColumn(table,"IsNavigation"); + Url = AddStringColumn(table,"Url", 500, true); + LayoutType = AddStringColumn(table,"LayoutType", 200); + UserId = AddIntegerColumn(table,"UserId", true); + IsPersonalizable = AddBooleanColumn(table,"IsPersonalizable"); + DefaultContainerType = AddStringColumn(table,"DefaultContainerType", 200, true); + + AddAuditableColumns(table); + AddDeletableColumns(table); + + return this; + } + + public OperationBuilder PageId { get; private set; } + + public OperationBuilder SiteId { get; private set; } + + public OperationBuilder Path { get; private set; } + public OperationBuilder Name { get; private set; } + + public OperationBuilder Title { get; private set; } + + public OperationBuilder ThemeType { get; private set; } + + public OperationBuilder Icon { get; private set; } + + public OperationBuilder ParentId { get; private set; } + + public OperationBuilder Order { get; private set; } + + public OperationBuilder IsNavigation { get; private set; } + + public OperationBuilder Url { get; private set; } + + public OperationBuilder LayoutType { get; private set; } + + public OperationBuilder UserId { get; private set; } + + public OperationBuilder IsPersonalizable { get; private set; } + + public OperationBuilder DefaultContainerType { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/PageModuleEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/PageModuleEntityBuilder.cs new file mode 100644 index 00000000..16ea23fb --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/PageModuleEntityBuilder.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class PageModuleEntityBuilder : DeletableAuditableBaseEntityBuilder + { + private const string _entityTableName = "PageModule"; + private readonly PrimaryKey _primaryKey = new("PK_PageModule", x => x.PageModuleId); + private readonly ForeignKey _moduleForeignKey = new("FK_PageModule_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.NoAction); + private readonly ForeignKey _pageForeignKey = new("FK_PageModule_Page", x => x.PageId, "Page", "PageId", ReferentialAction.Cascade); + + public PageModuleEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_moduleForeignKey); + ForeignKeys.Add(_pageForeignKey); + } + + protected override PageModuleEntityBuilder BuildTable(ColumnsBuilder table) + { + PageModuleId = AddAutoIncrementColumn(table,"PageModuleId"); + PageId = AddIntegerColumn(table,"PageId"); + ModuleId = AddIntegerColumn(table,"ModuleId"); + Title = AddStringColumn(table,"Title", 200); + Pane = AddStringColumn(table,"Pane", 50); + Order = AddIntegerColumn(table,"Order"); + ContainerType = AddStringColumn(table,"ContainerType", 200); + + AddAuditableColumns(table); + AddDeletableColumns(table); + + return this; + } + + public OperationBuilder PageModuleId { get; private set; } + + public OperationBuilder PageId { get; private set; } + + public OperationBuilder ModuleId { get; private set; } + + public OperationBuilder Title { get; private set; } + + public OperationBuilder Pane { get; private set; } + + public OperationBuilder Order { get; private set; } + + public OperationBuilder ContainerType { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/PermissionEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/PermissionEntityBuilder.cs new file mode 100644 index 00000000..f5f21473 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/PermissionEntityBuilder.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class PermissionEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "Permission"; + private readonly PrimaryKey _primaryKey = new("PK_Permission", x => x.PermissionId); + private readonly ForeignKey _siteForeignKey = new("FK_Permission_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade); + private readonly ForeignKey _userForeignKey = new("FK_Permission_User", x => x.UserId, "User", "UserId", ReferentialAction.NoAction); + private readonly ForeignKey _roleForeignKey = new("FK_Permission_Role", x => x.RoleId, "Role", "RoleId", ReferentialAction.NoAction); + + public PermissionEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_siteForeignKey); + ForeignKeys.Add(_userForeignKey); + ForeignKeys.Add(_roleForeignKey); + } + + protected override PermissionEntityBuilder BuildTable(ColumnsBuilder table) + { + PermissionId = AddAutoIncrementColumn(table,"PermissionId"); + SiteId = AddIntegerColumn(table,"SiteId"); + EntityName = AddStringColumn(table,"EntityName", 50); + EntityId = AddIntegerColumn(table,"EntityId"); + PermissionName = AddStringColumn(table,"PermissionName", 50); + RoleId = AddIntegerColumn(table,"RoleId", true); + UserId = AddIntegerColumn(table,"UserId", true); + IsAuthorized = AddBooleanColumn(table,"IsAuthorized"); + + AddAuditableColumns(table); + + return this; + } + + public OperationBuilder PermissionId { get; set; } + + public OperationBuilder SiteId { get; set; } + + public OperationBuilder EntityName { get; set; } + + public OperationBuilder EntityId { get; set; } + + public OperationBuilder PermissionName { get; set; } + + public OperationBuilder RoleId { get; set; } + + public OperationBuilder UserId { get; set; } + + public OperationBuilder IsAuthorized { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/ProfileEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/ProfileEntityBuilder.cs new file mode 100644 index 00000000..593242a7 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/ProfileEntityBuilder.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class ProfileEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "Profile"; + private readonly PrimaryKey _primaryKey = new("PK_Profile", x => x.ProfileId); + private readonly ForeignKey _siteForeignKey = new("FK_Profile_Sites", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade); + + public ProfileEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_siteForeignKey); + } + + protected override ProfileEntityBuilder BuildTable(ColumnsBuilder table) + { + ProfileId = AddAutoIncrementColumn(table,"ProfileId"); + SiteId = AddIntegerColumn(table,"SiteId", true); + Name = AddStringColumn(table,"Name", 50); + Title = AddStringColumn(table,"Title", 50); + Description = AddStringColumn(table,"Description", 256, true); + Category = AddStringColumn(table,"Category", 50); + ViewOrder = AddIntegerColumn(table,"ViewOrder"); + MaxLength = AddIntegerColumn(table,"MaxLength"); + DefaultValue = AddStringColumn(table,"DefaultValue", 2000, true); + IsRequired = AddBooleanColumn(table,"IsRequired"); + IsPrivate = AddBooleanColumn(table,"IsPrivate"); + + AddAuditableColumns(table); + + return this; } + + public OperationBuilder ProfileId { get; set; } + + public OperationBuilder SiteId { get; set; } + + public OperationBuilder Name { get; set; } + + public OperationBuilder Title { get; set; } + + public OperationBuilder Description { get; set; } + + public OperationBuilder Category { get; set; } + + public OperationBuilder ViewOrder { get; set; } + + public OperationBuilder MaxLength { get; set; } + + public OperationBuilder DefaultValue { get; set; } + + public OperationBuilder IsRequired { get; set; } + + public OperationBuilder IsPrivate { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/RoleEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/RoleEntityBuilder.cs new file mode 100644 index 00000000..52feffa8 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/RoleEntityBuilder.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class RoleEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "Role"; + private readonly PrimaryKey _primaryKey = new("PK_Role", x => x.RoleId); + private readonly ForeignKey _siteForeignKey = new("FK_Role_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade); + + public RoleEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_siteForeignKey); + } + + protected override RoleEntityBuilder BuildTable(ColumnsBuilder table) + { + RoleId = AddAutoIncrementColumn(table,"RoleId"); + SiteId = AddIntegerColumn(table,"SiteId", true); + Name = AddStringColumn(table,"Name", 256); + Description = AddStringColumn(table,"Description", 256); + IsAutoAssigned = AddBooleanColumn(table,"IsAutoAssigned"); + IsSystem = AddBooleanColumn(table,"IsSystem"); + + AddAuditableColumns(table); + + return this; + } + + public OperationBuilder RoleId { get; set; } + + public OperationBuilder SiteId { get; set; } + + public OperationBuilder Name { get; set; } + + public OperationBuilder Description { get; set; } + + public OperationBuilder IsAutoAssigned { get; set; } + + public OperationBuilder IsSystem { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/SettingEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SettingEntityBuilder.cs new file mode 100644 index 00000000..4956203f --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/SettingEntityBuilder.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class SettingEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "Setting"; + private readonly PrimaryKey _primaryKey = new("PK_Setting", x => x.SettingId); + + public SettingEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + } + + protected override SettingEntityBuilder BuildTable(ColumnsBuilder table) + { + SettingId = AddAutoIncrementColumn(table,"SettingId"); + EntityName = AddStringColumn(table,"EntityName", 50); + EntityId = AddIntegerColumn(table,"EntityId"); + SettingName = AddStringColumn(table,"SettingName", 50); + SettingValue = AddMaxStringColumn(table,"SettingValue"); + + AddAuditableColumns(table); + + return this; + } + + public OperationBuilder SettingId { get; set; } + + public OperationBuilder EntityName { get; set; } + + public OperationBuilder EntityId { get; set; } + + public OperationBuilder SettingName { get; set; } + + public OperationBuilder SettingValue { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/SiteEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SiteEntityBuilder.cs new file mode 100644 index 00000000..cce461e7 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/SiteEntityBuilder.cs @@ -0,0 +1,67 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class SiteEntityBuilder : DeletableAuditableBaseEntityBuilder + { + private const string _entityTableName = "Site"; + private readonly PrimaryKey _primaryKey = new("PK_Site", x => x.SiteId); + + public SiteEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + } + + protected override SiteEntityBuilder BuildTable(ColumnsBuilder table) + { + SiteId = AddAutoIncrementColumn(table,"SiteId"); + TenantId = AddIntegerColumn(table,"TenantId"); + Name = AddStringColumn(table,"Name", 200); + LogoFileId = AddIntegerColumn(table,"LogoFileId", true); + FaviconFileId = AddIntegerColumn(table,"FaviconFileId", true); + DefaultThemeType = AddStringColumn(table,"DefaultThemeType", 200); + DefaultLayoutType = AddStringColumn(table,"DefaultLayoutType", 200); + DefaultContainerType = AddStringColumn(table,"DefaultContainerType", 200); + PwaIsEnabled = AddBooleanColumn(table,"PwaIsEnabled"); + PwaAppIconFileId = AddIntegerColumn(table,"PwaAppIconFileId", true); + PwaSplashIconFileId = AddIntegerColumn(table,"PwaSplashIconFileId", true); + AllowRegistration = AddBooleanColumn(table,"AllowRegistration"); + + AddAuditableColumns(table); + AddDeletableColumns(table); + + return this; + } + + public OperationBuilder SiteId { get; private set; } + + public OperationBuilder TenantId { get; private set; } + + public OperationBuilder Name { get; private set; } + + public OperationBuilder LogoFileId { get; private set; } + + public OperationBuilder FaviconFileId { get; private set; } + + public OperationBuilder DefaultThemeType { get; private set; } + + public OperationBuilder DefaultLayoutType { get; private set; } + + public OperationBuilder DefaultContainerType { get; private set; } + + public OperationBuilder PwaIsEnabled { get; private set; } + + public OperationBuilder PwaAppIconFileId { get; private set; } + + public OperationBuilder PwaSplashIconFileId { get; private set; } + + public OperationBuilder AllowRegistration { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/TenantEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/TenantEntityBuilder.cs new file mode 100644 index 00000000..98201072 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/TenantEntityBuilder.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class TenantEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "Tenant"; + private readonly PrimaryKey _primaryKey = new("PK_Tenant", x => x.TenantId); + + public TenantEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database): base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + } + + protected override TenantEntityBuilder BuildTable(ColumnsBuilder table) + { + TenantId = AddAutoIncrementColumn(table,"TenantId"); + Name = AddStringColumn(table,"Name", 100); + DBConnectionString = AddStringColumn(table,"DBConnectionString", 1024); + Version = AddStringColumn(table,"Version", 50, true); + + AddAuditableColumns(table); + + return this; + } + + public OperationBuilder TenantId { get; private set; } + + public OperationBuilder Name { get;private set; } + + public OperationBuilder DBConnectionString { get; private set;} + + public OperationBuilder Version { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/UserEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/UserEntityBuilder.cs new file mode 100644 index 00000000..d070f027 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/UserEntityBuilder.cs @@ -0,0 +1,52 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class UserEntityBuilder : DeletableAuditableBaseEntityBuilder + { + private const string _entityTableName = "User"; + private readonly PrimaryKey _primaryKey = new("PK_User", x => x.UserId); + + public UserEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + } + + protected override UserEntityBuilder BuildTable(ColumnsBuilder table) + { + UserId = AddAutoIncrementColumn(table,"UserId"); + Username = AddStringColumn(table,"Username", 256); + DisplayName = AddStringColumn(table,"DisplayName", 50); + Email = AddStringColumn(table,"Email", 256); + PhotoFileId = AddIntegerColumn(table,"PhotoFileId", true); + LastLoginOn = AddDateTimeColumn(table,"LastLoginOn", true); + LastIPAddress = AddStringColumn(table,"LastIpAddress", 50); + + AddAuditableColumns(table); + AddDeletableColumns(table); + + return this; + } + + public OperationBuilder UserId { get; private set; } + + public OperationBuilder Username { get; private set; } + + public OperationBuilder DisplayName { get; private set; } + + public OperationBuilder Email { get; private set; } + + public OperationBuilder PhotoFileId { get; private set; } + + public OperationBuilder LastLoginOn { get; private set; } + + public OperationBuilder LastIPAddress { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/UserRoleEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/UserRoleEntityBuilder.cs new file mode 100644 index 00000000..9b112151 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/UserRoleEntityBuilder.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class UserRoleEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "UserRole"; + private readonly PrimaryKey _primaryKey = new("PK_UserRole", x => x.UserRoleId); + private readonly ForeignKey _userForeignKey = new("FK_UserRole_User", x => x.UserId, "User", "UserId", ReferentialAction.Cascade); + private readonly ForeignKey _roleForeignKey = new("FK_UserRole_Role", x => x.RoleId, "Role", "RoleId", ReferentialAction.NoAction); + + public UserRoleEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_userForeignKey); + ForeignKeys.Add(_roleForeignKey); + } + + protected override UserRoleEntityBuilder BuildTable(ColumnsBuilder table) + { + UserRoleId = AddAutoIncrementColumn(table,"UserRoleId"); + UserId = AddIntegerColumn(table,"UserId"); + RoleId = AddIntegerColumn(table,"RoleId"); + EffectiveDate = AddDateTimeColumn(table,"EffectiveDate", true); + ExpiryDate = AddDateTimeColumn(table,"ExpiryDate", true); + + AddAuditableColumns(table); + + return this; + } + + public OperationBuilder UserRoleId { get; set; } + + public OperationBuilder UserId { get; set; } + + public OperationBuilder RoleId { get; set; } + + public OperationBuilder EffectiveDate { get; set; } + + public OperationBuilder ExpiryDate { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/Framework/ForeignKey.cs b/Oqtane.Server/Migrations/Framework/ForeignKey.cs new file mode 100644 index 00000000..09f219e6 --- /dev/null +++ b/Oqtane.Server/Migrations/Framework/ForeignKey.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Migrations.EntityBuilders; + +namespace Oqtane.Migrations +{ + public readonly struct ForeignKey where TEntityBuilder : BaseEntityBuilder + { + public ForeignKey(string name, Expression> column, string principalTable, string principalColumn, ReferentialAction onDeleteAction) + { + Name = name; + Column = column; + PrincipalTable = principalTable; + PrincipalColumn = principalColumn; + OnDeleteAction = onDeleteAction; + } + + public string Name { get; } + + public Expression> Column { get;} + + public ReferentialAction OnDeleteAction { get; } + + public string PrincipalTable { get; } + + public string PrincipalColumn { get; } + + + } +} diff --git a/Oqtane.Server/Migrations/Framework/MultiDatabaseMigration.cs b/Oqtane.Server/Migrations/Framework/MultiDatabaseMigration.cs new file mode 100644 index 00000000..ca9a0217 --- /dev/null +++ b/Oqtane.Server/Migrations/Framework/MultiDatabaseMigration.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; + +namespace Oqtane.Migrations +{ + public abstract class MultiDatabaseMigration : Migration + { + private readonly IEnumerable _databases; + + protected MultiDatabaseMigration(IEnumerable databases) + { + _databases = databases; + } + + protected IOqtaneDatabase ActiveDatabase => _databases.FirstOrDefault(d => d.Provider == ActiveProvider); + + protected string RewriteName(string name) + { + return ActiveDatabase.RewriteName(name); + } + } +} diff --git a/Oqtane.Server/Migrations/Framework/MultiDatabaseMigrationsAssembly.cs b/Oqtane.Server/Migrations/Framework/MultiDatabaseMigrationsAssembly.cs new file mode 100644 index 00000000..6373f351 --- /dev/null +++ b/Oqtane.Server/Migrations/Framework/MultiDatabaseMigrationsAssembly.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Internal; +using Oqtane.Interfaces; +using Oqtane.Repository.Databases.Interfaces; + +namespace Oqtane.Migrations.Framework +{ + public class MultiDatabaseMigrationsAssembly: MigrationsAssembly + { + private readonly IEnumerable _databases; + + public MultiDatabaseMigrationsAssembly( + ICurrentDbContext currentContext, + IDbContextOptions options, + IMigrationsIdGenerator idGenerator, + IDiagnosticsLogger logger) + : base(currentContext, options, idGenerator, logger) + { + var multiDatabaseContext = currentContext.Context as IMultiDatabase; + if (multiDatabaseContext != null) _databases = multiDatabaseContext.Databases; + } + public override Migration CreateMigration(TypeInfo migrationClass, string activeProvider) + { + var hasCtorWithCacheOptions = migrationClass.GetConstructor(new[] { typeof(IEnumerable) }) != null; + + if (hasCtorWithCacheOptions) + { + var migration = (Migration)Activator.CreateInstance(migrationClass.AsType(), _databases); + if (migration != null) + { + migration.ActiveProvider = activeProvider; + return migration; + } + } + + return base.CreateMigration(migrationClass, activeProvider); + } + } +} diff --git a/Oqtane.Server/Migrations/Framework/PrimaryKey.cs b/Oqtane.Server/Migrations/Framework/PrimaryKey.cs new file mode 100644 index 00000000..e6280c5f --- /dev/null +++ b/Oqtane.Server/Migrations/Framework/PrimaryKey.cs @@ -0,0 +1,21 @@ +using System; +using System.Linq.Expressions; +using Oqtane.Migrations.EntityBuilders; + +namespace Oqtane.Migrations +{ + public readonly struct PrimaryKey where TEntityBuilder : BaseEntityBuilder + { + public PrimaryKey(string name, Expression> columns) + { + Name = name; + Columns = columns; + } + + public string Name { get; } + + public Expression> Columns { get;} + + + } +} diff --git a/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs b/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs index 74bf8564..eeabff6d 100644 --- a/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs +++ b/Oqtane.Server/Modules/HtmlText/Controllers/HtmlTextController.cs @@ -25,12 +25,12 @@ namespace Oqtane.Modules.HtmlText.Controllers // GET api//5 [HttpGet("{id}")] [Authorize(Policy = PolicyNames.ViewModule)] - public List Get(int id) + public List Get(int id) { - var list = new List(); + var list = new List(); try { - HtmlTextInfo htmlText = null; + Models.HtmlText htmlText = null; if (_entityId == id) { htmlText = _htmlText.GetHtmlText(id); @@ -48,7 +48,7 @@ namespace Oqtane.Modules.HtmlText.Controllers // POST api/ [HttpPost] [Authorize(Policy = PolicyNames.EditModule)] - public HtmlTextInfo Post([FromBody] HtmlTextInfo htmlText) + public Models.HtmlText Post([FromBody] Models.HtmlText htmlText) { try { @@ -69,7 +69,7 @@ namespace Oqtane.Modules.HtmlText.Controllers // PUT api//5 [HttpPut("{id}")] [Authorize(Policy = PolicyNames.EditModule)] - public HtmlTextInfo Put(int id, [FromBody] HtmlTextInfo htmlText) + public Models.HtmlText Put(int id, [FromBody] Models.HtmlText htmlText) { try { diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs index 7e068076..40f7bde4 100644 --- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs +++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs @@ -1,40 +1,38 @@ -using Oqtane.Infrastructure; +using System; +using System.Collections.Generic; +using Oqtane.Infrastructure; using Oqtane.Models; using Oqtane.Repository; using Oqtane.Modules.HtmlText.Models; using Oqtane.Modules.HtmlText.Repository; using System.Net; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Enums; +using Oqtane.Interfaces; + +// ReSharper disable ConvertToUsingDeclaration namespace Oqtane.Modules.HtmlText.Manager { - public class HtmlTextManager : IInstallable, IPortable + public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable { - private IHtmlTextRepository _htmlTexts; - private ISqlRepository _sql; + private readonly IHtmlTextRepository _htmlText; + private readonly IEnumerable _databases; - public HtmlTextManager(IHtmlTextRepository htmltexts, ISqlRepository sql) + public HtmlTextManager(IHtmlTextRepository htmlText, IEnumerable databases) { - _htmlTexts = htmltexts; - _sql = sql; - } - - public bool Install(Tenant tenant, string version) - { - return _sql.ExecuteScript(tenant, GetType().Assembly, "HtmlText." + version + ".sql"); - } - - public bool Uninstall(Tenant tenant) - { - return _sql.ExecuteScript(tenant, GetType().Assembly, "HtmlText.Uninstall.sql"); + _htmlText = htmlText; + _databases = databases; } public string ExportModule(Module module) { string content = ""; - HtmlTextInfo htmltext = _htmlTexts.GetHtmlText(module.ModuleId); - if (htmltext != null) + var htmlText = _htmlText.GetHtmlText(module.ModuleId); + if (htmlText != null) { - content = WebUtility.HtmlEncode(htmltext.Content); + content = WebUtility.HtmlEncode(htmlText.Content); } return content; } @@ -42,19 +40,31 @@ namespace Oqtane.Modules.HtmlText.Manager public void ImportModule(Module module, string content, string version) { content = WebUtility.HtmlDecode(content); - HtmlTextInfo htmltext = _htmlTexts.GetHtmlText(module.ModuleId); - if (htmltext != null) + var htmlText = _htmlText.GetHtmlText(module.ModuleId); + if (htmlText != null) { - htmltext.Content = content; - _htmlTexts.UpdateHtmlText(htmltext); + htmlText.Content = content; + _htmlText.UpdateHtmlText(htmlText); } else { - htmltext = new HtmlTextInfo(); - htmltext.ModuleId = module.ModuleId; - htmltext.Content = content; - _htmlTexts.AddHtmlText(htmltext); + htmlText = new Models.HtmlText(); + htmlText.ModuleId = module.ModuleId; + htmlText.Content = content; + _htmlText.AddHtmlText(htmlText); } } + + public bool Install(Tenant tenant, string version) + { + var dbConfig = new DbConfig(null, null, _databases) {ConnectionString = tenant.DBConnectionString, DatabaseType = tenant.DBType}; + return Migrate(new HtmlTextContext(dbConfig, null), tenant, MigrationType.Up); + } + + public bool Uninstall(Tenant tenant) + { + var dbConfig = new DbConfig(null, null, _databases) {ConnectionString = tenant.DBConnectionString, DatabaseType = tenant.DBType}; + return Migrate(new HtmlTextContext(dbConfig, null), tenant, MigrationType.Down); + } } } diff --git a/Oqtane.Server/Modules/HtmlText/Migrations/01000000_InitializeModule.cs b/Oqtane.Server/Modules/HtmlText/Migrations/01000000_InitializeModule.cs new file mode 100644 index 00000000..5ed5242c --- /dev/null +++ b/Oqtane.Server/Modules/HtmlText/Migrations/01000000_InitializeModule.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Interfaces; +using Oqtane.Migrations; +using Oqtane.Modules.HtmlText.Migrations.EntityBuilders; +using Oqtane.Modules.HtmlText.Repository; + +namespace Oqtane.Modules.HtmlText.Migrations +{ + [DbContext(typeof(HtmlTextContext))] + [Migration("HtmlText.01.00.00.00")] + public class InitializeModule : MultiDatabaseMigration + { + public InitializeModule(IEnumerable databases) : base(databases) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Create HtmlText table + var entityBuilder = new HtmlTextEntityBuilder(migrationBuilder, ActiveDatabase); + entityBuilder.Create(); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + //Drop HtmlText table + var entityBuilder = new HtmlTextEntityBuilder(migrationBuilder, ActiveDatabase); + entityBuilder.Drop(); + } + } +} diff --git a/Oqtane.Server/Modules/HtmlText/Migrations/EntityBuilders/HtmlTextEntityBuilder.cs b/Oqtane.Server/Modules/HtmlText/Migrations/EntityBuilders/HtmlTextEntityBuilder.cs new file mode 100644 index 00000000..0f912485 --- /dev/null +++ b/Oqtane.Server/Modules/HtmlText/Migrations/EntityBuilders/HtmlTextEntityBuilder.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; +using Oqtane.Migrations; +using Oqtane.Migrations.EntityBuilders; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Modules.HtmlText.Migrations.EntityBuilders +{ + public class HtmlTextEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "HtmlText"; + private readonly PrimaryKey _primaryKey = new("PK_HtmlText", x => x.HtmlTextId); + private readonly ForeignKey _moduleForeignKey = new("FK_HtmlText_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.Cascade); + + public HtmlTextEntityBuilder(MigrationBuilder migrationBuilder, IOqtaneDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_moduleForeignKey); + } + + protected override HtmlTextEntityBuilder BuildTable(ColumnsBuilder table) + { + HtmlTextId = AddAutoIncrementColumn(table,"HtmlTextId"); + ModuleId = AddIntegerColumn(table,"ModuleId"); + Content = AddMaxStringColumn(table,"Content"); + + AddAuditableColumns(table); + + return this; + } + + public OperationBuilder HtmlTextId { get; set; } + + public OperationBuilder ModuleId { get; set; } + + public OperationBuilder Content { get; set; } + } +} diff --git a/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextContext.cs b/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextContext.cs index 70cd2065..cd62722f 100644 --- a/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextContext.cs +++ b/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextContext.cs @@ -1,17 +1,21 @@ -using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; using Oqtane.Modules.HtmlText.Models; using Oqtane.Repository; -using Microsoft.AspNetCore.Http; +using Oqtane.Interfaces; +using Oqtane.Repository.Databases.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global namespace Oqtane.Modules.HtmlText.Repository { - public class HtmlTextContext : DBContextBase, IService + public class HtmlTextContext : DBContextBase, IService, IMultiDatabase { - public virtual DbSet HtmlText { get; set; } - - public HtmlTextContext(ITenantResolver tenantResolver, IHttpContextAccessor accessor) : base(tenantResolver, accessor) + public HtmlTextContext(IDbConfig dbConfig, ITenantResolver tenantResolver) : base(dbConfig, tenantResolver) { - // ContextBase handles multi-tenant database connections } + + public virtual DbSet HtmlText { get; set; } } } diff --git a/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextRepository.cs b/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextRepository.cs index c97b0de7..633a5b25 100644 --- a/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextRepository.cs +++ b/Oqtane.Server/Modules/HtmlText/Repository/HtmlTextRepository.cs @@ -13,20 +13,20 @@ namespace Oqtane.Modules.HtmlText.Repository _db = context; } - public HtmlTextInfo GetHtmlText(int moduleId) + public Models.HtmlText GetHtmlText(int moduleId) { return _db.HtmlText.FirstOrDefault(item => item.ModuleId == moduleId); } - public HtmlTextInfo AddHtmlText(HtmlTextInfo htmlText) + public Models.HtmlText AddHtmlText(Models.HtmlText htmlText) { _db.HtmlText.Add(htmlText); _db.SaveChanges(); return htmlText; } - public HtmlTextInfo UpdateHtmlText(HtmlTextInfo htmlText) + public Models.HtmlText UpdateHtmlText(Models.HtmlText htmlText) { _db.Entry(htmlText).State = EntityState.Modified; _db.SaveChanges(); @@ -35,7 +35,7 @@ namespace Oqtane.Modules.HtmlText.Repository public void DeleteHtmlText(int moduleId) { - HtmlTextInfo htmlText = _db.HtmlText.FirstOrDefault(item => item.ModuleId == moduleId); + Models.HtmlText htmlText = _db.HtmlText.FirstOrDefault(item => item.ModuleId == moduleId); if (htmlText != null) _db.HtmlText.Remove(htmlText); _db.SaveChanges(); } diff --git a/Oqtane.Server/Modules/HtmlText/Repository/IHtmlTextRepository.cs b/Oqtane.Server/Modules/HtmlText/Repository/IHtmlTextRepository.cs index cbe5d0e4..75daf276 100644 --- a/Oqtane.Server/Modules/HtmlText/Repository/IHtmlTextRepository.cs +++ b/Oqtane.Server/Modules/HtmlText/Repository/IHtmlTextRepository.cs @@ -4,9 +4,9 @@ namespace Oqtane.Modules.HtmlText.Repository { public interface IHtmlTextRepository { - HtmlTextInfo GetHtmlText(int moduleId); - HtmlTextInfo AddHtmlText(HtmlTextInfo htmlText); - HtmlTextInfo UpdateHtmlText(HtmlTextInfo htmlText); + Models.HtmlText GetHtmlText(int moduleId); + Models.HtmlText AddHtmlText(Models.HtmlText htmlText); + Models.HtmlText UpdateHtmlText(Models.HtmlText htmlText); void DeleteHtmlText(int moduleId); } } diff --git a/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.1.0.0.sql b/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.1.0.0.sql index 5c54eae2..fa3b0fd2 100644 --- a/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.1.0.0.sql +++ b/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.1.0.0.sql @@ -1,4 +1,4 @@ -CREATE TABLE [dbo].[HtmlText]( +CREATE TABLE [dbo].[HtmlText]( [HtmlTextId] [int] IDENTITY(1,1) NOT NULL, [ModuleId] [int] NOT NULL, [Content] [nvarchar](max) NOT NULL, diff --git a/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.Uninstall.sql b/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.Uninstall.sql index b0831b67..34011c03 100644 --- a/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.Uninstall.sql +++ b/Oqtane.Server/Modules/HtmlText/Scripts/HtmlText.Uninstall.sql @@ -1,2 +1,2 @@ -DROP TABLE [dbo].[HtmlText] +DROP TABLE [dbo].[HtmlText] GO diff --git a/Oqtane.Server/Modules/MigratableModuleBase.cs b/Oqtane.Server/Modules/MigratableModuleBase.cs new file mode 100644 index 00000000..c282d324 --- /dev/null +++ b/Oqtane.Server/Modules/MigratableModuleBase.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Enums; +using Oqtane.Models; +using Oqtane.Modules.HtmlText.Repository; +using Oqtane.Repository; + +namespace Oqtane.Modules +{ + public class MigratableModuleBase + { + public bool Migrate(DBContextBase dbContext, Tenant tenant, MigrationType migrationType) + { + var result = true; + + using (dbContext) + { + try + { + var migrator = dbContext.GetService(); + if (migrationType == MigrationType.Down) + { + migrator.Migrate(Migration.InitialDatabase); + } + else + { + migrator.Migrate(); + } + } + catch (Exception e) + { + Console.WriteLine(e); + result = false; + } + + } + return result; + + } + } +} diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 480083e6..5f937608 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -44,20 +44,29 @@ + + - - - - - - - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - + + + + diff --git a/Oqtane.Server/Repository/Context/DBContextBase.cs b/Oqtane.Server/Repository/Context/DBContextBase.cs index 9d488f45..d3066502 100644 --- a/Oqtane.Server/Repository/Context/DBContextBase.cs +++ b/Oqtane.Server/Repository/Context/DBContextBase.cs @@ -1,37 +1,102 @@ using System; +using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.Extensions.Configuration; using Oqtane.Extensions; -using Oqtane.Models; +using Oqtane.Interfaces; +using Oqtane.Migrations.Framework; +using Oqtane.Repository.Databases.Interfaces; +using Oqtane.Shared; + +// ReSharper disable BuiltInTypeReferenceStyleForMemberAccess namespace Oqtane.Repository { public class DBContextBase : IdentityUserContext { - private ITenantResolver _tenantResolver; - private IHttpContextAccessor _accessor; + private readonly ITenantResolver _tenantResolver; + private readonly IHttpContextAccessor _accessor; + private readonly IConfiguration _configuration; + private string _connectionString; + private string _databaseType; - public DBContextBase(ITenantResolver tenantResolver, IHttpContextAccessor accessor) + public DBContextBase(ITenantResolver tenantResolver, IHttpContextAccessor httpContextAccessor) { + _connectionString = String.Empty; _tenantResolver = tenantResolver; - _accessor = accessor; + _accessor = httpContextAccessor; } + public DBContextBase(IDbConfig dbConfig, ITenantResolver tenantResolver) + { + _accessor = dbConfig.Accessor; + _configuration = dbConfig.Configuration; + _connectionString = dbConfig.ConnectionString; + _databaseType = dbConfig.DatabaseType; + Databases = dbConfig.Databases; + _tenantResolver = tenantResolver; + } + + public IEnumerable Databases { get; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - var tenant = _tenantResolver.GetTenant(); - if (tenant != null) + optionsBuilder.ReplaceService(); + + if (string.IsNullOrEmpty(_connectionString) && _tenantResolver != null) { - var connectionString = tenant.DBConnectionString - .Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString()); - optionsBuilder.UseOqtaneDatabase(connectionString); + var tenant = _tenantResolver.GetTenant(); + + if (tenant != null) + { + _connectionString = tenant.DBConnectionString + .Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString()); + _databaseType = tenant.DBType; + } + else + { + if (!String.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection"))) + { + _connectionString = _configuration.GetConnectionString("DefaultConnection") + .Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString()); + } + _databaseType = _configuration.GetSection(SettingKeys.DatabaseSection)[SettingKeys.DatabaseTypeKey]; + } } + + if (!string.IsNullOrEmpty(_connectionString) && !string.IsNullOrEmpty(_databaseType)) + { + if (Databases != null) + { + optionsBuilder.UseOqtaneDatabase(Databases.Single(d => d.Name == _databaseType), _connectionString); + } + else + { + optionsBuilder.UseOqtaneDatabase(_databaseType, _connectionString); + } + } + base.OnConfiguring(optionsBuilder); } + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + if (Databases != null) + { + var database = Databases.Single(d => d.Name == _databaseType); + + database.UpdateIdentityStoreTableNames(builder); + } + + } + public override int SaveChanges() { DbContextUtils.SaveChanges(this, _accessor); diff --git a/Oqtane.Server/Repository/Context/DbConfig.cs b/Oqtane.Server/Repository/Context/DbConfig.cs new file mode 100644 index 00000000..97e60f8f --- /dev/null +++ b/Oqtane.Server/Repository/Context/DbConfig.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Oqtane.Interfaces; + +namespace Oqtane.Repository +{ + public class DbConfig : IDbConfig + { + public DbConfig(IHttpContextAccessor accessor, IConfiguration configuration, IEnumerable databases) + { + Accessor = accessor; + Configuration = configuration; + Databases = databases; + } + + public IHttpContextAccessor Accessor { get; } + + public IConfiguration Configuration { get; } + + public IEnumerable Databases { get; set; } + + public string ConnectionString { get; set; } + + public string DatabaseType { get; set; } + } +} diff --git a/Oqtane.Server/Repository/Context/DbContextUtils.cs b/Oqtane.Server/Repository/Context/DbContextUtils.cs index 1b4014cb..aa204cad 100644 --- a/Oqtane.Server/Repository/Context/DbContextUtils.cs +++ b/Oqtane.Server/Repository/Context/DbContextUtils.cs @@ -6,7 +6,7 @@ using Oqtane.Models; namespace Oqtane.Repository { - public class DbContextUtils + public static class DbContextUtils { public static void SaveChanges(DbContext context, IHttpContextAccessor accessor) { diff --git a/Oqtane.Server/Repository/Context/InstallationContext.cs b/Oqtane.Server/Repository/Context/InstallationContext.cs index 6eaf5a65..d64bd5f0 100644 --- a/Oqtane.Server/Repository/Context/InstallationContext.cs +++ b/Oqtane.Server/Repository/Context/InstallationContext.cs @@ -1,26 +1,36 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; using Oqtane.Extensions; +using Oqtane.Interfaces; using Oqtane.Models; +// ReSharper disable CheckNamespace +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + namespace Oqtane.Repository { public class InstallationContext : DbContext { private readonly string _connectionString; + private readonly IOqtaneDatabase _database; - public InstallationContext(string connectionString) + public InstallationContext(IOqtaneDatabase database, string connectionString) { _connectionString = connectionString; + _database = database; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseOqtaneDatabase(_connectionString); + => optionsBuilder.UseOqtaneDatabase(_database, _connectionString); public virtual DbSet Alias { get; set; } public virtual DbSet Tenant { get; set; } public virtual DbSet ModuleDefinition { get; set; } public virtual DbSet Job { get; set; } + public virtual DbSet JobLog { get; set; } + + } } diff --git a/Oqtane.Server/Repository/Context/MasterDBContext.cs b/Oqtane.Server/Repository/Context/MasterDBContext.cs index dd94dedd..a5040c33 100644 --- a/Oqtane.Server/Repository/Context/MasterDBContext.cs +++ b/Oqtane.Server/Repository/Context/MasterDBContext.cs @@ -1,33 +1,65 @@ using System; +using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Migrations; using Oqtane.Models; using Microsoft.Extensions.Configuration; using Oqtane.Extensions; +using Oqtane.Interfaces; +using Oqtane.Migrations.Framework; +using Oqtane.Repository.Databases.Interfaces; +using Oqtane.Shared; + +// ReSharper disable BuiltInTypeReferenceStyleForMemberAccess +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable CheckNamespace namespace Oqtane.Repository { - public class MasterDBContext : DbContext + public class MasterDBContext : DbContext, IMultiDatabase { - private readonly IHttpContextAccessor _accessor; - private readonly IConfiguration _configuration; + private readonly IDbConfig _dbConfig; - public MasterDBContext(DbContextOptions options, IHttpContextAccessor accessor, IConfiguration configuration) : base(options) + public MasterDBContext(DbContextOptions options, IDbConfig dbConfig) : base(options) { - _accessor = accessor; - _configuration = configuration; + _dbConfig = dbConfig; + Databases = dbConfig.Databases; } + public IEnumerable Databases { get; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - if (!String.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection"))) - { - var connectionString = _configuration.GetConnectionString("DefaultConnection") - .Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString()); + optionsBuilder.ReplaceService(); - optionsBuilder.UseOqtaneDatabase(connectionString); + var connectionString = _dbConfig.ConnectionString; + var configuration = _dbConfig.Configuration; + var databaseType = _dbConfig.DatabaseType; + + if(string.IsNullOrEmpty(connectionString) && configuration != null) + { + if (!String.IsNullOrEmpty(configuration.GetConnectionString("DefaultConnection"))) + { + connectionString = configuration.GetConnectionString("DefaultConnection") + .Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString()); + } + + databaseType = configuration.GetSection(SettingKeys.DatabaseSection)[SettingKeys.DatabaseTypeKey]; } + + if (!string.IsNullOrEmpty(connectionString) && !string.IsNullOrEmpty(databaseType)) + { + if (Databases != null) + { + optionsBuilder.UseOqtaneDatabase(Databases.Single(d => d.Name == databaseType), connectionString); + } + else + { + optionsBuilder.UseOqtaneDatabase(databaseType, connectionString); + } + } + base.OnConfiguring(optionsBuilder); } @@ -39,7 +71,7 @@ namespace Oqtane.Repository public override int SaveChanges() { - DbContextUtils.SaveChanges(this, _accessor); + DbContextUtils.SaveChanges(this, _dbConfig.Accessor); return base.SaveChanges(); } diff --git a/Oqtane.Server/Repository/Context/TenantDBContext.cs b/Oqtane.Server/Repository/Context/TenantDBContext.cs index d6fd06c4..8aa6ccc2 100644 --- a/Oqtane.Server/Repository/Context/TenantDBContext.cs +++ b/Oqtane.Server/Repository/Context/TenantDBContext.cs @@ -1,11 +1,19 @@ -using Microsoft.AspNetCore.Http; +using System.Collections.Generic; using Microsoft.EntityFrameworkCore; +using Oqtane.Interfaces; using Oqtane.Models; +using Oqtane.Repository.Databases.Interfaces; + +// ReSharper disable CheckNamespace +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global namespace Oqtane.Repository { - public class TenantDBContext : DBContextBase + public class TenantDBContext : DBContextBase, IMultiDatabase { + public TenantDBContext(IDbConfig dbConfig, ITenantResolver tenantResolver) : base(dbConfig, tenantResolver) { } + public virtual DbSet Site { get; set; } public virtual DbSet Page { get; set; } public virtual DbSet PageModule { get; set; } @@ -20,13 +28,6 @@ namespace Oqtane.Repository public virtual DbSet Notification { get; set; } public virtual DbSet Folder { get; set; } public virtual DbSet File { get; set; } - public virtual DbSet Language { get; set; } - - public TenantDBContext(ITenantResolver tenantResolver, IHttpContextAccessor accessor) : base(tenantResolver, accessor) - { - // DBContextBase handles multi-tenant database connections - } - } } diff --git a/Oqtane.Server/Repository/Interfaces/IDbConfig.cs b/Oqtane.Server/Repository/Interfaces/IDbConfig.cs new file mode 100644 index 00000000..2d364505 --- /dev/null +++ b/Oqtane.Server/Repository/Interfaces/IDbConfig.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Oqtane.Interfaces; + +namespace Oqtane.Repository +{ + public interface IDbConfig + { + public IHttpContextAccessor Accessor { get; } + + public IConfiguration Configuration { get; } + + public IEnumerable Databases { get; set; } + + public string ConnectionString { get; set; } + + public string DatabaseType { get; set; } + } +} diff --git a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs index a1402e3b..4d53fce6 100644 --- a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs @@ -1,5 +1,5 @@ -using System.Data.SqlClient; -using System.Reflection; +using System.Reflection; +using Microsoft.Data.SqlClient; using Oqtane.Models; namespace Oqtane.Repository @@ -7,8 +7,13 @@ namespace Oqtane.Repository public interface ISqlRepository { void ExecuteScript(Tenant tenant, string script); + + bool ExecuteScript(string connectionString, Assembly assembly, string filename); + bool ExecuteScript(Tenant tenant, Assembly assembly, string filename); + int ExecuteNonQuery(Tenant tenant, string query); + SqlDataReader ExecuteReader(Tenant tenant, string query); } } diff --git a/Oqtane.Server/Repository/SqlRepository.cs b/Oqtane.Server/Repository/SqlRepository.cs index fcc4caf3..6a35876a 100644 --- a/Oqtane.Server/Repository/SqlRepository.cs +++ b/Oqtane.Server/Repository/SqlRepository.cs @@ -1,10 +1,13 @@ using System; using System.Data; -using System.Data.SqlClient; using System.IO; using System.Linq; using System.Reflection; +using Microsoft.Data.SqlClient; using Oqtane.Models; +// ReSharper disable ConvertToUsingDeclaration +// ReSharper disable InvertIf +// ReSharper disable BuiltInTypeReferenceStyleForMemberAccess namespace Oqtane.Repository { @@ -13,35 +16,41 @@ namespace Oqtane.Repository public void ExecuteScript(Tenant tenant, string script) { - // execute script in curent tenant - foreach (string query in script.Split("GO", StringSplitOptions.RemoveEmptyEntries)) + // execute script in current tenant + foreach (var query in script.Split("GO", StringSplitOptions.RemoveEmptyEntries)) { ExecuteNonQuery(tenant, query); } } - public bool ExecuteScript(Tenant tenant, Assembly assembly, string filename) + public bool ExecuteScript(string connectionString, Assembly assembly, string fileName) { - // script must be included as an Embedded Resource within an assembly - bool success = true; - string script = ""; + var success = true; + var script = GetScriptFromAssembly(assembly, fileName); - if (assembly != null) + if (!string.IsNullOrEmpty(script)) { - string name = assembly.GetManifestResourceNames().FirstOrDefault(item => item.EndsWith("." + filename)); - if (name != null) + try { - Stream resourceStream = assembly.GetManifestResourceStream(name); - if (resourceStream != null) + foreach (var query in script.Split("GO", StringSplitOptions.RemoveEmptyEntries)) { - using (var reader = new StreamReader(resourceStream)) - { - script = reader.ReadToEnd(); - } + ExecuteNonQuery(connectionString, query); } } + catch + { + success = false; + } } + return success; + } + + public bool ExecuteScript(Tenant tenant, Assembly assembly, string fileName) + { + var success = true; + var script = GetScriptFromAssembly(assembly, fileName); + if (!string.IsNullOrEmpty(script)) { try @@ -58,13 +67,27 @@ namespace Oqtane.Repository } public int ExecuteNonQuery(Tenant tenant, string query) + { + return ExecuteNonQuery(tenant.DBConnectionString, query); + } + + public SqlDataReader ExecuteReader(Tenant tenant, string query) { SqlConnection conn = new SqlConnection(FormatConnectionString(tenant.DBConnectionString)); SqlCommand cmd = conn.CreateCommand(); + PrepareCommand(conn, cmd, query); + var dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); + return dr; + } + + private int ExecuteNonQuery(string connectionString, string query) + { + var conn = new SqlConnection(FormatConnectionString(connectionString)); + var cmd = conn.CreateCommand(); using (conn) { PrepareCommand(conn, cmd, query); - int val = -1; + var val = -1; try { val = cmd.ExecuteNonQuery(); @@ -77,13 +100,28 @@ namespace Oqtane.Repository } } - public SqlDataReader ExecuteReader(Tenant tenant, string query) + private string GetScriptFromAssembly(Assembly assembly, string fileName) { - SqlConnection conn = new SqlConnection(FormatConnectionString(tenant.DBConnectionString)); - SqlCommand cmd = conn.CreateCommand(); - PrepareCommand(conn, cmd, query); - var dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); - return dr; + // script must be included as an Embedded Resource within an assembly + var script = ""; + + if (assembly != null) + { + var name = assembly.GetManifestResourceNames().FirstOrDefault(item => item.EndsWith("." + fileName)); + if (name != null) + { + var resourceStream = assembly.GetManifestResourceStream(name); + if (resourceStream != null) + { + using (var reader = new StreamReader(resourceStream)) + { + script = reader.ReadToEnd(); + } + } + } + } + + return script; } private void PrepareCommand(SqlConnection conn, SqlCommand cmd, string query) diff --git a/Oqtane.Server/Scripts/MigrateMaster.sql b/Oqtane.Server/Scripts/MigrateMaster.sql new file mode 100644 index 00000000..3da59696 --- /dev/null +++ b/Oqtane.Server/Scripts/MigrateMaster.sql @@ -0,0 +1,17 @@ +IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'dbo.SchemaVersions') AND OBJECTPROPERTY(id, N'IsTable') = 1) + BEGIN + IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'dbo.__EFMigrationsHistory') AND OBJECTPROPERTY(id, N'IsTable') = 1) + BEGIN + CREATE TABLE __EFMigrationsHistory + ( + MigrationId nvarchar(150) NOT NULL CONSTRAINT PK___EFMigrationsHistory PRIMARY KEY, + ProductVersion nvarchar(32) NOT NULL + ) + END + INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion) + VALUES ('Master.01.00.00.00', '5.0.0') + INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion) + SELECT REPLACE(REPLACE(ScriptName, 'Oqtane.Scripts.', ''), '.sql', '') As MigrationId, ProductVersion = '5.0.0' + FROM SchemaVersions + WHERE ScriptName LIKE 'Oqtane.Scripts.Master.01%' + END \ No newline at end of file diff --git a/Oqtane.Server/Scripts/MigrateTenant.sql b/Oqtane.Server/Scripts/MigrateTenant.sql new file mode 100644 index 00000000..ca4ae38e --- /dev/null +++ b/Oqtane.Server/Scripts/MigrateTenant.sql @@ -0,0 +1,20 @@ +IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'dbo.SchemaVersions') AND OBJECTPROPERTY(id, N'IsTable') = 1) + BEGIN + IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'dbo.__EFMigrationsHistory') AND OBJECTPROPERTY(id, N'IsTable') = 1) + BEGIN + CREATE TABLE __EFMigrationsHistory + ( + MigrationId nvarchar(150) NOT NULL CONSTRAINT PK___EFMigrationsHistory PRIMARY KEY, + ProductVersion nvarchar(32) NOT NULL + ) + END + INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion) + VALUES ('Tenant.01.00.00.00', '5.0.0') + INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion) + SELECT REPLACE(REPLACE(ScriptName, 'Oqtane.Scripts.', ''), '.sql', '') As MigrationId, ProductVersion = '5.0.0' + FROM SchemaVersions + WHERE ScriptName LIKE 'Oqtane.Scripts.Tenant.01%' + OR ScriptName LIKE 'Oqtane.Scripts.Tenant.02%' + INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion) + VALUES ('HtmlText.01.00.00.00', '5.0.0') + END \ No newline at end of file diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index d76e1ef0..881d3e91 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -129,9 +129,6 @@ namespace Oqtane services.AddSingleton(); - services.AddDbContext(options => { }); - services.AddDbContext(options => { }); - services.AddIdentityCore(options => { }) .AddEntityFrameworkStores() .AddSignInManager() @@ -210,8 +207,12 @@ namespace Oqtane services.AddTransient(); services.AddTransient(); - // load the external assemblies into the app domain, install services + // load the external assemblies into the app domain, install services services.AddOqtane(_runtime, _supportedCultures); + services.AddScoped(); + services.AddDbContext(options => { }); + services.AddDbContext(options => { }); + services.AddMvc() .AddNewtonsoftJson() diff --git a/Oqtane.Server/appsettings.json b/Oqtane.Server/appsettings.json index 1af87b4c..68bd1906 100644 --- a/Oqtane.Server/appsettings.json +++ b/Oqtane.Server/appsettings.json @@ -1,4 +1,7 @@ { + "Database": { + "DatabaseType": "" + }, "ConnectionStrings": { "DefaultConnection": "" }, diff --git a/Oqtane.Server/wwwroot/Modules/Oqtane.Blogs/Module.css b/Oqtane.Server/wwwroot/Modules/Oqtane.Blogs/Module.css new file mode 100644 index 00000000..0856a263 --- /dev/null +++ b/Oqtane.Server/wwwroot/Modules/Oqtane.Blogs/Module.css @@ -0,0 +1 @@ +/* Module Custom Styles */ \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Modules/Oqtane.Blogs/Module.js b/Oqtane.Server/wwwroot/Modules/Oqtane.Blogs/Module.js new file mode 100644 index 00000000..1b415a08 --- /dev/null +++ b/Oqtane.Server/wwwroot/Modules/Oqtane.Blogs/Module.js @@ -0,0 +1 @@ +/* Module Script */ \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Modules/Oqtane.Blogs/assets.json b/Oqtane.Server/wwwroot/Modules/Oqtane.Blogs/assets.json new file mode 100644 index 00000000..72f421fc --- /dev/null +++ b/Oqtane.Server/wwwroot/Modules/Oqtane.Blogs/assets.json @@ -0,0 +1 @@ +["\\bin\\Debug\\net5.0\\Oqtane.Blogs.Client.Oqtane.dll","\\bin\\Debug\\net5.0\\Oqtane.Blogs.Client.Oqtane.pdb","\\bin\\Debug\\net5.0\\Oqtane.Blogs.Server.Oqtane.dll","\\bin\\Debug\\net5.0\\Oqtane.Blogs.Server.Oqtane.pdb","\\bin\\Debug\\net5.0\\Oqtane.Blogs.Shared.Oqtane.dll","\\bin\\Debug\\net5.0\\Oqtane.Blogs.Shared.Oqtane.pdb","\\wwwroot\\Modules\\Oqtane.Blogs\\Module.css","\\wwwroot\\Modules\\Oqtane.Blogs\\Module.js"] \ No newline at end of file diff --git a/Oqtane.Shared/Enums/MigrationType.cs b/Oqtane.Shared/Enums/MigrationType.cs new file mode 100644 index 00000000..e428520a --- /dev/null +++ b/Oqtane.Shared/Enums/MigrationType.cs @@ -0,0 +1,8 @@ +namespace Oqtane.Enums +{ + public enum MigrationType + { + Up, + Down + } +} diff --git a/Oqtane.Shared/Interfaces/IOqtaneDatabase.cs b/Oqtane.Shared/Interfaces/IOqtaneDatabase.cs new file mode 100644 index 00000000..225ccffd --- /dev/null +++ b/Oqtane.Shared/Interfaces/IOqtaneDatabase.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Models; + +namespace Oqtane.Interfaces +{ + public interface IOqtaneDatabase + { + public string FriendlyName { get; } + + public string Name { get; } + + public string Provider { get; } + + public List ConnectionStringFields { get; } + + public OperationBuilder AddAutoIncrementColumn(ColumnsBuilder table, string name); + + public string BuildConnectionString(); + + public string ConcatenateSql(params string[] values); + + public string RewriteName(string name); + + public void UpdateIdentityStoreTableNames(ModelBuilder builder); + + public DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString); + } +} diff --git a/Oqtane.Shared/Models/ConnectionStringField.cs b/Oqtane.Shared/Models/ConnectionStringField.cs new file mode 100644 index 00000000..f3f216a8 --- /dev/null +++ b/Oqtane.Shared/Models/ConnectionStringField.cs @@ -0,0 +1,13 @@ +namespace Oqtane.Models +{ + public class ConnectionStringField + { + public string FriendlyName { get; set; } + + public string HelpText { get; set; } + + public string Name { get; set; } + + public string Value { get; set; } + } +} diff --git a/Oqtane.Shared/Models/Database.cs b/Oqtane.Shared/Models/Database.cs new file mode 100644 index 00000000..e1855242 --- /dev/null +++ b/Oqtane.Shared/Models/Database.cs @@ -0,0 +1,9 @@ +namespace Oqtane.Models +{ + public class Database + { + public string Name { get; set; } + + public string Type { get; set; } + } +} diff --git a/Oqtane.Shared/Models/Tenant.cs b/Oqtane.Shared/Models/Tenant.cs index 645f1e97..d0891563 100644 --- a/Oqtane.Shared/Models/Tenant.cs +++ b/Oqtane.Shared/Models/Tenant.cs @@ -7,6 +7,7 @@ namespace Oqtane.Models public int TenantId { get; set; } public string Name { get; set; } public string DBConnectionString { get; set; } + public string DBType { get; set; } public string Version { get; set; } public string CreatedBy { get; set; } public DateTime CreatedOn { get; set; } diff --git a/Oqtane.Shared/Modules/HtmlText/Models/HtmlTextInfo.cs b/Oqtane.Shared/Modules/HtmlText/Models/HtmlText.cs similarity index 88% rename from Oqtane.Shared/Modules/HtmlText/Models/HtmlTextInfo.cs rename to Oqtane.Shared/Modules/HtmlText/Models/HtmlText.cs index e51a41fd..25efbb23 100644 --- a/Oqtane.Shared/Modules/HtmlText/Models/HtmlTextInfo.cs +++ b/Oqtane.Shared/Modules/HtmlText/Models/HtmlText.cs @@ -5,8 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Oqtane.Modules.HtmlText.Models { - [Table("HtmlText")] - public class HtmlTextInfo : IAuditable + public class HtmlText : IAuditable { [Key] public int HtmlTextId { get; set; } diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 3be16172..8c724c5f 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -18,9 +18,17 @@ + + - + + + + + + C:\Users\charl\.nuget\packages\microsoft.entityframeworkcore.relational\5.0.2\lib\netstandard2.1\Microsoft.EntityFrameworkCore.Relational.dll + diff --git a/Oqtane.Shared/Shared/InstallConfig.cs b/Oqtane.Shared/Shared/InstallConfig.cs index 9aa81eda..2669431c 100644 --- a/Oqtane.Shared/Shared/InstallConfig.cs +++ b/Oqtane.Shared/Shared/InstallConfig.cs @@ -3,6 +3,7 @@ namespace Oqtane.Shared public class InstallConfig { public string ConnectionString { get; set; } + public string DatabaseType { get; set; } public string Aliases { get; set; } public string TenantName { get; set; } public bool IsNewTenant { get; set; } diff --git a/Oqtane.Shared/Shared/OqtaneDatabaseBase.cs b/Oqtane.Shared/Shared/OqtaneDatabaseBase.cs new file mode 100644 index 00000000..ff107ff5 --- /dev/null +++ b/Oqtane.Shared/Shared/OqtaneDatabaseBase.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Interfaces; +using Oqtane.Models; + +namespace Oqtane.Shared +{ + public abstract class OqtaneDatabaseBase : IOqtaneDatabase + { + protected OqtaneDatabaseBase(string name, string friendlyName, List connectionStringFields) + { + Name = name; + FriendlyName = friendlyName; + ConnectionStringFields = connectionStringFields; + } + + public string FriendlyName { get; } + + public string Name { get; } + + public abstract string Provider { get; } + + public List ConnectionStringFields { get; } + + public abstract OperationBuilder AddAutoIncrementColumn(ColumnsBuilder table, string name); + + public abstract string BuildConnectionString(); + + public virtual string ConcatenateSql(params string[] values) + { + var returnValue = String.Empty; + for (var i = 0; i < values.Length; i++) + { + if (i > 0) + { + returnValue += " + "; + } + returnValue += values[i]; + } + + return returnValue; + } + + public virtual string RewriteName(string name) + { + return name; + } + + public virtual void UpdateIdentityStoreTableNames(ModelBuilder builder) + { + + } + + public abstract DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString); + } +} diff --git a/Oqtane.Shared/Shared/SettingKeys.cs b/Oqtane.Shared/Shared/SettingKeys.cs index 6524e333..0656bbc5 100644 --- a/Oqtane.Shared/Shared/SettingKeys.cs +++ b/Oqtane.Shared/Shared/SettingKeys.cs @@ -2,12 +2,18 @@ { public static class SettingKeys { + public const string ConnectionStringsSection = "ConnectionStrings"; + public const string DatabaseSection = "Database"; public const string InstallationSection = "Installation"; + + public const string ConnectionStringKey = "DefaultConnection"; + + public const string DatabaseTypeKey = "DatabaseType"; + public const string DefaultAliasKey = "DefaultAlias"; public const string HostPasswordKey = "HostPassword"; public const string HostEmailKey = "HostEmail"; public const string SiteTemplateKey = "SiteTemplate"; - public const string ConnectionStringKey = "DefaultConnection"; public const string DefaultThemeKey = "DefaultTheme"; public const string DefaultLayoutKey = "DefaultLayout"; public const string DefaultContainerKey = "DefaultContainer"; diff --git a/Oqtane.Test/Oqtane.Test.csproj b/Oqtane.Test/Oqtane.Test.csproj index 04048585..b64a1433 100644 --- a/Oqtane.Test/Oqtane.Test.csproj +++ b/Oqtane.Test/Oqtane.Test.csproj @@ -18,7 +18,7 @@ - + diff --git a/Oqtane.sln b/Oqtane.sln index 70e85123..9548b9c6 100644 --- a/Oqtane.sln +++ b/Oqtane.sln @@ -20,6 +20,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Oqtane.Database.MySQL", "Oqtane.Database.MySQL\Oqtane.Database.MySQL.csproj", "{A996FD2D-DAC8-4DFA-92B2-51DF32C6E014}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Oqtane.Database.PostgreSQL", "Oqtane.Database.PostgreSQL\Oqtane.Database.PostgreSQL.csproj", "{3B29B35F-65E7-4819-9AED-EAC7FCFA309B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Oqtane.Database.Sqlite", "Oqtane.Database.Sqlite\Oqtane.Database.Sqlite.csproj", "{E4F50CA9-19A6-465A-9469-C033748AD95B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -46,6 +52,18 @@ Global {823B556D-8D4E-4BB8-A65A-C4EB5E7E7424}.Debug|Any CPU.Build.0 = Debug|Any CPU {823B556D-8D4E-4BB8-A65A-C4EB5E7E7424}.Release|Any CPU.ActiveCfg = Release|Any CPU {823B556D-8D4E-4BB8-A65A-C4EB5E7E7424}.Release|Any CPU.Build.0 = Release|Any CPU + {A996FD2D-DAC8-4DFA-92B2-51DF32C6E014}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A996FD2D-DAC8-4DFA-92B2-51DF32C6E014}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A996FD2D-DAC8-4DFA-92B2-51DF32C6E014}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A996FD2D-DAC8-4DFA-92B2-51DF32C6E014}.Release|Any CPU.Build.0 = Release|Any CPU + {3B29B35F-65E7-4819-9AED-EAC7FCFA309B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B29B35F-65E7-4819-9AED-EAC7FCFA309B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B29B35F-65E7-4819-9AED-EAC7FCFA309B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B29B35F-65E7-4819-9AED-EAC7FCFA309B}.Release|Any CPU.Build.0 = Release|Any CPU + {E4F50CA9-19A6-465A-9469-C033748AD95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4F50CA9-19A6-465A-9469-C033748AD95B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4F50CA9-19A6-465A-9469-C033748AD95B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4F50CA9-19A6-465A-9469-C033748AD95B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index dc4d7909..19073205 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ There is a separate [Documentation repository](https://github.com/oqtane/oqtane. # Roadmap This project is a work in progress and the schedule for implementing enhancements is dependent upon the availability of community members who are willing/able to assist. -V.2.1.0 ( Q1 2021 ) +V.2.1.0 ( May 2021 ) - [ ] Cross Platform Database Support ( ie. SQLite ) - see [#964](https://github.com/oqtane/oqtane.framework/discussions/964) - [ ] EF Core Migrations for Database Installation/Upgrade - see [#964](https://github.com/oqtane/oqtane.framework/discussions/964)