Added database loading logic

This commit is contained in:
2025-01-14 14:05:15 +01:00
parent 313f6e046a
commit 6115dcf8e1
13 changed files with 139 additions and 35 deletions

View File

@@ -9,18 +9,19 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="Added basic configuration"> <list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="Added admin page navigation">
<change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/IHopFrameAuthHandler.cs" afterDir="false" /> <change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/IModelManager.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameLayout.razor" afterDir="false" /> <change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/ModelManager.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameNavigation.razor" afterDir="false" /> <change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Models/Post.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameSideMenu.razor" afterDir="false" /> <change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Services/AuthService.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameHome.razor" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/wwwroot/hopframe.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Config/HopFrameConfig.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/HopFrameConfig.cs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Services/IContextExplorer.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/IContextExplorer.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/HopFrame.Web.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/HopFrame.Web.csproj" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/ContextExplorer.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/ContextExplorer.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/ServiceCollectionExtensions.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/ServiceCollectionExtensions.cs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameLayout.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameLayout.razor" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/_Imports.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/_Imports.razor" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameNavigation.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameNavigation.razor" afterDir="false" />
<change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/Components/Pages/Home.razor" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Components/Pages/Home.razor" afterDir="false" />
<change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/DatabaseContext.cs" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/DatabaseContext.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/Models/User.cs" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Models/User.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
@@ -62,6 +63,9 @@
&quot;second&quot;: &quot;2d65fdcb-5f13-45ad-a7ba-91dd4a88d6e4&quot; &quot;second&quot;: &quot;2d65fdcb-5f13-45ad-a7ba-91dd4a88d6e4&quot;
} }
}</component> }</component>
<component name="HighlightingSettingsPerFile">
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d858ddb35a8e36df5573b7612542f9ad50f426b8ab43818587d1ac65fab14829/DatabaseGeneratedAttribute.cs" root0="FORCE_HIGHLIGHTING" />
</component>
<component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" /> <component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" />
<component name="ProjectColorInfo">{ <component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 3 &quot;associatedIndex&quot;: 3
@@ -140,7 +144,7 @@
<workItem from="1736788853462" duration="780000" /> <workItem from="1736788853462" duration="780000" />
<workItem from="1736845367516" duration="283000" /> <workItem from="1736845367516" duration="283000" />
<workItem from="1736845655122" duration="165000" /> <workItem from="1736845655122" duration="165000" />
<workItem from="1736845825812" duration="9326000" /> <workItem from="1736845825812" duration="13786000" />
</task> </task>
<task id="LOCAL-00001" summary="Added basic configuration"> <task id="LOCAL-00001" summary="Added basic configuration">
<option name="closed" value="true" /> <option name="closed" value="true" />
@@ -150,7 +154,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1736850899254</updated> <updated>1736850899254</updated>
</task> </task>
<option name="localTasksCounter" value="2" /> <task id="LOCAL-00002" summary="Added admin page navigation">
<option name="closed" value="true" />
<created>1736855209077</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1736855209077</updated>
</task>
<option name="localTasksCounter" value="3" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -161,6 +173,7 @@
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" /> <option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
<MESSAGE value="Added basic configuration" /> <MESSAGE value="Added basic configuration" />
<option name="LAST_COMMIT_MESSAGE" value="Added basic configuration" /> <MESSAGE value="Added admin page navigation" />
<option name="LAST_COMMIT_MESSAGE" value="Added admin page navigation" />
</component> </component>
</project> </project>

View File

@@ -4,5 +4,6 @@ namespace HopFrame.Core.Services;
public interface IContextExplorer { public interface IContextExplorer {
public IEnumerable<string> GetTableNames(); public IEnumerable<string> GetTableNames();
public TableConfig? GetTable(string name); public TableConfig? GetTable(string tableName);
public ITableManager? GetTableManager(string tableName);
} }

View File

@@ -0,0 +1,5 @@
namespace HopFrame.Core.Services;
public interface ITableManager {
public Task<IEnumerable<object>> LoadPage(int page, int perPage = 25);
}

View File

@@ -1,8 +1,9 @@
using HopFrame.Core.Config; using HopFrame.Core.Config;
using Microsoft.EntityFrameworkCore;
namespace HopFrame.Core.Services.Implementations; namespace HopFrame.Core.Services.Implementations;
internal sealed class ContextExplorer(HopFrameConfig config) : IContextExplorer { internal sealed class ContextExplorer(HopFrameConfig config, IServiceProvider provider) : IContextExplorer {
public IEnumerable<string> GetTableNames() { public IEnumerable<string> GetTableNames() {
foreach (var context in config.Contexts) { foreach (var context in config.Contexts) {
foreach (var table in context.Tables) { foreach (var table in context.Tables) {
@@ -12,13 +13,28 @@ internal sealed class ContextExplorer(HopFrameConfig config) : IContextExplorer
} }
} }
public TableConfig? GetTable(string name) { public TableConfig? GetTable(string tableName) {
foreach (var context in config.Contexts) { foreach (var context in config.Contexts) {
var table = context.Tables.FirstOrDefault(table => table.PropertyName == name); var table = context.Tables.FirstOrDefault(table => table.PropertyName == tableName);
if (table is not null) if (table is not null)
return table; return table;
} }
return null; return null;
} }
public ITableManager? GetTableManager(string tableName) {
foreach (var context in config.Contexts) {
var table = context.Tables.FirstOrDefault(table => table.PropertyName == tableName);
if (table is null) continue;
var dbContext = provider.GetService(context.ContextType) as DbContext;
if (dbContext is null) return null;
var type = typeof(TableManager<>).MakeGenericType(table.TableType);
return Activator.CreateInstance(type, dbContext) as ITableManager;
}
return null;
}
} }

View File

@@ -0,0 +1,15 @@
using Microsoft.EntityFrameworkCore;
namespace HopFrame.Core.Services.Implementations;
internal sealed class TableManager<TModel>(DbContext context) : ITableManager where TModel : class {
public async Task<IEnumerable<object>> LoadPage(int page, int perPage = 25) {
var table = context.Set<TModel>();
return await table
.Skip(page * perPage)
.Take(perPage)
.ToArrayAsync();
}
}

View File

@@ -1,5 +1,6 @@
@using HopFrame.Core.Config @using HopFrame.Core.Config
@using HopFrame.Core.Services @using HopFrame.Core.Services
@using Microsoft.Extensions.DependencyInjection
@inherits LayoutComponentBase @inherits LayoutComponentBase
<link rel="stylesheet" type='text/css' href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css" /> <link rel="stylesheet" type='text/css' href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css" />
@@ -29,19 +30,16 @@
</FluentFooter> </FluentFooter>
</FluentLayout> </FluentLayout>
@inject IServiceProvider Provider @inject IHopFrameAuthHandler Handler
@inject HopFrameConfig Config @inject HopFrameConfig Config
@inject NavigationManager Navigator @inject NavigationManager Navigator
@code { @code {
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
if (!string.IsNullOrEmpty(Config.BasePolicy)) { var authorized = await Handler.IsAuthenticatedAsync(Config.BasePolicy);
var handler = Provider.GetService(Config.AuthHandler!) as IHopFrameAuthHandler;
var authorized = await handler!.IsAuthenticatedAsync(Config.BasePolicy);
if (!authorized) { if (!authorized) {
Navigator.NavigateTo((Config.LoginPageRewrite ?? "/login") + "?redirect=" + Navigator.Uri); Navigator.NavigateTo((Config.LoginPageRewrite ?? "/login") + "?redirect=/" + Navigator.ToBaseRelativePath(Navigator.Uri), true);
}
} }
} }

View File

@@ -1,28 +1,28 @@
@using System.Text @using System.Text
@using HopFrame.Core.Config @using HopFrame.Core.Config
@using HopFrame.Core.Services @using HopFrame.Core.Services
<FluentHeader Class="hopframe-header"> <FluentHeader Class="hopframe-header">
<a href="/admin" style="text-decoration: none; color: @Color.Neutral.ToAttributeValue()">HopFrame</a> <a href="/admin" style="text-decoration: none; color: @Color.Neutral.ToAttributeValue();">HopFrame</a>
<FluentSpacer/>
@if (Config.DisplayUserInfo) { @if (Config.DisplayUserInfo) {
<FluentPersona Name="@_displayName" Initials="@_initials" ImageSize="32px" TextPosition="TextPosition.Start"/> <FluentPersona Name="@_displayName" Initials="@_initials" ImageSize="32px" TextPosition="TextPosition.Start" Style="margin-left: auto"/>
} }
</FluentHeader> </FluentHeader>
@inject HopFrameConfig Config @inject HopFrameConfig Config
@inject IServiceProvider Provider @inject IHopFrameAuthHandler Handler
@code { @code {
private string? _displayName; private string? _displayName;
private string? _initials; private string? _initials;
private string? _searchValue;
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
if (Config.DisplayUserInfo) { if (Config.DisplayUserInfo) {
var handler = Provider.GetService(Config.AuthHandler!) as IHopFrameAuthHandler; _displayName = await Handler.GetCurrentUserDisplayNameAsync();
_displayName = await handler!.GetCurrentUserDisplayNameAsync();
_initials = GetInitials(_displayName); _initials = GetInitials(_displayName);
} }
} }

View File

@@ -8,11 +8,22 @@
Welcome to your new Fluent Blazor app. Welcome to your new Fluent Blazor app.
@inject IContextExplorer Explorer @inject IContextExplorer Explorer
@inject DatabaseContext Context
@code { @code {
protected override void OnInitialized() { protected override async Task OnInitializedAsync() {
Console.WriteLine(string.Join(", ", Explorer.GetTableNames())); for (int i = 0; i < 10; i++) {
Context.Users.Add(new() {
Email = "leon@ladenbau-hoppe.de",
Id = Guid.CreateVersion7()
});
}
await Context.SaveChangesAsync();
var manager = Explorer.GetTableManager("Users");
var page = await manager!.LoadPage(0);
Console.WriteLine(string.Join(", ", page));
} }
} }

View File

@@ -6,5 +6,13 @@ namespace HopFrame.Testing;
public class DatabaseContext(DbContextOptions<DatabaseContext> options) : DbContext(options) { public class DatabaseContext(DbContextOptions<DatabaseContext> options) : DbContext(options) {
public DbSet<User> Users { get; set; } public DbSet<User> Users { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Post>()
.HasOne<User>()
.WithMany();
}
} }

View File

@@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace HopFrame.Testing.Models;
public class Post {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[MaxLength(255)]
public required string Caption { get; set; }
public required string Content { get; set; }
[ForeignKey("author")]
public User? Author { get; set; }
}

View File

@@ -7,4 +7,8 @@ public class User {
public string? Password { get; set; } public string? Password { get; set; }
public string? FirstName { get; set; } public string? FirstName { get; set; }
public string? LastName { get; set; } public string? LastName { get; set; }
public override string ToString() {
return Id.ToString();
}
} }

View File

@@ -1,6 +1,8 @@
using HopFrame.Core.Services;
using HopFrame.Testing; using HopFrame.Testing;
using Microsoft.FluentUI.AspNetCore.Components; using Microsoft.FluentUI.AspNetCore.Components;
using HopFrame.Testing.Components; using HopFrame.Testing.Components;
using HopFrame.Testing.Services;
using HopFrame.Web; using HopFrame.Web;
using HopFrame.Web.Components.Pages; using HopFrame.Web.Components.Pages;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -17,10 +19,12 @@ builder.Services.AddDbContext<DatabaseContext>(options => {
}); });
builder.Services.AddHopFrame(options => { builder.Services.AddHopFrame(options => {
options.DisplayUserInfo(false); options.SetAuthHandler<AuthService>();
options.AddDbContext<DatabaseContext>(); options.AddDbContext<DatabaseContext>();
}); });
builder.Services.AddTransient<IHopFrameAuthHandler, AuthService>();
var app = builder.Build(); var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.

View File

@@ -0,0 +1,12 @@
using HopFrame.Core.Services;
namespace HopFrame.Testing.Services;
public class AuthService : IHopFrameAuthHandler {
public Task<bool> IsAuthenticatedAsync(string? policy) {
return Task.FromResult(true);
}
public Task<string> GetCurrentUserDisplayNameAsync() {
return Task.FromResult("Leon Hoppe");
}
}