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" />
</component>
<component name="ChangeListManager">
<list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="Added basic configuration">
<change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/IHopFrameAuthHandler.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameLayout.razor" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameNavigation.razor" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameSideMenu.razor" 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" />
<list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="Added admin page navigation">
<change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/IModelManager.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/ModelManager.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Models/Post.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Services/AuthService.cs" 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.Web/HopFrame.Web.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/HopFrame.Web.csproj" 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/_Imports.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/_Imports.razor" 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.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/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/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" />
</list>
<option name="SHOW_DIALOG" value="false" />
@@ -62,6 +63,9 @@
&quot;second&quot;: &quot;2d65fdcb-5f13-45ad-a7ba-91dd4a88d6e4&quot;
}
}</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="ProjectColorInfo">{
&quot;associatedIndex&quot;: 3
@@ -140,7 +144,7 @@
<workItem from="1736788853462" duration="780000" />
<workItem from="1736845367516" duration="283000" />
<workItem from="1736845655122" duration="165000" />
<workItem from="1736845825812" duration="9326000" />
<workItem from="1736845825812" duration="13786000" />
</task>
<task id="LOCAL-00001" summary="Added basic configuration">
<option name="closed" value="true" />
@@ -150,7 +154,15 @@
<option name="project" value="LOCAL" />
<updated>1736850899254</updated>
</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 />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -161,6 +173,7 @@
<component name="VcsManagerConfiguration">
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
<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>
</project>

View File

@@ -4,5 +4,6 @@ namespace HopFrame.Core.Services;
public interface IContextExplorer {
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 Microsoft.EntityFrameworkCore;
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() {
foreach (var context in config.Contexts) {
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) {
var table = context.Tables.FirstOrDefault(table => table.PropertyName == name);
var table = context.Tables.FirstOrDefault(table => table.PropertyName == tableName);
if (table is not null)
return table;
}
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.Services
@using Microsoft.Extensions.DependencyInjection
@inherits LayoutComponentBase
<link rel="stylesheet" type='text/css' href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css" />
@@ -29,19 +30,16 @@
</FluentFooter>
</FluentLayout>
@inject IServiceProvider Provider
@inject IHopFrameAuthHandler Handler
@inject HopFrameConfig Config
@inject NavigationManager Navigator
@code {
protected override async Task OnInitializedAsync() {
if (!string.IsNullOrEmpty(Config.BasePolicy)) {
var handler = Provider.GetService(Config.AuthHandler!) as IHopFrameAuthHandler;
var authorized = await handler!.IsAuthenticatedAsync(Config.BasePolicy);
var authorized = await Handler.IsAuthenticatedAsync(Config.BasePolicy);
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 HopFrame.Core.Config
@using HopFrame.Core.Services
<FluentHeader Class="hopframe-header">
<a href="/admin" style="text-decoration: none; color: @Color.Neutral.ToAttributeValue()">HopFrame</a>
<FluentSpacer/>
<a href="/admin" style="text-decoration: none; color: @Color.Neutral.ToAttributeValue();">HopFrame</a>
@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>
@inject HopFrameConfig Config
@inject IServiceProvider Provider
@inject IHopFrameAuthHandler Handler
@code {
private string? _displayName;
private string? _initials;
private string? _searchValue;
protected override async Task OnInitializedAsync() {
if (Config.DisplayUserInfo) {
var handler = Provider.GetService(Config.AuthHandler!) as IHopFrameAuthHandler;
_displayName = await handler!.GetCurrentUserDisplayNameAsync();
_displayName = await Handler.GetCurrentUserDisplayNameAsync();
_initials = GetInitials(_displayName);
}
}

View File

@@ -8,11 +8,22 @@
Welcome to your new Fluent Blazor app.
@inject IContextExplorer Explorer
@inject DatabaseContext Context
@code {
protected override void OnInitialized() {
Console.WriteLine(string.Join(", ", Explorer.GetTableNames()));
protected override async Task OnInitializedAsync() {
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 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? FirstName { 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 Microsoft.FluentUI.AspNetCore.Components;
using HopFrame.Testing.Components;
using HopFrame.Testing.Services;
using HopFrame.Web;
using HopFrame.Web.Components.Pages;
using Microsoft.EntityFrameworkCore;
@@ -17,10 +19,12 @@ builder.Services.AddDbContext<DatabaseContext>(options => {
});
builder.Services.AddHopFrame(options => {
options.DisplayUserInfo(false);
options.SetAuthHandler<AuthService>();
options.AddDbContext<DatabaseContext>();
});
builder.Services.AddTransient<IHopFrameAuthHandler, AuthService>();
var app = builder.Build();
// 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");
}
}