diff --git a/Oqtane.Application/Server/Oqtane.Application.Server.csproj b/Oqtane.Application/Server/Oqtane.Application.Server.csproj index 9ac15e6d..67e8ecdf 100644 --- a/Oqtane.Application/Server/Oqtane.Application.Server.csproj +++ b/Oqtane.Application/Server/Oqtane.Application.Server.csproj @@ -11,6 +11,15 @@ true + + + + + + + + + diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Edit.razor b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Edit.razor new file mode 100644 index 00000000..59badca4 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Edit.razor @@ -0,0 +1,107 @@ +@using Oqtane.Modules.Controls +@using [Owner].Module.[Module].Services +@using [Owner].Module.[Module].Models + +@namespace [Owner].Module.[Module] +@inherits ModuleBase +@inject I[Module]Service [Module]Service +@inject NavigationManager NavigationManager +@inject IStringLocalizer Localizer + +
+
+
+ +
+ +
+
+
+ + @Localizer["Cancel"] +

+ @if (PageState.Action == "Edit") + { + + } +
+ +@code { + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; + + public override string Actions => "Add,Edit"; + + public override string Title => "Manage [Module]"; + + private ElementReference form; + private bool validated = false; + + private int _id; + private string _name; + private string _createdby; + private DateTime _createdon; + private string _modifiedby; + private DateTime _modifiedon; + + protected override async Task OnInitializedAsync() + { + try + { + if (PageState.Action == "Edit") + { + _id = Int32.Parse(PageState.QueryString["id"]); + [Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId); + 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(Localizer["Message.LoadError"], MessageType.Error); + } + } + + private async Task Save() + { + try + { + validated = true; + var interop = new Oqtane.UI.Interop(JSRuntime); + if (await interop.FormValid(form)) + { + 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, ModuleState.ModuleId); + [Module].Name = _name; + await [Module]Service.Update[Module]Async([Module]); + await logger.LogInformation("[Module] Updated {[Module]}", [Module]); + } + NavigationManager.NavigateTo(NavigateUrl()); + } + else + { + AddModuleMessage(Localizer["Message.SaveValidation"], MessageType.Warning); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Saving [Module] {Error}", ex.Message); + AddModuleMessage(Localizer["Message.SaveError"], MessageType.Error); + } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Index.razor b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Index.razor new file mode 100644 index 00000000..2b32fcb4 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Index.razor @@ -0,0 +1,79 @@ +@using [Owner].Module.[Module].Services +@using [Owner].Module.[Module].Models + +@namespace [Owner].Module.[Module] +@inherits ModuleBase +@inject I[Module]Service [Module]Service +@inject NavigationManager NavigationManager +@inject IStringLocalizer Localizer + +@if (_[Module]s == null) +{ +

Loading...

+} +else +{ + +
+
+ @if (@_[Module]s.Count != 0) + { + +
+   +   + @Localizer["Name"] +
+ + + + @context.Name + +
+ } + else + { +

@Localizer["Message.DisplayNone"]

+ } +} + +@code { + public override string RenderMode => RenderModes.Static; + + public override List Resources => new List() + { + new Stylesheet(ModulePath() + "[Owner].Module.[Module]/Module.css"), + new Script(ModulePath() + "[Owner].Module.[Module]/Module.js") + }; + + List<[Module]> _[Module]s; + + protected override async Task OnInitializedAsync() + { + 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(Localizer["Message.LoadError"], MessageType.Error); + } + } + + private async Task Delete([Module] [Module]) + { + try + { + await [Module]Service.Delete[Module]Async([Module].[Module]Id, ModuleState.ModuleId); + await logger.LogInformation("[Module] Deleted {[Module]}", [Module]); + _[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId); + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Deleting [Module] {[Module]} {Error}", [Module], ex.Message); + AddModuleMessage(Localizer["Message.DeleteError"], MessageType.Error); + } + } +} \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Interop.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Interop.cs new file mode 100644 index 00000000..bcb7d729 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Interop.cs @@ -0,0 +1,15 @@ +using Microsoft.JSInterop; +using System.Threading.Tasks; + +namespace [Owner].Module.[Module] +{ + public class Interop + { + private readonly IJSRuntime _jsRuntime; + + public Interop(IJSRuntime jsRuntime) + { + _jsRuntime = jsRuntime; + } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/ModuleInfo.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/ModuleInfo.cs new file mode 100644 index 00000000..15c96b86 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/ModuleInfo.cs @@ -0,0 +1,16 @@ +using Oqtane.Models; +using Oqtane.Modules; + +namespace [Owner].Module.[Module] +{ + public class ModuleInfo : IModule + { + public ModuleDefinition ModuleDefinition => new ModuleDefinition + { + Name = "[Module]", + Description = "[Description]", + Version = "1.0.0", + ServerManagerType = "[ServerManagerType]" + }; + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Settings.razor b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Settings.razor new file mode 100644 index 00000000..624dee2a --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Settings.razor @@ -0,0 +1,47 @@ +@namespace [Owner].Module.[Module] +@inherits ModuleBase +@inject ISettingService SettingService +@inject IStringLocalizer Localizer + +
+
+ +
+ +
+
+
+ +@code { + private string resourceType = "[Owner].Module.[Module].Settings, [Owner].Module.[Module].Client.Oqtane"; // for localization + public override string Title => "[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) + { + AddModuleMessage(ex.Message, MessageType.Error); + } + } + + public async Task UpdateSettings() + { + try + { + var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId); + settings = SettingService.SetSetting(settings, "SettingName", _value); + await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId); + } + catch (Exception ex) + { + AddModuleMessage(ex.Message, MessageType.Error); + } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Edit.resx b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Edit.resx new file mode 100644 index 00000000..eebd66cd --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Edit.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name: + + + Enter the name + + + Save + + + Cancel + + + Error Loading [Module] + + + Please Provide All Required Information + + + Error Saving [Module] + + \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Index.resx b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Index.resx new file mode 100644 index 00000000..721a853a --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Index.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + + Add [Module] + + + Edit + + + Delete + + + Delete [Module] + + + Are You Sure You Wish To Delete This [Module]? + + + No [Module]s To Display + + + Error Loading [Module] + + + Error Deleting [Module] + + \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Settings.resx b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Settings.resx new file mode 100644 index 00000000..ba0390d8 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Settings.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name: + + + Enter a value + + \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Services/[Module]Service.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Services/[Module]Service.cs new file mode 100644 index 00000000..d433d698 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Services/[Module]Service.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Oqtane.Services; +using Oqtane.Shared; + +namespace [Owner].Module.[Module].Services +{ + public interface I[Module]Service + { + Task> Get[Module]sAsync(int ModuleId); + + Task Get[Module]Async(int [Module]Id, int ModuleId); + + Task Add[Module]Async(Models.[Module] [Module]); + + Task Update[Module]Async(Models.[Module] [Module]); + + Task Delete[Module]Async(int [Module]Id, int ModuleId); + } + + public class [Module]Service : ServiceBase, I[Module]Service + { + public [Module]Service(HttpClient http, SiteState siteState) : base(http, siteState) { } + + private string Apiurl => CreateApiUrl("[Module]"); + + public async Task> Get[Module]sAsync(int ModuleId) + { + List [Module]s = await GetJsonAsync>(CreateAuthorizationPolicyUrl($"{Apiurl}?moduleid={ModuleId}", EntityNames.Module, ModuleId), Enumerable.Empty().ToList()); + return [Module]s.OrderBy(item => item.Name).ToList(); + } + + public async Task Get[Module]Async(int [Module]Id, int ModuleId) + { + return await GetJsonAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module]Id}/{ModuleId}", EntityNames.Module, ModuleId)); + } + + public async Task Add[Module]Async(Models.[Module] [Module]) + { + return await PostJsonAsync(CreateAuthorizationPolicyUrl($"{Apiurl}", EntityNames.Module, [Module].ModuleId), [Module]); + } + + public async Task Update[Module]Async(Models.[Module] [Module]) + { + return await PutJsonAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module].[Module]Id}", EntityNames.Module, [Module].ModuleId), [Module]); + } + + public async Task Delete[Module]Async(int [Module]Id, int ModuleId) + { + await DeleteAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{[Module]Id}/{ModuleId}", EntityNames.Module, ModuleId)); + } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Startup/ClientStartup.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Startup/ClientStartup.cs new file mode 100644 index 00000000..95ed096c --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Startup/ClientStartup.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Linq; +using Oqtane.Services; +using [Owner].Module.[Module].Services; + +namespace [Owner].Module.[Module].Startup +{ + public class ClientStartup : IClientStartup + { + public void ConfigureServices(IServiceCollection services) + { + if (!services.Any(s => s.ServiceType == typeof(I[Module]Service))) + { + services.AddScoped(); + } + } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Controllers/[Module]Controller.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Controllers/[Module]Controller.cs new file mode 100644 index 00000000..5dd25b2b --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Controllers/[Module]Controller.cs @@ -0,0 +1,114 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Oqtane.Shared; +using Oqtane.Enums; +using Oqtane.Infrastructure; +using [Owner].Module.[Module].Services; +using Oqtane.Controllers; +using System.Net; +using System.Threading.Tasks; + +namespace [Owner].Module.[Module].Controllers +{ + [Route(ControllerRoutes.ApiRoute)] + public class [Module]Controller : ModuleControllerBase + { + private readonly I[Module]Service _[Module]Service; + + public [Module]Controller(I[Module]Service [Module]Service, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor) + { + _[Module]Service = [Module]Service; + } + + // GET: api/?moduleid=x + [HttpGet] + [Authorize(Policy = PolicyNames.ViewModule)] + public async Task> Get(string moduleid) + { + int ModuleId; + if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId)) + { + return await _[Module]Service.Get[Module]sAsync(ModuleId); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {ModuleId}", moduleid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return null; + } + } + + // GET api//5 + [HttpGet("{id}/{moduleid}")] + [Authorize(Policy = PolicyNames.ViewModule)] + public async Task Get(int id, int moduleid) + { + Models.[Module] [Module] = await _[Module]Service.Get[Module]Async(id, moduleid); + if ([Module] != null && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId)) + { + return [Module]; + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {[Module]Id} {ModuleId}", id, moduleid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return null; + } + } + + // POST api/ + [HttpPost] + [Authorize(Policy = PolicyNames.EditModule)] + public async Task Post([FromBody] Models.[Module] [Module]) + { + if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId)) + { + [Module] = await _[Module]Service.Add[Module]Async([Module]); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Post Attempt {[Module]}", [Module]); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + [Module] = null; + } + return [Module]; + } + + // PUT api//5 + [HttpPut("{id}")] + [Authorize(Policy = PolicyNames.EditModule)] + public async Task Put(int id, [FromBody] Models.[Module] [Module]) + { + if (ModelState.IsValid && [Module].[Module]Id == id && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId)) + { + [Module] = await _[Module]Service.Update[Module]Async([Module]); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Put Attempt {[Module]}", [Module]); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + [Module] = null; + } + return [Module]; + } + + // DELETE api//5 + [HttpDelete("{id}/{moduleid}")] + [Authorize(Policy = PolicyNames.EditModule)] + public async Task Delete(int id, int moduleid) + { + Models.[Module] [Module] = await _[Module]Service.Get[Module]Async(id, moduleid); + if ([Module] != null && IsAuthorizedEntityId(EntityNames.Module, [Module].ModuleId)) + { + await _[Module]Service.Delete[Module]Async(id, [Module].ModuleId); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Delete Attempt {[Module]Id} {ModuleId}", id, moduleid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } + } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Manager/[Module]Manager.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Manager/[Module]Manager.cs new file mode 100644 index 00000000..0779a608 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Manager/[Module]Manager.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using Oqtane.Modules; +using Oqtane.Models; +using Oqtane.Infrastructure; +using Oqtane.Interfaces; +using Oqtane.Enums; +using Oqtane.Repository; +using [Owner].Module.[Module].Repository; +using System.Threading.Tasks; + +namespace [Owner].Module.[Module].Manager +{ + public class [Module]Manager : MigratableModuleBase, IInstallable, IPortable, ISearchable + { + private readonly I[Module]Repository _[Module]Repository; + private readonly IDBContextDependencies _DBContextDependencies; + + public [Module]Manager(I[Module]Repository [Module]Repository, IDBContextDependencies DBContextDependencies) + { + _[Module]Repository = [Module]Repository; + _DBContextDependencies = DBContextDependencies; + } + + public bool Install(Tenant tenant, string version) + { + return Migrate(new [Module]Context(_DBContextDependencies), tenant, MigrationType.Up); + } + + public bool Uninstall(Tenant tenant) + { + return Migrate(new [Module]Context(_DBContextDependencies), tenant, MigrationType.Down); + } + + public string ExportModule(Oqtane.Models.Module module) + { + string content = ""; + List [Module]s = _[Module]Repository.Get[Module]s(module.ModuleId).ToList(); + if ([Module]s != null) + { + content = JsonSerializer.Serialize([Module]s); + } + return content; + } + + public void ImportModule(Oqtane.Models.Module module, string content, string version) + { + List [Module]s = null; + if (!string.IsNullOrEmpty(content)) + { + [Module]s = JsonSerializer.Deserialize>(content); + } + if ([Module]s != null) + { + foreach(var [Module] in [Module]s) + { + _[Module]Repository.Add[Module](new Models.[Module] { ModuleId = module.ModuleId, Name = [Module].Name }); + } + } + } + + public Task> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn) + { + var searchContentList = new List(); + + foreach (var [Module] in _[Module]Repository.Get[Module]s(pageModule.ModuleId)) + { + if ([Module].ModifiedOn >= lastIndexedOn) + { + searchContentList.Add(new SearchContent + { + EntityName = "[Owner][Module]", + EntityId = [Module].[Module]Id.ToString(), + Title = [Module].Name, + Body = [Module].Name, + ContentModifiedBy = [Module].ModifiedBy, + ContentModifiedOn = [Module].ModifiedOn + }); + } + } + + return Task.FromResult(searchContentList); + } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Migrations/01000000_[Module]Initialize.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Migrations/01000000_[Module]Initialize.cs new file mode 100644 index 00000000..be35bee0 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Migrations/01000000_[Module]Initialize.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations; +using [Owner].Module.[Module].Migrations.EntityBuilders; +using [Owner].Module.[Module].Repository; + +namespace [Owner].Module.[Module].Migrations +{ + [DbContext(typeof([Module]Context))] + [Migration("[Owner].Module.[Module].01.00.00.00")] + public class [Module]Initialize : MultiDatabaseMigration + { + public [Module]Initialize(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var entityBuilder = new [Module]EntityBuilder(migrationBuilder, ActiveDatabase); + entityBuilder.Create(); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var entityBuilder = new [Module]EntityBuilder(migrationBuilder, ActiveDatabase); + entityBuilder.Drop(); + } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Migrations/EntityBuilders/[Module]EntityBuilder.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Migrations/EntityBuilders/[Module]EntityBuilder.cs new file mode 100644 index 00000000..32ec9adb --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Migrations/EntityBuilders/[Module]EntityBuilder.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations; +using Oqtane.Migrations.EntityBuilders; + +namespace [Owner].Module.[Module].Migrations.EntityBuilders +{ + public class [Module]EntityBuilder : AuditableBaseEntityBuilder<[Module]EntityBuilder> + { + private const string _entityTableName = "[Owner][Module]"; + private readonly PrimaryKey<[Module]EntityBuilder> _primaryKey = new("PK_[Owner][Module]", x => x.[Module]Id); + private readonly ForeignKey<[Module]EntityBuilder> _moduleForeignKey = new("FK_[Owner][Module]_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.Cascade); + + public [Module]EntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_moduleForeignKey); + } + + protected override [Module]EntityBuilder BuildTable(ColumnsBuilder table) + { + [Module]Id = AddAutoIncrementColumn(table,"[Module]Id"); + ModuleId = AddIntegerColumn(table,"ModuleId"); + Name = AddMaxStringColumn(table,"Name"); + AddAuditableColumns(table); + return this; + } + + public OperationBuilder [Module]Id { get; set; } + public OperationBuilder ModuleId { get; set; } + public OperationBuilder Name { get; set; } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Repository/[Module]Context.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Repository/[Module]Context.cs new file mode 100644 index 00000000..2cce3945 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Repository/[Module]Context.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Http; +using Oqtane.Modules; +using Oqtane.Repository; +using Oqtane.Infrastructure; +using Oqtane.Repository.Databases.Interfaces; + +namespace [Owner].Module.[Module].Repository +{ + public class [Module]Context : DBContextBase, ITransientService, IMultiDatabase + { + public virtual DbSet [Module] { get; set; } + + public [Module]Context(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies) + { + // ContextBase handles multi-tenant database connections + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity().ToTable(ActiveDatabase.RewriteName("[Owner][Module]")); + } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Repository/[Module]Repository.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Repository/[Module]Repository.cs new file mode 100644 index 00000000..75e26527 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Repository/[Module]Repository.cs @@ -0,0 +1,75 @@ +using Microsoft.EntityFrameworkCore; +using System.Linq; +using System.Collections.Generic; +using Oqtane.Modules; + +namespace [Owner].Module.[Module].Repository +{ + public interface I[Module]Repository + { + IEnumerable Get[Module]s(int ModuleId); + Models.[Module] Get[Module](int [Module]Id); + Models.[Module] Get[Module](int [Module]Id, bool tracking); + Models.[Module] Add[Module](Models.[Module] [Module]); + Models.[Module] Update[Module](Models.[Module] [Module]); + void Delete[Module](int [Module]Id); + } + + public class [Module]Repository : I[Module]Repository, ITransientService + { + private readonly IDbContextFactory<[Module]Context> _factory; + + public [Module]Repository(IDbContextFactory<[Module]Context> factory) + { + _factory = factory; + } + + public IEnumerable Get[Module]s(int ModuleId) + { + using var db = _factory.CreateDbContext(); + return db.[Module].Where(item => item.ModuleId == ModuleId).ToList(); + } + + public Models.[Module] Get[Module](int [Module]Id) + { + return Get[Module]([Module]Id, true); + } + + public Models.[Module] Get[Module](int [Module]Id, bool tracking) + { + using var db = _factory.CreateDbContext(); + if (tracking) + { + return db.[Module].Find([Module]Id); + } + else + { + return db.[Module].AsNoTracking().FirstOrDefault(item => item.[Module]Id == [Module]Id); + } + } + + public Models.[Module] Add[Module](Models.[Module] [Module]) + { + using var db = _factory.CreateDbContext(); + db.[Module].Add([Module]); + db.SaveChanges(); + return [Module]; + } + + public Models.[Module] Update[Module](Models.[Module] [Module]) + { + using var db = _factory.CreateDbContext(); + db.Entry([Module]).State = EntityState.Modified; + db.SaveChanges(); + return [Module]; + } + + public void Delete[Module](int [Module]Id) + { + using var db = _factory.CreateDbContext(); + Models.[Module] [Module] = db.[Module].Find([Module]Id); + db.[Module].Remove([Module]); + db.SaveChanges(); + } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Services/[Module]Service.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Services/[Module]Service.cs new file mode 100644 index 00000000..63d11ccc --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Services/[Module]Service.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Oqtane.Enums; +using Oqtane.Infrastructure; +using Oqtane.Models; +using Oqtane.Security; +using Oqtane.Shared; +using [Owner].Module.[Module].Repository; + +namespace [Owner].Module.[Module].Services +{ + public class Server[Module]Service : I[Module]Service + { + private readonly I[Module]Repository _[Module]Repository; + private readonly IUserPermissions _userPermissions; + private readonly ILogManager _logger; + private readonly IHttpContextAccessor _accessor; + private readonly Alias _alias; + + public Server[Module]Service(I[Module]Repository [Module]Repository, IUserPermissions userPermissions, ITenantManager tenantManager, ILogManager logger, IHttpContextAccessor accessor) + { + _[Module]Repository = [Module]Repository; + _userPermissions = userPermissions; + _logger = logger; + _accessor = accessor; + _alias = tenantManager.GetAlias(); + } + + public Task> Get[Module]sAsync(int ModuleId) + { + if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.View)) + { + return Task.FromResult(_[Module]Repository.Get[Module]s(ModuleId).ToList()); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {ModuleId}", ModuleId); + return null; + } + } + + public Task Get[Module]Async(int [Module]Id, int ModuleId) + { + if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.View)) + { + return Task.FromResult(_[Module]Repository.Get[Module]([Module]Id)); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Get Attempt {[Module]Id} {ModuleId}", [Module]Id, ModuleId); + return null; + } + } + + public Task Add[Module]Async(Models.[Module] [Module]) + { + if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, [Module].ModuleId, PermissionNames.Edit)) + { + [Module] = _[Module]Repository.Add[Module]([Module]); + _logger.Log(LogLevel.Information, this, LogFunction.Create, "[Module] Added {[Module]}", [Module]); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Add Attempt {[Module]}", [Module]); + [Module] = null; + } + return Task.FromResult([Module]); + } + + public Task Update[Module]Async(Models.[Module] [Module]) + { + if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, [Module].ModuleId, PermissionNames.Edit)) + { + [Module] = _[Module]Repository.Update[Module]([Module]); + _logger.Log(LogLevel.Information, this, LogFunction.Update, "[Module] Updated {[Module]}", [Module]); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Update Attempt {[Module]}", [Module]); + [Module] = null; + } + return Task.FromResult([Module]); + } + + public Task Delete[Module]Async(int [Module]Id, int ModuleId) + { + if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit)) + { + _[Module]Repository.Delete[Module]([Module]Id); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "[Module] Deleted {[Module]Id}", [Module]Id); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Delete Attempt {[Module]Id} {ModuleId}", [Module]Id, ModuleId); + } + return Task.CompletedTask; + } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Startup/[Module]ServerStartup.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Startup/[Module]ServerStartup.cs new file mode 100644 index 00000000..c4aaf22b --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/Startup/[Module]ServerStartup.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Oqtane.Infrastructure; +using [Owner].Module.[Module].Repository; +using [Owner].Module.[Module].Services; + +namespace [Owner].Module.[Module].Startup +{ + public class [Module]ServerStartup : IServerStartup + { + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + // not implemented + } + + public void ConfigureMvc(IMvcBuilder mvcBuilder) + { + // not implemented + } + + public void ConfigureServices(IServiceCollection services) + { + services.AddTransient(); + services.AddDbContextFactory<[Module]Context>(opt => { }, ServiceLifetime.Transient); + } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/MyCompany.MyProject.[Module]/Module.css b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/MyCompany.MyProject.[Module]/Module.css new file mode 100644 index 00000000..0856a263 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/MyCompany.MyProject.[Module]/Module.css @@ -0,0 +1 @@ +/* Module Custom Styles */ \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/MyCompany.MyProject.[Module]/Module.js b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/MyCompany.MyProject.[Module]/Module.js new file mode 100644 index 00000000..8f072470 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Server/wwwroot/Modules/MyCompany.MyProject.[Module]/Module.js @@ -0,0 +1,5 @@ +/* Module Script */ +var [Owner] = [Owner] || {}; + +[Owner].[Module] = { +}; \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Shared/Models/[Module].cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Shared/Models/[Module].cs new file mode 100644 index 00000000..7236fb25 --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Shared/Models/[Module].cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Oqtane.Models; + +namespace [Owner].Module.[Module].Models +{ + [Table("[Owner][Module]")] + public class [Module] : ModelBase + { + [Key] + public int [Module]Id { get; set; } + public int ModuleId { get; set; } + public string Name { get; set; } + } +} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/template.json b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/template.json new file mode 100644 index 00000000..a2710cdc --- /dev/null +++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/template.json @@ -0,0 +1,6 @@ +{ + "Title": "Default Module Template", + "Type": "Internal", + "Version": "10.0.0", + "Namespace": "[Owner].Module.[Module]" +}