Enhance role selection functionality in ListSelectableRolesQueryHandler and update frontend role display
- Updated ListSelectableRolesQueryHandler to include IIdentityService and ICurrentUser for user authorization and profile retrieval. - Added logic to filter out the current user's role and admin roles from the selectable roles list. - Enhanced CreateRoleRequestDialog to display role options with additional information, including max configs and IP limits, using localization support. - Updated i18n resources to include new role option formatting for both Russian and English.
This commit is contained in:
+13
-2
@@ -1,11 +1,15 @@
|
|||||||
|
using PnvPanel.Application.Auth;
|
||||||
using PnvPanel.Application.Common.Interfaces;
|
using PnvPanel.Application.Common.Interfaces;
|
||||||
using PnvPanel.Application.Common.Messaging;
|
using PnvPanel.Application.Common.Messaging;
|
||||||
using PnvPanel.Application.Common.Models;
|
using PnvPanel.Application.Common.Models;
|
||||||
|
|
||||||
namespace PnvPanel.Application.Support.ListSelectableRoles;
|
namespace PnvPanel.Application.Support.ListSelectableRoles;
|
||||||
|
|
||||||
public sealed class ListSelectableRolesQueryHandler(IRoleService roleService)
|
public sealed class ListSelectableRolesQueryHandler(
|
||||||
: IQueryHandler<ListSelectableRolesQuery, Result<IReadOnlyList<RoleDto>>>
|
IRoleService roleService,
|
||||||
|
IIdentityService identityService,
|
||||||
|
ICurrentUser currentUser
|
||||||
|
) : IQueryHandler<ListSelectableRolesQuery, Result<IReadOnlyList<RoleDto>>>
|
||||||
{
|
{
|
||||||
// Совпадает со значением Infrastructure.Identity.RoleNames.Admin — см. пояснение в
|
// Совпадает со значением Infrastructure.Identity.RoleNames.Admin — см. пояснение в
|
||||||
// CreateRoleRequestTicketCommandHandler (Application не может ссылаться на Infrastructure).
|
// CreateRoleRequestTicketCommandHandler (Application не может ссылаться на Infrastructure).
|
||||||
@@ -16,9 +20,16 @@ public sealed class ListSelectableRolesQueryHandler(IRoleService roleService)
|
|||||||
CancellationToken cancellationToken
|
CancellationToken cancellationToken
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
if (currentUser.UserId is not { } userId)
|
||||||
|
return Result.Failure<IReadOnlyList<RoleDto>>(AuthErrors.Unauthorized);
|
||||||
|
|
||||||
|
var profile = await identityService.GetProfileAsync(userId, cancellationToken);
|
||||||
var roles = await roleService.ListRolesAsync(cancellationToken);
|
var roles = await roleService.ListRolesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// Без admin (нельзя запросить) и без текущей роли пользователя (уже есть — нечего запрашивать).
|
||||||
var selectable = roles
|
var selectable = roles
|
||||||
.Where(r => !r.Name.Equals(AdminRoleName, StringComparison.OrdinalIgnoreCase))
|
.Where(r => !r.Name.Equals(AdminRoleName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Where(r => profile is null || r.Id != profile.RoleId)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return Result.Success<IReadOnlyList<RoleDto>>(selectable);
|
return Result.Success<IReadOnlyList<RoleDto>>(selectable);
|
||||||
|
|||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
using NSubstitute;
|
||||||
|
using PnvPanel.Application.Common.Interfaces;
|
||||||
|
using PnvPanel.Application.Support.ListSelectableRoles;
|
||||||
|
using PnvPanel.Application.Tests.TestSupport;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace PnvPanel.Application.Tests.Support.ListSelectableRoles;
|
||||||
|
|
||||||
|
public class ListSelectableRolesQueryHandlerTests
|
||||||
|
{
|
||||||
|
private readonly IRoleService _roleService = Substitute.For<IRoleService>();
|
||||||
|
private readonly IIdentityService _identityService = Substitute.For<IIdentityService>();
|
||||||
|
|
||||||
|
private static CurrentUserProfile Profile(Guid userId, Guid roleId, string role) =>
|
||||||
|
new(
|
||||||
|
userId,
|
||||||
|
"user",
|
||||||
|
roleId,
|
||||||
|
role,
|
||||||
|
IsActivated: true,
|
||||||
|
IsBlocked: false,
|
||||||
|
MaxConfigs: 3,
|
||||||
|
MaxIpLimit: 1,
|
||||||
|
SubscriptionToken: "token"
|
||||||
|
);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Handle_ExcludesAdminAndCurrentUserRole()
|
||||||
|
{
|
||||||
|
var userId = Guid.NewGuid();
|
||||||
|
var currentRoleId = Guid.NewGuid();
|
||||||
|
var extendedRoleId = Guid.NewGuid();
|
||||||
|
var adminRoleId = Guid.NewGuid();
|
||||||
|
|
||||||
|
_roleService
|
||||||
|
.ListRolesAsync(Arg.Any<CancellationToken>())
|
||||||
|
.Returns(
|
||||||
|
new List<RoleDto>
|
||||||
|
{
|
||||||
|
new(adminRoleId, "admin", -1, -1, IsSystem: true),
|
||||||
|
new(currentRoleId, "user", 3, 1, IsSystem: true),
|
||||||
|
new(extendedRoleId, "extended", 10, 5, IsSystem: false),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
_identityService
|
||||||
|
.GetProfileAsync(userId, Arg.Any<CancellationToken>())
|
||||||
|
.Returns(Profile(userId, currentRoleId, "user"));
|
||||||
|
|
||||||
|
var currentUser = FakeCurrentUser.Authenticated(userId, "alice");
|
||||||
|
var handler = new ListSelectableRolesQueryHandler(
|
||||||
|
_roleService,
|
||||||
|
_identityService,
|
||||||
|
currentUser
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = await handler.Handle(new ListSelectableRolesQuery(), CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.Equal(["extended"], result.Value.Select(r => r.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Handle_WhenProfileMissing_StillExcludesAdmin()
|
||||||
|
{
|
||||||
|
var userId = Guid.NewGuid();
|
||||||
|
var userRoleId = Guid.NewGuid();
|
||||||
|
var adminRoleId = Guid.NewGuid();
|
||||||
|
|
||||||
|
_roleService
|
||||||
|
.ListRolesAsync(Arg.Any<CancellationToken>())
|
||||||
|
.Returns(
|
||||||
|
new List<RoleDto>
|
||||||
|
{
|
||||||
|
new(adminRoleId, "admin", -1, -1, IsSystem: true),
|
||||||
|
new(userRoleId, "user", 3, 1, IsSystem: true),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
_identityService
|
||||||
|
.GetProfileAsync(userId, Arg.Any<CancellationToken>())
|
||||||
|
.Returns((CurrentUserProfile?)null);
|
||||||
|
|
||||||
|
var currentUser = FakeCurrentUser.Authenticated(userId, "alice");
|
||||||
|
var handler = new ListSelectableRolesQueryHandler(
|
||||||
|
_roleService,
|
||||||
|
_identityService,
|
||||||
|
currentUser
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = await handler.Handle(new ListSelectableRolesQuery(), CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.Equal(["user"], result.Value.Select(r => r.Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,7 +107,11 @@ export function CreateRoleRequestDialog() {
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
{rolesQuery.data?.map((role) => (
|
{rolesQuery.data?.map((role) => (
|
||||||
<SelectItem key={role.id} value={role.id}>
|
<SelectItem key={role.id} value={role.id}>
|
||||||
{role.name}
|
{t('support.roleOption', {
|
||||||
|
name: role.name,
|
||||||
|
configs: role.maxConfigs < 0 ? t('unlimited') : role.maxConfigs,
|
||||||
|
ip: role.maxIpLimit < 0 ? t('unlimited') : role.maxIpLimit,
|
||||||
|
})}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ const resources = {
|
|||||||
existingRole: 'Существующая роль',
|
existingRole: 'Существующая роль',
|
||||||
newRole: 'Новая роль',
|
newRole: 'Новая роль',
|
||||||
selectRole: 'Выберите роль',
|
selectRole: 'Выберите роль',
|
||||||
|
roleOption: '{{name}} — конфигов: {{configs}}, IP: {{ip}}',
|
||||||
newRoleName: 'Название роли',
|
newRoleName: 'Название роли',
|
||||||
newRoleMaxConfigs: 'Количество конфигов (-1 — без лимита)',
|
newRoleMaxConfigs: 'Количество конфигов (-1 — без лимита)',
|
||||||
newRoleMaxIpLimit: 'Количество IP (-1 — без лимита)',
|
newRoleMaxIpLimit: 'Количество IP (-1 — без лимита)',
|
||||||
@@ -548,6 +549,7 @@ const resources = {
|
|||||||
existingRole: 'Existing role',
|
existingRole: 'Existing role',
|
||||||
newRole: 'New role',
|
newRole: 'New role',
|
||||||
selectRole: 'Select a role',
|
selectRole: 'Select a role',
|
||||||
|
roleOption: '{{name}} — configs: {{configs}}, IP: {{ip}}',
|
||||||
newRoleName: 'Role name',
|
newRoleName: 'Role name',
|
||||||
newRoleMaxConfigs: 'Max configs (-1 = unlimited)',
|
newRoleMaxConfigs: 'Max configs (-1 = unlimited)',
|
||||||
newRoleMaxIpLimit: 'Max IPs (-1 = unlimited)',
|
newRoleMaxIpLimit: 'Max IPs (-1 = unlimited)',
|
||||||
|
|||||||
Reference in New Issue
Block a user