diff --git a/src/Turnierplan.App/Client/src/app/portal/components/rbac-offcanvas/rbac-offcanvas.component.html b/src/Turnierplan.App/Client/src/app/portal/components/rbac-offcanvas/rbac-offcanvas.component.html index 758ef58d..c1a85dfd 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/rbac-offcanvas/rbac-offcanvas.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/components/rbac-offcanvas/rbac-offcanvas.component.html @@ -41,7 +41,7 @@ } @if (assignment.isInherited) { -
+
} } - -{{ principal.principalId }} +{{ displayName$ | async }} diff --git a/src/Turnierplan.App/Client/src/app/portal/components/rbac-principal/rbac-principal.component.ts b/src/Turnierplan.App/Client/src/app/portal/components/rbac-principal/rbac-principal.component.ts index 63a450ba..4cdba553 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/rbac-principal/rbac-principal.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/components/rbac-principal/rbac-principal.component.ts @@ -1,14 +1,33 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { PrincipalDto, PrincipalKind } from '../../../api'; +import { PrincipalsService } from '../../../api/services/principals.service'; +import { catchError, Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; @Component({ selector: 'tp-rbac-principal', standalone: false, templateUrl: './rbac-principal.component.html' }) -export class RbacPrincipalComponent { +export class RbacPrincipalComponent implements OnInit { protected readonly PrincipalKind = PrincipalKind; + protected displayName$?: Observable; + @Input() public principal!: PrincipalDto; + + constructor(private readonly principalsService: PrincipalsService) {} + + public ngOnInit(): void { + this.displayName$ = this.principalsService + .getPrincipalName({ + principalKind: this.principal.kind, + principalId: this.principal.principalId + }) + .pipe( + map((result) => result.name), + catchError(() => of(this.principal.principalId)) + ); + } } diff --git a/src/Turnierplan.App/Endpoints/Principals/GetPrincipalNameEndpoint.cs b/src/Turnierplan.App/Endpoints/Principals/GetPrincipalNameEndpoint.cs new file mode 100644 index 00000000..256d0a62 --- /dev/null +++ b/src/Turnierplan.App/Endpoints/Principals/GetPrincipalNameEndpoint.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Mvc; +using Turnierplan.Core.ApiKey; +using Turnierplan.Core.RoleAssignment; +using Turnierplan.Core.User; + +namespace Turnierplan.App.Endpoints.Principals; + +internal sealed class GetPrincipalNameEndpoint : EndpointBase +{ + protected override HttpMethod Method => HttpMethod.Get; + + protected override string Route => "/api/principals/name"; + + protected override Delegate Handler => Handle; + + private static async Task Handle( + [FromQuery] PrincipalKind principalKind, + [FromQuery] Guid principalId, + IApiKeyRepository apiKeyRepository, + IUserRepository userRepository) + { + string? name; + + switch (principalKind) + { + case PrincipalKind.ApiKey: + var apiKey = await apiKeyRepository.GetByPrincipalIdAsync(principalId).ConfigureAwait(false); + name = apiKey?.Name; + break; + case PrincipalKind.User: + var user = await userRepository.GetByPrincipalIdAsync(principalId).ConfigureAwait(false); + name = user?.Name; + break; + default: + return Results.BadRequest("Invalid principal kind specified."); + } + + return name is not null + ? Results.Ok(new GetPrincipalNameEndpointResponse(name)) + : Results.NotFound(); + } + + public sealed record GetPrincipalNameEndpointResponse(string Name); +} diff --git a/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs b/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs index 6bd5c7d5..b468ece4 100644 --- a/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs +++ b/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs @@ -4,5 +4,7 @@ namespace Turnierplan.Core.ApiKey; public interface IApiKeyRepository : IRepositoryWithPublicId { + Task GetByPrincipalIdAsync(Guid id); + Task> GetRequestsInTimeRange(ApiKey apiKey, DateTime start, DateTime end); } diff --git a/src/Turnierplan.Core/User/IUserRepository.cs b/src/Turnierplan.Core/User/IUserRepository.cs index 7b94f56a..e3703563 100644 --- a/src/Turnierplan.Core/User/IUserRepository.cs +++ b/src/Turnierplan.Core/User/IUserRepository.cs @@ -8,5 +8,7 @@ public interface IUserRepository : IRepository Task GetByIdAsync(Guid id); + Task GetByPrincipalIdAsync(Guid id); + Task GetByEmailAsync(string email); } diff --git a/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs b/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs index 608f65d9..ff3d68c1 100644 --- a/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs +++ b/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs @@ -23,6 +23,11 @@ public ApiKeyRepository(TurnierplanContext context) .FirstOrDefaultAsync(); } + public Task GetByPrincipalIdAsync(Guid id) + { + return DbSet.Where(x => x.PrincipalId == id).FirstOrDefaultAsync(); + } + public Task> GetRequestsInTimeRange(ApiKey apiKey, DateTime start, DateTime end) { return _requests.Where(x => x.ApiKey == apiKey && x.Timestamp >= start && x.Timestamp <= end) diff --git a/src/Turnierplan.Dal/Repositories/UserRepository.cs b/src/Turnierplan.Dal/Repositories/UserRepository.cs index 3fb65403..d4b4deac 100644 --- a/src/Turnierplan.Dal/Repositories/UserRepository.cs +++ b/src/Turnierplan.Dal/Repositories/UserRepository.cs @@ -16,6 +16,11 @@ public Task> GetAllUsers() return DbSet.Where(x => x.Id.Equals(id)).FirstOrDefaultAsync(); } + public Task GetByPrincipalIdAsync(Guid id) + { + return DbSet.Where(x => x.PrincipalId == id).FirstOrDefaultAsync(); + } + public Task GetByEmailAsync(string email) { var normalizedEMail = User.NormalizeEmail(email);