From d9265e127e6c07bf24f4f17d5732405e70edda39 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 24 Mar 2020 14:08:29 -0400 Subject: [PATCH] SQL maanager, Module Creator, module settings enhancements --- .../Modules/Admin/ModuleCreator/Index.razor | 58 ++++++ .../Modules/Admin/ModuleCreator/Module.cs | 22 +++ .../Oqtane.Client/Modules/[Module]/Edit.razor | 92 ++++++++++ .../Modules/[Module]/Index.razor | 76 ++++++++ .../[Module]/Services/I[Module]Service.cs | 19 ++ .../[Module]/Services/[Module]Service.cs | 57 ++++++ .../Modules/[Module]/Settings.razor | 47 +++++ .../Modules/[Module]/[Module].cs | 22 +++ .../Controllers/[Module]Controller.cs | 75 ++++++++ .../[Module]/Manager/[Module]Manager.cs | 48 +++++ .../Repository/I[Module]Repository.cs | 14 ++ .../[Module]/Repository/[Module]Context.cs | 17 ++ .../[Module]/Repository/[Module]Repository.cs | 49 ++++++ .../Modules/[Module]/Scripts/00.00.01.sql | 26 +++ .../Modules/Models/[Module]/[Module].cs | 18 ++ .../Modules/Admin/Modules/Settings.razor | 165 +++++++++++------- Oqtane.Client/Modules/Admin/Sql/Index.razor | 104 +++++++++++ Oqtane.Client/Modules/Controls/Pager.razor | 10 +- Oqtane.Client/Oqtane.Client.csproj | 7 + .../Interfaces/IModuleDefinitionService.cs | 1 + .../Services/Interfaces/ISqlService.cs | 10 ++ .../Services/ModuleDefinitionService.cs | 4 + Oqtane.Client/Services/SqlService.cs | 34 ++++ Oqtane.Client/Startup.cs | 1 + .../Controllers/ModuleDefinitionController.cs | 73 +++++++- Oqtane.Server/Controllers/SqlController.cs | 65 +++++++ .../Repository/Interfaces/ISqlRepository.cs | 11 ++ Oqtane.Server/Repository/SiteRepository.cs | 28 +++ Oqtane.Server/Repository/SqlRepository.cs | 48 +++++ Oqtane.Server/Startup.cs | 3 + Oqtane.Shared/Models/SqlQuery.cs | 11 ++ .../Models/HtmlText}/HtmlTextInfo.cs | 0 32 files changed, 1146 insertions(+), 69 deletions(-) create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Module.cs create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Edit.razor create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Index.razor create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Services/I[Module]Service.cs create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Services/[Module]Service.cs create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Settings.razor create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/[Module].cs create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Controllers/[Module]Controller.cs create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Manager/[Module]Manager.cs create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Repository/I[Module]Repository.cs create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Repository/[Module]Context.cs create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Repository/[Module]Repository.cs create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Scripts/00.00.01.sql create mode 100644 Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Shared/Modules/Models/[Module]/[Module].cs create mode 100644 Oqtane.Client/Modules/Admin/Sql/Index.razor create mode 100644 Oqtane.Client/Services/Interfaces/ISqlService.cs create mode 100644 Oqtane.Client/Services/SqlService.cs create mode 100644 Oqtane.Server/Controllers/SqlController.cs create mode 100644 Oqtane.Server/Repository/Interfaces/ISqlRepository.cs create mode 100644 Oqtane.Server/Repository/SqlRepository.cs create mode 100644 Oqtane.Shared/Models/SqlQuery.cs rename Oqtane.Shared/{Models => Modules/Models/HtmlText}/HtmlTextInfo.cs (100%) diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor b/Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor new file mode 100644 index 00000000..087f52ab --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Index.razor @@ -0,0 +1,58 @@ +@namespace Oqtane.Modules.Admin.ModuleCreator +@inherits ModuleBase +@inject NavigationManager NavigationManager +@inject IModuleDefinitionService ModuleDefinitionService +@inject IModuleService ModuleService + + + + + + + + + + +
+ + + +
+ + + +
+ + + +@code { + public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } } + + string _name = ""; + string _description = ""; + + protected override void OnInitialized() + { + AddModuleMessage("Please Note That Once You Select The Create Module Button The Application Must Restart In Order To Complete The Process.", MessageType.Info); + } + + private async Task CreateModule() + { + try + { + if (!string.IsNullOrEmpty(_name)) + { + ModuleDefinition moduleDefinition = new ModuleDefinition { Name = _name, Description = _description }; + await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition, ModuleState.ModuleId); + } + else + { + AddModuleMessage("You Must Provide A Name For The Module", MessageType.Warning); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Creating Module"); + } + } +} diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Module.cs b/Oqtane.Client/Modules/Admin/ModuleCreator/Module.cs new file mode 100644 index 00000000..0760879b --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Module.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Oqtane.Modules.Admin.ModuleCreator +{ + public class Module : IModule + { + public Dictionary Properties + { + get + { + Dictionary properties = new Dictionary + { + { "Name", "Module Creator" }, + { "Description", "Enables software developers to quickly create modules by automating many of the initial module creation tasks" }, + { "Version", "1.0.0" }, + { "Categories", "Developer" } + }; + return properties; + } + } + } +} diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Edit.razor b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Edit.razor new file mode 100644 index 00000000..f0fed00e --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Edit.razor @@ -0,0 +1,92 @@ +@namespace Oqtane.Modules.[Module]s +@using Oqtane.Services.[Module]s +@using Oqtane.Models.[Module]s +@using Oqtane.Modules.Controls +@inherits ModuleBase +@inject NavigationManager NavigationManager +@inject I[Module]Service [Module]Service + + + + + + +
+ + + +
+ +Cancel +
+
+@if (PageState.Action == "Edit") +{ + +} + +@code { + + public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Edit; } } + public override string Actions { get { return "Add,Edit"; } } + + int _id; + string _name; + string _createdby; + DateTime _createdon; + string _modifiedby; + DateTime _modifiedon; + + protected override async Task OnInitializedAsync() + { + if (PageState.Action == "Edit") + { + try + { + _id = Int32.Parse(PageState.QueryString["id"]); + [Module] [Module] = await [Module]Service.Get[Module]Async(_id); + if ([Module] != null) + { + _name = [Module].Name; + _createdby = [Module].CreatedBy; + _createdon = [Module].CreatedOn; + _modifiedby = [Module].ModifiedBy; + _modifiedon = [Module].ModifiedOn; + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading [Module] {[Module]Id} {Error}", _id, ex.Message); + AddModuleMessage("Error Loading [Module]", MessageType.Error); + } + } + } + + private async Task Save() + { + try + { + if (PageState.Action == "Add") + { + [Module] [Module] = new [Module](); + [Module].ModuleId = ModuleState.ModuleId; + [Module].Name = _name; + [Module] = await [Module]Service.Add[Module]Async([Module]); + await logger.LogInformation("[Module] Added {[Module]}", [Module]); + } + else + { + [Module] [Module] = await [Module]Service.Get[Module]Async(_id); + [Module].Name = _name; + await [Module]Service.Update[Module]Async([Module]); + await logger.LogInformation("[Module] Updated {[Module]}", [Module]); + } + NavigationManager.NavigateTo(NavigateUrl()); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Saving [Module] {Error}", ex.Message); + AddModuleMessage("Error Saving [Module]", MessageType.Error); + } + } +} diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Index.razor b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Index.razor new file mode 100644 index 00000000..a0f03fe2 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Index.razor @@ -0,0 +1,76 @@ +@namespace Oqtane.Modules.[Module]s +@using Oqtane.Services.[Module]s +@using Oqtane.Models.[Module]s +@inherits ModuleBase +@inject NavigationManager NavigationManager +@inject I[Module]Service [Module]Service + +@if (_[Module]s == null) +{ +

Loading...

+} +else +{ + +

+ + +
+ + + @context.Name +
+
+
+} + +
+[Module] Module Created Successfully. You Can Access The Files At The Following Locations:

+C:\Users\Shaun.Walker\Source\Repos\sbwalker\oqtane.framework\Oqtane.Client\Modules\[Module]\
+- Index.razor - main component for your module
+- Edit.razor - component for adding or editing content
+- Settings.razor - component for managing module settings
+- Module.cs - implements IModule interface to provide configuration settings for your module
+- Services\I[Module]Service.cs - interface for defining service API methods
+- Services\[Module]Service.cs - implements service API interface methods

+C:\Users\Shaun.Walker\Source\Repos\sbwalker\oqtane.framework\Oqtane.Server\Modules\[Module]\
+- Controllers\[Module]Controller.cs - API methods implemented using a REST pattern
+- Manager\[Module]Manager.cs - implements optional module interfaces for features such as import/export of content
+- Repository\I[Module]Repository.cs - interface for defining repository methods
+- Repository\[Module]Respository.cs - implements repository interface methods for data access using EF Core
+- Repository\[Module]Context.cs - provides a DB Context for data access
+- Scripts\01.00.00.sql - database schema definition

+C:\Users\Shaun.Walker\Source\Repos\sbwalker\oqtane.framework\Oqtane.Shared\Modules\Models\[Module]\
+- [Module].cs - model definition

+ +@code { + List<[Module]> _[Module]s; + + protected override async Task OnParametersSetAsync() + { + try + { + _[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading [Module] {Error}", ex.Message); + AddModuleMessage("Error Loading [Module]", MessageType.Error); + } + } + + private async Task Delete([Module] [Module]) + { + try + { + await [Module]Service.Delete[Module]Async([Module].[Module]Id); + await logger.LogInformation("[Module] Deleted {[Module]}", [Module]); + NavigationManager.NavigateTo(NavigateUrl()); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Deleting [Module] {[Module]} {Error}", [Module], ex.Message); + AddModuleMessage("Error Deleting [Module]", MessageType.Error); + } + } +} \ No newline at end of file diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Services/I[Module]Service.cs b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Services/I[Module]Service.cs new file mode 100644 index 00000000..f481dc02 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Services/I[Module]Service.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Oqtane.Models.[Module]s; + +namespace Oqtane.Services.[Module]s +{ + public interface I[Module]Service + { + Task> Get[Module]sAsync(int ModuleId); + + Task<[Module]> Get[Module]Async(int [Module]Id); + + Task<[Module]> Add[Module]Async([Module] [Module]); + + Task<[Module]> Update[Module]Async([Module] [Module]); + + Task Delete[Module]Async(int [Module]Id); + } +} diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Services/[Module]Service.cs b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Services/[Module]Service.cs new file mode 100644 index 00000000..7935c184 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Services/[Module]Service.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Oqtane.Models.[Module]s; +using Oqtane.Modules; +using Oqtane.Services; +using Oqtane.Shared; + +namespace Oqtane.Services.[Module]s +{ + public class [Module]Service : ServiceBase, I[Module]Service, IService + { + private readonly HttpClient _http; + private readonly NavigationManager _navigationManager; + private readonly SiteState _siteState; + + public [Module]Service(HttpClient http, SiteState siteState, NavigationManager navigationManager) + { + _http = http; + _siteState = siteState; + _navigationManager = navigationManager; + } + + private string Apiurl + { + get { return CreateApiUrl(_siteState.Alias, _navigationManager.Uri, "[Module]"); } + } + + public async Task> Get[Module]sAsync(int ModuleId) + { + List<[Module]> [Module]s = await _http.GetJsonAsync>(Apiurl + "?moduleid=" + ModuleId.ToString()); + return [Module]s.OrderBy(item => item.Name).ToList(); + } + + public async Task<[Module]> Get[Module]Async(int [Module]Id) + { + return await _http.GetJsonAsync<[Module]>(Apiurl + "/" + [Module]Id.ToString()); + } + + public async Task<[Module]> Add[Module]Async([Module] [Module]) + { + return await _http.PostJsonAsync<[Module]>(Apiurl + "?entityid=" + [Module].ModuleId, [Module]); + } + + public async Task<[Module]> Update[Module]Async([Module] [Module]) + { + return await _http.PutJsonAsync<[Module]>(Apiurl + "/" + [Module].[Module]Id + "?entityid=" + [Module].ModuleId, [Module]); + } + + public async Task Delete[Module]Async(int [Module]Id) + { + await _http.DeleteAsync(Apiurl + "/" + [Module]Id.ToString()); + } + } +} diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Settings.razor b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Settings.razor new file mode 100644 index 00000000..b657eb6a --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/Settings.razor @@ -0,0 +1,47 @@ +@namespace Oqtane.Modules.[Module]s +@inherits ModuleBase +@inject ISettingService SettingService + + + + + + +
+ + + +
+ +@code { + public override string Title { get { return "[Module] Settings"; } } + + string _value; + + protected override async Task OnInitializedAsync() + { + try + { + Dictionary settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId); + _value = SettingService.GetSetting(settings, "SettingName", ""); + } + catch (Exception ex) + { + ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error); + } + } + + public async Task UpdateSettings() + { + try + { + Dictionary settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId); + SettingService.SetSetting(settings, "SettingName", _value); + await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId); + } + catch (Exception ex) + { + ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error); + } + } +} diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/[Module].cs b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/[Module].cs new file mode 100644 index 00000000..040fd244 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Client/Modules/[Module]/[Module].cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Oqtane.Modules.[Module]s +{ + public class Module : IModule + { + public Dictionary Properties + { + get + { + Dictionary properties = new Dictionary + { + { "Name", "[Module]" }, + { "Description", "[Module]" }, + { "Version", "1.0.0" }, + { "ServerAssemblyName", "Oqtane.Server" } + }; + return properties; + } + } + } +} diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Controllers/[Module]Controller.cs b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Controllers/[Module]Controller.cs new file mode 100644 index 00000000..023e42a4 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Controllers/[Module]Controller.cs @@ -0,0 +1,75 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Oqtane.Models.[Module]s; +using Oqtane.Repository.[Module]s; +using Oqtane.Shared; +using System.Collections.Generic; +using Oqtane.Enums; +using Oqtane.Infrastructure.Interfaces; + +namespace Oqtane.Controllers.[Module]s +{ + [Route("{site}/api/[controller]")] + public class [Module]Controller : Controller + { + private readonly I[Module]Repository _[Module]s; + private readonly ILogManager _logger; + + public [Module]Controller(I[Module]Repository [Module]s, ILogManager logger) + { + _[Module]s = [Module]s; + _logger = logger; + } + + // GET: api/?moduleid=x + [HttpGet] + [Authorize(Roles = Constants.RegisteredRole)] + public IEnumerable<[Module]> Get(string moduleid) + { + return _[Module]s.Get[Module]s(int.Parse(moduleid)); + } + + // GET api//5 + [HttpGet("{id}")] + [Authorize(Roles = Constants.RegisteredRole)] + public [Module] Get(int id) + { + return _[Module]s.Get[Module](id); + } + + // POST api/ + [HttpPost] + [Authorize(Roles = Constants.AdminRole)] + public [Module] Post([FromBody] [Module] [Module]) + { + if (ModelState.IsValid) + { + [Module] = _[Module]s.Add[Module]([Module]); + _logger.Log(LogLevel.Information, this, LogFunction.Create, "[Module] Added {[Module]}", [Module]); + } + return [Module]; + } + + // PUT api//5 + [HttpPut("{id}")] + [Authorize(Roles = Constants.AdminRole)] + public [Module] Put(int id, [FromBody] [Module] [Module]) + { + if (ModelState.IsValid) + { + [Module] = _[Module]s.Update[Module]([Module]); + _logger.Log(LogLevel.Information, this, LogFunction.Update, "[Module] Updated {[Module]}", [Module]); + } + return [Module]; + } + + // DELETE api//5 + [HttpDelete("{id}")] + [Authorize(Roles = Constants.AdminRole)] + public void Delete(int id) + { + _[Module]s.Delete[Module](id); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "[Module] Deleted {[Module]Id}", id); + } + } +} diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Manager/[Module]Manager.cs b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Manager/[Module]Manager.cs new file mode 100644 index 00000000..8380c97d --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Manager/[Module]Manager.cs @@ -0,0 +1,48 @@ +using Oqtane.Models.[Module]s; +using Oqtane.Repository.[Module]s; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; + +namespace Oqtane.Modules.[Module]s +{ + public class [Module]Manager : IPortable + { + private I[Module]Repository _[Module]s; + + public [Module]Manager(I[Module]Repository [Module]s) + { + _[Module]s = [Module]s; + } + + public string ExportModule(Models.Module module) + { + string content = ""; + List<[Module]> [Module]s = _[Module]s.Get[Module]s(module.ModuleId).ToList(); + if ([Module]s != null) + { + content = JsonSerializer.Serialize([Module]s); + } + return content; + } + + public void ImportModule(Models.Module module, string content, string version) + { + List<[Module]> [Module]s = null; + if (!string.IsNullOrEmpty(content)) + { + [Module]s = JsonSerializer.Deserialize>(content); + } + if ([Module]s != null) + { + foreach([Module] [Module] in [Module]s) + { + [Module] _[Module] = new [Module](); + _[Module].ModuleId = module.ModuleId; + _[Module].Name = [Module].Name; + _[Module]s.Add[Module](_[Module]); + } + } + } + } +} \ No newline at end of file diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Repository/I[Module]Repository.cs b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Repository/I[Module]Repository.cs new file mode 100644 index 00000000..7a607ad2 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Repository/I[Module]Repository.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Oqtane.Models.[Module]s; + +namespace Oqtane.Repository.[Module]s +{ + public interface I[Module]Repository + { + IEnumerable<[Module]> Get[Module]s(int ModuleId); + [Module] Get[Module](int [Module]Id); + [Module] Add[Module]([Module] [Module]); + [Module] Update[Module]([Module] [Module]); + void Delete[Module](int [Module]Id); + } +} diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Repository/[Module]Context.cs b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Repository/[Module]Context.cs new file mode 100644 index 00000000..9a438a88 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Repository/[Module]Context.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Http; +using Oqtane.Models.[Module]s; +using Oqtane.Modules; + +namespace Oqtane.Repository.[Module]s +{ + public class [Module]Context : DBContextBase, IService + { + public virtual DbSet<[Module]> [Module] { get; set; } + + public [Module]Context(ITenantResolver tenantResolver, IHttpContextAccessor accessor) : base(tenantResolver, accessor) + { + // ContextBase handles multi-tenant database connections + } + } +} diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Repository/[Module]Repository.cs b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Repository/[Module]Repository.cs new file mode 100644 index 00000000..c6383fce --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Repository/[Module]Repository.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore; +using System.Linq; +using System.Collections.Generic; +using Oqtane.Models.[Module]s; +using Oqtane.Modules; + +namespace Oqtane.Repository.[Module]s +{ + public class [Module]Repository : I[Module]Repository, IService + { + private readonly [Module]Context _db; + + public [Module]Repository([Module]Context context) + { + _db = context; + } + + public IEnumerable<[Module]> Get[Module]s(int ModuleId) + { + return _db.[Module].Where(item => item.ModuleId == ModuleId); + } + + public [Module] Get[Module](int [Module]Id) + { + return _db.[Module].Find([Module]Id); + } + + public [Module] Add[Module]([Module] [Module]) + { + _db.[Module].Add([Module]); + _db.SaveChanges(); + return [Module]; + } + + public [Module] Update[Module]([Module] [Module]) + { + _db.Entry([Module]).State = EntityState.Modified; + _db.SaveChanges(); + return [Module]; + } + + public void Delete[Module](int [Module]Id) + { + [Module] [Module] = _db.[Module].Find([Module]Id); + _db.[Module].Remove([Module]); + _db.SaveChanges(); + } + } +} diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Scripts/00.00.01.sql b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Scripts/00.00.01.sql new file mode 100644 index 00000000..f97f4daf --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Server/Modules/[Module]/Scripts/00.00.01.sql @@ -0,0 +1,26 @@ +/* +Create [Module] table +*/ + +CREATE TABLE [dbo].[[Module]]( + [[Module]Id] [int] IDENTITY(1,1) NOT NULL, + [ModuleId] [int] NOT NULL, + [Name] [nvarchar](256) NOT NULL, + [CreatedBy] [nvarchar](256) NOT NULL, + [CreatedOn] [datetime] NOT NULL, + [ModifiedBy] [nvarchar](256) NOT NULL, + [ModifiedOn] [datetime] NOT NULL, + CONSTRAINT [PK_[Module]] PRIMARY KEY CLUSTERED + ( + [[Module]Id] ASC + ) +) +GO + +/* +Create foreign key relationships +*/ +ALTER TABLE [dbo].[[Module]] WITH CHECK ADD CONSTRAINT [FK_[Module]_Module] FOREIGN KEY([ModuleId]) +REFERENCES [dbo].Module ([ModuleId]) +ON DELETE CASCADE +GO \ No newline at end of file diff --git a/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Shared/Modules/Models/[Module]/[Module].cs b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Shared/Modules/Models/[Module]/[Module].cs new file mode 100644 index 00000000..6ebba399 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/ModuleCreator/Templates/Oqtane.Shared/Modules/Models/[Module]/[Module].cs @@ -0,0 +1,18 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Oqtane.Models.[Module]s +{ + public class [Module] : IAuditable + { + [Key] + public int [Module]Id { get; set; } + public int ModuleId { get; set; } + public string Name { get; set; } + + public string CreatedBy { get; set; } + public DateTime CreatedOn { get; set; } + public string ModifiedBy { get; set; } + public DateTime ModifiedOn { get; set; } + } +} diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index 73bd79c7..560fa398 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -5,75 +5,108 @@ @inject IModuleService ModuleService @inject IPageModuleService PageModuleService - - - - - - - - - - - - - - - - - - - -
- - - -
- - - -
- - - -
- - - -
+@if (_containers != null) +{ +
+
-@DynamicComponent + - -Cancel +
+
+
+ + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
+
+ @if (_settingsModuleType != null) + { +
+
+ @DynamicComponent +
+ } +
+
+
+ + Cancel +} @code { public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Edit; } } public override string Title { get { return "Module Settings"; } } - Dictionary _containers = new Dictionary(); + Dictionary _containers; string _title; string _containerType; string _permissionNames = ""; string _permissions; string _pageId; -#pragma warning disable 649 PermissionGrid _permissionGrid; -#pragma warning restore 649 + Type _settingsModuleType; + string _settingstitle = "Other Settings"; RenderFragment DynamicComponent { get; set; } object _settings; @@ -86,16 +119,19 @@ _permissionNames = ModuleState.ModuleDefinition.PermissionNames; _pageId = ModuleState.PageId.ToString(); - DynamicComponent = builder => + _settingsModuleType = Type.GetType(ModuleState.ModuleType); + if (_settingsModuleType != null) { - Type moduleType = Type.GetType(ModuleState.ModuleType); - if (moduleType != null) + var moduleobject = Activator.CreateInstance(_settingsModuleType); + _settingstitle = (string)_settingsModuleType.GetProperty("Title").GetValue(moduleobject, null); + + DynamicComponent = builder => { - builder.OpenComponent(0, moduleType); - builder.AddComponentReferenceCapture(1, inst => { _settings = Convert.ChangeType(inst, moduleType); }); + builder.OpenComponent(0, _settingsModuleType); + builder.AddComponentReferenceCapture(1, inst => { _settings = Convert.ChangeType(inst, _settingsModuleType); }); builder.CloseComponent(); - } - }; + }; + } } private async Task SaveModule() @@ -111,10 +147,13 @@ await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - Type moduleType = Type.GetType(ModuleState.ModuleType); - if (moduleType != null) + if (_settingsModuleType != null) { - moduleType.GetMethod("UpdateSettings")?.Invoke(_settings, null); // method must be public in settings component + Type moduleType = Type.GetType(ModuleState.ModuleType); + if (moduleType != null) + { + moduleType.GetMethod("UpdateSettings")?.Invoke(_settings, null); // method must be public in settings component + } } NavigationManager.NavigateTo(NavigateUrl()); diff --git a/Oqtane.Client/Modules/Admin/Sql/Index.razor b/Oqtane.Client/Modules/Admin/Sql/Index.razor new file mode 100644 index 00000000..0f3705c9 --- /dev/null +++ b/Oqtane.Client/Modules/Admin/Sql/Index.razor @@ -0,0 +1,104 @@ +@namespace Oqtane.Modules.Admin.Sql +@inherits ModuleBase +@inject NavigationManager NavigationManager +@inject ITenantService TenantService +@inject ISqlService SqlService + +@if (_tenants == null) +{ +

Loading...

+} +else +{ + + + + + + + + + +
+ + + +
+ + + +
+ +

+ @if (!string.IsNullOrEmpty(_results)) + { + @((MarkupString)_results) + } +} + +@code { + public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } } + + List _tenants; + string _tenantid = "-1"; + string _sql = ""; + string _results = ""; + + protected override async Task OnInitializedAsync() + { + _tenants = await TenantService.GetTenantsAsync(); + } + + private async Task Execute() + { + if (_tenantid != "-1" && !string.IsNullOrEmpty(_sql)) + { + SqlQuery sqlquery = new SqlQuery { TenantId = int.Parse(_tenantid), Query = _sql }; + sqlquery = await SqlService.ExecuteQueryAsync(sqlquery); + _results = DisplayResults(sqlquery.Results); + } + else + { + AddModuleMessage("You Must Select A Tenant And Provide A SQL Query", MessageType.Warning); + } + } + + private string DisplayResults(List> results) + { + string table = ""; + foreach (Dictionary item in results) + { + if (table == "") + { + table = "
"; + table += ""; + foreach (KeyValuePair kvp in item) + { + table += ""; + } + table += ""; + } + table += ""; + foreach (KeyValuePair kvp in item) + { + table += ""; + } + table += ""; + } + if (table != "") + { + table += "
" + kvp.Key + "
" + kvp.Value + "
"; + } + else + { + table = "No Results Returned"; + } + return table; + } +} diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor index 64ee715f..bfcc8cb3 100644 --- a/Oqtane.Client/Modules/Controls/Pager.razor +++ b/Oqtane.Client/Modules/Controls/Pager.razor @@ -1,6 +1,6 @@ @namespace Oqtane.Modules.Controls @inherits ModuleBase -@typeparam TAbleItem +@typeparam TableItem

@if(Format == "Table") @@ -78,13 +78,13 @@ public RenderFragment Header { get; set; } [Parameter] - public RenderFragment Row { get; set; } + public RenderFragment Row { get; set; } [Parameter] - public RenderFragment Detail { get; set; } + public RenderFragment Detail { get; set; } [Parameter] - public IEnumerable Items { get; set; } + public IEnumerable Items { get; set; } [Parameter] public string PageSize { get; set; } @@ -95,7 +95,7 @@ [Parameter] public string Class { get; set; } - IEnumerable ItemList { get; set; } + IEnumerable ItemList { get; set; } protected override void OnParametersSet() { diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index a95adf1b..66ba174f 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -27,6 +27,13 @@ TRACE;WASM + + + + + + + diff --git a/Oqtane.Client/Services/Interfaces/IModuleDefinitionService.cs b/Oqtane.Client/Services/Interfaces/IModuleDefinitionService.cs index e0bc3a24..8560c661 100644 --- a/Oqtane.Client/Services/Interfaces/IModuleDefinitionService.cs +++ b/Oqtane.Client/Services/Interfaces/IModuleDefinitionService.cs @@ -13,5 +13,6 @@ namespace Oqtane.Services Task InstallModuleDefinitionsAsync(); Task DeleteModuleDefinitionAsync(int moduleDefinitionId, int siteId); Task LoadModuleDefinitionsAsync(int siteId, Runtime runtime); + Task CreateModuleDefinitionAsync(ModuleDefinition moduleDefinition, int moduleId); } } diff --git a/Oqtane.Client/Services/Interfaces/ISqlService.cs b/Oqtane.Client/Services/Interfaces/ISqlService.cs new file mode 100644 index 00000000..f563a795 --- /dev/null +++ b/Oqtane.Client/Services/Interfaces/ISqlService.cs @@ -0,0 +1,10 @@ +using Oqtane.Models; +using System.Threading.Tasks; + +namespace Oqtane.Services +{ + public interface ISqlService + { + Task ExecuteQueryAsync(SqlQuery sqlquery); + } +} diff --git a/Oqtane.Client/Services/ModuleDefinitionService.cs b/Oqtane.Client/Services/ModuleDefinitionService.cs index 0d3cc4aa..cc537e86 100644 --- a/Oqtane.Client/Services/ModuleDefinitionService.cs +++ b/Oqtane.Client/Services/ModuleDefinitionService.cs @@ -92,5 +92,9 @@ namespace Oqtane.Services } } } + public async Task CreateModuleDefinitionAsync(ModuleDefinition moduleDefinition, int moduleId) + { + await _http.PostJsonAsync(Apiurl + "?moduleid=" + moduleId.ToString(), moduleDefinition); + } } } diff --git a/Oqtane.Client/Services/SqlService.cs b/Oqtane.Client/Services/SqlService.cs new file mode 100644 index 00000000..52371df8 --- /dev/null +++ b/Oqtane.Client/Services/SqlService.cs @@ -0,0 +1,34 @@ +using Oqtane.Models; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Oqtane.Shared; +using System.Collections.Generic; +using System.Linq; + +namespace Oqtane.Services +{ + public class SqlService : ServiceBase, ISqlService + { + private readonly HttpClient _http; + private readonly SiteState _siteState; + private readonly NavigationManager _navigationManager; + + public SqlService(HttpClient http, SiteState siteState, NavigationManager navigationManager) + { + _http = http; + _siteState = siteState; + _navigationManager = navigationManager; + } + + private string Apiurl + { + get { return CreateApiUrl(_siteState.Alias, _navigationManager.Uri, "Sql"); } + } + + public async Task ExecuteQueryAsync(SqlQuery sqlquery) + { + return await _http.PostJsonAsync(Apiurl, sqlquery); + } + } +} diff --git a/Oqtane.Client/Startup.cs b/Oqtane.Client/Startup.cs index b7991d90..8d4dfdeb 100644 --- a/Oqtane.Client/Startup.cs +++ b/Oqtane.Client/Startup.cs @@ -59,6 +59,7 @@ namespace Oqtane.Client services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // dynamically register module contexts and repository services Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index 6e4ea17f..5e4659ce 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -11,6 +11,7 @@ using Oqtane.Enums; using Oqtane.Infrastructure.Interfaces; using Oqtane.Repository; using Oqtane.Security; +using System; // ReSharper disable StringIndexOfIsCultureSpecific.1 namespace Oqtane.Controllers @@ -19,17 +20,23 @@ namespace Oqtane.Controllers public class ModuleDefinitionController : Controller { private readonly IModuleDefinitionRepository _moduleDefinitions; + private readonly IModuleRepository _modules; private readonly IUserPermissions _userPermissions; private readonly IInstallationManager _installationManager; private readonly IWebHostEnvironment _environment; + private readonly ITenantResolver _resolver; + private readonly ISqlRepository _sql; private readonly ILogManager _logger; - public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, ILogManager logger) + public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository modules, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantResolver resolver, ISqlRepository sql, ILogManager logger) { _moduleDefinitions = moduleDefinitions; + _modules = modules; _userPermissions = userPermissions; _installationManager = installationManager; _environment = environment; + _resolver = resolver; + _sql = sql; _logger = logger; } @@ -124,5 +131,69 @@ namespace Oqtane.Controllers return File(file, "application/octet-stream", assemblyname); } + // POST api/?moduleid=x + [HttpPost] + [Authorize(Roles = Constants.HostRole)] + public void Post([FromBody] ModuleDefinition moduleDefinition, string moduleid) + { + if (ModelState.IsValid) + { + string rootPath = Directory.GetParent(_environment.ContentRootPath).FullName; + string templatePath = Path.Combine(rootPath, "Oqtane.Client\\Modules\\Admin\\ModuleCreator\\Templates\\"); + ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, moduleDefinition); + moduleDefinition.ModuleDefinitionName = "Oqtane.Modules." + moduleDefinition.Name + "s, Oqtane.Client"; + _logger.Log(LogLevel.Information, this, LogFunction.Create, "Module Definition Created {ModuleDefinition}", moduleDefinition); + Models.Module module = _modules.GetModule(int.Parse(moduleid)); + module.ModuleDefinitionName = moduleDefinition.ModuleDefinitionName; + _modules.UpdateModule(module); + _installationManager.RestartApplication(); + } + } + + private void ProcessTemplatesRecursively(DirectoryInfo current, string rootPath, ModuleDefinition moduleDefinition) + { + // process folder + string folderPath = current.FullName.Replace("Oqtane.Client\\Modules\\Admin\\ModuleCreator\\Templates\\", ""); + folderPath = folderPath.Replace("[Module]", moduleDefinition.Name); + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + + FileInfo[] files = current.GetFiles("*.*"); + if (files != null) + { + foreach (FileInfo file in files) + { + // process file + string filePath = Path.Combine(folderPath, file.Name); + filePath = filePath.Replace("[Module]", moduleDefinition.Name); + + string text = System.IO.File.ReadAllText(file.FullName); + text = text.Replace("[Module]", moduleDefinition.Name); + text = text.Replace("[Description]", moduleDefinition.Description); + text = text.Replace("[RootPath]", rootPath); + text = text.Replace("[Folder]", folderPath); + text = text.Replace("[File]", Path.GetFileName(filePath)); + System.IO.File.WriteAllText(filePath, text); + + if (Path.GetExtension(filePath) == ".sql") + { + // execute script + foreach (string query in text.Split("GO", StringSplitOptions.RemoveEmptyEntries)) + { + _sql.ExecuteNonQuery(_resolver.GetTenant(), query); + } + } + } + + DirectoryInfo[] folders = current.GetDirectories(); + + foreach (DirectoryInfo folder in folders.Reverse()) + { + ProcessTemplatesRecursively(folder, rootPath, moduleDefinition); + } + } + } } } diff --git a/Oqtane.Server/Controllers/SqlController.cs b/Oqtane.Server/Controllers/SqlController.cs new file mode 100644 index 00000000..1ec75d19 --- /dev/null +++ b/Oqtane.Server/Controllers/SqlController.cs @@ -0,0 +1,65 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Oqtane.Models; +using System.Collections.Generic; +using Oqtane.Shared; +using Oqtane.Infrastructure.Interfaces; +using Oqtane.Repository; +using Oqtane.Enums; +using System.Data.SqlClient; +using System.Data; +using System.Dynamic; +using Newtonsoft.Json; +using System; + +namespace Oqtane.Controllers +{ + [Route("{site}/api/[controller]")] + public class SqlController : Controller + { + private readonly ITenantRepository _tenants; + private readonly ISqlRepository _sql; + private readonly ILogManager _logger; + + public SqlController(ITenantRepository tenants, ISqlRepository sql, ILogManager logger) + { + _tenants = tenants; + _sql = sql; + _logger = logger; + } + + // POST: api/ + [HttpPost] + [Authorize(Roles = Constants.HostRole)] + public SqlQuery Post([FromBody] SqlQuery sqlquery) + { + var results = new List>(); + Dictionary row; + Tenant tenant = _tenants.GetTenant(sqlquery.TenantId); + try + { + foreach (string query in sqlquery.Query.Split("GO", StringSplitOptions.RemoveEmptyEntries)) + { + SqlDataReader dr = _sql.ExecuteReader(tenant, query); + _logger.Log(LogLevel.Information, this, LogFunction.Other, "Sql Query {Query} Executed on Tenant {TenantId}", query, sqlquery.TenantId); + while (dr.Read()) + { + row = new Dictionary(); + for (var field = 0; field < dr.FieldCount; field++) + { + row[dr.GetName(field)] = dr.IsDBNull(field) ? "" : dr.GetValue(field).ToString(); + } + results.Add(row); + } + } + } + catch (Exception ex) + { + _logger.Log(LogLevel.Error, this, LogFunction.Other, "Sql Query {Query} Executed on Tenant {TenantId} Results In An Error {Error}", sqlquery.Query, sqlquery.TenantId, ex.Message); + } + sqlquery.Results = results; + return sqlquery; + } + + } +} diff --git a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs new file mode 100644 index 00000000..4bd04e91 --- /dev/null +++ b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs @@ -0,0 +1,11 @@ +using System.Data.SqlClient; +using Oqtane.Models; + +namespace Oqtane.Repository +{ + public interface ISqlRepository + { + int ExecuteNonQuery(Tenant tenant, string query); + SqlDataReader ExecuteReader(Tenant tenant, string query); + } +} diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 09ec53fc..dc10fb3b 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -361,6 +361,34 @@ namespace Oqtane.Repository } }); pageTemplates.Add(new PageTemplate + { + Name = "Sql Management", + Parent = "Admin", + Path = "admin/sql", + Icon = "spreadsheet", + IsNavigation = false, + IsPersonalizable = false, + EditMode = true, + PagePermissions = _permissionRepository.EncodePermissions(new List + { + new Permission(PermissionNames.View, Constants.AdminRole, true), + new Permission(PermissionNames.Edit, Constants.AdminRole, true) + }), + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = "Oqtane.Modules.Admin.Sql, Oqtane.Client", Title = "Sql Management", Pane = "Content", + ModulePermissions = _permissionRepository.EncodePermissions(new List + { + new Permission(PermissionNames.View, Constants.AdminRole, true), + new Permission(PermissionNames.Edit, Constants.AdminRole, true) + }), + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate { Name = "Upgrade Service", Parent = "Admin", Path = "admin/upgrade", Icon = "aperture", IsNavigation = false, IsPersonalizable = false, EditMode = true, PagePermissions = _permissionRepository.EncodePermissions(new List diff --git a/Oqtane.Server/Repository/SqlRepository.cs b/Oqtane.Server/Repository/SqlRepository.cs new file mode 100644 index 00000000..c9bdf734 --- /dev/null +++ b/Oqtane.Server/Repository/SqlRepository.cs @@ -0,0 +1,48 @@ +using System; +using System.Data; +using System.Data.SqlClient; +using Oqtane.Models; + +namespace Oqtane.Repository +{ + public class SqlRepository : ISqlRepository + { + + public int ExecuteNonQuery(Tenant tenant, string query) + { + SqlConnection conn = new SqlConnection(FormatConnectionString(tenant.DBConnectionString)); + SqlCommand cmd = conn.CreateCommand(); + using (conn) + { + PrepareCommand(conn, cmd, query); + int val = cmd.ExecuteNonQuery(); + return val; + } + } + + 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 void PrepareCommand(SqlConnection conn, SqlCommand cmd, string query) + { + if (conn.State != ConnectionState.Open) + { + conn.Open(); + } + cmd.Connection = conn; + cmd.CommandText = query; + cmd.CommandType = CommandType.Text; + } + + private string FormatConnectionString(string connectionString) + { + return connectionString.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString()); + } + } +} diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index b961fd06..07a47ee7 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -107,6 +107,7 @@ namespace Oqtane services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddSingleton(); @@ -185,6 +186,7 @@ namespace Oqtane services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddOqtaneModules(); services.AddOqtaneThemes(); @@ -338,6 +340,7 @@ namespace Oqtane services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddOqtaneModules(); services.AddOqtaneThemes(); diff --git a/Oqtane.Shared/Models/SqlQuery.cs b/Oqtane.Shared/Models/SqlQuery.cs new file mode 100644 index 00000000..035c1178 --- /dev/null +++ b/Oqtane.Shared/Models/SqlQuery.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Oqtane.Models +{ + public class SqlQuery + { + public int TenantId { get; set; } + public string Query { get; set; } + public List> Results { get; set; } + } +} diff --git a/Oqtane.Shared/Models/HtmlTextInfo.cs b/Oqtane.Shared/Modules/Models/HtmlText/HtmlTextInfo.cs similarity index 100% rename from Oqtane.Shared/Models/HtmlTextInfo.cs rename to Oqtane.Shared/Modules/Models/HtmlText/HtmlTextInfo.cs