Implemented admin crud ui for users page. Fixed some smaller issues
This commit is contained in:
@@ -5,6 +5,7 @@ using MoonCore.Extended.Helpers;
|
|||||||
using MoonCore.Models;
|
using MoonCore.Models;
|
||||||
using Moonlight.ApiServer.Database.Entities;
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
using Moonlight.Shared.Http.Requests.Admin.Users;
|
using Moonlight.Shared.Http.Requests.Admin.Users;
|
||||||
|
using Moonlight.Shared.Http.Responses.Admin.Users;
|
||||||
|
|
||||||
namespace Moonlight.ApiServer.Http.Controllers.Admin.Users;
|
namespace Moonlight.ApiServer.Http.Controllers.Admin.Users;
|
||||||
|
|
||||||
@@ -12,25 +13,25 @@ namespace Moonlight.ApiServer.Http.Controllers.Admin.Users;
|
|||||||
[Route("api/admin/users")]
|
[Route("api/admin/users")]
|
||||||
public class UsersController : Controller
|
public class UsersController : Controller
|
||||||
{
|
{
|
||||||
private readonly CrudHelper<User> CrudHelper;
|
private readonly CrudHelper<User, UserDetailResponse> CrudHelper;
|
||||||
private readonly DatabaseRepository<User> UserRepository;
|
private readonly DatabaseRepository<User> UserRepository;
|
||||||
|
|
||||||
public UsersController(CrudHelper<User> crudHelper, DatabaseRepository<User> userRepository)
|
public UsersController(CrudHelper<User, UserDetailResponse> crudHelper, DatabaseRepository<User> userRepository)
|
||||||
{
|
{
|
||||||
CrudHelper = crudHelper;
|
CrudHelper = crudHelper;
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IPagedData<User>> Get([FromQuery] int page, [FromQuery] int pageSize = 50)
|
public async Task<IPagedData<UserDetailResponse>> Get([FromQuery] int page, [FromQuery] int pageSize = 50)
|
||||||
=> await CrudHelper.Get(page, pageSize);
|
=> await CrudHelper.Get(page, pageSize);
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
public async Task<User> GetSingle(int id)
|
public async Task<UserDetailResponse> GetSingle(int id)
|
||||||
=> await CrudHelper.GetSingle(id);
|
=> await CrudHelper.GetSingle(id);
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<User> Create([FromBody] CreateUserRequest request)
|
public async Task<UserDetailResponse> Create([FromBody] CreateUserRequest request)
|
||||||
{
|
{
|
||||||
// Reformat values
|
// Reformat values
|
||||||
request.Username = request.Username.ToLower().Trim();
|
request.Username = request.Username.ToLower().Trim();
|
||||||
@@ -49,9 +50,9 @@ public class UsersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPatch("{id}")]
|
[HttpPatch("{id}")]
|
||||||
public async Task<User> Update([FromRoute] int id, [FromBody] UpdateUserRequest request)
|
public async Task<UserDetailResponse> Update([FromRoute] int id, [FromBody] UpdateUserRequest request)
|
||||||
{
|
{
|
||||||
var user = await CrudHelper.GetSingle(id);
|
var user = await CrudHelper.GetSingleModel(id);
|
||||||
|
|
||||||
// Reformat values
|
// Reformat values
|
||||||
request.Username = request.Username.ToLower().Trim();
|
request.Username = request.Username.ToLower().Trim();
|
||||||
@@ -63,7 +64,14 @@ public class UsersController : Controller
|
|||||||
|
|
||||||
if (UserRepository.Get().Any(x => x.Email == request.Email && x.Id != user.Id))
|
if (UserRepository.Get().Any(x => x.Email == request.Email && x.Id != user.Id))
|
||||||
throw new HttpApiException("A user with that email address already exists", 400);
|
throw new HttpApiException("A user with that email address already exists", 400);
|
||||||
|
|
||||||
|
// Perform hashing the password if required
|
||||||
|
if (!string.IsNullOrEmpty(request.Password))
|
||||||
|
{
|
||||||
|
request.Password = HashHelper.Hash(request.Password);
|
||||||
|
user.TokenValidTimestamp = DateTime.UtcNow; // This change will get applied by the crud helper
|
||||||
|
}
|
||||||
|
|
||||||
return await CrudHelper.Update(user, request);
|
return await CrudHelper.Update(user, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="MoonCore" Version="1.5.8" />
|
<PackageReference Include="MoonCore" Version="1.5.8" />
|
||||||
<PackageReference Include="MoonCore.Extended" Version="1.0.4" />
|
<PackageReference Include="MoonCore.Extended" Version="1.0.6" />
|
||||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.0" />
|
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.0" />
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ var databaseHelper = new DatabaseHelper(
|
|||||||
|
|
||||||
builder.Services.AddSingleton(databaseHelper);
|
builder.Services.AddSingleton(databaseHelper);
|
||||||
builder.Services.AddScoped(typeof(DatabaseRepository<>));
|
builder.Services.AddScoped(typeof(DatabaseRepository<>));
|
||||||
builder.Services.AddScoped(typeof(CrudHelper<>));
|
builder.Services.AddScoped(typeof(CrudHelper<,>));
|
||||||
|
|
||||||
builder.Services.AddDbContext<CoreDataContext>();
|
builder.Services.AddDbContext<CoreDataContext>();
|
||||||
databaseHelper.AddDbContext<CoreDataContext>();
|
databaseHelper.AddDbContext<CoreDataContext>();
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
From: "transform opacity-100 scale-100"
|
From: "transform opacity-100 scale-100"
|
||||||
To: "transform opacity-0 scale-95"
|
To: "transform opacity-0 scale-95"
|
||||||
-->
|
-->
|
||||||
<div class="@(ShowProfileNav ? "opacity-100" : "opacity-0") transition ease-out duration-100 absolute right-0 z-10 mt-2.5 w-44 origin-top-right rounded-md bg-gray-750 py-2 shadow-lg ring-1 ring-gray-100/5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
|
<div class="@(ShowProfileNav ? "opacity-100" : "opacity-0 hidden") transition ease-out duration-100 absolute right-0 z-10 mt-2.5 w-44 origin-top-right rounded-md bg-gray-750 py-2 shadow-lg ring-1 ring-gray-100/5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
|
||||||
<!-- Active: "bg-gray-50", Not Active: "" -->
|
<!-- Active: "bg-gray-50", Not Active: "" -->
|
||||||
<a href="/admin" class="block px-3 py-1 text-sm leading-6 text-gray-100 hover:text-primary-500" role="menuitem" tabindex="-1" id="user-menu-item-0">Your profile</a>
|
<a href="/admin" class="block px-3 py-1 text-sm leading-6 text-gray-100 hover:text-primary-500" role="menuitem" tabindex="-1" id="user-menu-item-0">Your profile</a>
|
||||||
<a @onclick="Logout" @onclick:preventDefault href="#" class="block px-3 py-1 text-sm leading-6 text-gray-100 hover:text-primary-500" role="menuitem" tabindex="-1" id="user-menu-item-1">Sign out</a>
|
<a @onclick="Logout" @onclick:preventDefault href="#" class="block px-3 py-1 text-sm leading-6 text-gray-100 hover:text-primary-500" role="menuitem" tabindex="-1" id="user-menu-item-1">Sign out</a>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
var url = new Uri(Navigation.Uri);
|
var url = new Uri(Navigation.Uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="relative z-40 lg:hidden transition-opacity @(Layout.ShowMobileNavigation ? "opacity-100" : "opacity-0")" role="dialog" aria-modal="true">
|
<div class="relative z-40 lg:hidden transition-opacity @(Layout.ShowMobileNavigation ? "opacity-100" : "opacity-0 hidden")" role="dialog" aria-modal="true">
|
||||||
<div class="fixed inset-0 bg-gray-800/80"></div>
|
<div class="fixed inset-0 bg-gray-800/80"></div>
|
||||||
|
|
||||||
<div class="fixed inset-0 flex justify-center bg-gray-900">
|
<div class="fixed inset-0 flex justify-center bg-gray-900">
|
||||||
|
|||||||
65
Moonlight.Client/UI/Views/Admin/Users/Index.razor
Normal file
65
Moonlight.Client/UI/Views/Admin/Users/Index.razor
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
@page "/admin/users"
|
||||||
|
|
||||||
|
@using MoonCore.Blazor.Tailwind.Forms.Components
|
||||||
|
@using MoonCore.Helpers
|
||||||
|
@using MoonCore.Models
|
||||||
|
@using Moonlight.Shared.Http.Requests.Admin.Users
|
||||||
|
@using Moonlight.Shared.Http.Responses.Admin.Users
|
||||||
|
|
||||||
|
@inject HttpApiClient HttpApiClient
|
||||||
|
|
||||||
|
<Crud TItem="UserDetailResponse"
|
||||||
|
TCreateForm="CreateUserRequest"
|
||||||
|
TUpdateForm="UpdateUserRequest"
|
||||||
|
OnConfigure="OnConfigure">
|
||||||
|
<View>
|
||||||
|
<Column TItem="UserDetailResponse" Field="@(x => x.Id)" Title="Id" />
|
||||||
|
<Column TItem="UserDetailResponse" Field="@(x => x.Username)" Title="Username" />
|
||||||
|
<Column TItem="UserDetailResponse" Field="@(x => x.Email)" Title="Email" />
|
||||||
|
</View>
|
||||||
|
</Crud>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private void OnConfigure(CrudOptions<UserDetailResponse, CreateUserRequest, UpdateUserRequest> crudOptions)
|
||||||
|
{
|
||||||
|
crudOptions.ItemName = "User";
|
||||||
|
|
||||||
|
crudOptions.ItemLoader = async (page, pageSize)
|
||||||
|
=> await HttpApiClient.GetJson<PagedData<UserDetailResponse>>($"api/admin/users?page={page}&pageSize={pageSize}");
|
||||||
|
|
||||||
|
crudOptions.SingleItemLoader = async id
|
||||||
|
=> await HttpApiClient.GetJson<UserDetailResponse>($"api/admin/users/{id}");
|
||||||
|
|
||||||
|
crudOptions.QueryIdentifier = item => item.Id.ToString(); //TODO: Make this default
|
||||||
|
|
||||||
|
crudOptions.OnCreate = async request
|
||||||
|
=> await HttpApiClient.Post("api/admin/users", request);
|
||||||
|
|
||||||
|
crudOptions.OnUpdate = async (item, request)
|
||||||
|
=> await HttpApiClient.Patch($"api/admin/users/{item.Id}", request);
|
||||||
|
|
||||||
|
crudOptions.OnDelete = async item
|
||||||
|
=> await HttpApiClient.Delete($"api/admin/users/{item.Id}");
|
||||||
|
|
||||||
|
crudOptions.OnConfigureCreate = configuration =>
|
||||||
|
{
|
||||||
|
configuration.WithField(x => x.Username);
|
||||||
|
configuration.WithField(x => x.Email);
|
||||||
|
configuration.WithField(x => x.Password)
|
||||||
|
.WithComponent<StringComponent>(component => component.Type = "password");
|
||||||
|
};
|
||||||
|
|
||||||
|
crudOptions.OnConfigureUpdate = (_, configuration) =>
|
||||||
|
{
|
||||||
|
configuration.WithField(x => x.Username);
|
||||||
|
configuration.WithField(x => x.Email);
|
||||||
|
|
||||||
|
configuration.WithField(x => x.Password, fieldConfiguration =>
|
||||||
|
{
|
||||||
|
fieldConfiguration.Description = "Optional. Specify if you want to change this accounts password";
|
||||||
|
})
|
||||||
|
.WithComponent<StringComponent>(component => component.Type = "password");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Moonlight.Shared.Http.Responses.Admin.Users;
|
||||||
|
|
||||||
|
public class UserDetailResponse
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user