Skip to content

Commit bf30e81

Browse files
committed
Add User Groups feature to Identity & Blazor UI
- Introduce Group, GroupRole, UserGroup entities and migrations - Add permissions and endpoints for group CRUD and membership - Implement group-based role inheritance for users - Seed system groups ("All Users", "Administrators") - Update claims generation to include group roles - Add Blazor UI for group management and membership - Extend API client for new group endpoints and DTOs - Automatically add new users to default groups - Add IGroupRoleService for resolving group-derived roles
1 parent 2289c08 commit bf30e81

56 files changed

Lines changed: 5349 additions & 313 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/BuildingBlocks/Shared/Identity/IdentityPermissionConstants.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,13 @@ public static class Sessions
2525
public const string ViewAll = "Permissions.Sessions.ViewAll";
2626
public const string RevokeAll = "Permissions.Sessions.RevokeAll";
2727
}
28+
29+
public static class Groups
30+
{
31+
public const string View = "Permissions.Groups.View";
32+
public const string Create = "Permissions.Groups.Create";
33+
public const string Update = "Permissions.Groups.Update";
34+
public const string Delete = "Permissions.Groups.Delete";
35+
public const string ManageMembers = "Permissions.Groups.ManageMembers";
36+
}
2837
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace FSH.Modules.Identity.Contracts.DTOs;
2+
3+
public class GroupDto
4+
{
5+
public Guid Id { get; set; }
6+
public string Name { get; set; } = default!;
7+
public string? Description { get; set; }
8+
public bool IsDefault { get; set; }
9+
public bool IsSystemGroup { get; set; }
10+
public int MemberCount { get; set; }
11+
public IReadOnlyCollection<string>? RoleIds { get; set; }
12+
public IReadOnlyCollection<string>? RoleNames { get; set; }
13+
public DateTime CreatedAt { get; set; }
14+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace FSH.Modules.Identity.Contracts.DTOs;
2+
3+
public class GroupMemberDto
4+
{
5+
public string UserId { get; set; } = default!;
6+
public string? UserName { get; set; }
7+
public string? Email { get; set; }
8+
public string? FirstName { get; set; }
9+
public string? LastName { get; set; }
10+
public DateTime AddedAt { get; set; }
11+
public string? AddedBy { get; set; }
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace FSH.Modules.Identity.Contracts.Services;
2+
3+
/// <summary>
4+
/// Service for retrieving roles derived from group memberships.
5+
/// </summary>
6+
public interface IGroupRoleService
7+
{
8+
/// <summary>
9+
/// Gets all role names that a user has through their group memberships.
10+
/// </summary>
11+
/// <param name="userId">The user ID to get group roles for.</param>
12+
/// <param name="ct">Cancellation token.</param>
13+
/// <returns>List of distinct role names from all groups the user belongs to.</returns>
14+
Task<IReadOnlyList<string>> GetUserGroupRolesAsync(string userId, CancellationToken ct = default);
15+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using Mediator;
2+
3+
namespace FSH.Modules.Identity.Contracts.v1.Groups.AddUsersToGroup;
4+
5+
public sealed record AddUsersToGroupCommand(Guid GroupId, List<string> UserIds) : ICommand<AddUsersToGroupResponse>;
6+
7+
public sealed record AddUsersToGroupResponse(int AddedCount, List<string> AlreadyMemberUserIds);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using FSH.Modules.Identity.Contracts.DTOs;
2+
using Mediator;
3+
4+
namespace FSH.Modules.Identity.Contracts.v1.Groups.CreateGroup;
5+
6+
public sealed record CreateGroupCommand(
7+
string Name,
8+
string? Description,
9+
bool IsDefault,
10+
List<string>? RoleIds) : ICommand<GroupDto>;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using Mediator;
2+
3+
namespace FSH.Modules.Identity.Contracts.v1.Groups.DeleteGroup;
4+
5+
public sealed record DeleteGroupCommand(Guid Id) : ICommand<Unit>;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using FSH.Modules.Identity.Contracts.DTOs;
2+
using Mediator;
3+
4+
namespace FSH.Modules.Identity.Contracts.v1.Groups.GetGroupById;
5+
6+
public sealed record GetGroupByIdQuery(Guid Id) : IQuery<GroupDto>;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using FSH.Modules.Identity.Contracts.DTOs;
2+
using Mediator;
3+
4+
namespace FSH.Modules.Identity.Contracts.v1.Groups.GetGroupMembers;
5+
6+
public sealed record GetGroupMembersQuery(Guid GroupId) : IQuery<IEnumerable<GroupMemberDto>>;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using FSH.Modules.Identity.Contracts.DTOs;
2+
using Mediator;
3+
4+
namespace FSH.Modules.Identity.Contracts.v1.Groups.GetGroups;
5+
6+
public sealed record GetGroupsQuery(string? SearchTerm = null) : IQuery<IEnumerable<GroupDto>>;

0 commit comments

Comments
 (0)