From 415d361532bc586af8502258e8a973149610cf04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 21 Jun 2025 09:35:20 +0200 Subject: [PATCH 1/8] Change ApiKey ID to Guid --- .../Security/AccessValidatorTest.cs | 2 +- .../RoleAssignments/CreateRoleAssignmentEndpoint.cs | 8 ++++---- .../RoleAssignments/DeleteRoleAssignmentEndpoint.cs | 6 +++--- .../RoleAssignments/GetRoleAssignmentsEndpoint.cs | 8 ++++++-- src/Turnierplan.App/Helpers/RbacScopeHelper.cs | 2 +- .../Mapping/Rules/RoleAssignmentMappingRule.cs | 2 +- src/Turnierplan.App/Security/AccessValidator.cs | 6 +++--- src/Turnierplan.Core/ApiKey/ApiKey.cs | 8 ++++---- src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs | 2 +- .../RoleAssignment/IRoleAssignmentRepository.cs | 2 +- src/Turnierplan.Core/RoleAssignment/RoleAssignment.cs | 2 +- src/Turnierplan.Core/SeedWork/Entity.cs | 4 +++- .../SeedWork/IEntityWithRoleAssignments.cs | 2 +- .../RoleAssignmentEntityTypeConfiguration.cs | 2 +- src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs | 2 +- src/Turnierplan.Dal/Repositories/DocumentRepository.cs | 2 +- src/Turnierplan.Dal/Repositories/FolderRepository.cs | 2 +- src/Turnierplan.Dal/Repositories/ImageRepository.cs | 2 +- .../Repositories/OrganizationRepository.cs | 2 +- .../Repositories/RepositoryBaseWithPublicId.cs | 4 ++-- .../Repositories/RoleAssignmentRepositoryBase.cs | 2 +- src/Turnierplan.Dal/Repositories/TournamentRepository.cs | 2 +- src/Turnierplan.Dal/Repositories/VenueRepository.cs | 2 +- 23 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs b/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs index 52bb9ae1..ef31afcd 100644 --- a/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs +++ b/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs @@ -42,7 +42,7 @@ public void IsActionAllowed___When_Called_With_Indirect_Target___Returns_Expecte organization.AddRoleAssignment(Role.Contributor, otherPrincipal); void Test(Func factory) - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { var target = factory(); diff --git a/src/Turnierplan.App/Endpoints/RoleAssignments/CreateRoleAssignmentEndpoint.cs b/src/Turnierplan.App/Endpoints/RoleAssignments/CreateRoleAssignmentEndpoint.cs index ddd14de3..2b26c6f2 100644 --- a/src/Turnierplan.App/Endpoints/RoleAssignments/CreateRoleAssignmentEndpoint.cs +++ b/src/Turnierplan.App/Endpoints/RoleAssignments/CreateRoleAssignmentEndpoint.cs @@ -68,17 +68,17 @@ private static async Task Handle( : await task.ConfigureAwait(false); } - private static async Task CreateRoleAssignmentAsync( + private static async Task CreateRoleAssignmentAsync( CreateRoleAssignmentEndpointRequest request, - IRepositoryWithPublicId repository, + IRepositoryWithPublicId repository, PublicId targetId, IAccessValidator accessValidator, IApiKeyRepository apiKeyRepository, IUserRepository userRepository, - IRoleAssignmentRepository roleAssignmentRepository, + IRoleAssignmentRepository roleAssignmentRepository, IMapper mapper, CancellationToken cancellationToken) - where T : Entity, IEntityWithRoleAssignments + where TEntity : Entity, IEntityWithRoleAssignments { var entity = await repository.GetByPublicIdAsync(targetId).ConfigureAwait(false); diff --git a/src/Turnierplan.App/Endpoints/RoleAssignments/DeleteRoleAssignmentEndpoint.cs b/src/Turnierplan.App/Endpoints/RoleAssignments/DeleteRoleAssignmentEndpoint.cs index f255d6e3..ae05a04c 100644 --- a/src/Turnierplan.App/Endpoints/RoleAssignments/DeleteRoleAssignmentEndpoint.cs +++ b/src/Turnierplan.App/Endpoints/RoleAssignments/DeleteRoleAssignmentEndpoint.cs @@ -60,13 +60,13 @@ private static async Task Handle( : await task.ConfigureAwait(false); } - private static async Task DeleteRoleAssignmentAsync( - IRepositoryWithPublicId repository, + private static async Task DeleteRoleAssignmentAsync( + IRepositoryWithPublicId repository, PublicId targetId, IAccessValidator accessValidator, Guid roleAssignmentId, CancellationToken cancellationToken) - where T : Entity, IEntityWithRoleAssignments + where TEntity : Entity, IEntityWithRoleAssignments { var entity = await repository.GetByPublicIdAsync(targetId).ConfigureAwait(false); diff --git a/src/Turnierplan.App/Endpoints/RoleAssignments/GetRoleAssignmentsEndpoint.cs b/src/Turnierplan.App/Endpoints/RoleAssignments/GetRoleAssignmentsEndpoint.cs index da41bcb4..cb6617b0 100644 --- a/src/Turnierplan.App/Endpoints/RoleAssignments/GetRoleAssignmentsEndpoint.cs +++ b/src/Turnierplan.App/Endpoints/RoleAssignments/GetRoleAssignmentsEndpoint.cs @@ -54,8 +54,12 @@ private static async Task Handle( : await task.ConfigureAwait(false); } - private static async Task GetRoleAssignmentsAsync(IRepositoryWithPublicId repository, PublicId targetId, IAccessValidator accessValidator, IMapper mapper) - where T : Entity, IEntityWithRoleAssignments + private static async Task GetRoleAssignmentsAsync( + IRepositoryWithPublicId repository, + PublicId targetId, + IAccessValidator accessValidator, + IMapper mapper) + where TEntity : Entity, IEntityWithRoleAssignments { var entity = await repository.GetByPublicIdAsync(targetId).ConfigureAwait(false); diff --git a/src/Turnierplan.App/Helpers/RbacScopeHelper.cs b/src/Turnierplan.App/Helpers/RbacScopeHelper.cs index 4805c003..6e683bd3 100644 --- a/src/Turnierplan.App/Helpers/RbacScopeHelper.cs +++ b/src/Turnierplan.App/Helpers/RbacScopeHelper.cs @@ -8,7 +8,7 @@ namespace Turnierplan.App.Helpers; internal static partial class RbacScopeHelper { public static string GetScopeId(this T entity) - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { return $"{typeof(T).Name}:{entity.PublicId.ToString()}"; } diff --git a/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs b/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs index dcc03da9..89aef876 100644 --- a/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs +++ b/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs @@ -12,7 +12,7 @@ namespace Turnierplan.App.Mapping.Rules; internal abstract class RoleAssignmentMappingRuleBase : MappingRuleBase, RoleAssignmentDto> - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { protected override RoleAssignmentDto Map(IMapper mapper, MappingContext context, RoleAssignment source) { diff --git a/src/Turnierplan.App/Security/AccessValidator.cs b/src/Turnierplan.App/Security/AccessValidator.cs index 9aea835d..6e27ba9e 100644 --- a/src/Turnierplan.App/Security/AccessValidator.cs +++ b/src/Turnierplan.App/Security/AccessValidator.cs @@ -12,7 +12,7 @@ namespace Turnierplan.App.Security; internal interface IAccessValidator { bool IsActionAllowed(IEntityWithRoleAssignments target, Actions.Action action) - where T : Entity, IEntityWithRoleAssignments; + where T : Entity, IEntityWithRoleAssignments; } internal sealed class AccessValidator : IAccessValidator @@ -25,7 +25,7 @@ public AccessValidator(IHttpContextAccessor contextAccessor) } public bool IsActionAllowed(IEntityWithRoleAssignments target, Actions.Action action) - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { if (_httpContext.IsCurrentUserAdministrator()) { @@ -38,7 +38,7 @@ public bool IsActionAllowed(IEntityWithRoleAssignments target, Actions.Act } internal static bool IsActionAllowed(IEntityWithRoleAssignments target, Actions.Action action, Principal principal) - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { var activePrincipalRoles = target.RoleAssignments .Where(x => x.Principal.Equals(principal)) diff --git a/src/Turnierplan.Core/ApiKey/ApiKey.cs b/src/Turnierplan.Core/ApiKey/ApiKey.cs index 76e50c67..5c7d58a5 100644 --- a/src/Turnierplan.Core/ApiKey/ApiKey.cs +++ b/src/Turnierplan.Core/ApiKey/ApiKey.cs @@ -3,7 +3,7 @@ namespace Turnierplan.Core.ApiKey; -public sealed class ApiKey : Entity, IEntityWithRoleAssignments, IEntityWithOrganization +public sealed class ApiKey : Entity, IEntityWithRoleAssignments, IEntityWithOrganization { internal readonly List> _roleAssignments = new(); internal readonly List _requests = new(); @@ -12,7 +12,7 @@ public ApiKey(Organization.Organization organization, string name, string? descr { organization._apiKeys.Add(this); - Id = 0; + Id = Guid.NewGuid(); PublicId = new PublicId.PublicId(); Organization = organization; Name = name; @@ -23,7 +23,7 @@ public ApiKey(Organization.Organization organization, string name, string? descr IsActive = true; } - internal ApiKey(long id, PublicId.PublicId publicId, string name, string description, string secretHash, DateTime createdAt, DateTime expiryDate, bool isActive) + internal ApiKey(Guid id, PublicId.PublicId publicId, string name, string description, string secretHash, DateTime createdAt, DateTime expiryDate, bool isActive) { Id = id; PublicId = publicId; @@ -35,7 +35,7 @@ internal ApiKey(long id, PublicId.PublicId publicId, string name, string descrip IsActive = isActive; } - public override long Id { get; protected set; } + public override Guid Id { get; protected set; } public PublicId.PublicId PublicId { get; } diff --git a/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs b/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs index 6bd5c7d5..0783e6b5 100644 --- a/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs +++ b/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs @@ -2,7 +2,7 @@ namespace Turnierplan.Core.ApiKey; -public interface IApiKeyRepository : IRepositoryWithPublicId +public interface IApiKeyRepository : IRepositoryWithPublicId { Task> GetRequestsInTimeRange(ApiKey apiKey, DateTime start, DateTime end); } diff --git a/src/Turnierplan.Core/RoleAssignment/IRoleAssignmentRepository.cs b/src/Turnierplan.Core/RoleAssignment/IRoleAssignmentRepository.cs index e2a745af..68d16c75 100644 --- a/src/Turnierplan.Core/RoleAssignment/IRoleAssignmentRepository.cs +++ b/src/Turnierplan.Core/RoleAssignment/IRoleAssignmentRepository.cs @@ -3,4 +3,4 @@ namespace Turnierplan.Core.RoleAssignment; public interface IRoleAssignmentRepository : IRepository, Guid> - where T : Entity, IEntityWithRoleAssignments; + where T : Entity, IEntityWithRoleAssignments; diff --git a/src/Turnierplan.Core/RoleAssignment/RoleAssignment.cs b/src/Turnierplan.Core/RoleAssignment/RoleAssignment.cs index d1dec9b0..26cb3fcd 100644 --- a/src/Turnierplan.Core/RoleAssignment/RoleAssignment.cs +++ b/src/Turnierplan.Core/RoleAssignment/RoleAssignment.cs @@ -3,7 +3,7 @@ namespace Turnierplan.Core.RoleAssignment; public sealed class RoleAssignment : Entity - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { internal RoleAssignment(T scope, Role role, Principal principal, string? description = null) { diff --git a/src/Turnierplan.Core/SeedWork/Entity.cs b/src/Turnierplan.Core/SeedWork/Entity.cs index 66c549f7..e4e5bdea 100644 --- a/src/Turnierplan.Core/SeedWork/Entity.cs +++ b/src/Turnierplan.Core/SeedWork/Entity.cs @@ -1,6 +1,8 @@ namespace Turnierplan.Core.SeedWork; -public abstract class Entity +public abstract class Entity; + +public abstract class Entity : Entity { public abstract TIdentifier Id { get; protected set; } } diff --git a/src/Turnierplan.Core/SeedWork/IEntityWithRoleAssignments.cs b/src/Turnierplan.Core/SeedWork/IEntityWithRoleAssignments.cs index e79a62f7..21f5551d 100644 --- a/src/Turnierplan.Core/SeedWork/IEntityWithRoleAssignments.cs +++ b/src/Turnierplan.Core/SeedWork/IEntityWithRoleAssignments.cs @@ -3,7 +3,7 @@ namespace Turnierplan.Core.SeedWork; public interface IEntityWithRoleAssignments : IEntityWithPublicId - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { string Name { get; } diff --git a/src/Turnierplan.Dal/EntityConfigurations/RoleAssignmentEntityTypeConfiguration.cs b/src/Turnierplan.Dal/EntityConfigurations/RoleAssignmentEntityTypeConfiguration.cs index c81deafa..bd8733f7 100644 --- a/src/Turnierplan.Dal/EntityConfigurations/RoleAssignmentEntityTypeConfiguration.cs +++ b/src/Turnierplan.Dal/EntityConfigurations/RoleAssignmentEntityTypeConfiguration.cs @@ -7,7 +7,7 @@ namespace Turnierplan.Dal.EntityConfigurations; public sealed class RoleAssignmentEntityTypeConfiguration : IEntityTypeConfiguration> - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { public void Configure(EntityTypeBuilder> builder) { diff --git a/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs b/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs index 608f65d9..72f9cefe 100644 --- a/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs +++ b/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs @@ -4,7 +4,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class ApiKeyRepository : RepositoryBaseWithPublicId, IApiKeyRepository +internal sealed class ApiKeyRepository : RepositoryBaseWithPublicId, IApiKeyRepository { private readonly DbSet _requests; diff --git a/src/Turnierplan.Dal/Repositories/DocumentRepository.cs b/src/Turnierplan.Dal/Repositories/DocumentRepository.cs index b45b4930..9ce0e2a1 100644 --- a/src/Turnierplan.Dal/Repositories/DocumentRepository.cs +++ b/src/Turnierplan.Dal/Repositories/DocumentRepository.cs @@ -4,7 +4,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class DocumentRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Documents), IDocumentRepository +internal sealed class DocumentRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Documents), IDocumentRepository { public async Task GetByPublicIdAsync(PublicId id, bool includeTournamentDetails = false) { diff --git a/src/Turnierplan.Dal/Repositories/FolderRepository.cs b/src/Turnierplan.Dal/Repositories/FolderRepository.cs index a12a0368..cefb0d5d 100644 --- a/src/Turnierplan.Dal/Repositories/FolderRepository.cs +++ b/src/Turnierplan.Dal/Repositories/FolderRepository.cs @@ -4,7 +4,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class FolderRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Folders), IFolderRepository +internal sealed class FolderRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Folders), IFolderRepository { public override Task GetByPublicIdAsync(PublicId id) { diff --git a/src/Turnierplan.Dal/Repositories/ImageRepository.cs b/src/Turnierplan.Dal/Repositories/ImageRepository.cs index 398f7aab..c92c0638 100644 --- a/src/Turnierplan.Dal/Repositories/ImageRepository.cs +++ b/src/Turnierplan.Dal/Repositories/ImageRepository.cs @@ -4,7 +4,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class ImageRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Images), IImageRepository +internal sealed class ImageRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Images), IImageRepository { public override Task GetByPublicIdAsync(PublicId id) { diff --git a/src/Turnierplan.Dal/Repositories/OrganizationRepository.cs b/src/Turnierplan.Dal/Repositories/OrganizationRepository.cs index d6137650..3bc0ca07 100644 --- a/src/Turnierplan.Dal/Repositories/OrganizationRepository.cs +++ b/src/Turnierplan.Dal/Repositories/OrganizationRepository.cs @@ -5,7 +5,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class OrganizationRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Organizations), IOrganizationRepository +internal sealed class OrganizationRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Organizations), IOrganizationRepository { public override Task GetByPublicIdAsync(PublicId id) { diff --git a/src/Turnierplan.Dal/Repositories/RepositoryBaseWithPublicId.cs b/src/Turnierplan.Dal/Repositories/RepositoryBaseWithPublicId.cs index 1d6e22c1..646c081e 100644 --- a/src/Turnierplan.Dal/Repositories/RepositoryBaseWithPublicId.cs +++ b/src/Turnierplan.Dal/Repositories/RepositoryBaseWithPublicId.cs @@ -4,8 +4,8 @@ namespace Turnierplan.Dal.Repositories; -public abstract class RepositoryBaseWithPublicId : RepositoryBase, IRepositoryWithPublicId - where TEntity : Entity, IEntityWithPublicId +public abstract class RepositoryBaseWithPublicId : RepositoryBase, IRepositoryWithPublicId + where TEntity : Entity, IEntityWithPublicId { protected RepositoryBaseWithPublicId(IUnitOfWork unitOfWork, DbSet dbSet) : base(unitOfWork, dbSet) diff --git a/src/Turnierplan.Dal/Repositories/RoleAssignmentRepositoryBase.cs b/src/Turnierplan.Dal/Repositories/RoleAssignmentRepositoryBase.cs index 6319ecc7..11be992d 100644 --- a/src/Turnierplan.Dal/Repositories/RoleAssignmentRepositoryBase.cs +++ b/src/Turnierplan.Dal/Repositories/RoleAssignmentRepositoryBase.cs @@ -11,7 +11,7 @@ namespace Turnierplan.Dal.Repositories; internal abstract class RoleAssignmentRepositoryBase(TurnierplanContext context, DbSet> dbSet) : RepositoryBase, Guid>(context, dbSet), IRoleAssignmentRepository - where T : Entity, IEntityWithRoleAssignments; + where T : Entity, IEntityWithRoleAssignments; internal sealed class ApiKeyRoleAssignmentRepository(TurnierplanContext context) : RoleAssignmentRepositoryBase(context, context.ApiKeyRoleAssignments); diff --git a/src/Turnierplan.Dal/Repositories/TournamentRepository.cs b/src/Turnierplan.Dal/Repositories/TournamentRepository.cs index 302fbbee..cab60924 100644 --- a/src/Turnierplan.Dal/Repositories/TournamentRepository.cs +++ b/src/Turnierplan.Dal/Repositories/TournamentRepository.cs @@ -4,7 +4,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class TournamentRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Tournaments), ITournamentRepository +internal sealed class TournamentRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Tournaments), ITournamentRepository { public override Task GetByPublicIdAsync(PublicId id) { diff --git a/src/Turnierplan.Dal/Repositories/VenueRepository.cs b/src/Turnierplan.Dal/Repositories/VenueRepository.cs index 254d64d8..07037630 100644 --- a/src/Turnierplan.Dal/Repositories/VenueRepository.cs +++ b/src/Turnierplan.Dal/Repositories/VenueRepository.cs @@ -4,7 +4,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class VenueRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Venues), IVenueRepository +internal sealed class VenueRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Venues), IVenueRepository { public override Task GetByPublicIdAsync(PublicId id) { From 5ac46a666c5c74d6b9cf9ddf4eb21f8333970ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 21 Jun 2025 10:21:42 +0200 Subject: [PATCH 2/8] Revert "Change ApiKey ID to Guid" This reverts commit 415d361532bc586af8502258e8a973149610cf04. --- .../Security/AccessValidatorTest.cs | 2 +- .../RoleAssignments/CreateRoleAssignmentEndpoint.cs | 8 ++++---- .../RoleAssignments/DeleteRoleAssignmentEndpoint.cs | 6 +++--- .../RoleAssignments/GetRoleAssignmentsEndpoint.cs | 8 ++------ src/Turnierplan.App/Helpers/RbacScopeHelper.cs | 2 +- .../Mapping/Rules/RoleAssignmentMappingRule.cs | 2 +- src/Turnierplan.App/Security/AccessValidator.cs | 6 +++--- src/Turnierplan.Core/ApiKey/ApiKey.cs | 8 ++++---- src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs | 2 +- .../RoleAssignment/IRoleAssignmentRepository.cs | 2 +- src/Turnierplan.Core/RoleAssignment/RoleAssignment.cs | 2 +- src/Turnierplan.Core/SeedWork/Entity.cs | 4 +--- .../SeedWork/IEntityWithRoleAssignments.cs | 2 +- .../RoleAssignmentEntityTypeConfiguration.cs | 2 +- src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs | 2 +- src/Turnierplan.Dal/Repositories/DocumentRepository.cs | 2 +- src/Turnierplan.Dal/Repositories/FolderRepository.cs | 2 +- src/Turnierplan.Dal/Repositories/ImageRepository.cs | 2 +- .../Repositories/OrganizationRepository.cs | 2 +- .../Repositories/RepositoryBaseWithPublicId.cs | 4 ++-- .../Repositories/RoleAssignmentRepositoryBase.cs | 2 +- src/Turnierplan.Dal/Repositories/TournamentRepository.cs | 2 +- src/Turnierplan.Dal/Repositories/VenueRepository.cs | 2 +- 23 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs b/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs index ef31afcd..52bb9ae1 100644 --- a/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs +++ b/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs @@ -42,7 +42,7 @@ public void IsActionAllowed___When_Called_With_Indirect_Target___Returns_Expecte organization.AddRoleAssignment(Role.Contributor, otherPrincipal); void Test(Func factory) - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { var target = factory(); diff --git a/src/Turnierplan.App/Endpoints/RoleAssignments/CreateRoleAssignmentEndpoint.cs b/src/Turnierplan.App/Endpoints/RoleAssignments/CreateRoleAssignmentEndpoint.cs index 2b26c6f2..ddd14de3 100644 --- a/src/Turnierplan.App/Endpoints/RoleAssignments/CreateRoleAssignmentEndpoint.cs +++ b/src/Turnierplan.App/Endpoints/RoleAssignments/CreateRoleAssignmentEndpoint.cs @@ -68,17 +68,17 @@ private static async Task Handle( : await task.ConfigureAwait(false); } - private static async Task CreateRoleAssignmentAsync( + private static async Task CreateRoleAssignmentAsync( CreateRoleAssignmentEndpointRequest request, - IRepositoryWithPublicId repository, + IRepositoryWithPublicId repository, PublicId targetId, IAccessValidator accessValidator, IApiKeyRepository apiKeyRepository, IUserRepository userRepository, - IRoleAssignmentRepository roleAssignmentRepository, + IRoleAssignmentRepository roleAssignmentRepository, IMapper mapper, CancellationToken cancellationToken) - where TEntity : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { var entity = await repository.GetByPublicIdAsync(targetId).ConfigureAwait(false); diff --git a/src/Turnierplan.App/Endpoints/RoleAssignments/DeleteRoleAssignmentEndpoint.cs b/src/Turnierplan.App/Endpoints/RoleAssignments/DeleteRoleAssignmentEndpoint.cs index ae05a04c..f255d6e3 100644 --- a/src/Turnierplan.App/Endpoints/RoleAssignments/DeleteRoleAssignmentEndpoint.cs +++ b/src/Turnierplan.App/Endpoints/RoleAssignments/DeleteRoleAssignmentEndpoint.cs @@ -60,13 +60,13 @@ private static async Task Handle( : await task.ConfigureAwait(false); } - private static async Task DeleteRoleAssignmentAsync( - IRepositoryWithPublicId repository, + private static async Task DeleteRoleAssignmentAsync( + IRepositoryWithPublicId repository, PublicId targetId, IAccessValidator accessValidator, Guid roleAssignmentId, CancellationToken cancellationToken) - where TEntity : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { var entity = await repository.GetByPublicIdAsync(targetId).ConfigureAwait(false); diff --git a/src/Turnierplan.App/Endpoints/RoleAssignments/GetRoleAssignmentsEndpoint.cs b/src/Turnierplan.App/Endpoints/RoleAssignments/GetRoleAssignmentsEndpoint.cs index cb6617b0..da41bcb4 100644 --- a/src/Turnierplan.App/Endpoints/RoleAssignments/GetRoleAssignmentsEndpoint.cs +++ b/src/Turnierplan.App/Endpoints/RoleAssignments/GetRoleAssignmentsEndpoint.cs @@ -54,12 +54,8 @@ private static async Task Handle( : await task.ConfigureAwait(false); } - private static async Task GetRoleAssignmentsAsync( - IRepositoryWithPublicId repository, - PublicId targetId, - IAccessValidator accessValidator, - IMapper mapper) - where TEntity : Entity, IEntityWithRoleAssignments + private static async Task GetRoleAssignmentsAsync(IRepositoryWithPublicId repository, PublicId targetId, IAccessValidator accessValidator, IMapper mapper) + where T : Entity, IEntityWithRoleAssignments { var entity = await repository.GetByPublicIdAsync(targetId).ConfigureAwait(false); diff --git a/src/Turnierplan.App/Helpers/RbacScopeHelper.cs b/src/Turnierplan.App/Helpers/RbacScopeHelper.cs index 6e683bd3..4805c003 100644 --- a/src/Turnierplan.App/Helpers/RbacScopeHelper.cs +++ b/src/Turnierplan.App/Helpers/RbacScopeHelper.cs @@ -8,7 +8,7 @@ namespace Turnierplan.App.Helpers; internal static partial class RbacScopeHelper { public static string GetScopeId(this T entity) - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { return $"{typeof(T).Name}:{entity.PublicId.ToString()}"; } diff --git a/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs b/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs index 89aef876..dcc03da9 100644 --- a/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs +++ b/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs @@ -12,7 +12,7 @@ namespace Turnierplan.App.Mapping.Rules; internal abstract class RoleAssignmentMappingRuleBase : MappingRuleBase, RoleAssignmentDto> - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { protected override RoleAssignmentDto Map(IMapper mapper, MappingContext context, RoleAssignment source) { diff --git a/src/Turnierplan.App/Security/AccessValidator.cs b/src/Turnierplan.App/Security/AccessValidator.cs index 6e27ba9e..9aea835d 100644 --- a/src/Turnierplan.App/Security/AccessValidator.cs +++ b/src/Turnierplan.App/Security/AccessValidator.cs @@ -12,7 +12,7 @@ namespace Turnierplan.App.Security; internal interface IAccessValidator { bool IsActionAllowed(IEntityWithRoleAssignments target, Actions.Action action) - where T : Entity, IEntityWithRoleAssignments; + where T : Entity, IEntityWithRoleAssignments; } internal sealed class AccessValidator : IAccessValidator @@ -25,7 +25,7 @@ public AccessValidator(IHttpContextAccessor contextAccessor) } public bool IsActionAllowed(IEntityWithRoleAssignments target, Actions.Action action) - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { if (_httpContext.IsCurrentUserAdministrator()) { @@ -38,7 +38,7 @@ public bool IsActionAllowed(IEntityWithRoleAssignments target, Actions.Act } internal static bool IsActionAllowed(IEntityWithRoleAssignments target, Actions.Action action, Principal principal) - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { var activePrincipalRoles = target.RoleAssignments .Where(x => x.Principal.Equals(principal)) diff --git a/src/Turnierplan.Core/ApiKey/ApiKey.cs b/src/Turnierplan.Core/ApiKey/ApiKey.cs index 5c7d58a5..76e50c67 100644 --- a/src/Turnierplan.Core/ApiKey/ApiKey.cs +++ b/src/Turnierplan.Core/ApiKey/ApiKey.cs @@ -3,7 +3,7 @@ namespace Turnierplan.Core.ApiKey; -public sealed class ApiKey : Entity, IEntityWithRoleAssignments, IEntityWithOrganization +public sealed class ApiKey : Entity, IEntityWithRoleAssignments, IEntityWithOrganization { internal readonly List> _roleAssignments = new(); internal readonly List _requests = new(); @@ -12,7 +12,7 @@ public ApiKey(Organization.Organization organization, string name, string? descr { organization._apiKeys.Add(this); - Id = Guid.NewGuid(); + Id = 0; PublicId = new PublicId.PublicId(); Organization = organization; Name = name; @@ -23,7 +23,7 @@ public ApiKey(Organization.Organization organization, string name, string? descr IsActive = true; } - internal ApiKey(Guid id, PublicId.PublicId publicId, string name, string description, string secretHash, DateTime createdAt, DateTime expiryDate, bool isActive) + internal ApiKey(long id, PublicId.PublicId publicId, string name, string description, string secretHash, DateTime createdAt, DateTime expiryDate, bool isActive) { Id = id; PublicId = publicId; @@ -35,7 +35,7 @@ internal ApiKey(Guid id, PublicId.PublicId publicId, string name, string descrip IsActive = isActive; } - public override Guid Id { get; protected set; } + public override long Id { get; protected set; } public PublicId.PublicId PublicId { get; } diff --git a/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs b/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs index 0783e6b5..6bd5c7d5 100644 --- a/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs +++ b/src/Turnierplan.Core/ApiKey/IApiKeyRepository.cs @@ -2,7 +2,7 @@ namespace Turnierplan.Core.ApiKey; -public interface IApiKeyRepository : IRepositoryWithPublicId +public interface IApiKeyRepository : IRepositoryWithPublicId { Task> GetRequestsInTimeRange(ApiKey apiKey, DateTime start, DateTime end); } diff --git a/src/Turnierplan.Core/RoleAssignment/IRoleAssignmentRepository.cs b/src/Turnierplan.Core/RoleAssignment/IRoleAssignmentRepository.cs index 68d16c75..e2a745af 100644 --- a/src/Turnierplan.Core/RoleAssignment/IRoleAssignmentRepository.cs +++ b/src/Turnierplan.Core/RoleAssignment/IRoleAssignmentRepository.cs @@ -3,4 +3,4 @@ namespace Turnierplan.Core.RoleAssignment; public interface IRoleAssignmentRepository : IRepository, Guid> - where T : Entity, IEntityWithRoleAssignments; + where T : Entity, IEntityWithRoleAssignments; diff --git a/src/Turnierplan.Core/RoleAssignment/RoleAssignment.cs b/src/Turnierplan.Core/RoleAssignment/RoleAssignment.cs index 26cb3fcd..d1dec9b0 100644 --- a/src/Turnierplan.Core/RoleAssignment/RoleAssignment.cs +++ b/src/Turnierplan.Core/RoleAssignment/RoleAssignment.cs @@ -3,7 +3,7 @@ namespace Turnierplan.Core.RoleAssignment; public sealed class RoleAssignment : Entity - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { internal RoleAssignment(T scope, Role role, Principal principal, string? description = null) { diff --git a/src/Turnierplan.Core/SeedWork/Entity.cs b/src/Turnierplan.Core/SeedWork/Entity.cs index e4e5bdea..66c549f7 100644 --- a/src/Turnierplan.Core/SeedWork/Entity.cs +++ b/src/Turnierplan.Core/SeedWork/Entity.cs @@ -1,8 +1,6 @@ namespace Turnierplan.Core.SeedWork; -public abstract class Entity; - -public abstract class Entity : Entity +public abstract class Entity { public abstract TIdentifier Id { get; protected set; } } diff --git a/src/Turnierplan.Core/SeedWork/IEntityWithRoleAssignments.cs b/src/Turnierplan.Core/SeedWork/IEntityWithRoleAssignments.cs index 21f5551d..e79a62f7 100644 --- a/src/Turnierplan.Core/SeedWork/IEntityWithRoleAssignments.cs +++ b/src/Turnierplan.Core/SeedWork/IEntityWithRoleAssignments.cs @@ -3,7 +3,7 @@ namespace Turnierplan.Core.SeedWork; public interface IEntityWithRoleAssignments : IEntityWithPublicId - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { string Name { get; } diff --git a/src/Turnierplan.Dal/EntityConfigurations/RoleAssignmentEntityTypeConfiguration.cs b/src/Turnierplan.Dal/EntityConfigurations/RoleAssignmentEntityTypeConfiguration.cs index bd8733f7..c81deafa 100644 --- a/src/Turnierplan.Dal/EntityConfigurations/RoleAssignmentEntityTypeConfiguration.cs +++ b/src/Turnierplan.Dal/EntityConfigurations/RoleAssignmentEntityTypeConfiguration.cs @@ -7,7 +7,7 @@ namespace Turnierplan.Dal.EntityConfigurations; public sealed class RoleAssignmentEntityTypeConfiguration : IEntityTypeConfiguration> - where T : Entity, IEntityWithRoleAssignments + where T : Entity, IEntityWithRoleAssignments { public void Configure(EntityTypeBuilder> builder) { diff --git a/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs b/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs index 72f9cefe..608f65d9 100644 --- a/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs +++ b/src/Turnierplan.Dal/Repositories/ApiKeyRepository.cs @@ -4,7 +4,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class ApiKeyRepository : RepositoryBaseWithPublicId, IApiKeyRepository +internal sealed class ApiKeyRepository : RepositoryBaseWithPublicId, IApiKeyRepository { private readonly DbSet _requests; diff --git a/src/Turnierplan.Dal/Repositories/DocumentRepository.cs b/src/Turnierplan.Dal/Repositories/DocumentRepository.cs index 9ce0e2a1..b45b4930 100644 --- a/src/Turnierplan.Dal/Repositories/DocumentRepository.cs +++ b/src/Turnierplan.Dal/Repositories/DocumentRepository.cs @@ -4,7 +4,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class DocumentRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Documents), IDocumentRepository +internal sealed class DocumentRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Documents), IDocumentRepository { public async Task GetByPublicIdAsync(PublicId id, bool includeTournamentDetails = false) { diff --git a/src/Turnierplan.Dal/Repositories/FolderRepository.cs b/src/Turnierplan.Dal/Repositories/FolderRepository.cs index cefb0d5d..a12a0368 100644 --- a/src/Turnierplan.Dal/Repositories/FolderRepository.cs +++ b/src/Turnierplan.Dal/Repositories/FolderRepository.cs @@ -4,7 +4,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class FolderRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Folders), IFolderRepository +internal sealed class FolderRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Folders), IFolderRepository { public override Task GetByPublicIdAsync(PublicId id) { diff --git a/src/Turnierplan.Dal/Repositories/ImageRepository.cs b/src/Turnierplan.Dal/Repositories/ImageRepository.cs index c92c0638..398f7aab 100644 --- a/src/Turnierplan.Dal/Repositories/ImageRepository.cs +++ b/src/Turnierplan.Dal/Repositories/ImageRepository.cs @@ -4,7 +4,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class ImageRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Images), IImageRepository +internal sealed class ImageRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Images), IImageRepository { public override Task GetByPublicIdAsync(PublicId id) { diff --git a/src/Turnierplan.Dal/Repositories/OrganizationRepository.cs b/src/Turnierplan.Dal/Repositories/OrganizationRepository.cs index 3bc0ca07..d6137650 100644 --- a/src/Turnierplan.Dal/Repositories/OrganizationRepository.cs +++ b/src/Turnierplan.Dal/Repositories/OrganizationRepository.cs @@ -5,7 +5,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class OrganizationRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Organizations), IOrganizationRepository +internal sealed class OrganizationRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Organizations), IOrganizationRepository { public override Task GetByPublicIdAsync(PublicId id) { diff --git a/src/Turnierplan.Dal/Repositories/RepositoryBaseWithPublicId.cs b/src/Turnierplan.Dal/Repositories/RepositoryBaseWithPublicId.cs index 646c081e..1d6e22c1 100644 --- a/src/Turnierplan.Dal/Repositories/RepositoryBaseWithPublicId.cs +++ b/src/Turnierplan.Dal/Repositories/RepositoryBaseWithPublicId.cs @@ -4,8 +4,8 @@ namespace Turnierplan.Dal.Repositories; -public abstract class RepositoryBaseWithPublicId : RepositoryBase, IRepositoryWithPublicId - where TEntity : Entity, IEntityWithPublicId +public abstract class RepositoryBaseWithPublicId : RepositoryBase, IRepositoryWithPublicId + where TEntity : Entity, IEntityWithPublicId { protected RepositoryBaseWithPublicId(IUnitOfWork unitOfWork, DbSet dbSet) : base(unitOfWork, dbSet) diff --git a/src/Turnierplan.Dal/Repositories/RoleAssignmentRepositoryBase.cs b/src/Turnierplan.Dal/Repositories/RoleAssignmentRepositoryBase.cs index 11be992d..6319ecc7 100644 --- a/src/Turnierplan.Dal/Repositories/RoleAssignmentRepositoryBase.cs +++ b/src/Turnierplan.Dal/Repositories/RoleAssignmentRepositoryBase.cs @@ -11,7 +11,7 @@ namespace Turnierplan.Dal.Repositories; internal abstract class RoleAssignmentRepositoryBase(TurnierplanContext context, DbSet> dbSet) : RepositoryBase, Guid>(context, dbSet), IRoleAssignmentRepository - where T : Entity, IEntityWithRoleAssignments; + where T : Entity, IEntityWithRoleAssignments; internal sealed class ApiKeyRoleAssignmentRepository(TurnierplanContext context) : RoleAssignmentRepositoryBase(context, context.ApiKeyRoleAssignments); diff --git a/src/Turnierplan.Dal/Repositories/TournamentRepository.cs b/src/Turnierplan.Dal/Repositories/TournamentRepository.cs index cab60924..302fbbee 100644 --- a/src/Turnierplan.Dal/Repositories/TournamentRepository.cs +++ b/src/Turnierplan.Dal/Repositories/TournamentRepository.cs @@ -4,7 +4,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class TournamentRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Tournaments), ITournamentRepository +internal sealed class TournamentRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Tournaments), ITournamentRepository { public override Task GetByPublicIdAsync(PublicId id) { diff --git a/src/Turnierplan.Dal/Repositories/VenueRepository.cs b/src/Turnierplan.Dal/Repositories/VenueRepository.cs index 07037630..254d64d8 100644 --- a/src/Turnierplan.Dal/Repositories/VenueRepository.cs +++ b/src/Turnierplan.Dal/Repositories/VenueRepository.cs @@ -4,7 +4,7 @@ namespace Turnierplan.Dal.Repositories; -internal sealed class VenueRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Venues), IVenueRepository +internal sealed class VenueRepository(TurnierplanContext context) : RepositoryBaseWithPublicId(context, context.Venues), IVenueRepository { public override Task GetByPublicIdAsync(PublicId id) { From 77edd1cbef409698d00c5b97533f7c04441e2441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 21 Jun 2025 10:43:08 +0200 Subject: [PATCH 3/8] Add 'PrincipalId' property --- .../Security/AccessValidatorTest.cs | 12 ++++++------ src/Turnierplan.App/Endpoints/EndpointBase.cs | 2 +- .../Endpoints/Identity/IdentityEndpointBase.cs | 3 +++ .../Organizations/GetOrganizationsEndpoint.cs | 3 +-- .../Extensions/HttpContextExtensions.cs | 16 ++++++++++++---- .../Mapping/Rules/RoleAssignmentMappingRule.cs | 2 +- src/Turnierplan.App/Models/PrincipalDto.cs | 2 +- .../Security/ApiKeyAuthenticationHandler.cs | 4 +++- src/Turnierplan.App/Security/ClaimTypes.cs | 3 ++- src/Turnierplan.Core/ApiKey/ApiKey.cs | 6 +++++- .../Extensions/PrincipalExtensions.cs | 4 ++-- .../RoleAssignment/Principal.cs | 8 ++++---- src/Turnierplan.Core/User/User.cs | 6 +++++- .../Converters/PrincipalConverterTest.cs | 18 ++++++++++-------- ...nmentConverter.cs => PrincipalConverter.cs} | 10 ++++++---- 15 files changed, 62 insertions(+), 37 deletions(-) rename src/Turnierplan.Dal/Converters/{RoleAssignmentConverter.cs => PrincipalConverter.cs} (64%) diff --git a/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs b/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs index 52bb9ae1..6c0f743c 100644 --- a/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs +++ b/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs @@ -17,8 +17,8 @@ public void IsActionAllowed___When_Called_With_Basic_Target___Returns_Expected_R { var target = new Organization("Test"); - var principal = new Principal(PrincipalKind.User, "faa6d5d3-93ad-410e-bc81-171a04cf0130"); - var otherPrincipal = new Principal(PrincipalKind.User, "98f8cb8c-606f-47fc-805f-244210e1df51"); + var principal = new Principal(PrincipalKind.User, Guid.Parse("faa6d5d3-93ad-410e-bc81-171a04cf0130")); + var otherPrincipal = new Principal(PrincipalKind.User, Guid.Parse("98f8cb8c-606f-47fc-805f-244210e1df51")); target.AddRoleAssignment(Role.Reader, principal); target.AddRoleAssignment(Role.Contributor, otherPrincipal); @@ -35,8 +35,8 @@ public void IsActionAllowed___When_Called_With_Indirect_Target___Returns_Expecte { var organization = new Organization("Test"); - var principal = new Principal(PrincipalKind.User, "faa6d5d3-93ad-410e-bc81-171a04cf0130"); - var otherPrincipal = new Principal(PrincipalKind.User, "98f8cb8c-606f-47fc-805f-244210e1df51"); + var principal = new Principal(PrincipalKind.User, Guid.Parse("faa6d5d3-93ad-410e-bc81-171a04cf0130")); + var otherPrincipal = new Principal(PrincipalKind.User, Guid.Parse("98f8cb8c-606f-47fc-805f-244210e1df51")); organization.AddRoleAssignment(Role.Reader, principal); organization.AddRoleAssignment(Role.Contributor, otherPrincipal); @@ -66,8 +66,8 @@ public void IsActionAllowed___When_Called_With_Tournament_Target_And_Role_Assign var organization = new Organization("Test"); var folder = new Folder(organization, "Test"); - var principal = new Principal(PrincipalKind.User, "faa6d5d3-93ad-410e-bc81-171a04cf0130"); - var otherPrincipal = new Principal(PrincipalKind.User, "98f8cb8c-606f-47fc-805f-244210e1df51"); + var principal = new Principal(PrincipalKind.User, Guid.Parse("faa6d5d3-93ad-410e-bc81-171a04cf0130")); + var otherPrincipal = new Principal(PrincipalKind.User, Guid.Parse("98f8cb8c-606f-47fc-805f-244210e1df51")); folder.AddRoleAssignment(Role.Reader, principal); folder.AddRoleAssignment(Role.Contributor, otherPrincipal); diff --git a/src/Turnierplan.App/Endpoints/EndpointBase.cs b/src/Turnierplan.App/Endpoints/EndpointBase.cs index b01201da..c2b83693 100644 --- a/src/Turnierplan.App/Endpoints/EndpointBase.cs +++ b/src/Turnierplan.App/Endpoints/EndpointBase.cs @@ -69,7 +69,7 @@ private void ConfigureAuthorization(RouteHandlerBuilder builder) ? [AuthenticationSchemes.AuthenticationSchemeApiKey, AuthenticationSchemes.AuthenticationSchemeSession] : [AuthenticationSchemes.AuthenticationSchemeSession]; - policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type.Equals(ClaimTypes.ApiKeyId) || x.Type.Equals(ClaimTypes.UserId))); + policy.RequireClaim(ClaimTypes.PrincipalId); if (RequireAdministrator == true) { diff --git a/src/Turnierplan.App/Endpoints/Identity/IdentityEndpointBase.cs b/src/Turnierplan.App/Endpoints/Identity/IdentityEndpointBase.cs index 4d839bd5..e858c4eb 100644 --- a/src/Turnierplan.App/Endpoints/Identity/IdentityEndpointBase.cs +++ b/src/Turnierplan.App/Endpoints/Identity/IdentityEndpointBase.cs @@ -4,6 +4,7 @@ using Microsoft.IdentityModel.Tokens; using Turnierplan.App.Options; using Turnierplan.App.Security; +using Turnierplan.Core.RoleAssignment; using Turnierplan.Core.User; using ClaimTypes = Turnierplan.App.Security.ClaimTypes; @@ -36,6 +37,8 @@ protected string CreateTokenForUser(User user, bool isRefreshToken) claims.Add(new Claim(ClaimTypes.DisplayName, user.Name)); claims.Add(new Claim(ClaimTypes.EMailAddress, user.EMail)); claims.Add(new Claim(ClaimTypes.UserId, user.Id.ToString())); + claims.Add(new Claim(ClaimTypes.PrincipalId, user.PrincipalId.ToString())); + claims.Add(new Claim(ClaimTypes.PrincipalKind, nameof(PrincipalKind.User))); if (user.IsAdministrator) { diff --git a/src/Turnierplan.App/Endpoints/Organizations/GetOrganizationsEndpoint.cs b/src/Turnierplan.App/Endpoints/Organizations/GetOrganizationsEndpoint.cs index 4f0c0dc0..79de1cc4 100644 --- a/src/Turnierplan.App/Endpoints/Organizations/GetOrganizationsEndpoint.cs +++ b/src/Turnierplan.App/Endpoints/Organizations/GetOrganizationsEndpoint.cs @@ -27,8 +27,7 @@ private static async Task Handle( } else { - var userId = context.GetCurrentUserIdOrThrow(); - var principal = new Principal(PrincipalKind.User, userId.ToString()); + var principal = context.GetActivePrincipal(); organizations = await repository.GetByPrincipalAsync(principal).ConfigureAwait(false); } diff --git a/src/Turnierplan.App/Extensions/HttpContextExtensions.cs b/src/Turnierplan.App/Extensions/HttpContextExtensions.cs index b73f452a..f161d116 100644 --- a/src/Turnierplan.App/Extensions/HttpContextExtensions.cs +++ b/src/Turnierplan.App/Extensions/HttpContextExtensions.cs @@ -26,19 +26,27 @@ public static bool IsCurrentUserAdministrator(this HttpContext context) public static Principal GetActivePrincipal(this HttpContext context) { + PrincipalKind? kind = null; + Guid? principalId = null; + foreach (var claim in context.User.Claims) { - if (claim.Type.Equals(ClaimTypes.ApiKeyId)) + if (claim.Type.Equals(ClaimTypes.PrincipalKind)) { - return new Principal(PrincipalKind.ApiKey, claim.Value); + kind = Enum.Parse(claim.Value); } - if (claim.Type.Equals(ClaimTypes.UserId)) + if (claim.Type.Equals(ClaimTypes.PrincipalId)) { - return new Principal(PrincipalKind.User, claim.Value); + principalId = Guid.Parse(claim.Value); } } + if (kind.HasValue && principalId.HasValue) + { + return new Principal(kind.Value, principalId.Value); + } + throw new InvalidOperationException("Could not determine active principal."); } diff --git a/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs b/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs index dcc03da9..a868b2e8 100644 --- a/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs +++ b/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs @@ -26,7 +26,7 @@ protected override RoleAssignmentDto Map(IMapper mapper, MappingContext context, Principal = new PrincipalDto { Kind = source.Principal.Kind, - ObjectId = source.Principal.ObjectId + PrincipalId = source.Principal.PrincipalId }, Description = source.Description, IsInherited = false diff --git a/src/Turnierplan.App/Models/PrincipalDto.cs b/src/Turnierplan.App/Models/PrincipalDto.cs index 0cdc2f42..cee5ca10 100644 --- a/src/Turnierplan.App/Models/PrincipalDto.cs +++ b/src/Turnierplan.App/Models/PrincipalDto.cs @@ -6,5 +6,5 @@ public sealed record PrincipalDto { public required PrincipalKind Kind { get; init; } - public required string ObjectId { get; init; } + public required Guid PrincipalId { get; init; } } diff --git a/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs b/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs index 6da34734..96c26a76 100644 --- a/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs +++ b/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Options; using Turnierplan.Core.ApiKey; using Turnierplan.Core.PublicId; +using Turnierplan.Core.RoleAssignment; using Turnierplan.Dal; namespace Turnierplan.App.Security; @@ -79,7 +80,8 @@ protected override async Task HandleAuthenticateAsync() await _apiKeyRepository.UnitOfWork.SaveChangesAsync().ConfigureAwait(false); var identity = new ClaimsIdentity(claims: [ - new Claim(ClaimTypes.ApiKeyId, apiKey.Id.ToString()) + new Claim(ClaimTypes.PrincipalId, apiKey.PrincipalId.ToString()), + new Claim(ClaimTypes.PrincipalKind, nameof(PrincipalKind.ApiKey)) ]); var principal = new ClaimsPrincipal([ identity ]); diff --git a/src/Turnierplan.App/Security/ClaimTypes.cs b/src/Turnierplan.App/Security/ClaimTypes.cs index 2712d03f..64ad9898 100644 --- a/src/Turnierplan.App/Security/ClaimTypes.cs +++ b/src/Turnierplan.App/Security/ClaimTypes.cs @@ -3,9 +3,10 @@ namespace Turnierplan.App.Security; internal static class ClaimTypes { public const string Administrator = "adm"; - public const string ApiKeyId = "turnierplan_api_key_id"; public const string DisplayName = "name"; public const string EMailAddress = "mail"; + public const string PrincipalKind = "principalkind"; + public const string PrincipalId = "principalid"; public const string SecurityStamp = "sst"; public const string TokenType = "typ"; public const string UserId = "uid"; diff --git a/src/Turnierplan.Core/ApiKey/ApiKey.cs b/src/Turnierplan.Core/ApiKey/ApiKey.cs index 76e50c67..4db31f11 100644 --- a/src/Turnierplan.Core/ApiKey/ApiKey.cs +++ b/src/Turnierplan.Core/ApiKey/ApiKey.cs @@ -13,6 +13,7 @@ public ApiKey(Organization.Organization organization, string name, string? descr organization._apiKeys.Add(this); Id = 0; + PrincipalId = Guid.NewGuid(); PublicId = new PublicId.PublicId(); Organization = organization; Name = name; @@ -23,9 +24,10 @@ public ApiKey(Organization.Organization organization, string name, string? descr IsActive = true; } - internal ApiKey(long id, PublicId.PublicId publicId, string name, string description, string secretHash, DateTime createdAt, DateTime expiryDate, bool isActive) + internal ApiKey(long id, Guid principalId, PublicId.PublicId publicId, string name, string description, string secretHash, DateTime createdAt, DateTime expiryDate, bool isActive) { Id = id; + PrincipalId = principalId; PublicId = publicId; Name = name; Description = description; @@ -37,6 +39,8 @@ internal ApiKey(long id, PublicId.PublicId publicId, string name, string descrip public override long Id { get; protected set; } + public Guid PrincipalId { get; } + public PublicId.PublicId PublicId { get; } public Organization.Organization Organization { get; internal set; } = null!; diff --git a/src/Turnierplan.Core/Extensions/PrincipalExtensions.cs b/src/Turnierplan.Core/Extensions/PrincipalExtensions.cs index 9e74bf4c..d2e78f70 100644 --- a/src/Turnierplan.Core/Extensions/PrincipalExtensions.cs +++ b/src/Turnierplan.Core/Extensions/PrincipalExtensions.cs @@ -6,11 +6,11 @@ public static class PrincipalExtensions { public static Principal AsPrincipal(this ApiKey.ApiKey apiKey) { - return new Principal(PrincipalKind.ApiKey, apiKey.Id.ToString()); + return new Principal(PrincipalKind.ApiKey, apiKey.PrincipalId); } public static Principal AsPrincipal(this User.User user) { - return new Principal(PrincipalKind.User, user.Id.ToString()); + return new Principal(PrincipalKind.User, user.PrincipalId); } } diff --git a/src/Turnierplan.Core/RoleAssignment/Principal.cs b/src/Turnierplan.Core/RoleAssignment/Principal.cs index 536bd2e4..713bd033 100644 --- a/src/Turnierplan.Core/RoleAssignment/Principal.cs +++ b/src/Turnierplan.Core/RoleAssignment/Principal.cs @@ -4,15 +4,15 @@ namespace Turnierplan.Core.RoleAssignment; public sealed record Principal { - public Principal(PrincipalKind kind, string objectId) + public Principal(PrincipalKind kind, Guid principalId) { Kind = kind; - ObjectId = objectId; + PrincipalId = principalId; } [JsonPropertyName("k")] public PrincipalKind Kind { get; } - [JsonPropertyName("oid")] - public string ObjectId { get; } + [JsonPropertyName("pid")] + public Guid PrincipalId { get; } } diff --git a/src/Turnierplan.Core/User/User.cs b/src/Turnierplan.Core/User/User.cs index ab8d908f..2a94a13d 100644 --- a/src/Turnierplan.Core/User/User.cs +++ b/src/Turnierplan.Core/User/User.cs @@ -9,6 +9,7 @@ public User(string name, string email) email = email.Trim(); Id = Guid.NewGuid(); + PrincipalId = Guid.NewGuid(); CreatedAt = DateTime.UtcNow; Name = name; EMail = email; @@ -19,9 +20,10 @@ public User(string name, string email) SecurityStamp = Guid.Empty; } - internal User(Guid id, DateTime createdAt, string name, string eMail, string normalizedEMail, string passwordHash, bool isAdministrator, DateTime lastPasswordChange, Guid securityStamp) + internal User(Guid id, Guid principalId, DateTime createdAt, string name, string eMail, string normalizedEMail, string passwordHash, bool isAdministrator, DateTime lastPasswordChange, Guid securityStamp) { Id = id; + PrincipalId = principalId; CreatedAt = createdAt; Name = name; EMail = eMail; @@ -34,6 +36,8 @@ internal User(Guid id, DateTime createdAt, string name, string eMail, string nor public override Guid Id { get; protected set; } + public Guid PrincipalId { get; } + public DateTime CreatedAt { get; } public string Name { get; set; } diff --git a/src/Turnierplan.Dal.Test.Unit/Converters/PrincipalConverterTest.cs b/src/Turnierplan.Dal.Test.Unit/Converters/PrincipalConverterTest.cs index f9792f9f..5ef6a5de 100644 --- a/src/Turnierplan.Dal.Test.Unit/Converters/PrincipalConverterTest.cs +++ b/src/Turnierplan.Dal.Test.Unit/Converters/PrincipalConverterTest.cs @@ -7,11 +7,11 @@ namespace Turnierplan.Dal.Test.Unit.Converters; public sealed class PrincipalConverterTest { [Theory] - [InlineData(PrincipalKind.ApiKey, "123", "ApiKey:123")] + [InlineData(PrincipalKind.ApiKey, "81c42278-5046-4602-9e2b-e16dc7ad9b03", "ApiKey:81c42278-5046-4602-9e2b-e16dc7ad9b03")] [InlineData(PrincipalKind.User, "2e839c1b-04ea-43c9-9bd1-614bf9586859", "User:2e839c1b-04ea-43c9-9bd1-614bf9586859")] - public void FormatPrincipal___When_Called___Produces_Expected_Result(PrincipalKind kind, string objectId, string expectedResult) + public void FormatPrincipal___When_Called___Produces_Expected_Result(PrincipalKind kind, string principalId, string expectedResult) { - var principal = new Principal(kind, objectId); + var principal = new Principal(kind, Guid.Parse(principalId)); var result = PrincipalConverter.FormatPrincipal(principal); @@ -19,22 +19,24 @@ public void FormatPrincipal___When_Called___Produces_Expected_Result(PrincipalKi } [Theory] - [InlineData("ApiKey:123", PrincipalKind.ApiKey, "123")] + [InlineData("ApiKey:81c42278-5046-4602-9e2b-e16dc7ad9b03", PrincipalKind.ApiKey, "81c42278-5046-4602-9e2b-e16dc7ad9b03")] [InlineData("User:2e839c1b-04ea-43c9-9bd1-614bf9586859", PrincipalKind.User, "2e839c1b-04ea-43c9-9bd1-614bf9586859")] - public void ParsePrincipal___When_Called_With_Valid_String___Returns_Expected_Result(string representation, PrincipalKind expectedKind, string expectedObjectId) + public void ParsePrincipal___When_Called_With_Valid_String___Returns_Expected_Result(string representation, PrincipalKind expectedKind, string expectedPrincipalId) { var result = PrincipalConverter.ParsePrincipal(representation); result.Kind.Should().Be(expectedKind); - result.ObjectId.Should().Be(expectedObjectId); + result.PrincipalId.Should().Be(Guid.Parse(expectedPrincipalId)); } [Theory] [InlineData("ApiKey:")] - [InlineData("ApiKey:123 ")] + [InlineData("ApiKey:123")] [InlineData(" ApiKey:123")] - [InlineData("ApiKey:4285231c-cd63-4eb5-adb5-1604d00b2e8e")] + [InlineData("ApiKey:4285231c-cd63-4eb5-adb5-1604d00b2e8")] + [InlineData("User:")] [InlineData("User:123")] + [InlineData(" User:123")] [InlineData("User:4285231c-cd63-4eb5-adb5-1604d00b2e8")] public void ParsePrincipal___When_Called_With_Invalid_String___Throws_Exception(string representation) { diff --git a/src/Turnierplan.Dal/Converters/RoleAssignmentConverter.cs b/src/Turnierplan.Dal/Converters/PrincipalConverter.cs similarity index 64% rename from src/Turnierplan.Dal/Converters/RoleAssignmentConverter.cs rename to src/Turnierplan.Dal/Converters/PrincipalConverter.cs index a73302d8..9e4621c4 100644 --- a/src/Turnierplan.Dal/Converters/RoleAssignmentConverter.cs +++ b/src/Turnierplan.Dal/Converters/PrincipalConverter.cs @@ -14,21 +14,23 @@ public PrincipalConverter() internal static string FormatPrincipal(Principal principal) { - return $"{principal.Kind}:{principal.ObjectId}"; + return $"{principal.Kind}:{principal.PrincipalId}"; } internal static Principal ParsePrincipal(string input) { var match = PrincipalRegex().Match(input); - if (!match.Success || !Enum.TryParse(match.Groups["Kind"].Value, out var kind)) + if (!match.Success + || !Enum.TryParse(match.Groups["Kind"].Value, out var kind) + || !Guid.TryParse(match.Groups["PrincipalId"].Value, out var principalId)) { throw new TurnierplanException("Invalid principal string."); } - return new Principal(kind, match.Groups["ObjectId"].Value); + return new Principal(kind, principalId); } - [GeneratedRegex(@"^(?:(?ApiKey):(?\d+))$|^(?:(?User):(?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))$")] + [GeneratedRegex(@"^(?ApiKey|User):(?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$")] private static partial Regex PrincipalRegex(); } From 9c767ded1b877ce292b41ea1ce7d05457c61703d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 21 Jun 2025 10:49:12 +0200 Subject: [PATCH 4/8] Remove migration --- .../20250615154535_Add_RBAC.Designer.cs | 1299 ----------------- .../Migrations/20250615154535_Add_RBAC.cs | 356 ----- .../TurnierplanContextModelSnapshot.cs | 339 +---- 3 files changed, 72 insertions(+), 1922 deletions(-) delete mode 100644 src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs delete mode 100644 src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs diff --git a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs b/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs deleted file mode 100644 index 24a40f37..00000000 --- a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs +++ /dev/null @@ -1,1299 +0,0 @@ -// -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Turnierplan.Dal; - -#nullable disable - -namespace Turnierplan.Dal.Migrations -{ - [DbContext(typeof(TurnierplanContext))] - [Migration("20250615154535_Add_RBAC")] - partial class Add_RBAC - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.6") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("ExpiryDate") - .HasColumnType("timestamp with time zone"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(25) - .HasColumnType("character varying(25)"); - - b.Property("OrganizationId") - .HasColumnType("bigint"); - - b.Property("PublicId") - .HasColumnType("bigint"); - - b.Property("SecretHash") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("PublicId") - .IsUnique(); - - b.ToTable("ApiKeys", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKeyRequest", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiKeyId") - .HasColumnType("bigint"); - - b.Property("Path") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("character varying(150)"); - - b.Property("Timestamp") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ApiKeyId"); - - b.HasIndex("Timestamp"); - - b.ToTable("ApiKeyRequests", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.Document.Document", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Configuration") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("GenerationCount") - .HasColumnType("integer"); - - b.Property("LastGeneration") - .HasColumnType("timestamp with time zone"); - - b.Property("LastModifiedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("PublicId") - .HasColumnType("bigint"); - - b.Property("TournamentId") - .HasColumnType("bigint"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("PublicId") - .IsUnique(); - - b.HasIndex("TournamentId"); - - b.ToTable("Documents", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.Folder.Folder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.Property("OrganizationId") - .HasColumnType("bigint"); - - b.Property("PublicId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("PublicId") - .IsUnique(); - - b.ToTable("Folders", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.Image.Image", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("FileSize") - .HasColumnType("bigint"); - - b.Property("FileType") - .IsRequired() - .HasMaxLength(5) - .HasColumnType("character varying(5)"); - - b.Property("Height") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("OrganizationId") - .HasColumnType("bigint"); - - b.Property("PublicId") - .HasColumnType("bigint"); - - b.Property("ResourceIdentifier") - .HasColumnType("uuid"); - - b.Property("Type") - .HasColumnType("integer"); - - b.Property("Width") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("PublicId") - .IsUnique(); - - b.HasIndex("ResourceIdentifier") - .IsUnique(); - - b.ToTable("Images", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.Organization.Organization", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("PublicId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("PublicId") - .IsUnique(); - - b.ToTable("Organizations", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApiKeyId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Principal") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ApiKeyId"); - - b.ToTable("IAM_ApiKey", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("FolderId") - .HasColumnType("bigint"); - - b.Property("Principal") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("FolderId"); - - b.ToTable("IAM_Folder", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("ImageId") - .HasColumnType("bigint"); - - b.Property("Principal") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ImageId"); - - b.ToTable("IAM_Image", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("OrganizationId") - .HasColumnType("bigint"); - - b.Property("Principal") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("IAM_Organization", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Principal") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .HasColumnType("integer"); - - b.Property("TournamentId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("TournamentId"); - - b.ToTable("IAM_Tournament", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Principal") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .HasColumnType("integer"); - - b.Property("VenueId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("VenueId"); - - b.ToTable("IAM_Venue", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.Tournament.Group", b => - { - b.Property("TournamentId") - .HasColumnType("bigint"); - - b.Property("Id") - .HasColumnType("integer"); - - b.Property("AlphabeticalId") - .HasColumnType("character(1)"); - - b.Property("DisplayName") - .HasMaxLength(25) - .HasColumnType("character varying(25)"); - - b.HasKey("TournamentId", "Id"); - - b.ToTable("Groups", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.Tournament.GroupParticipant", b => - { - b.Property("TournamentId") - .HasColumnType("bigint"); - - b.Property("GroupId") - .HasColumnType("integer"); - - b.Property("TeamId") - .HasColumnType("integer"); - - b.Property("Order") - .HasColumnType("integer"); - - b.Property("Priority") - .HasColumnType("integer"); - - b.HasKey("TournamentId", "GroupId", "TeamId"); - - b.HasIndex("TournamentId", "TeamId"); - - b.ToTable("GroupParticipants", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.Tournament.Match", b => - { - b.Property("TournamentId") - .HasColumnType("bigint"); - - b.Property("Id") - .HasColumnType("integer"); - - b.Property("Court") - .HasColumnType("smallint"); - - b.Property("FinalsRound") - .HasColumnType("integer"); - - b.Property("GroupId") - .HasColumnType("integer"); - - b.Property("Index") - .HasColumnType("integer"); - - b.Property("IsCurrentlyPlaying") - .HasColumnType("boolean"); - - b.Property("Kickoff") - .HasColumnType("timestamp with time zone"); - - b.Property("OutcomeType") - .HasColumnType("integer"); - - b.Property("PlayoffPosition") - .HasColumnType("integer"); - - b.Property("ScoreA") - .HasColumnType("integer"); - - b.Property("ScoreB") - .HasColumnType("integer"); - - b.Property("TeamSelectorA") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("TeamSelectorB") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.HasKey("TournamentId", "Id"); - - b.HasIndex("TournamentId", "GroupId"); - - b.ToTable("Matches", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.Tournament.Team", b => - { - b.Property("TournamentId") - .HasColumnType("bigint"); - - b.Property("Id") - .HasColumnType("integer"); - - b.Property("EntryFeePaidAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("OutOfCompetition") - .HasColumnType("boolean"); - - b.HasKey("TournamentId", "Id"); - - b.ToTable("Teams", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.Tournament.Tournament", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("FolderId") - .HasColumnType("bigint"); - - b.Property("IsMigrated") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("OrganizationId") - .HasColumnType("bigint"); - - b.Property("OrganizerLogoId") - .HasColumnType("bigint"); - - b.Property("PublicId") - .HasColumnType("bigint"); - - b.Property("PublicPageViews") - .HasColumnType("integer"); - - b.Property("SponsorBannerId") - .HasColumnType("bigint"); - - b.Property("SponsorLogoId") - .HasColumnType("bigint"); - - b.Property("VenueId") - .HasColumnType("bigint"); - - b.Property("Visibility") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("FolderId"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("OrganizerLogoId"); - - b.HasIndex("PublicId") - .IsUnique(); - - b.HasIndex("SponsorBannerId"); - - b.HasIndex("SponsorLogoId"); - - b.HasIndex("VenueId"); - - b.ToTable("Tournaments", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.User.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("EMail") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("IsAdministrator") - .HasColumnType("boolean"); - - b.Property("LastLogin") - .HasColumnType("timestamp with time zone"); - - b.Property("LastPasswordChange") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("NormalizedEMail") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("PasswordHash") - .IsRequired() - .HasColumnType("text"); - - b.Property("SecurityStamp") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEMail") - .IsUnique(); - - b.ToTable("Users", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.Venue.Venue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.PrimitiveCollection>("AddressDetails") - .IsRequired() - .HasColumnType("text[]"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.PrimitiveCollection>("ExternalLinks") - .IsRequired() - .HasColumnType("text[]"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("OrganizationId") - .HasColumnType("bigint"); - - b.Property("PublicId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.HasIndex("PublicId") - .IsUnique(); - - b.ToTable("Venues", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKey", b => - { - b.HasOne("Turnierplan.Core.Organization.Organization", "Organization") - .WithMany("ApiKeys") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKeyRequest", b => - { - b.HasOne("Turnierplan.Core.ApiKey.ApiKey", "ApiKey") - .WithMany("Requests") - .HasForeignKey("ApiKeyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiKey"); - }); - - modelBuilder.Entity("Turnierplan.Core.Document.Document", b => - { - b.HasOne("Turnierplan.Core.Tournament.Tournament", "Tournament") - .WithMany("Documents") - .HasForeignKey("TournamentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Tournament"); - }); - - modelBuilder.Entity("Turnierplan.Core.Folder.Folder", b => - { - b.HasOne("Turnierplan.Core.Organization.Organization", "Organization") - .WithMany("Folders") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Turnierplan.Core.Image.Image", b => - { - b.HasOne("Turnierplan.Core.Organization.Organization", "Organization") - .WithMany("Images") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.HasOne("Turnierplan.Core.ApiKey.ApiKey", "Scope") - .WithMany("RoleAssignments") - .HasForeignKey("ApiKeyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.HasOne("Turnierplan.Core.Folder.Folder", "Scope") - .WithMany("RoleAssignments") - .HasForeignKey("FolderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.HasOne("Turnierplan.Core.Image.Image", "Scope") - .WithMany("RoleAssignments") - .HasForeignKey("ImageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.HasOne("Turnierplan.Core.Organization.Organization", "Scope") - .WithMany("RoleAssignments") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.HasOne("Turnierplan.Core.Tournament.Tournament", "Scope") - .WithMany("RoleAssignments") - .HasForeignKey("TournamentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.HasOne("Turnierplan.Core.Venue.Venue", "Scope") - .WithMany("RoleAssignments") - .HasForeignKey("VenueId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("Turnierplan.Core.Tournament.Group", b => - { - b.HasOne("Turnierplan.Core.Tournament.Tournament", null) - .WithMany("Groups") - .HasForeignKey("TournamentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Turnierplan.Core.Tournament.GroupParticipant", b => - { - b.HasOne("Turnierplan.Core.Tournament.Group", "Group") - .WithMany("Participants") - .HasForeignKey("TournamentId", "GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Turnierplan.Core.Tournament.Team", "Team") - .WithMany() - .HasForeignKey("TournamentId", "TeamId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Group"); - - b.Navigation("Team"); - }); - - modelBuilder.Entity("Turnierplan.Core.Tournament.Match", b => - { - b.HasOne("Turnierplan.Core.Tournament.Tournament", null) - .WithMany("Matches") - .HasForeignKey("TournamentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Turnierplan.Core.Tournament.Group", "Group") - .WithMany() - .HasForeignKey("TournamentId", "GroupId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("Turnierplan.Core.Tournament.Team", b => - { - b.HasOne("Turnierplan.Core.Tournament.Tournament", null) - .WithMany("Teams") - .HasForeignKey("TournamentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Turnierplan.Core.Tournament.Tournament", b => - { - b.HasOne("Turnierplan.Core.Folder.Folder", "Folder") - .WithMany("Tournaments") - .HasForeignKey("FolderId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Turnierplan.Core.Organization.Organization", "Organization") - .WithMany("Tournaments") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Turnierplan.Core.Image.Image", "OrganizerLogo") - .WithMany() - .HasForeignKey("OrganizerLogoId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Turnierplan.Core.Image.Image", "SponsorBanner") - .WithMany() - .HasForeignKey("SponsorBannerId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Turnierplan.Core.Image.Image", "SponsorLogo") - .WithMany() - .HasForeignKey("SponsorLogoId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Turnierplan.Core.Venue.Venue", "Venue") - .WithMany("Tournaments") - .HasForeignKey("VenueId") - .OnDelete(DeleteBehavior.SetNull); - - b.OwnsOne("Turnierplan.Core.Tournament.ComputationConfiguration", "ComputationConfiguration", b1 => - { - b1.Property("TournamentId") - .HasColumnType("bigint"); - - b1.PrimitiveCollection("ComparisonModes") - .IsRequired() - .HasColumnType("integer[]") - .HasAnnotation("Relational:JsonPropertyName", "cmp"); - - b1.Property("MatchDrawnPoints") - .HasColumnType("integer") - .HasAnnotation("Relational:JsonPropertyName", "d"); - - b1.Property("MatchLostPoints") - .HasColumnType("integer") - .HasAnnotation("Relational:JsonPropertyName", "l"); - - b1.Property("MatchWonPoints") - .HasColumnType("integer") - .HasAnnotation("Relational:JsonPropertyName", "w"); - - b1.HasKey("TournamentId"); - - b1.ToTable("Tournaments", "turnierplan"); - - b1.ToJson("ComputationConfiguration"); - - b1.WithOwner() - .HasForeignKey("TournamentId"); - }); - - b.OwnsOne("Turnierplan.Core.Tournament.MatchPlanConfiguration", "MatchPlanConfiguration", b1 => - { - b1.Property("TournamentId") - .HasColumnType("bigint"); - - b1.HasKey("TournamentId"); - - b1.ToTable("Tournaments", "turnierplan"); - - b1.ToJson("MatchPlanConfiguration"); - - b1.WithOwner() - .HasForeignKey("TournamentId"); - - b1.OwnsOne("Turnierplan.Core.Tournament.FinalsRoundConfig", "FinalsRoundConfig", b2 => - { - b2.Property("MatchPlanConfigurationTournamentId") - .HasColumnType("bigint"); - - b2.Property("EnableThirdPlacePlayoff") - .HasColumnType("boolean") - .HasAnnotation("Relational:JsonPropertyName", "3rd"); - - b2.Property("FirstFinalsRoundOrder") - .HasColumnType("integer") - .HasAnnotation("Relational:JsonPropertyName", "fo"); - - b2.PrimitiveCollection>("TeamSelectors") - .HasColumnType("text[]") - .HasAnnotation("Relational:JsonPropertyName", "ts"); - - b2.HasKey("MatchPlanConfigurationTournamentId"); - - b2.ToTable("Tournaments", "turnierplan"); - - b2.HasAnnotation("Relational:JsonPropertyName", "fr"); - - b2.WithOwner() - .HasForeignKey("MatchPlanConfigurationTournamentId"); - - b2.OwnsMany("Turnierplan.Core.Tournament.AdditionalPlayoffConfig", "AdditionalPlayoffs", b3 => - { - b3.Property("FinalsRoundConfigMatchPlanConfigurationTournamentId") - .HasColumnType("bigint"); - - b3.Property("__synthesizedOrdinal") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - b3.Property("PlayoffPosition") - .HasColumnType("integer") - .HasAnnotation("Relational:JsonPropertyName", "p"); - - b3.Property("TeamSelectorA") - .IsRequired() - .HasColumnType("text") - .HasAnnotation("Relational:JsonPropertyName", "a"); - - b3.Property("TeamSelectorB") - .IsRequired() - .HasColumnType("text") - .HasAnnotation("Relational:JsonPropertyName", "b"); - - b3.HasKey("FinalsRoundConfigMatchPlanConfigurationTournamentId", "__synthesizedOrdinal"); - - b3.ToTable("Tournaments", "turnierplan"); - - b3.HasAnnotation("Relational:JsonPropertyName", "ap"); - - b3.WithOwner() - .HasForeignKey("FinalsRoundConfigMatchPlanConfigurationTournamentId"); - }); - - b2.Navigation("AdditionalPlayoffs"); - }); - - b1.OwnsOne("Turnierplan.Core.Tournament.GroupRoundConfig", "GroupRoundConfig", b2 => - { - b2.Property("MatchPlanConfigurationTournamentId") - .HasColumnType("bigint"); - - b2.Property("GroupMatchOrder") - .HasColumnType("integer") - .HasAnnotation("Relational:JsonPropertyName", "o"); - - b2.Property("GroupPhaseRounds") - .HasColumnType("integer") - .HasAnnotation("Relational:JsonPropertyName", "r"); - - b2.HasKey("MatchPlanConfigurationTournamentId"); - - b2.ToTable("Tournaments", "turnierplan"); - - b2.HasAnnotation("Relational:JsonPropertyName", "gr"); - - b2.WithOwner() - .HasForeignKey("MatchPlanConfigurationTournamentId"); - }); - - b1.OwnsOne("Turnierplan.Core.Tournament.ScheduleConfig", "ScheduleConfig", b2 => - { - b2.Property("MatchPlanConfigurationTournamentId") - .HasColumnType("bigint"); - - b2.Property("FinalsPhaseNumberOfCourts") - .HasColumnType("smallint") - .HasAnnotation("Relational:JsonPropertyName", "fc"); - - b2.Property("FinalsPhasePauseTime") - .HasColumnType("interval") - .HasAnnotation("Relational:JsonPropertyName", "fp"); - - b2.Property("FinalsPhasePlayTime") - .HasColumnType("interval") - .HasAnnotation("Relational:JsonPropertyName", "fd"); - - b2.Property("FirstMatchKickoff") - .HasColumnType("timestamp with time zone") - .HasAnnotation("Relational:JsonPropertyName", "f"); - - b2.Property("GroupPhaseNumberOfCourts") - .HasColumnType("smallint") - .HasAnnotation("Relational:JsonPropertyName", "gc"); - - b2.Property("GroupPhasePauseTime") - .HasColumnType("interval") - .HasAnnotation("Relational:JsonPropertyName", "gp"); - - b2.Property("GroupPhasePlayTime") - .HasColumnType("interval") - .HasAnnotation("Relational:JsonPropertyName", "gd"); - - b2.Property("PauseBetweenGroupAndFinalsPhase") - .HasColumnType("interval") - .HasAnnotation("Relational:JsonPropertyName", "p"); - - b2.HasKey("MatchPlanConfigurationTournamentId"); - - b2.ToTable("Tournaments", "turnierplan"); - - b2.HasAnnotation("Relational:JsonPropertyName", "sc"); - - b2.WithOwner() - .HasForeignKey("MatchPlanConfigurationTournamentId"); - }); - - b1.Navigation("FinalsRoundConfig"); - - b1.Navigation("GroupRoundConfig"); - - b1.Navigation("ScheduleConfig"); - }); - - b.OwnsOne("Turnierplan.Core.Tournament.PresentationConfiguration", "PresentationConfiguration", b1 => - { - b1.Property("TournamentId") - .HasColumnType("bigint"); - - b1.Property("ShowOrganizerLogo") - .HasColumnType("boolean") - .HasAnnotation("Relational:JsonPropertyName", "ol"); - - b1.Property("ShowResults") - .HasColumnType("integer") - .HasAnnotation("Relational:JsonPropertyName", "o"); - - b1.Property("ShowSponsorLogo") - .HasColumnType("boolean") - .HasAnnotation("Relational:JsonPropertyName", "sl"); - - b1.HasKey("TournamentId"); - - b1.ToTable("Tournaments", "turnierplan"); - - b1.ToJson("PresentationConfiguration"); - - b1.WithOwner() - .HasForeignKey("TournamentId"); - - b1.OwnsOne("Turnierplan.Core.Tournament.PresentationConfiguration+HeaderLine", "Header1", b2 => - { - b2.Property("PresentationConfigurationTournamentId") - .HasColumnType("bigint"); - - b2.Property("Content") - .HasColumnType("integer") - .HasAnnotation("Relational:JsonPropertyName", "c"); - - b2.Property("CustomContent") - .HasColumnType("text") - .HasAnnotation("Relational:JsonPropertyName", "cc"); - - b2.HasKey("PresentationConfigurationTournamentId"); - - b2.ToTable("Tournaments", "turnierplan"); - - b2.HasAnnotation("Relational:JsonPropertyName", "h1"); - - b2.WithOwner() - .HasForeignKey("PresentationConfigurationTournamentId"); - }); - - b1.OwnsOne("Turnierplan.Core.Tournament.PresentationConfiguration+HeaderLine", "Header2", b2 => - { - b2.Property("PresentationConfigurationTournamentId") - .HasColumnType("bigint"); - - b2.Property("Content") - .HasColumnType("integer") - .HasAnnotation("Relational:JsonPropertyName", "c"); - - b2.Property("CustomContent") - .HasColumnType("text") - .HasAnnotation("Relational:JsonPropertyName", "cc"); - - b2.HasKey("PresentationConfigurationTournamentId"); - - b2.ToTable("Tournaments", "turnierplan"); - - b2.HasAnnotation("Relational:JsonPropertyName", "h2"); - - b2.WithOwner() - .HasForeignKey("PresentationConfigurationTournamentId"); - }); - - b1.Navigation("Header1") - .IsRequired(); - - b1.Navigation("Header2") - .IsRequired(); - }); - - b.Navigation("ComputationConfiguration") - .IsRequired(); - - b.Navigation("Folder"); - - b.Navigation("MatchPlanConfiguration"); - - b.Navigation("Organization"); - - b.Navigation("OrganizerLogo"); - - b.Navigation("PresentationConfiguration") - .IsRequired(); - - b.Navigation("SponsorBanner"); - - b.Navigation("SponsorLogo"); - - b.Navigation("Venue"); - }); - - modelBuilder.Entity("Turnierplan.Core.Venue.Venue", b => - { - b.HasOne("Turnierplan.Core.Organization.Organization", "Organization") - .WithMany("Venues") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKey", b => - { - b.Navigation("Requests"); - - b.Navigation("RoleAssignments"); - }); - - modelBuilder.Entity("Turnierplan.Core.Folder.Folder", b => - { - b.Navigation("RoleAssignments"); - - b.Navigation("Tournaments"); - }); - - modelBuilder.Entity("Turnierplan.Core.Image.Image", b => - { - b.Navigation("RoleAssignments"); - }); - - modelBuilder.Entity("Turnierplan.Core.Organization.Organization", b => - { - b.Navigation("ApiKeys"); - - b.Navigation("Folders"); - - b.Navigation("Images"); - - b.Navigation("RoleAssignments"); - - b.Navigation("Tournaments"); - - b.Navigation("Venues"); - }); - - modelBuilder.Entity("Turnierplan.Core.Tournament.Group", b => - { - b.Navigation("Participants"); - }); - - modelBuilder.Entity("Turnierplan.Core.Tournament.Tournament", b => - { - b.Navigation("Documents"); - - b.Navigation("Groups"); - - b.Navigation("Matches"); - - b.Navigation("RoleAssignments"); - - b.Navigation("Teams"); - }); - - modelBuilder.Entity("Turnierplan.Core.Venue.Venue", b => - { - b.Navigation("RoleAssignments"); - - b.Navigation("Tournaments"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs b/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs deleted file mode 100644 index 99d20e83..00000000 --- a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs +++ /dev/null @@ -1,356 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Turnierplan.Dal.Migrations -{ - /// - public partial class Add_RBAC : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Organizations_Users_OwnerId", - schema: "turnierplan", - table: "Organizations"); - - migrationBuilder.DropTable( - name: "UserRoles", - schema: "turnierplan"); - - migrationBuilder.DropTable( - name: "Roles", - schema: "turnierplan"); - - migrationBuilder.DropIndex( - name: "IX_Organizations_OwnerId", - schema: "turnierplan", - table: "Organizations"); - - // The call to DropColumn() was generated here. - // See comment at the end of method - - migrationBuilder.AddColumn( - name: "IsAdministrator", - schema: "turnierplan", - table: "Users", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.CreateTable( - name: "IAM_ApiKey", - schema: "turnierplan", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - ApiKeyId = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - Role = table.Column(type: "integer", nullable: false), - Principal = table.Column(type: "text", nullable: false), - Description = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IAM_ApiKey", x => x.Id); - table.ForeignKey( - name: "FK_IAM_ApiKey_ApiKeys_ApiKeyId", - column: x => x.ApiKeyId, - principalSchema: "turnierplan", - principalTable: "ApiKeys", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "IAM_Folder", - schema: "turnierplan", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - FolderId = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - Role = table.Column(type: "integer", nullable: false), - Principal = table.Column(type: "text", nullable: false), - Description = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IAM_Folder", x => x.Id); - table.ForeignKey( - name: "FK_IAM_Folder_Folders_FolderId", - column: x => x.FolderId, - principalSchema: "turnierplan", - principalTable: "Folders", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "IAM_Image", - schema: "turnierplan", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - ImageId = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - Role = table.Column(type: "integer", nullable: false), - Principal = table.Column(type: "text", nullable: false), - Description = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IAM_Image", x => x.Id); - table.ForeignKey( - name: "FK_IAM_Image_Images_ImageId", - column: x => x.ImageId, - principalSchema: "turnierplan", - principalTable: "Images", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "IAM_Organization", - schema: "turnierplan", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - OrganizationId = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - Role = table.Column(type: "integer", nullable: false), - Principal = table.Column(type: "text", nullable: false), - Description = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IAM_Organization", x => x.Id); - table.ForeignKey( - name: "FK_IAM_Organization_Organizations_OrganizationId", - column: x => x.OrganizationId, - principalSchema: "turnierplan", - principalTable: "Organizations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "IAM_Tournament", - schema: "turnierplan", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - TournamentId = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - Role = table.Column(type: "integer", nullable: false), - Principal = table.Column(type: "text", nullable: false), - Description = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IAM_Tournament", x => x.Id); - table.ForeignKey( - name: "FK_IAM_Tournament_Tournaments_TournamentId", - column: x => x.TournamentId, - principalSchema: "turnierplan", - principalTable: "Tournaments", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "IAM_Venue", - schema: "turnierplan", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - VenueId = table.Column(type: "bigint", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - Role = table.Column(type: "integer", nullable: false), - Principal = table.Column(type: "text", nullable: false), - Description = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IAM_Venue", x => x.Id); - table.ForeignKey( - name: "FK_IAM_Venue_Venues_VenueId", - column: x => x.VenueId, - principalSchema: "turnierplan", - principalTable: "Venues", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_IAM_ApiKey_ApiKeyId", - schema: "turnierplan", - table: "IAM_ApiKey", - column: "ApiKeyId"); - - migrationBuilder.CreateIndex( - name: "IX_IAM_Folder_FolderId", - schema: "turnierplan", - table: "IAM_Folder", - column: "FolderId"); - - migrationBuilder.CreateIndex( - name: "IX_IAM_Image_ImageId", - schema: "turnierplan", - table: "IAM_Image", - column: "ImageId"); - - migrationBuilder.CreateIndex( - name: "IX_IAM_Organization_OrganizationId", - schema: "turnierplan", - table: "IAM_Organization", - column: "OrganizationId"); - - migrationBuilder.CreateIndex( - name: "IX_IAM_Tournament_TournamentId", - schema: "turnierplan", - table: "IAM_Tournament", - column: "TournamentId"); - - migrationBuilder.CreateIndex( - name: "IX_IAM_Venue_VenueId", - schema: "turnierplan", - table: "IAM_Venue", - column: "VenueId"); - - // The DropColumn() call was moved here manually. This is done so that the - // corresponding role assignments can be created before the data is destroyed. - - // 1000 is the numerical value for the "Owner" role - - migrationBuilder.Sql(""" -INSERT INTO turnierplan."IAM_Organization" ("Id", "OrganizationId", "CreatedAt", "Role", "Principal", "Description") -SELECT gen_random_uuid(), "Organizations"."Id", NOW(), 1000, ('User:' || "Organizations"."OwnerId"), '' -FROM turnierplan."Organizations"; -"""); - - migrationBuilder.DropColumn( - name: "OwnerId", - schema: "turnierplan", - table: "Organizations"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "IAM_ApiKey", - schema: "turnierplan"); - - migrationBuilder.DropTable( - name: "IAM_Folder", - schema: "turnierplan"); - - migrationBuilder.DropTable( - name: "IAM_Image", - schema: "turnierplan"); - - migrationBuilder.DropTable( - name: "IAM_Organization", - schema: "turnierplan"); - - migrationBuilder.DropTable( - name: "IAM_Tournament", - schema: "turnierplan"); - - migrationBuilder.DropTable( - name: "IAM_Venue", - schema: "turnierplan"); - - migrationBuilder.DropColumn( - name: "IsAdministrator", - schema: "turnierplan", - table: "Users"); - - migrationBuilder.AddColumn( - name: "OwnerId", - schema: "turnierplan", - table: "Organizations", - type: "uuid", - nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); - - migrationBuilder.CreateTable( - name: "Roles", - schema: "turnierplan", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - Name = table.Column(type: "character varying(16)", maxLength: 16, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Roles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "UserRoles", - schema: "turnierplan", - columns: table => new - { - RoleId = table.Column(type: "uuid", nullable: false), - UserId = table.Column(type: "uuid", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UserRoles", x => new { x.RoleId, x.UserId }); - table.ForeignKey( - name: "FK_UserRoles_Roles_RoleId", - column: x => x.RoleId, - principalSchema: "turnierplan", - principalTable: "Roles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_UserRoles_Users_UserId", - column: x => x.UserId, - principalSchema: "turnierplan", - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.InsertData( - schema: "turnierplan", - table: "Roles", - columns: new[] { "Id", "Name" }, - values: new object[] { new Guid("9da7acec-ed66-4698-a2d6-927c9ee3f83a"), "Administrator" }); - - migrationBuilder.CreateIndex( - name: "IX_Organizations_OwnerId", - schema: "turnierplan", - table: "Organizations", - column: "OwnerId"); - - migrationBuilder.CreateIndex( - name: "IX_Roles_Name", - schema: "turnierplan", - table: "Roles", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UserRoles_UserId", - schema: "turnierplan", - table: "UserRoles", - column: "UserId"); - - migrationBuilder.AddForeignKey( - name: "FK_Organizations_Users_OwnerId", - schema: "turnierplan", - table: "Organizations", - column: "OwnerId", - principalSchema: "turnierplan", - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - } -} diff --git a/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs b/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs index 0b302773..ad6b2460 100644 --- a/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs +++ b/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs @@ -18,7 +18,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "9.0.6") + .HasAnnotation("ProductVersion", "9.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -250,203 +250,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(40) .HasColumnType("character varying(40)"); + b.Property("OwnerId") + .HasColumnType("uuid"); + b.Property("PublicId") .HasColumnType("bigint"); b.HasKey("Id"); + b.HasIndex("OwnerId"); + b.HasIndex("PublicId") .IsUnique(); b.ToTable("Organizations", "turnierplan"); }); - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ApiKeyId") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Principal") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ApiKeyId"); - - b.ToTable("IAM_ApiKey", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("FolderId") - .HasColumnType("bigint"); - - b.Property("Principal") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("FolderId"); - - b.ToTable("IAM_Folder", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("ImageId") - .HasColumnType("bigint"); - - b.Property("Principal") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ImageId"); - - b.ToTable("IAM_Image", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("OrganizationId") - .HasColumnType("bigint"); - - b.Property("Principal") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("OrganizationId"); - - b.ToTable("IAM_Organization", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Principal") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .HasColumnType("integer"); - - b.Property("TournamentId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("TournamentId"); - - b.ToTable("IAM_Tournament", "turnierplan"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Principal") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .HasColumnType("integer"); - - b.Property("VenueId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("VenueId"); - - b.ToTable("IAM_Venue", "turnierplan"); - }); - modelBuilder.Entity("Turnierplan.Core.Tournament.Group", b => { b.Property("TournamentId") @@ -636,6 +455,32 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Tournaments", "turnierplan"); }); + modelBuilder.Entity("Turnierplan.Core.User.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Roles", "turnierplan"); + + b.HasData( + new + { + Id = new Guid("9da7acec-ed66-4698-a2d6-927c9ee3f83a"), + Name = "Administrator" + }); + }); + modelBuilder.Entity("Turnierplan.Core.User.User", b => { b.Property("Id") @@ -650,9 +495,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(100) .HasColumnType("character varying(100)"); - b.Property("IsAdministrator") - .HasColumnType("boolean"); - b.Property("LastLogin") .HasColumnType("timestamp with time zone"); @@ -729,6 +571,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Venues", "turnierplan"); }); + modelBuilder.Entity("UserRoles", b => + { + b.Property("RoleId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("RoleId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("UserRoles", "turnierplan"); + }); + modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKey", b => { b.HasOne("Turnierplan.Core.Organization.Organization", "Organization") @@ -784,70 +641,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Organization"); }); - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.HasOne("Turnierplan.Core.ApiKey.ApiKey", "Scope") - .WithMany("RoleAssignments") - .HasForeignKey("ApiKeyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.HasOne("Turnierplan.Core.Folder.Folder", "Scope") - .WithMany("RoleAssignments") - .HasForeignKey("FolderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.HasOne("Turnierplan.Core.Image.Image", "Scope") - .WithMany("RoleAssignments") - .HasForeignKey("ImageId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.HasOne("Turnierplan.Core.Organization.Organization", "Scope") - .WithMany("RoleAssignments") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => - { - b.HasOne("Turnierplan.Core.Tournament.Tournament", "Scope") - .WithMany("RoleAssignments") - .HasForeignKey("TournamentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + modelBuilder.Entity("Turnierplan.Core.Organization.Organization", b => { - b.HasOne("Turnierplan.Core.Venue.Venue", "Scope") - .WithMany("RoleAssignments") - .HasForeignKey("VenueId") - .OnDelete(DeleteBehavior.Cascade) + b.HasOne("Turnierplan.Core.User.User", null) + .WithMany("Organizations") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Restrict) .IsRequired(); - - b.Navigation("Scope"); }); modelBuilder.Entity("Turnierplan.Core.Tournament.Group", b => @@ -1232,23 +1032,29 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Organization"); }); - modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKey", b => + modelBuilder.Entity("UserRoles", b => { - b.Navigation("Requests"); + b.HasOne("Turnierplan.Core.User.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("RoleAssignments"); + b.HasOne("Turnierplan.Core.User.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); - modelBuilder.Entity("Turnierplan.Core.Folder.Folder", b => + modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKey", b => { - b.Navigation("RoleAssignments"); - - b.Navigation("Tournaments"); + b.Navigation("Requests"); }); - modelBuilder.Entity("Turnierplan.Core.Image.Image", b => + modelBuilder.Entity("Turnierplan.Core.Folder.Folder", b => { - b.Navigation("RoleAssignments"); + b.Navigation("Tournaments"); }); modelBuilder.Entity("Turnierplan.Core.Organization.Organization", b => @@ -1259,8 +1065,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Images"); - b.Navigation("RoleAssignments"); - b.Navigation("Tournaments"); b.Navigation("Venues"); @@ -1279,15 +1083,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Matches"); - b.Navigation("RoleAssignments"); - b.Navigation("Teams"); }); - modelBuilder.Entity("Turnierplan.Core.Venue.Venue", b => + modelBuilder.Entity("Turnierplan.Core.User.User", b => { - b.Navigation("RoleAssignments"); + b.Navigation("Organizations"); + }); + modelBuilder.Entity("Turnierplan.Core.Venue.Venue", b => + { b.Navigation("Tournaments"); }); #pragma warning restore 612, 618 From 96108f85fd56ba4f972435a4961dfce0064bc14d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 21 Jun 2025 10:51:55 +0200 Subject: [PATCH 5/8] Fix entity configs --- .../EntityConfigurations/ApiKeyEntityTypeConfiguration.cs | 6 ++++++ .../EntityConfigurations/UserEntityTypeConfiguration.cs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/Turnierplan.Dal/EntityConfigurations/ApiKeyEntityTypeConfiguration.cs b/src/Turnierplan.Dal/EntityConfigurations/ApiKeyEntityTypeConfiguration.cs index 6720878e..6dbde546 100644 --- a/src/Turnierplan.Dal/EntityConfigurations/ApiKeyEntityTypeConfiguration.cs +++ b/src/Turnierplan.Dal/EntityConfigurations/ApiKeyEntityTypeConfiguration.cs @@ -16,6 +16,12 @@ public void Configure(EntityTypeBuilder builder) builder.Property(x => x.Id) .IsRequired(); + builder.Property(x => x.PrincipalId) + .IsRequired(); + + builder.HasIndex(x => x.PrincipalId) + .IsUnique(); + builder.Property(x => x.PublicId) .HasConversion(); diff --git a/src/Turnierplan.Dal/EntityConfigurations/UserEntityTypeConfiguration.cs b/src/Turnierplan.Dal/EntityConfigurations/UserEntityTypeConfiguration.cs index 56a15510..42d81879 100644 --- a/src/Turnierplan.Dal/EntityConfigurations/UserEntityTypeConfiguration.cs +++ b/src/Turnierplan.Dal/EntityConfigurations/UserEntityTypeConfiguration.cs @@ -15,6 +15,12 @@ public void Configure(EntityTypeBuilder builder) builder.Property(x => x.Id) .IsRequired(); + builder.Property(x => x.PrincipalId) + .IsRequired(); + + builder.HasIndex(x => x.PrincipalId) + .IsUnique(); + builder.Property(x => x.CreatedAt) .IsRequired(); From 654b26828b7db84f874d78f9844108db34bbed59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 21 Jun 2025 10:52:03 +0200 Subject: [PATCH 6/8] Revert "Remove migration" This reverts commit 9c767ded1b877ce292b41ea1ce7d05457c61703d. --- .../20250615154535_Add_RBAC.Designer.cs | 1299 +++++++++++++++++ .../Migrations/20250615154535_Add_RBAC.cs | 356 +++++ .../TurnierplanContextModelSnapshot.cs | 339 ++++- 3 files changed, 1922 insertions(+), 72 deletions(-) create mode 100644 src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs create mode 100644 src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs diff --git a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs b/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs new file mode 100644 index 00000000..24a40f37 --- /dev/null +++ b/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs @@ -0,0 +1,1299 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Turnierplan.Dal; + +#nullable disable + +namespace Turnierplan.Dal.Migrations +{ + [DbContext(typeof(TurnierplanContext))] + [Migration("20250615154535_Add_RBAC")] + partial class Add_RBAC + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("OrganizationId") + .HasColumnType("bigint"); + + b.Property("PublicId") + .HasColumnType("bigint"); + + b.Property("SecretHash") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("PublicId") + .IsUnique(); + + b.ToTable("ApiKeys", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKeyRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ApiKeyId") + .HasColumnType("bigint"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApiKeyId"); + + b.HasIndex("Timestamp"); + + b.ToTable("ApiKeyRequests", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.Document.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Configuration") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GenerationCount") + .HasColumnType("integer"); + + b.Property("LastGeneration") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PublicId") + .HasColumnType("bigint"); + + b.Property("TournamentId") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("PublicId") + .IsUnique(); + + b.HasIndex("TournamentId"); + + b.ToTable("Documents", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.Folder.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("OrganizationId") + .HasColumnType("bigint"); + + b.Property("PublicId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("PublicId") + .IsUnique(); + + b.ToTable("Folders", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.Image.Image", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("Height") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("bigint"); + + b.Property("PublicId") + .HasColumnType("bigint"); + + b.Property("ResourceIdentifier") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("Width") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("PublicId") + .IsUnique(); + + b.HasIndex("ResourceIdentifier") + .IsUnique(); + + b.ToTable("Images", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.Organization.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("PublicId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("PublicId") + .IsUnique(); + + b.ToTable("Organizations", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiKeyId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Principal") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApiKeyId"); + + b.ToTable("IAM_ApiKey", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("FolderId") + .HasColumnType("bigint"); + + b.Property("Principal") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("FolderId"); + + b.ToTable("IAM_Folder", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ImageId") + .HasColumnType("bigint"); + + b.Property("Principal") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("IAM_Image", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("OrganizationId") + .HasColumnType("bigint"); + + b.Property("Principal") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("IAM_Organization", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Principal") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("TournamentId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TournamentId"); + + b.ToTable("IAM_Tournament", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Principal") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("VenueId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("VenueId"); + + b.ToTable("IAM_Venue", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.Tournament.Group", b => + { + b.Property("TournamentId") + .HasColumnType("bigint"); + + b.Property("Id") + .HasColumnType("integer"); + + b.Property("AlphabeticalId") + .HasColumnType("character(1)"); + + b.Property("DisplayName") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.HasKey("TournamentId", "Id"); + + b.ToTable("Groups", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.Tournament.GroupParticipant", b => + { + b.Property("TournamentId") + .HasColumnType("bigint"); + + b.Property("GroupId") + .HasColumnType("integer"); + + b.Property("TeamId") + .HasColumnType("integer"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("Priority") + .HasColumnType("integer"); + + b.HasKey("TournamentId", "GroupId", "TeamId"); + + b.HasIndex("TournamentId", "TeamId"); + + b.ToTable("GroupParticipants", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.Tournament.Match", b => + { + b.Property("TournamentId") + .HasColumnType("bigint"); + + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Court") + .HasColumnType("smallint"); + + b.Property("FinalsRound") + .HasColumnType("integer"); + + b.Property("GroupId") + .HasColumnType("integer"); + + b.Property("Index") + .HasColumnType("integer"); + + b.Property("IsCurrentlyPlaying") + .HasColumnType("boolean"); + + b.Property("Kickoff") + .HasColumnType("timestamp with time zone"); + + b.Property("OutcomeType") + .HasColumnType("integer"); + + b.Property("PlayoffPosition") + .HasColumnType("integer"); + + b.Property("ScoreA") + .HasColumnType("integer"); + + b.Property("ScoreB") + .HasColumnType("integer"); + + b.Property("TeamSelectorA") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("TeamSelectorB") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.HasKey("TournamentId", "Id"); + + b.HasIndex("TournamentId", "GroupId"); + + b.ToTable("Matches", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.Tournament.Team", b => + { + b.Property("TournamentId") + .HasColumnType("bigint"); + + b.Property("Id") + .HasColumnType("integer"); + + b.Property("EntryFeePaidAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(60) + .HasColumnType("character varying(60)"); + + b.Property("OutOfCompetition") + .HasColumnType("boolean"); + + b.HasKey("TournamentId", "Id"); + + b.ToTable("Teams", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.Tournament.Tournament", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FolderId") + .HasColumnType("bigint"); + + b.Property("IsMigrated") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(60) + .HasColumnType("character varying(60)"); + + b.Property("OrganizationId") + .HasColumnType("bigint"); + + b.Property("OrganizerLogoId") + .HasColumnType("bigint"); + + b.Property("PublicId") + .HasColumnType("bigint"); + + b.Property("PublicPageViews") + .HasColumnType("integer"); + + b.Property("SponsorBannerId") + .HasColumnType("bigint"); + + b.Property("SponsorLogoId") + .HasColumnType("bigint"); + + b.Property("VenueId") + .HasColumnType("bigint"); + + b.Property("Visibility") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("FolderId"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("OrganizerLogoId"); + + b.HasIndex("PublicId") + .IsUnique(); + + b.HasIndex("SponsorBannerId"); + + b.HasIndex("SponsorLogoId"); + + b.HasIndex("VenueId"); + + b.ToTable("Tournaments", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.User.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EMail") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IsAdministrator") + .HasColumnType("boolean"); + + b.Property("LastLogin") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChange") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("NormalizedEMail") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEMail") + .IsUnique(); + + b.ToTable("Users", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.Venue.Venue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.PrimitiveCollection>("AddressDetails") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.PrimitiveCollection>("ExternalLinks") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(60) + .HasColumnType("character varying(60)"); + + b.Property("OrganizationId") + .HasColumnType("bigint"); + + b.Property("PublicId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("PublicId") + .IsUnique(); + + b.ToTable("Venues", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKey", b => + { + b.HasOne("Turnierplan.Core.Organization.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKeyRequest", b => + { + b.HasOne("Turnierplan.Core.ApiKey.ApiKey", "ApiKey") + .WithMany("Requests") + .HasForeignKey("ApiKeyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiKey"); + }); + + modelBuilder.Entity("Turnierplan.Core.Document.Document", b => + { + b.HasOne("Turnierplan.Core.Tournament.Tournament", "Tournament") + .WithMany("Documents") + .HasForeignKey("TournamentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tournament"); + }); + + modelBuilder.Entity("Turnierplan.Core.Folder.Folder", b => + { + b.HasOne("Turnierplan.Core.Organization.Organization", "Organization") + .WithMany("Folders") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Turnierplan.Core.Image.Image", b => + { + b.HasOne("Turnierplan.Core.Organization.Organization", "Organization") + .WithMany("Images") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.HasOne("Turnierplan.Core.ApiKey.ApiKey", "Scope") + .WithMany("RoleAssignments") + .HasForeignKey("ApiKeyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.HasOne("Turnierplan.Core.Folder.Folder", "Scope") + .WithMany("RoleAssignments") + .HasForeignKey("FolderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.HasOne("Turnierplan.Core.Image.Image", "Scope") + .WithMany("RoleAssignments") + .HasForeignKey("ImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.HasOne("Turnierplan.Core.Organization.Organization", "Scope") + .WithMany("RoleAssignments") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.HasOne("Turnierplan.Core.Tournament.Tournament", "Scope") + .WithMany("RoleAssignments") + .HasForeignKey("TournamentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.HasOne("Turnierplan.Core.Venue.Venue", "Scope") + .WithMany("RoleAssignments") + .HasForeignKey("VenueId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("Turnierplan.Core.Tournament.Group", b => + { + b.HasOne("Turnierplan.Core.Tournament.Tournament", null) + .WithMany("Groups") + .HasForeignKey("TournamentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Turnierplan.Core.Tournament.GroupParticipant", b => + { + b.HasOne("Turnierplan.Core.Tournament.Group", "Group") + .WithMany("Participants") + .HasForeignKey("TournamentId", "GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turnierplan.Core.Tournament.Team", "Team") + .WithMany() + .HasForeignKey("TournamentId", "TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("Turnierplan.Core.Tournament.Match", b => + { + b.HasOne("Turnierplan.Core.Tournament.Tournament", null) + .WithMany("Matches") + .HasForeignKey("TournamentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Turnierplan.Core.Tournament.Group", "Group") + .WithMany() + .HasForeignKey("TournamentId", "GroupId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Turnierplan.Core.Tournament.Team", b => + { + b.HasOne("Turnierplan.Core.Tournament.Tournament", null) + .WithMany("Teams") + .HasForeignKey("TournamentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Turnierplan.Core.Tournament.Tournament", b => + { + b.HasOne("Turnierplan.Core.Folder.Folder", "Folder") + .WithMany("Tournaments") + .HasForeignKey("FolderId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Turnierplan.Core.Organization.Organization", "Organization") + .WithMany("Tournaments") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Turnierplan.Core.Image.Image", "OrganizerLogo") + .WithMany() + .HasForeignKey("OrganizerLogoId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Turnierplan.Core.Image.Image", "SponsorBanner") + .WithMany() + .HasForeignKey("SponsorBannerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Turnierplan.Core.Image.Image", "SponsorLogo") + .WithMany() + .HasForeignKey("SponsorLogoId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Turnierplan.Core.Venue.Venue", "Venue") + .WithMany("Tournaments") + .HasForeignKey("VenueId") + .OnDelete(DeleteBehavior.SetNull); + + b.OwnsOne("Turnierplan.Core.Tournament.ComputationConfiguration", "ComputationConfiguration", b1 => + { + b1.Property("TournamentId") + .HasColumnType("bigint"); + + b1.PrimitiveCollection("ComparisonModes") + .IsRequired() + .HasColumnType("integer[]") + .HasAnnotation("Relational:JsonPropertyName", "cmp"); + + b1.Property("MatchDrawnPoints") + .HasColumnType("integer") + .HasAnnotation("Relational:JsonPropertyName", "d"); + + b1.Property("MatchLostPoints") + .HasColumnType("integer") + .HasAnnotation("Relational:JsonPropertyName", "l"); + + b1.Property("MatchWonPoints") + .HasColumnType("integer") + .HasAnnotation("Relational:JsonPropertyName", "w"); + + b1.HasKey("TournamentId"); + + b1.ToTable("Tournaments", "turnierplan"); + + b1.ToJson("ComputationConfiguration"); + + b1.WithOwner() + .HasForeignKey("TournamentId"); + }); + + b.OwnsOne("Turnierplan.Core.Tournament.MatchPlanConfiguration", "MatchPlanConfiguration", b1 => + { + b1.Property("TournamentId") + .HasColumnType("bigint"); + + b1.HasKey("TournamentId"); + + b1.ToTable("Tournaments", "turnierplan"); + + b1.ToJson("MatchPlanConfiguration"); + + b1.WithOwner() + .HasForeignKey("TournamentId"); + + b1.OwnsOne("Turnierplan.Core.Tournament.FinalsRoundConfig", "FinalsRoundConfig", b2 => + { + b2.Property("MatchPlanConfigurationTournamentId") + .HasColumnType("bigint"); + + b2.Property("EnableThirdPlacePlayoff") + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "3rd"); + + b2.Property("FirstFinalsRoundOrder") + .HasColumnType("integer") + .HasAnnotation("Relational:JsonPropertyName", "fo"); + + b2.PrimitiveCollection>("TeamSelectors") + .HasColumnType("text[]") + .HasAnnotation("Relational:JsonPropertyName", "ts"); + + b2.HasKey("MatchPlanConfigurationTournamentId"); + + b2.ToTable("Tournaments", "turnierplan"); + + b2.HasAnnotation("Relational:JsonPropertyName", "fr"); + + b2.WithOwner() + .HasForeignKey("MatchPlanConfigurationTournamentId"); + + b2.OwnsMany("Turnierplan.Core.Tournament.AdditionalPlayoffConfig", "AdditionalPlayoffs", b3 => + { + b3.Property("FinalsRoundConfigMatchPlanConfigurationTournamentId") + .HasColumnType("bigint"); + + b3.Property("__synthesizedOrdinal") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b3.Property("PlayoffPosition") + .HasColumnType("integer") + .HasAnnotation("Relational:JsonPropertyName", "p"); + + b3.Property("TeamSelectorA") + .IsRequired() + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "a"); + + b3.Property("TeamSelectorB") + .IsRequired() + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "b"); + + b3.HasKey("FinalsRoundConfigMatchPlanConfigurationTournamentId", "__synthesizedOrdinal"); + + b3.ToTable("Tournaments", "turnierplan"); + + b3.HasAnnotation("Relational:JsonPropertyName", "ap"); + + b3.WithOwner() + .HasForeignKey("FinalsRoundConfigMatchPlanConfigurationTournamentId"); + }); + + b2.Navigation("AdditionalPlayoffs"); + }); + + b1.OwnsOne("Turnierplan.Core.Tournament.GroupRoundConfig", "GroupRoundConfig", b2 => + { + b2.Property("MatchPlanConfigurationTournamentId") + .HasColumnType("bigint"); + + b2.Property("GroupMatchOrder") + .HasColumnType("integer") + .HasAnnotation("Relational:JsonPropertyName", "o"); + + b2.Property("GroupPhaseRounds") + .HasColumnType("integer") + .HasAnnotation("Relational:JsonPropertyName", "r"); + + b2.HasKey("MatchPlanConfigurationTournamentId"); + + b2.ToTable("Tournaments", "turnierplan"); + + b2.HasAnnotation("Relational:JsonPropertyName", "gr"); + + b2.WithOwner() + .HasForeignKey("MatchPlanConfigurationTournamentId"); + }); + + b1.OwnsOne("Turnierplan.Core.Tournament.ScheduleConfig", "ScheduleConfig", b2 => + { + b2.Property("MatchPlanConfigurationTournamentId") + .HasColumnType("bigint"); + + b2.Property("FinalsPhaseNumberOfCourts") + .HasColumnType("smallint") + .HasAnnotation("Relational:JsonPropertyName", "fc"); + + b2.Property("FinalsPhasePauseTime") + .HasColumnType("interval") + .HasAnnotation("Relational:JsonPropertyName", "fp"); + + b2.Property("FinalsPhasePlayTime") + .HasColumnType("interval") + .HasAnnotation("Relational:JsonPropertyName", "fd"); + + b2.Property("FirstMatchKickoff") + .HasColumnType("timestamp with time zone") + .HasAnnotation("Relational:JsonPropertyName", "f"); + + b2.Property("GroupPhaseNumberOfCourts") + .HasColumnType("smallint") + .HasAnnotation("Relational:JsonPropertyName", "gc"); + + b2.Property("GroupPhasePauseTime") + .HasColumnType("interval") + .HasAnnotation("Relational:JsonPropertyName", "gp"); + + b2.Property("GroupPhasePlayTime") + .HasColumnType("interval") + .HasAnnotation("Relational:JsonPropertyName", "gd"); + + b2.Property("PauseBetweenGroupAndFinalsPhase") + .HasColumnType("interval") + .HasAnnotation("Relational:JsonPropertyName", "p"); + + b2.HasKey("MatchPlanConfigurationTournamentId"); + + b2.ToTable("Tournaments", "turnierplan"); + + b2.HasAnnotation("Relational:JsonPropertyName", "sc"); + + b2.WithOwner() + .HasForeignKey("MatchPlanConfigurationTournamentId"); + }); + + b1.Navigation("FinalsRoundConfig"); + + b1.Navigation("GroupRoundConfig"); + + b1.Navigation("ScheduleConfig"); + }); + + b.OwnsOne("Turnierplan.Core.Tournament.PresentationConfiguration", "PresentationConfiguration", b1 => + { + b1.Property("TournamentId") + .HasColumnType("bigint"); + + b1.Property("ShowOrganizerLogo") + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "ol"); + + b1.Property("ShowResults") + .HasColumnType("integer") + .HasAnnotation("Relational:JsonPropertyName", "o"); + + b1.Property("ShowSponsorLogo") + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "sl"); + + b1.HasKey("TournamentId"); + + b1.ToTable("Tournaments", "turnierplan"); + + b1.ToJson("PresentationConfiguration"); + + b1.WithOwner() + .HasForeignKey("TournamentId"); + + b1.OwnsOne("Turnierplan.Core.Tournament.PresentationConfiguration+HeaderLine", "Header1", b2 => + { + b2.Property("PresentationConfigurationTournamentId") + .HasColumnType("bigint"); + + b2.Property("Content") + .HasColumnType("integer") + .HasAnnotation("Relational:JsonPropertyName", "c"); + + b2.Property("CustomContent") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "cc"); + + b2.HasKey("PresentationConfigurationTournamentId"); + + b2.ToTable("Tournaments", "turnierplan"); + + b2.HasAnnotation("Relational:JsonPropertyName", "h1"); + + b2.WithOwner() + .HasForeignKey("PresentationConfigurationTournamentId"); + }); + + b1.OwnsOne("Turnierplan.Core.Tournament.PresentationConfiguration+HeaderLine", "Header2", b2 => + { + b2.Property("PresentationConfigurationTournamentId") + .HasColumnType("bigint"); + + b2.Property("Content") + .HasColumnType("integer") + .HasAnnotation("Relational:JsonPropertyName", "c"); + + b2.Property("CustomContent") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "cc"); + + b2.HasKey("PresentationConfigurationTournamentId"); + + b2.ToTable("Tournaments", "turnierplan"); + + b2.HasAnnotation("Relational:JsonPropertyName", "h2"); + + b2.WithOwner() + .HasForeignKey("PresentationConfigurationTournamentId"); + }); + + b1.Navigation("Header1") + .IsRequired(); + + b1.Navigation("Header2") + .IsRequired(); + }); + + b.Navigation("ComputationConfiguration") + .IsRequired(); + + b.Navigation("Folder"); + + b.Navigation("MatchPlanConfiguration"); + + b.Navigation("Organization"); + + b.Navigation("OrganizerLogo"); + + b.Navigation("PresentationConfiguration") + .IsRequired(); + + b.Navigation("SponsorBanner"); + + b.Navigation("SponsorLogo"); + + b.Navigation("Venue"); + }); + + modelBuilder.Entity("Turnierplan.Core.Venue.Venue", b => + { + b.HasOne("Turnierplan.Core.Organization.Organization", "Organization") + .WithMany("Venues") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKey", b => + { + b.Navigation("Requests"); + + b.Navigation("RoleAssignments"); + }); + + modelBuilder.Entity("Turnierplan.Core.Folder.Folder", b => + { + b.Navigation("RoleAssignments"); + + b.Navigation("Tournaments"); + }); + + modelBuilder.Entity("Turnierplan.Core.Image.Image", b => + { + b.Navigation("RoleAssignments"); + }); + + modelBuilder.Entity("Turnierplan.Core.Organization.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Folders"); + + b.Navigation("Images"); + + b.Navigation("RoleAssignments"); + + b.Navigation("Tournaments"); + + b.Navigation("Venues"); + }); + + modelBuilder.Entity("Turnierplan.Core.Tournament.Group", b => + { + b.Navigation("Participants"); + }); + + modelBuilder.Entity("Turnierplan.Core.Tournament.Tournament", b => + { + b.Navigation("Documents"); + + b.Navigation("Groups"); + + b.Navigation("Matches"); + + b.Navigation("RoleAssignments"); + + b.Navigation("Teams"); + }); + + modelBuilder.Entity("Turnierplan.Core.Venue.Venue", b => + { + b.Navigation("RoleAssignments"); + + b.Navigation("Tournaments"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs b/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs new file mode 100644 index 00000000..99d20e83 --- /dev/null +++ b/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs @@ -0,0 +1,356 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Turnierplan.Dal.Migrations +{ + /// + public partial class Add_RBAC : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Organizations_Users_OwnerId", + schema: "turnierplan", + table: "Organizations"); + + migrationBuilder.DropTable( + name: "UserRoles", + schema: "turnierplan"); + + migrationBuilder.DropTable( + name: "Roles", + schema: "turnierplan"); + + migrationBuilder.DropIndex( + name: "IX_Organizations_OwnerId", + schema: "turnierplan", + table: "Organizations"); + + // The call to DropColumn() was generated here. + // See comment at the end of method + + migrationBuilder.AddColumn( + name: "IsAdministrator", + schema: "turnierplan", + table: "Users", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateTable( + name: "IAM_ApiKey", + schema: "turnierplan", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ApiKeyId = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + Role = table.Column(type: "integer", nullable: false), + Principal = table.Column(type: "text", nullable: false), + Description = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IAM_ApiKey", x => x.Id); + table.ForeignKey( + name: "FK_IAM_ApiKey_ApiKeys_ApiKeyId", + column: x => x.ApiKeyId, + principalSchema: "turnierplan", + principalTable: "ApiKeys", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IAM_Folder", + schema: "turnierplan", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + FolderId = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + Role = table.Column(type: "integer", nullable: false), + Principal = table.Column(type: "text", nullable: false), + Description = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IAM_Folder", x => x.Id); + table.ForeignKey( + name: "FK_IAM_Folder_Folders_FolderId", + column: x => x.FolderId, + principalSchema: "turnierplan", + principalTable: "Folders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IAM_Image", + schema: "turnierplan", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ImageId = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + Role = table.Column(type: "integer", nullable: false), + Principal = table.Column(type: "text", nullable: false), + Description = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IAM_Image", x => x.Id); + table.ForeignKey( + name: "FK_IAM_Image_Images_ImageId", + column: x => x.ImageId, + principalSchema: "turnierplan", + principalTable: "Images", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IAM_Organization", + schema: "turnierplan", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrganizationId = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + Role = table.Column(type: "integer", nullable: false), + Principal = table.Column(type: "text", nullable: false), + Description = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IAM_Organization", x => x.Id); + table.ForeignKey( + name: "FK_IAM_Organization_Organizations_OrganizationId", + column: x => x.OrganizationId, + principalSchema: "turnierplan", + principalTable: "Organizations", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IAM_Tournament", + schema: "turnierplan", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TournamentId = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + Role = table.Column(type: "integer", nullable: false), + Principal = table.Column(type: "text", nullable: false), + Description = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IAM_Tournament", x => x.Id); + table.ForeignKey( + name: "FK_IAM_Tournament_Tournaments_TournamentId", + column: x => x.TournamentId, + principalSchema: "turnierplan", + principalTable: "Tournaments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IAM_Venue", + schema: "turnierplan", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + VenueId = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + Role = table.Column(type: "integer", nullable: false), + Principal = table.Column(type: "text", nullable: false), + Description = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IAM_Venue", x => x.Id); + table.ForeignKey( + name: "FK_IAM_Venue_Venues_VenueId", + column: x => x.VenueId, + principalSchema: "turnierplan", + principalTable: "Venues", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_IAM_ApiKey_ApiKeyId", + schema: "turnierplan", + table: "IAM_ApiKey", + column: "ApiKeyId"); + + migrationBuilder.CreateIndex( + name: "IX_IAM_Folder_FolderId", + schema: "turnierplan", + table: "IAM_Folder", + column: "FolderId"); + + migrationBuilder.CreateIndex( + name: "IX_IAM_Image_ImageId", + schema: "turnierplan", + table: "IAM_Image", + column: "ImageId"); + + migrationBuilder.CreateIndex( + name: "IX_IAM_Organization_OrganizationId", + schema: "turnierplan", + table: "IAM_Organization", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_IAM_Tournament_TournamentId", + schema: "turnierplan", + table: "IAM_Tournament", + column: "TournamentId"); + + migrationBuilder.CreateIndex( + name: "IX_IAM_Venue_VenueId", + schema: "turnierplan", + table: "IAM_Venue", + column: "VenueId"); + + // The DropColumn() call was moved here manually. This is done so that the + // corresponding role assignments can be created before the data is destroyed. + + // 1000 is the numerical value for the "Owner" role + + migrationBuilder.Sql(""" +INSERT INTO turnierplan."IAM_Organization" ("Id", "OrganizationId", "CreatedAt", "Role", "Principal", "Description") +SELECT gen_random_uuid(), "Organizations"."Id", NOW(), 1000, ('User:' || "Organizations"."OwnerId"), '' +FROM turnierplan."Organizations"; +"""); + + migrationBuilder.DropColumn( + name: "OwnerId", + schema: "turnierplan", + table: "Organizations"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "IAM_ApiKey", + schema: "turnierplan"); + + migrationBuilder.DropTable( + name: "IAM_Folder", + schema: "turnierplan"); + + migrationBuilder.DropTable( + name: "IAM_Image", + schema: "turnierplan"); + + migrationBuilder.DropTable( + name: "IAM_Organization", + schema: "turnierplan"); + + migrationBuilder.DropTable( + name: "IAM_Tournament", + schema: "turnierplan"); + + migrationBuilder.DropTable( + name: "IAM_Venue", + schema: "turnierplan"); + + migrationBuilder.DropColumn( + name: "IsAdministrator", + schema: "turnierplan", + table: "Users"); + + migrationBuilder.AddColumn( + name: "OwnerId", + schema: "turnierplan", + table: "Organizations", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.CreateTable( + name: "Roles", + schema: "turnierplan", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(16)", maxLength: 16, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Roles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UserRoles", + schema: "turnierplan", + columns: table => new + { + RoleId = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserRoles", x => new { x.RoleId, x.UserId }); + table.ForeignKey( + name: "FK_UserRoles_Roles_RoleId", + column: x => x.RoleId, + principalSchema: "turnierplan", + principalTable: "Roles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserRoles_Users_UserId", + column: x => x.UserId, + principalSchema: "turnierplan", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + schema: "turnierplan", + table: "Roles", + columns: new[] { "Id", "Name" }, + values: new object[] { new Guid("9da7acec-ed66-4698-a2d6-927c9ee3f83a"), "Administrator" }); + + migrationBuilder.CreateIndex( + name: "IX_Organizations_OwnerId", + schema: "turnierplan", + table: "Organizations", + column: "OwnerId"); + + migrationBuilder.CreateIndex( + name: "IX_Roles_Name", + schema: "turnierplan", + table: "Roles", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UserRoles_UserId", + schema: "turnierplan", + table: "UserRoles", + column: "UserId"); + + migrationBuilder.AddForeignKey( + name: "FK_Organizations_Users_OwnerId", + schema: "turnierplan", + table: "Organizations", + column: "OwnerId", + principalSchema: "turnierplan", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs b/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs index ad6b2460..0b302773 100644 --- a/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs +++ b/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs @@ -18,7 +18,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "9.0.1") + .HasAnnotation("ProductVersion", "9.0.6") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -250,22 +250,203 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(40) .HasColumnType("character varying(40)"); - b.Property("OwnerId") - .HasColumnType("uuid"); - b.Property("PublicId") .HasColumnType("bigint"); b.HasKey("Id"); - b.HasIndex("OwnerId"); - b.HasIndex("PublicId") .IsUnique(); b.ToTable("Organizations", "turnierplan"); }); + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApiKeyId") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Principal") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApiKeyId"); + + b.ToTable("IAM_ApiKey", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("FolderId") + .HasColumnType("bigint"); + + b.Property("Principal") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("FolderId"); + + b.ToTable("IAM_Folder", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ImageId") + .HasColumnType("bigint"); + + b.Property("Principal") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("IAM_Image", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("OrganizationId") + .HasColumnType("bigint"); + + b.Property("Principal") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("IAM_Organization", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Principal") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("TournamentId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TournamentId"); + + b.ToTable("IAM_Tournament", "turnierplan"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Principal") + .IsRequired() + .HasColumnType("text"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("VenueId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("VenueId"); + + b.ToTable("IAM_Venue", "turnierplan"); + }); + modelBuilder.Entity("Turnierplan.Core.Tournament.Group", b => { b.Property("TournamentId") @@ -455,32 +636,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Tournaments", "turnierplan"); }); - modelBuilder.Entity("Turnierplan.Core.User.Role", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(16) - .HasColumnType("character varying(16)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("Roles", "turnierplan"); - - b.HasData( - new - { - Id = new Guid("9da7acec-ed66-4698-a2d6-927c9ee3f83a"), - Name = "Administrator" - }); - }); - modelBuilder.Entity("Turnierplan.Core.User.User", b => { b.Property("Id") @@ -495,6 +650,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(100) .HasColumnType("character varying(100)"); + b.Property("IsAdministrator") + .HasColumnType("boolean"); + b.Property("LastLogin") .HasColumnType("timestamp with time zone"); @@ -571,21 +729,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Venues", "turnierplan"); }); - modelBuilder.Entity("UserRoles", b => - { - b.Property("RoleId") - .HasColumnType("uuid"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("RoleId", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("UserRoles", "turnierplan"); - }); - modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKey", b => { b.HasOne("Turnierplan.Core.Organization.Organization", "Organization") @@ -641,13 +784,70 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Organization"); }); - modelBuilder.Entity("Turnierplan.Core.Organization.Organization", b => + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => { - b.HasOne("Turnierplan.Core.User.User", null) - .WithMany("Organizations") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Restrict) + b.HasOne("Turnierplan.Core.ApiKey.ApiKey", "Scope") + .WithMany("RoleAssignments") + .HasForeignKey("ApiKeyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.HasOne("Turnierplan.Core.Folder.Folder", "Scope") + .WithMany("RoleAssignments") + .HasForeignKey("FolderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.HasOne("Turnierplan.Core.Image.Image", "Scope") + .WithMany("RoleAssignments") + .HasForeignKey("ImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.HasOne("Turnierplan.Core.Organization.Organization", "Scope") + .WithMany("RoleAssignments") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.HasOne("Turnierplan.Core.Tournament.Tournament", "Scope") + .WithMany("RoleAssignments") + .HasForeignKey("TournamentId") + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("Turnierplan.Core.RoleAssignment.RoleAssignment", b => + { + b.HasOne("Turnierplan.Core.Venue.Venue", "Scope") + .WithMany("RoleAssignments") + .HasForeignKey("VenueId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); }); modelBuilder.Entity("Turnierplan.Core.Tournament.Group", b => @@ -1032,31 +1232,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Organization"); }); - modelBuilder.Entity("UserRoles", b => - { - b.HasOne("Turnierplan.Core.User.Role", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Turnierplan.Core.User.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("Turnierplan.Core.ApiKey.ApiKey", b => { b.Navigation("Requests"); + + b.Navigation("RoleAssignments"); }); modelBuilder.Entity("Turnierplan.Core.Folder.Folder", b => { + b.Navigation("RoleAssignments"); + b.Navigation("Tournaments"); }); + modelBuilder.Entity("Turnierplan.Core.Image.Image", b => + { + b.Navigation("RoleAssignments"); + }); + modelBuilder.Entity("Turnierplan.Core.Organization.Organization", b => { b.Navigation("ApiKeys"); @@ -1065,6 +1259,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Images"); + b.Navigation("RoleAssignments"); + b.Navigation("Tournaments"); b.Navigation("Venues"); @@ -1083,16 +1279,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Matches"); - b.Navigation("Teams"); - }); + b.Navigation("RoleAssignments"); - modelBuilder.Entity("Turnierplan.Core.User.User", b => - { - b.Navigation("Organizations"); + b.Navigation("Teams"); }); modelBuilder.Entity("Turnierplan.Core.Venue.Venue", b => { + b.Navigation("RoleAssignments"); + b.Navigation("Tournaments"); }); #pragma warning restore 612, 618 From 2ec258ff9a4894b1f02727c561778c7b6c268ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 21 Jun 2025 10:57:28 +0200 Subject: [PATCH 7/8] Recreate migration --- ...cs => 20250621085230_Add_RBAC.Designer.cs} | 14 ++++- ...Add_RBAC.cs => 20250621085230_Add_RBAC.cs} | 60 ++++++++++++++++++- .../TurnierplanContextModelSnapshot.cs | 12 ++++ 3 files changed, 83 insertions(+), 3 deletions(-) rename src/Turnierplan.Dal/Migrations/{20250615154535_Add_RBAC.Designer.cs => 20250621085230_Add_RBAC.Designer.cs} (99%) rename src/Turnierplan.Dal/Migrations/{20250615154535_Add_RBAC.cs => 20250621085230_Add_RBAC.cs} (88%) diff --git a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs b/src/Turnierplan.Dal/Migrations/20250621085230_Add_RBAC.Designer.cs similarity index 99% rename from src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs rename to src/Turnierplan.Dal/Migrations/20250621085230_Add_RBAC.Designer.cs index 24a40f37..5af528c7 100644 --- a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs +++ b/src/Turnierplan.Dal/Migrations/20250621085230_Add_RBAC.Designer.cs @@ -13,7 +13,7 @@ namespace Turnierplan.Dal.Migrations { [DbContext(typeof(TurnierplanContext))] - [Migration("20250615154535_Add_RBAC")] + [Migration("20250621085230_Add_RBAC")] partial class Add_RBAC { /// @@ -56,6 +56,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("bigint"); + b.Property("PrincipalId") + .HasColumnType("uuid"); + b.Property("PublicId") .HasColumnType("bigint"); @@ -67,6 +70,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("OrganizationId"); + b.HasIndex("PrincipalId") + .IsUnique(); + b.HasIndex("PublicId") .IsUnique(); @@ -676,6 +682,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); + b.Property("PrincipalId") + .HasColumnType("uuid"); + b.Property("SecurityStamp") .HasColumnType("uuid"); @@ -684,6 +693,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("NormalizedEMail") .IsUnique(); + b.HasIndex("PrincipalId") + .IsUnique(); + b.ToTable("Users", "turnierplan"); }); diff --git a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs b/src/Turnierplan.Dal/Migrations/20250621085230_Add_RBAC.cs similarity index 88% rename from src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs rename to src/Turnierplan.Dal/Migrations/20250621085230_Add_RBAC.cs index 99d20e83..f86f03e4 100644 --- a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs +++ b/src/Turnierplan.Dal/Migrations/20250621085230_Add_RBAC.cs @@ -40,6 +40,22 @@ protected override void Up(MigrationBuilder migrationBuilder) nullable: false, defaultValue: false); + migrationBuilder.AddColumn( + name: "PrincipalId", + schema: "turnierplan", + table: "Users", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "PrincipalId", + schema: "turnierplan", + table: "ApiKeys", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + migrationBuilder.CreateTable( name: "IAM_ApiKey", schema: "turnierplan", @@ -184,6 +200,25 @@ protected override void Up(MigrationBuilder migrationBuilder) onDelete: ReferentialAction.Cascade); }); + migrationBuilder.Sql(""" +UPDATE turnierplan."ApiKeys" SET "PrincipalId" = gen_random_uuid(); +UPDATE turnierplan."Users" SET "PrincipalId" = gen_random_uuid(); +"""); + + migrationBuilder.CreateIndex( + name: "IX_Users_PrincipalId", + schema: "turnierplan", + table: "Users", + column: "PrincipalId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ApiKeys_PrincipalId", + schema: "turnierplan", + table: "ApiKeys", + column: "PrincipalId", + unique: true); + migrationBuilder.CreateIndex( name: "IX_IAM_ApiKey_ApiKeyId", schema: "turnierplan", @@ -227,8 +262,9 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.Sql(""" INSERT INTO turnierplan."IAM_Organization" ("Id", "OrganizationId", "CreatedAt", "Role", "Principal", "Description") -SELECT gen_random_uuid(), "Organizations"."Id", NOW(), 1000, ('User:' || "Organizations"."OwnerId"), '' -FROM turnierplan."Organizations"; +SELECT gen_random_uuid(), "Organizations"."Id", NOW(), 1000, ('User:' || "Users"."PrincipalId"), '' +FROM turnierplan."Organizations" +INNER JOIN turnierplan."Users" ON "Organizations"."OwnerId" = "Users"."Id"; """); migrationBuilder.DropColumn( @@ -264,11 +300,31 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "IAM_Venue", schema: "turnierplan"); + migrationBuilder.DropIndex( + name: "IX_Users_PrincipalId", + schema: "turnierplan", + table: "Users"); + + migrationBuilder.DropIndex( + name: "IX_ApiKeys_PrincipalId", + schema: "turnierplan", + table: "ApiKeys"); + migrationBuilder.DropColumn( name: "IsAdministrator", schema: "turnierplan", table: "Users"); + migrationBuilder.DropColumn( + name: "PrincipalId", + schema: "turnierplan", + table: "Users"); + + migrationBuilder.DropColumn( + name: "PrincipalId", + schema: "turnierplan", + table: "ApiKeys"); + migrationBuilder.AddColumn( name: "OwnerId", schema: "turnierplan", diff --git a/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs b/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs index 0b302773..ee029614 100644 --- a/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs +++ b/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs @@ -53,6 +53,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("bigint"); + b.Property("PrincipalId") + .HasColumnType("uuid"); + b.Property("PublicId") .HasColumnType("bigint"); @@ -64,6 +67,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("OrganizationId"); + b.HasIndex("PrincipalId") + .IsUnique(); + b.HasIndex("PublicId") .IsUnique(); @@ -673,6 +679,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); + b.Property("PrincipalId") + .HasColumnType("uuid"); + b.Property("SecurityStamp") .HasColumnType("uuid"); @@ -681,6 +690,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("NormalizedEMail") .IsUnique(); + b.HasIndex("PrincipalId") + .IsUnique(); + b.ToTable("Users", "turnierplan"); }); From 00deb252acabcf25324242fad14761a34eea554f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 21 Jun 2025 11:02:53 +0200 Subject: [PATCH 8/8] Update frontend --- .../components/rbac-offcanvas/rbac-offcanvas.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3ae767c7..d28ed5a3 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 @@ -39,7 +39,7 @@ } } - {{ assignment.principal.objectId }} + {{ assignment.principal.principalId }} @if (canDeleteAssignment(assignment)) {