From 213fa3026d3ee2ac48193207e58bb7a4b1d7faa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 21 Mar 2026 21:43:08 +0100 Subject: [PATCH 01/12] fix --- src/Turnierplan.App/Client/src/app/i18n/de.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Turnierplan.App/Client/src/app/i18n/de.ts b/src/Turnierplan.App/Client/src/app/i18n/de.ts index e32ef191..d507cac4 100644 --- a/src/Turnierplan.App/Client/src/app/i18n/de.ts +++ b/src/Turnierplan.App/Client/src/app/i18n/de.ts @@ -224,7 +224,7 @@ export const de = { Name: 'Name', References: { Header: 'Verwendungen', - Tooltip: 'Gibt an, von wie vielen Turnieren wird dieses Bild verwendet wird', + Tooltip: 'Gibt an, von wie vielen Turnieren dieses Bild verwendet wird', None: 'keine' }, NoImages: 'Es sind aktuell keine Bilder vorhanden.', From ffdf4f390904b3db8da4a1c5f3b6bfd9b269f79c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sun, 12 Apr 2026 13:19:59 +0200 Subject: [PATCH 02/12] Some code fixes + test for rbac scope helper --- .../Scenarios.cs | 7 ++- .../TestServer.cs | 15 ++++- .../Helpers/RbacScopeHelperTest.cs | 56 +++++++++++++++++++ .../Mapping/Rules/TournamentMappingRule.cs | 2 +- src/Turnierplan.App/Pages/Tournament.cshtml | 4 +- .../GroupResultsNthRankedSelectorTest.cs | 44 +++------------ .../GroupResultsNthRankedSelector.cs | 2 +- .../Renderer/ReceiptsRendererTest.cs | 2 +- .../Renderer/RendererTestBase.cs | 50 +++++++++++++---- .../Renderer/MatchPlanRenderer.cs | 43 ++++++-------- 10 files changed, 147 insertions(+), 78 deletions(-) create mode 100644 src/Turnierplan.App.Test.Unit/Helpers/RbacScopeHelperTest.cs diff --git a/src/Turnierplan.App.Test.Functional/Scenarios.cs b/src/Turnierplan.App.Test.Functional/Scenarios.cs index 16ca2de7..6a7b8e8c 100644 --- a/src/Turnierplan.App.Test.Functional/Scenarios.cs +++ b/src/Turnierplan.App.Test.Functional/Scenarios.cs @@ -14,7 +14,7 @@ namespace Turnierplan.App.Test.Functional; -public sealed class Scenarios +public sealed class Scenarios : IDisposable { private readonly TestServer _testServer = new(); @@ -98,6 +98,11 @@ await userClient.Organizations.PostAsync( } } + public void Dispose() + { + _testServer.Dispose(); + } + private static async Task ExpectApiErrorAsync(Func func, HttpStatusCode code) { ApiException? exception = null; diff --git a/src/Turnierplan.App.Test.Functional/TestServer.cs b/src/Turnierplan.App.Test.Functional/TestServer.cs index bab42a07..231f19f8 100644 --- a/src/Turnierplan.App.Test.Functional/TestServer.cs +++ b/src/Turnierplan.App.Test.Functional/TestServer.cs @@ -14,9 +14,10 @@ namespace Turnierplan.App.Test.Functional; -internal sealed class TestServer +internal sealed class TestServer : IDisposable { private readonly WebApplicationFactory _application; + private readonly List _httpClientRequestAdapters = []; public TestServer() { @@ -59,6 +60,8 @@ public async Task CreateClientForUserAsync(string username, s var httpClientRequestAdapter = new HttpClientRequestAdapter(authenticationProvider, httpClient: httpClient); var client = new TurnierplanClient(httpClientRequestAdapter); + _httpClientRequestAdapters.Add(httpClientRequestAdapter); + var loginResponse = await client.Api.Identity.Login.PostAsync(new LoginEndpointRequest { UserName = username, @@ -81,4 +84,14 @@ public T ExecuteContextAction(Func action) using var scope = _application.Services.CreateScope(); return action(scope.ServiceProvider.GetRequiredService()); } + + public void Dispose() + { + foreach (var httpClientRequestAdapter in _httpClientRequestAdapters) + { + httpClientRequestAdapter.Dispose(); + } + + _application.Dispose(); + } } diff --git a/src/Turnierplan.App.Test.Unit/Helpers/RbacScopeHelperTest.cs b/src/Turnierplan.App.Test.Unit/Helpers/RbacScopeHelperTest.cs new file mode 100644 index 00000000..ea55a7e7 --- /dev/null +++ b/src/Turnierplan.App.Test.Unit/Helpers/RbacScopeHelperTest.cs @@ -0,0 +1,56 @@ +using FluentAssertions; +using Turnierplan.App.Helpers; +using Turnierplan.Core.ApiKey; +using Turnierplan.Core.Folder; +using Turnierplan.Core.Image; +using Turnierplan.Core.Organization; +using Turnierplan.Core.PlanningRealm; +using Turnierplan.Core.PublicId; +using Turnierplan.Core.Tournament; +using Turnierplan.Core.Venue; +using Xunit; + +namespace Turnierplan.App.Test.Unit.Helpers; + +public sealed class RbacScopeHelperTest +{ + [Fact] + public void RbacScopeHelper___GetScopeId___Returns_Expected_Value() + { + var organization = new Organization("Test"); + var tournament = new Tournament(organization, "Test", Visibility.Public); + + var organizationId = organization.PublicId.ToString(); + var tournamentId = tournament.PublicId.ToString(); + + organization.GetScopeId().Should().Be($"Organization:{organizationId}"); + tournament.GetScopeId().Should().Be($"Tournament:{tournamentId}"); + } + + [Theory] + [InlineData("ApiKey:NobBmcA2jNc", nameof(ApiKey))] + [InlineData("Folder:NobBmcA2jNc", nameof(Folder))] + [InlineData("Image:NobBmcA2jNc", nameof(Image))] + [InlineData("Organization:NobBmcA2jNc", nameof(Organization))] + [InlineData("PlanningRealm:NobBmcA2jNc", nameof(PlanningRealm))] + [InlineData("Tournament:NobBmcA2jNc", nameof(Tournament))] + [InlineData("Venue:NobBmcA2jNc", nameof(Venue))] + public void RbacScopeHelper___TryParseScopeId___Works_As_Expected(string scopeId, string expectedTypeName) + { + RbacScopeHelper.TryParseScopeId(scopeId, out var actualTypeName, out var objectId).Should().BeTrue(); + + actualTypeName.Should().Be(expectedTypeName); + objectId.Should().Be(new PublicId("NobBmcA2jNc")); + } + + [Theory] + [InlineData("InvitationLink:NobBmcA2jNc")] + [InlineData("ApiKey:NobBmcA2jN")] + public void RbacScopeHelper___TryParseScopeId_With_Invalid_Scope_Id___Works_As_Expected(string scopeId) + { + RbacScopeHelper.TryParseScopeId(scopeId, out var actualTypeName, out var objectId).Should().BeFalse(); + + actualTypeName.Should().BeNull(); + objectId.Should().Be(PublicId.Empty); + } +} diff --git a/src/Turnierplan.App/Mapping/Rules/TournamentMappingRule.cs b/src/Turnierplan.App/Mapping/Rules/TournamentMappingRule.cs index 45955029..8b30f96a 100644 --- a/src/Turnierplan.App/Mapping/Rules/TournamentMappingRule.cs +++ b/src/Turnierplan.App/Mapping/Rules/TournamentMappingRule.cs @@ -143,7 +143,7 @@ protected override TournamentDto Map(IMapper mapper, MappingContext context, Tou FirstMatchKickoff = source.MatchPlanConfiguration?.ScheduleConfig?.FirstMatchKickoff, GroupPhaseConfig = MapGroupRoundConfiguration(source.MatchPlanConfiguration?.GroupRoundConfig, source.MatchPlanConfiguration?.ScheduleConfig), PauseBetweenGroupAndFinalsPhase = - source.MatchPlanConfiguration?.GroupRoundConfig is not null && source.MatchPlanConfiguration?.FinalsRoundConfig is not null + source.MatchPlanConfiguration?.GroupRoundConfig is not null && source.MatchPlanConfiguration.FinalsRoundConfig is not null ? source.MatchPlanConfiguration.ScheduleConfig?.PauseBetweenGroupAndFinalsPhase : null, FinalsPhaseConfig = MapFinalsRoundConfiguration(source.MatchPlanConfiguration?.FinalsRoundConfig, source.MatchPlanConfiguration?.ScheduleConfig) diff --git a/src/Turnierplan.App/Pages/Tournament.cshtml b/src/Turnierplan.App/Pages/Tournament.cshtml index 94236a5b..b47e48f2 100644 --- a/src/Turnierplan.App/Pages/Tournament.cshtml +++ b/src/Turnierplan.App/Pages/Tournament.cshtml @@ -88,11 +88,11 @@ } - if (displayVenueTile && tournament.Venue is not null) + if (displayVenueTile) { // The 'd-none class is replaced with 'd-flex' when the user requests the data to be shown
-
@tournament.Venue.Name
+
@tournament.Venue!.Name
@if (!string.IsNullOrWhiteSpace(tournament.Venue.Description)) { diff --git a/src/Turnierplan.Core.Test.Unit/Tournament/TeamSelectors/GroupResultsNthRankedSelectorTest.cs b/src/Turnierplan.Core.Test.Unit/Tournament/TeamSelectors/GroupResultsNthRankedSelectorTest.cs index db6f7d9e..880d4c4a 100644 --- a/src/Turnierplan.Core.Test.Unit/Tournament/TeamSelectors/GroupResultsNthRankedSelectorTest.cs +++ b/src/Turnierplan.Core.Test.Unit/Tournament/TeamSelectors/GroupResultsNthRankedSelectorTest.cs @@ -48,44 +48,18 @@ public void GroupResultsNthRankedSelector___Equals___Returns_Correct_Result(int[ #pragma warning restore xUnit1026 ) { - // Arrange var instance1 = new GroupResultsNthRankedSelector(targetGroupIds, ordinalNumber, placementRank); var instance2 = new GroupResultsNthRankedSelector(targetGroupIds.Reverse().ToArray(), ordinalNumber, placementRank); - // Act - var hashCodeEquals = instance1.GetHashCode() == instance2.GetHashCode(); - var results = new [] - { - instance1.Equals(instance1), - instance2.Equals(instance2), - instance1.Equals(instance2), - instance2.Equals(instance1), - Equals(instance1, instance2), - Equals(instance2, instance1), - ((TeamSelectorBase)instance1).Equals(instance1), - ((TeamSelectorBase)instance1).Equals(instance2), - ((TeamSelectorBase)instance2).Equals(instance2), - ((object)instance1).Equals(instance1), - ((object)instance1).Equals(instance2), - ((object)instance2).Equals(instance2) - }; - var resultsWithNull = new [] - { - instance1.Equals(null), - instance2.Equals(null), - Equals(instance1, null), - Equals(instance2, null), - Equals(null, instance1), - Equals(null, instance2), - ((TeamSelectorBase)instance1!).Equals(null), - ((TeamSelectorBase)instance2!).Equals(null), - ((object)instance1!).Equals(null), - ((object)instance2!).Equals(null) - }; + instance1.GetHashCode().Should().Be(instance2.GetHashCode()); - // Assert - results.Should().AllSatisfy(x => x.Should().BeTrue()); - resultsWithNull.Should().AllSatisfy(x => x.Should().BeFalse()); - hashCodeEquals.Should().BeTrue(); + instance1.Equals(instance1).Should().BeTrue(); + instance2.Equals(instance2).Should().BeTrue(); + instance1.Equals(instance2).Should().BeTrue(); + instance2.Equals(instance1).Should().BeTrue(); + Equals(instance1, instance2).Should().BeTrue(); + + instance1.Equals(null).Should().BeFalse(); + instance2.Equals(null).Should().BeFalse(); } } diff --git a/src/Turnierplan.Core/Tournament/TeamSelectors/GroupResultsNthRankedSelector.cs b/src/Turnierplan.Core/Tournament/TeamSelectors/GroupResultsNthRankedSelector.cs index 4650ac7a..56786af2 100644 --- a/src/Turnierplan.Core/Tournament/TeamSelectors/GroupResultsNthRankedSelector.cs +++ b/src/Turnierplan.Core/Tournament/TeamSelectors/GroupResultsNthRankedSelector.cs @@ -67,6 +67,6 @@ public override int GetHashCode() var groupIdsHashCode = TargetGroupIds.Order() .Aggregate(TargetGroupIds.Length, (current, groupId) => unchecked(current * 31 + groupId)); - return (groupIdsHashCode, OrdinalNumber, PlacementRank).GetHashCode(); + return HashCode.Combine(groupIdsHashCode, OrdinalNumber, PlacementRank); } } diff --git a/src/Turnierplan.PdfRendering.Test.Unit/Renderer/ReceiptsRendererTest.cs b/src/Turnierplan.PdfRendering.Test.Unit/Renderer/ReceiptsRendererTest.cs index 629f4fa6..8a800db5 100644 --- a/src/Turnierplan.PdfRendering.Test.Unit/Renderer/ReceiptsRendererTest.cs +++ b/src/Turnierplan.PdfRendering.Test.Unit/Renderer/ReceiptsRendererTest.cs @@ -108,7 +108,7 @@ public void ReceiptsRenderer___GenerateReceipts___Returns_Correct_Result(string[ { TeamName = data.Name, TeamCount = data.Count, - Amount = 10 * data.Count + Amount = 10.0 * data.Count })); } } diff --git a/src/Turnierplan.PdfRendering.Test.Unit/Renderer/RendererTestBase.cs b/src/Turnierplan.PdfRendering.Test.Unit/Renderer/RendererTestBase.cs index bae2eb06..40b8339c 100644 --- a/src/Turnierplan.PdfRendering.Test.Unit/Renderer/RendererTestBase.cs +++ b/src/Turnierplan.PdfRendering.Test.Unit/Renderer/RendererTestBase.cs @@ -17,45 +17,57 @@ namespace Turnierplan.PdfRendering.Test.Unit.Renderer; -public abstract partial class RendererTestBase +public abstract partial class RendererTestBase : IDisposable where TRenderer : IDocumentRenderer { - // ReSharper disable StaticMemberInGenericType private static readonly string[] __languageCodes = ["de"]; - private static readonly IServiceProvider __serviceProvider; - // ReSharper restore StaticMemberInGenericType static RendererTestBase() { QuestPDF.Settings.License = LicenseType.Community; + } + + private readonly ITestOutputHelper _testOutputHelper; + private readonly TelemetryConfiguration _telemetryConfiguration; + private readonly ServiceProvider _serviceProvider; + private bool _disposed; + + protected RendererTestBase(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + _telemetryConfiguration = new TelemetryConfiguration(); var serviceCollection = new ServiceCollection(); serviceCollection.AddLogging(); - serviceCollection.AddSingleton(new TelemetryClient(new TelemetryConfiguration())); + serviceCollection.AddSingleton(new TelemetryClient(_telemetryConfiguration)); serviceCollection.AddTurnierplanLocalization(); serviceCollection.AddTurnierplanDocumentRendering(); serviceCollection.AddSingleton(); - __serviceProvider = serviceCollection.BuildServiceProvider(); + _serviceProvider = serviceCollection.BuildServiceProvider(); } - private readonly ITestOutputHelper _testOutputHelper; + ~RendererTestBase() + { + Dispose(false); + } - protected RendererTestBase(ITestOutputHelper testOutputHelper) + public void Dispose() { - _testOutputHelper = testOutputHelper; + Dispose(true); + GC.SuppressFinalize(this); } protected TRenderer GetRenderer() { - return (TRenderer)__serviceProvider.GetRequiredService>().Single(x => x.GetType() == typeof(TRenderer)); + return (TRenderer)_serviceProvider.GetRequiredService>().Single(x => x.GetType() == typeof(TRenderer)); } protected void AssertRender(Tournament tournament, IDocumentConfiguration configuration) { foreach(var languageCode in __languageCodes) { - __serviceProvider.GetRequiredService().TryGetLocalization(languageCode, out var localization).Should().BeTrue(); + _serviceProvider.GetRequiredService().TryGetLocalization(languageCode, out var localization).Should().BeTrue(); using var stream = new MemoryStream(); GetRenderer().Render(tournament, configuration, new LocalizationWrapper(localization!), stream); @@ -72,6 +84,22 @@ protected void AssertRender(Tournament tournament, IDocumentConfiguration config } } + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _telemetryConfiguration.Dispose(); + _serviceProvider.Dispose(); + } + + _disposed = true; + } + private void SaveGeneratedPdf(string name, string languageCode, byte[] pdfData) { const string outputDirectory = "pdf-output"; diff --git a/src/Turnierplan.PdfRendering/Renderer/MatchPlanRenderer.cs b/src/Turnierplan.PdfRendering/Renderer/MatchPlanRenderer.cs index 8c486b33..95188a5c 100644 --- a/src/Turnierplan.PdfRendering/Renderer/MatchPlanRenderer.cs +++ b/src/Turnierplan.PdfRendering/Renderer/MatchPlanRenderer.cs @@ -73,7 +73,7 @@ string GetSectionHeader(string key) if (firstGroupMatch is not null && tournament.MatchPlanConfiguration?.ScheduleConfig is not null) { - column.Item().PaddingTop(16).AlignCenter().MatchTimeSection(firstGroupMatch.Kickoff, tournament.MatchPlanConfiguration.ScheduleConfig?.GroupPhasePlayTime, tournament.MatchPlanConfiguration.ScheduleConfig?.GroupPhasePauseTime, localization); + column.Item().PaddingTop(16).AlignCenter().MatchTimeSection(firstGroupMatch.Kickoff, tournament.MatchPlanConfiguration.ScheduleConfig.GroupPhasePlayTime, tournament.MatchPlanConfiguration.ScheduleConfig.GroupPhasePauseTime, localization); } column.Item().PaddingVertical(16).Text(GetSectionHeader("Documents.MatchPlan.Sections.Participants")).Underline(); @@ -111,7 +111,7 @@ string GetSectionHeader(string key) if (tournament.MatchPlanConfiguration?.ScheduleConfig is not null) { var firstFinalsMatch = decidingMatches.MinBy(x => x.Kickoff); - column2.Item().PaddingBottom(16).AlignCenter().MatchTimeSection(firstFinalsMatch!.Kickoff, tournament.MatchPlanConfiguration.ScheduleConfig?.FinalsPhasePlayTime, tournament.MatchPlanConfiguration.ScheduleConfig?.FinalsPhasePauseTime, localization); + column2.Item().PaddingBottom(16).AlignCenter().MatchTimeSection(firstFinalsMatch!.Kickoff, tournament.MatchPlanConfiguration.ScheduleConfig.FinalsPhasePlayTime, tournament.MatchPlanConfiguration.ScheduleConfig.FinalsPhasePauseTime, localization); } // All matches of each group should appear on the same page. I.e. there shall be no page breaks between matches with the same "color" @@ -288,9 +288,8 @@ private static IEnumerable GetHeaderRows(Tournament tournament, MatchPla _ => "Documents.MatchPlan.TournamentKickoff.DateAndDayOfWeekEvening" }, kickoff); break; - case MatchPlanDateFormat.NoDate: default: - throw new InvalidOperationException($"Date format is invalid: {configuration.DateFormat}"); + throw new InvalidOperationException($"Encountered unexpected date format: {configuration.DateFormat}"); } } } @@ -361,7 +360,7 @@ public void Groups(Tournament tournament, ILocalization localization, MatchPlanO extension(IContainer container) { - public void MatchTimeSection(DateTime? kickoff, TimeSpan? playTime, TimeSpan? pauseTime, ILocalization localization) + public void MatchTimeSection(DateTime? kickoff, TimeSpan playTime, TimeSpan pauseTime, ILocalization localization) { container.Row(row => { @@ -377,29 +376,23 @@ public void MatchTimeSection(DateTime? kickoff, TimeSpan? playTime, TimeSpan? pa }); } - if (playTime.HasValue) + row.AutoItem().Text(text => { - row.AutoItem().Text(text => - { - text.Span(localization.Get("Documents.MatchPlan.MatchTimes.PlayTimePre")); - var minutes = (int)playTime.Value.TotalMinutes; - var seconds = (int)playTime.Value.TotalSeconds - minutes * 60; - text.Span(localization.Get("Documents.MatchPlan.MatchTimes.PlayTime", minutes, seconds)).Underline().Bold(); - text.Span(localization.Get($"Documents.MatchPlan.MatchTimes.PlayTimePost.{(minutes == 1 && seconds == 0 ? "One" : "Many")}")); - }); - } + text.Span(localization.Get("Documents.MatchPlan.MatchTimes.PlayTimePre")); + var minutes = (int)playTime.TotalMinutes; + var seconds = (int)playTime.TotalSeconds - minutes * 60; + text.Span(localization.Get("Documents.MatchPlan.MatchTimes.PlayTime", minutes, seconds)).Underline().Bold(); + text.Span(localization.Get($"Documents.MatchPlan.MatchTimes.PlayTimePost.{(minutes == 1 && seconds == 0 ? "One" : "Many")}")); + }); - if (pauseTime.HasValue) + row.AutoItem().Text(text => { - row.AutoItem().Text(text => - { - text.Span(localization.Get("Documents.MatchPlan.MatchTimes.PauseTimePre")); - var minutes = (int)pauseTime.Value.TotalMinutes; - var seconds = (int)pauseTime.Value.TotalSeconds - minutes * 60; - text.Span(localization.Get("Documents.MatchPlan.MatchTimes.PauseTime", minutes, seconds)).Underline().Bold(); - text.Span(localization.Get($"Documents.MatchPlan.MatchTimes.PauseTimePost.{(minutes == 1 && seconds == 0 ? "One" : "Many")}")); - }); - } + text.Span(localization.Get("Documents.MatchPlan.MatchTimes.PauseTimePre")); + var minutes = (int)pauseTime.TotalMinutes; + var seconds = (int)pauseTime.TotalSeconds - minutes * 60; + text.Span(localization.Get("Documents.MatchPlan.MatchTimes.PauseTime", minutes, seconds)).Underline().Bold(); + text.Span(localization.Get($"Documents.MatchPlan.MatchTimes.PauseTimePost.{(minutes == 1 && seconds == 0 ? "One" : "Many")}")); + }); }); } From 119b1f61aca3bc38d2c1dbf4656e5221317e5957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sun, 12 Apr 2026 13:58:19 +0200 Subject: [PATCH 03/12] More code fixes + refactorings for try/catch stmts --- .../delete-widget/delete-widget.component.ts | 2 +- .../move-tournament-to-folder.component.html | 2 +- .../Converters/JsonPublicIdConverter.cs | 10 +-- .../Endpoints/Identity/RefreshEndpoint.cs | 80 ++++++++++++------- .../Pages/InvitationForm.cshtml.cs | 13 +-- .../Pages/Tournament.cshtml.cs | 13 +-- .../Pages/TournamentFullscreen.cshtml.cs | 13 +-- .../Security/ApiKeyAuthenticationHandler.cs | 13 +-- .../Security/JwtAuthenticationHandler.cs | 69 ++++++++-------- .../Security/SigningKeyProvider.cs | 2 +- .../Local/LocalImageStorage.cs | 2 +- .../Renderer/DocumentRendererBase.cs | 3 +- 12 files changed, 109 insertions(+), 113 deletions(-) diff --git a/src/Turnierplan.App/Client/src/app/portal/components/delete-widget/delete-widget.component.ts b/src/Turnierplan.App/Client/src/app/portal/components/delete-widget/delete-widget.component.ts index ca814369..819caf41 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/delete-widget/delete-widget.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/components/delete-widget/delete-widget.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output, TemplateRef } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'; import { NgClass } from '@angular/common'; import { FormsModule } from '@angular/forms'; diff --git a/src/Turnierplan.App/Client/src/app/portal/components/move-tournament-to-folder/move-tournament-to-folder.component.html b/src/Turnierplan.App/Client/src/app/portal/components/move-tournament-to-folder/move-tournament-to-folder.component.html index 36f6e331..2a109fb5 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/move-tournament-to-folder/move-tournament-to-folder.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/components/move-tournament-to-folder/move-tournament-to-folder.component.html @@ -1,4 +1,4 @@ -