Started creating the docs

This commit is contained in:
2024-07-23 19:15:01 +02:00
parent 643ceeb607
commit e31dccc298
28 changed files with 332 additions and 129 deletions

View File

@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="UserContentModel"> <component name="UserContentModel">
<attachedFolders /> <attachedFolders>
<Path>docs</Path>
</attachedFolders>
<explicitIncludes /> <explicitIncludes />
<explicitExcludes /> <explicitExcludes />
</component> </component>

View File

@@ -7,6 +7,6 @@ public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder); base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\DatabaseTest\\bin\\Debug\\net8.0\\test.db;Mode=ReadWrite;"); optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\RestApiTest\\bin\\Debug\\net8.0\\test.db;Mode=ReadWrite;");
} }
} }

View File

@@ -1,13 +1,13 @@
namespace HopFrame.Api.Models; namespace HopFrame.Api.Models;
public struct SingleValueResult<T>(T value) { public struct SingleValueResult<TValue>(TValue value) {
public T Value { get; set; } = value; public TValue Value { get; set; } = value;
public static implicit operator T(SingleValueResult<T> v) { public static implicit operator TValue(SingleValueResult<TValue> v) {
return v.Value; return v.Value;
} }
public static implicit operator SingleValueResult<T>(T v) { public static implicit operator SingleValueResult<TValue>(TValue v) {
return new SingleValueResult<T>(v); return new SingleValueResult<TValue>(v);
} }
} }

View File

@@ -24,4 +24,6 @@ public interface IUserService {
Task DeleteUser(User user); Task DeleteUser(User user);
Task<bool> CheckUserPassword(User user, string password); Task<bool> CheckUserPassword(User user, string password);
Task ChangePassword(User user, string password);
} }

View File

@@ -113,4 +113,16 @@ internal sealed class UserService<TDbContext>(TDbContext context) : IUserService
return entry.Password == hash; return entry.Password == hash;
} }
public async Task ChangePassword(User user, string password) {
var entry = await context.Users
.Where(entry => entry.Id == user.Id.ToString())
.SingleOrDefaultAsync();
if (entry is null) return;
var hash = EncryptionManager.Hash(password, Encoding.Default.GetBytes(user.CreatedAt.ToString(CultureInfo.InvariantCulture)));
entry.Password = hash;
await context.SaveChangesAsync();
}
} }

View File

@@ -0,0 +1,76 @@
@switch (Type) {
case HopIcon.Reload:
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="@GetClass()" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2z"/>
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466"/>
</svg>
break;
case HopIcon.ArrowUp:
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="@GetClass()" viewBox="0 0 16 16">
<path d="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/>
</svg>
break;
case HopIcon.ArrowDown:
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="@GetClass()" viewBox="0 0 16 16">
<path d="m7.247 4.86-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z"/>
</svg>
break;
case HopIcon.Cross:
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="@GetClass()" viewBox="0 0 16 16">
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
</svg>
break;
case HopIcon.User:
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="@GetClass()" viewBox="0 0 16 16">
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6"/>
</svg>
break;
case HopIcon.Group:
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="@GetClass()" viewBox="0 0 16 16">
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6m-5.784 6A2.24 2.24 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.3 6.3 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1zM4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5"/>
</svg>
break;
case HopIcon.Logout:
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="@GetClass()" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M6 12.5a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5v2a.5.5 0 0 1-1 0v-2A1.5 1.5 0 0 1 6.5 2h8A1.5 1.5 0 0 1 16 3.5v9a1.5 1.5 0 0 1-1.5 1.5h-8A1.5 1.5 0 0 1 5 12.5v-2a.5.5 0 0 1 1 0z"/>
<path fill-rule="evenodd" d="M.146 8.354a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L1.707 7.5H10.5a.5.5 0 0 1 0 1H1.707l2.147 2.146a.5.5 0 0 1-.708.708z"/>
</svg>
break;
}
<style>
svg.bi-nav {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
</style>
@code {
[Parameter] public HopIcon Type { get; set; }
[Parameter] public bool NavIcon { get; set; }
public enum HopIcon {
Reload,
ArrowUp,
ArrowDown,
User,
Group,
Logout,
Cross
}
private string GetClass() {
return NavIcon ? "bi-nav" : "bi";
}
}

View File

@@ -21,20 +21,20 @@ main {
align-items: center; align-items: center;
} }
.top-row ::deep a, .top-row ::deep .btn-link { .top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap; white-space: nowrap;
margin-left: 1.5rem; margin-left: 1.5rem;
text-decoration: none; text-decoration: none;
} }
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover { .top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline; text-decoration: underline;
} }
.top-row ::deep a:first-child { .top-row ::deep a:first-child {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
@media (max-width: 640.98px) { @media (max-width: 640.98px) {
.top-row { .top-row {
@@ -88,9 +88,9 @@ main {
z-index: 1000; z-index: 1000;
} }
#blazor-error-ui .dismiss { #blazor-error-ui .dismiss {
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
right: 0.75rem; right: 0.75rem;
top: 0.5rem; top: 0.5rem;
} }

View File

@@ -1,7 +1,9 @@
@using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Routing
@using HopFrame.Web.Pages.Administration.Components
<div class="top-row ps-3 navbar navbar-dark"> <div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="">HopFrame</a> <a class="navbar-brand" href="/administration">HopFrame</a>
</div> </div>
</div> </div>
@@ -11,26 +13,19 @@
<nav class="flex-column" style="display: flex; height: 100%"> <nav class="flex-column" style="display: flex; height: 100%">
<div class="nav-item px-3"> <div class="nav-item px-3">
<NavLink class="nav-link" href="administration/users"> <NavLink class="nav-link" href="administration/users">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-fill" viewBox="0 0 16 16"> <HopIconDisplay Type="HopIconDisplay.HopIcon.User" NavIcon="true"/> Users
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6"/>
</svg> Users
</NavLink> </NavLink>
</div> </div>
<div class="nav-item px-3"> <div class="nav-item px-3">
<NavLink class="nav-link" href="administration/groups"> <NavLink class="nav-link" href="administration/groups">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-people-fill" viewBox="0 0 16 16"> <HopIconDisplay Type="HopIconDisplay.HopIcon.Group" NavIcon="true"/> Groups
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6m-5.784 6A2.24 2.24 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.3 6.3 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1zM4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5"/>
</svg> Groups
</NavLink> </NavLink>
</div> </div>
<div class="nav-item px-3" style="margin-top: auto"> <div class="nav-item px-3" style="margin-top: auto">
<NavLink class="nav-link" href="login"> <NavLink class="nav-link" href="login">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-left" viewBox="0 0 16 16"> <HopIconDisplay Type="HopIconDisplay.HopIcon.Logout" NavIcon="true"/> Logout
<path fill-rule="evenodd" d="M6 12.5a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5v2a.5.5 0 0 1-1 0v-2A1.5 1.5 0 0 1 6.5 2h8A1.5 1.5 0 0 1 16 3.5v9a1.5 1.5 0 0 1-1.5 1.5h-8A1.5 1.5 0 0 1 5 12.5v-2a.5.5 0 0 1 1 0z"/>
<path fill-rule="evenodd" d="M.146 8.354a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L1.707 7.5H10.5a.5.5 0 0 1 0 1H1.707l2.147 2.146a.5.5 0 0 1-.708.708z"/>
</svg> Logout
</NavLink> </NavLink>
</div> </div>
</nav> </nav>

View File

@@ -17,7 +17,7 @@
.top-row { .top-row {
height: 3.5rem; height: 3.5rem;
background-color: rgba(0,0,0,0.4); background-color: rgba(0, 0, 0, 0.4);
} }
.navbar-brand { .navbar-brand {
@@ -34,32 +34,20 @@
background-size: cover; background-size: cover;
} }
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item { .nav-item {
font-size: 0.9rem; font-size: 0.9rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
.nav-item:first-of-type { .nav-item:first-of-type {
padding-top: 1rem; padding-top: 1rem;
} }
.nav-item:last-of-type { .nav-item:last-of-type {
padding-bottom: 1rem; padding-bottom: 1rem;
} }
.nav-item ::deep .nav-link { .nav-item ::deep .nav-link {
color: #d7d7d7; color: #d7d7d7;
background: none; background: none;
border: none; border: none;
@@ -69,15 +57,15 @@
align-items: center; align-items: center;
line-height: 3rem; line-height: 3rem;
width: 100%; width: 100%;
} }
.nav-item ::deep a.active { .nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37); background-color: rgba(255, 255, 255, 0.37);
color: white; color: white;
} }
.nav-item ::deep .nav-link:hover { .nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1); background-color: rgba(255, 255, 255, 0.1);
color: white; color: white;
} }

View File

@@ -8,6 +8,7 @@
@using static Microsoft.AspNetCore.Components.Web.RenderMode @using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Forms
@using HopFrame.Web.Components @using HopFrame.Web.Components
@using HopFrame.Web.Pages.Administration.Components
@layout AdminLayout @layout AdminLayout
@rendermode InteractiveServer @rendermode InteractiveServer
@@ -34,6 +35,10 @@
<InputText type="text" class="form-control" id="username" required @bind-Value="User.Username"/> <InputText type="text" class="form-control" id="username" required @bind-Value="User.Username"/>
<ValidationMessage For="() => User.Username"/> <ValidationMessage For="() => User.Username"/>
</div> </div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<InputText type="password" class="form-control" id="password" required @bind-Value="_password"/>
</div>
<div class="mb-3"> <div class="mb-3">
<label for="groups" class="form-label">Groups</label> <label for="groups" class="form-label">Groups</label>
@@ -43,9 +48,7 @@
@foreach (var group in _groups) { @foreach (var group in _groups) {
<li class="list-group-item"> <li class="list-group-item">
<button type="button" class="btn btn-danger btn-sm" style="margin-right: 15px" @onclick="() => RemoveGroup(group)"> <button type="button" class="btn btn-danger btn-sm" style="margin-right: 15px" @onclick="() => RemoveGroup(group)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16"> <HopIconDisplay Type="HopIconDisplay.HopIcon.Cross"/>
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
</svg>
</button> </button>
<span>@group.Name.Replace("group.", "")</span> <span>@group.Name.Replace("group.", "")</span>
@@ -78,9 +81,7 @@
@foreach (var perm in User.Permissions.Where(perm => !perm.PermissionName.StartsWith("group."))) { @foreach (var perm in User.Permissions.Where(perm => !perm.PermissionName.StartsWith("group."))) {
<li class="list-group-item"> <li class="list-group-item">
<button type="button" class="btn btn-danger btn-sm" style="margin-right: 15px" @onclick="() => RemovePermission(perm)"> <button type="button" class="btn btn-danger btn-sm" style="margin-right: 15px" @onclick="() => RemovePermission(perm)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16"> <HopIconDisplay Type="HopIconDisplay.HopIcon.Cross"/>
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
</svg>
</button> </button>
<span>@perm.PermissionName</span> <span>@perm.PermissionName</span>
@@ -119,6 +120,7 @@
private IList<PermissionGroup> _allGroups = new List<PermissionGroup>(); private IList<PermissionGroup> _allGroups = new List<PermissionGroup>();
private string _selectedGroup; private string _selectedGroup;
private string _permissionToAdd; private string _permissionToAdd;
private string _password;
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
if (Guid.TryParse(UserId, out var guid)) { if (Guid.TryParse(UserId, out var guid)) {
@@ -165,6 +167,10 @@
if (result.IsConfirmed) { if (result.IsConfirmed) {
await Users.UpdateUser(User); await Users.UpdateUser(User);
if (!string.IsNullOrWhiteSpace(_password)) {
await Users.ChangePassword(User, _password);
}
await Alerts.FireAsync(new SweetAlertOptions { await Alerts.FireAsync(new SweetAlertOptions {
Title = "User edited!", Title = "User edited!",
Icon = SweetAlertIcon.Success, Icon = SweetAlertIcon.Success,

View File

@@ -16,13 +16,10 @@
<AuthorizedView Permission="@AdminPermissions.ViewUsers" RedirectIfUnauthorized="login?redirect=/administration/users"/> <AuthorizedView Permission="@AdminPermissions.ViewUsers" RedirectIfUnauthorized="login?redirect=/administration/users"/>
<div class="title"> <div class="title">
<h3 style="user-select: none"> <h3>
Users administration Users administration
<span style="cursor: pointer" @onclick="Reload"> <span class="reload" @onclick="Reload">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16"> <HopIconDisplay Type="HopIconDisplay.HopIcon.Reload"/>
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2z"/>
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466"/>
</svg>
</span> </span>
</h3> </h3>
@@ -36,63 +33,36 @@
<table class="table table-stripped table-hover"> <table class="table table-stripped table-hover">
<thead> <thead>
<tr> <tr>
<th scope="col" style="user-select: none">#</th> <th scope="col">#</th>
<th scope="col" style="user-select: none"> <th scope="col">
<span style="cursor: pointer;" @onclick="() => OrderBy(OrderType.Email)">E-Mail</span> <span class="sorter" @onclick="() => OrderBy(OrderType.Email)">E-Mail</span>
@if (_currentOrder == OrderType.Email) { @if (_currentOrder == OrderType.Email) {
@if (_currentOrderDirection == OrderDirection.Asc) { <HopIconDisplay Type="_currentOrderDirection == OrderDirection.Desc ? HopIconDisplay.HopIcon.ArrowDown : HopIconDisplay.HopIcon.ArrowUp"/>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-caret-down-fill" viewBox="0 0 16 16">
<path d="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/>
</svg>
}
else {
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-caret-up-fill" viewBox="0 0 16 16">
<path d="m7.247 4.86-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z"/>
</svg>
}
} }
</th> </th>
<th scope="col" style="user-select: none"> <th scope="col">
<span style="cursor: pointer;" @onclick="() => OrderBy(OrderType.Username)">Username</span> <span class="sorter" @onclick="() => OrderBy(OrderType.Username)">Username</span>
@if (_currentOrder == OrderType.Username) { @if (_currentOrder == OrderType.Username) {
@if (_currentOrderDirection == OrderDirection.Asc) { <HopIconDisplay Type="_currentOrderDirection == OrderDirection.Desc ? HopIconDisplay.HopIcon.ArrowDown : HopIconDisplay.HopIcon.ArrowUp"/>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-caret-down-fill" viewBox="0 0 16 16">
<path d="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/>
</svg>
}
else {
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-caret-up-fill" viewBox="0 0 16 16">
<path d="m7.247 4.86-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z"/>
</svg>
}
} }
</th> </th>
<th scope="col" style="user-select: none"> <th scope="col">
<span style="cursor: pointer;" @onclick="() => OrderBy(OrderType.Registered)">Registered</span> <span class="sorter" @onclick="() => OrderBy(OrderType.Registered)">Registered</span>
@if (_currentOrder == OrderType.Registered) { @if (_currentOrder == OrderType.Registered) {
@if (_currentOrderDirection == OrderDirection.Asc) { <HopIconDisplay Type="_currentOrderDirection == OrderDirection.Desc ? HopIconDisplay.HopIcon.ArrowDown : HopIconDisplay.HopIcon.ArrowUp"/>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-caret-down-fill" viewBox="0 0 16 16">
<path d="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/>
</svg>
}
else {
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-caret-up-fill" viewBox="0 0 16 16">
<path d="m7.247 4.86-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z"/>
</svg>
}
} }
</th> </th>
<th scope="col" style="user-select: none">Primary Group</th> <th scope="col">Primary Group</th>
@if (_hasEditPrivileges || _hasDeletePrivileges) { @if (_hasEditPrivileges || _hasDeletePrivileges) {
<th scope="col" style="user-select: none">Actions</th> <th scope="col">Actions</th>
} }
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach (var user in _users) { @foreach (var user in _users) {
<tr> <tr>
<th scope="row">@user.Id</th> <td class="bold">@user.Id</td>
<td>@user.Email</td> <td>@user.Email</td>
<td>@user.Username</td> <td>@user.Username</td>
<td>@user.CreatedAt</td> <td>@user.CreatedAt</td>

View File

@@ -3,3 +3,15 @@
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
} }
th, h3 {
user-select: none;
}
.reload, .sorter {
cursor: pointer;
}
.bold {
font-weight: bold;
}

View File

@@ -13,8 +13,12 @@ public static class ServiceCollectionExtensions {
services.AddHttpClient(); services.AddHttpClient();
services.AddScoped<IAuthService, AuthService<TDbContext>>(); services.AddScoped<IAuthService, AuthService<TDbContext>>();
services.AddTransient<AuthMiddleware>(); services.AddTransient<AuthMiddleware>();
// Component library's
services.AddSweetAlert2(); services.AddSweetAlert2();
//TODO: Use https://blazorstrap.io/V5/V5
services.AddHopFrameAuthentication<TDbContext>(); services.AddHopFrameAuthentication<TDbContext>();
return services; return services;

View File

@@ -4,7 +4,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Database", "HopFra
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{58703056-8DAD-4221-BBE3-42425D2F4929}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{58703056-8DAD-4221-BBE3-42425D2F4929}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DatabaseTest", "DatabaseTest\DatabaseTest.csproj", "{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestApiTest", "RestApiTest\RestApiTest.csproj", "{921159CE-AF75-44C3-A3F9-6B9B1A4E85CF}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Security", "HopFrame.Security\HopFrame.Security.csproj", "{7F82E1C6-4A42-4337-9E03-2EE6429D004F}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HopFrame.Security", "HopFrame.Security\HopFrame.Security.csproj", "{7F82E1C6-4A42-4337-9E03-2EE6429D004F}"
EndProject EndProject

View File

@@ -3,7 +3,7 @@ using HopFrame.Security.Authorization;
using HopFrame.Security.Claims; using HopFrame.Security.Claims;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace DatabaseTest.Controllers; namespace RestApiTest.Controllers;
[ApiController] [ApiController]
[Route("test")] [Route("test")]

View File

@@ -1,12 +1,12 @@
using HopFrame.Database; using HopFrame.Database;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace DatabaseTest; namespace RestApiTest;
public class DatabaseContext : HopDbContextBase { public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder); base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\DatabaseTest\\bin\\Debug\\net8.0\\test.db;Mode=ReadWrite;"); optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\RestApiTest\\bin\\Debug\\net8.0\\test.db;Mode=ReadWrite;");
} }
} }

View File

@@ -1,4 +1,4 @@
using DatabaseTest; using RestApiTest;
using HopFrame.Api.Extensions; using HopFrame.Api.Extensions;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;

View File

@@ -0,0 +1,34 @@
@startuml ApiModels
namespace HopFrame.Security {
class UserLogin {
+Email: string
+Password: string
}
class UserRegister {
+Username: string
+Email: string
+Password: string
}
}
namespace HopFrame.Web {
class RegisterData {
+RepeatedPassword: string
}
}
namespace HopFrame.Api {
class SingleValueResult<TValue> {
+Value: TValue
}
class UserPasswordValidation {
+Password: string
}
}
UserRegister <|-- RegisterData
@enduml

View File

@@ -0,0 +1,37 @@
@startuml BaseModels
set namespaceSeparator none
namespace HopFrame.Database {
class User {
+Id: Guid
+Username: string
+Email: string
+CreatedAt: DateTime
+Permissions: IList<Permission>
}
class Permission {
+Id: long
+PermissionName: string
+Owner: Guid
+GrantedAt: DateTime
}
class PermissionGroup {
+Name: string
+IsDefaultGroup: bool
+Description: string
+CreatedAt: DateTime
+Permissions: IList<Permission>
}
interface IPermissionOwner {}
}
IPermissionOwner <|-- User
IPermissionOwner <|-- PermissionGroup
User .. Permission
PermissionGroup .. Permission
@enduml

View File

@@ -0,0 +1,41 @@
@startuml DatabaseModels
set namespaceSeparator none
namespace HopFrame.Database {
class UserEntry {
+Id: string
+Username: string
+Email: string
+Password: string
+CreatedAt: DateTime
}
class TokenEntry {
{static} +RefreshTokenType: int = 0
{static} +AccessTokenType: int = 1
+Type: int
+Token: string
+UserId: string
+CreatedAt: DateTime
}
class PermissionEntry {
+RecordId: long
+PermissionText: string
+UserId: string
+GrantedAt: DateTime
}
class GroupEntry {
+Name: string
+Default: bool
+Description: string
+CreatedAt: DateTime
}
}
UserEntry *-- TokenEntry
UserEntry *-- PermissionEntry
@enduml

View File

@@ -0,0 +1,21 @@
# Models for HopFrame
This page shows all models that HopFrame uses.
## Base Models
These are the models used by the various database services.
![](img/BaseModels.svg)
## API Models
These are the models used by the REST API and the Blazor API.
![](img/ApiModels.svg)
## Database Models
These are the models that correspond to the scheme in the Database
![](img/DatabaseModels.svg)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB