Compare commits
12 Commits
develop
...
claude/add
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b401882510 | ||
|
|
2ee1f68c3f | ||
|
|
5ff0df8685 | ||
| ce26a30693 | |||
| 91dd8d1603 | |||
| d1a72876d2 | |||
|
|
ba2a455a2b | ||
| 44b39b4393 | |||
| facc84157c | |||
| e4862f1878 | |||
| e0ecdad408 | |||
| 534ec5f36f |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -86,3 +86,11 @@ API/app.db-shm
|
|||||||
# Vite build output
|
# Vite build output
|
||||||
GUI/dist/
|
GUI/dist/
|
||||||
wwwroot/
|
wwwroot/
|
||||||
|
|
||||||
|
# SQLite Datenbank-Dateien
|
||||||
|
*.db
|
||||||
|
*.db-shm
|
||||||
|
*.db-wal
|
||||||
|
|
||||||
|
# AppSettings Development files
|
||||||
|
appsettings.Development.json
|
||||||
29
API.Tests/API.Tests.csproj
Normal file
29
API.Tests/API.Tests.csproj
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||||
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\API\API.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
184
API.Tests/Controllers/AgeGroupControllerTests.cs
Normal file
184
API.Tests/Controllers/AgeGroupControllerTests.cs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
using API.Controllers;
|
||||||
|
using API.Models.Internal.Altersgruppen;
|
||||||
|
using API.Repository.AgeGroupRepo;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
|
namespace API.Tests.Controllers;
|
||||||
|
|
||||||
|
public class AgeGroupControllerTests
|
||||||
|
{
|
||||||
|
private readonly Mock<IAgeGroupService> _mockService;
|
||||||
|
private readonly AgeGroupController _controller;
|
||||||
|
|
||||||
|
public AgeGroupControllerTests()
|
||||||
|
{
|
||||||
|
_mockService = new Mock<IAgeGroupService>();
|
||||||
|
_controller = new AgeGroupController(_mockService.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAll_ReturnsOkWithAllGroups()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var groups = new List<AltersGruppe>
|
||||||
|
{
|
||||||
|
new AltersGruppe { Id = "1", Name = "U10", StartingAge = 8, EndingAge = 10 },
|
||||||
|
new AltersGruppe { Id = "2", Name = "U12", StartingAge = 10, EndingAge = 12 }
|
||||||
|
};
|
||||||
|
_mockService.Setup(s => s.GetAllAsync()).ReturnsAsync(groups);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetAll();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = Assert.IsType<OkObjectResult>(result);
|
||||||
|
var returnedGroups = Assert.IsType<List<AltersGruppe>>(okResult.Value);
|
||||||
|
Assert.Equal(2, returnedGroups.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAll_EmptyList_ReturnsOkWithEmptyList()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_mockService.Setup(s => s.GetAllAsync()).ReturnsAsync(new List<AltersGruppe>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetAll();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = Assert.IsType<OkObjectResult>(result);
|
||||||
|
var returnedGroups = Assert.IsType<List<AltersGruppe>>(okResult.Value);
|
||||||
|
Assert.Empty(returnedGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetOne_ExistingId_ReturnsOkWithGroup()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var group = new AltersGruppe { Id = "123", Name = "U14", StartingAge = 12, EndingAge = 14 };
|
||||||
|
_mockService.Setup(s => s.GetAsync("123")).ReturnsAsync(group);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetOne("123");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = Assert.IsType<OkObjectResult>(result);
|
||||||
|
var returnedGroup = Assert.IsType<AltersGruppe>(okResult.Value);
|
||||||
|
Assert.Equal("U14", returnedGroup.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetOne_NonExistingId_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_mockService.Setup(s => s.GetAsync("nonexistent")).ReturnsAsync((AltersGruppe?)null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetOne("nonexistent");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsType<NotFoundResult>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Create_ValidGroup_ReturnsCreatedAtAction()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoingGroup = new AltersGruppeIngoing { Name = "U16", StartingAge = 14, EndingAge = 16 };
|
||||||
|
var createdGroup = new AltersGruppe { Id = "new-id", Name = "U16", StartingAge = 14, EndingAge = 16 };
|
||||||
|
|
||||||
|
_mockService.Setup(s => s.CreateAsync(It.IsAny<AltersGruppe>())).ReturnsAsync(createdGroup);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.Create(ingoingGroup);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var createdResult = Assert.IsType<CreatedAtActionResult>(result);
|
||||||
|
Assert.Equal(nameof(AgeGroupController.GetOne), createdResult.ActionName);
|
||||||
|
|
||||||
|
var returnedGroup = Assert.IsType<AltersGruppe>(createdResult.Value);
|
||||||
|
Assert.Equal("U16", returnedGroup.Name);
|
||||||
|
Assert.Equal("new-id", createdResult.RouteValues?["Id"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Update_ExistingGroup_ReturnsOkWithUpdatedGroup()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoingGroup = new AltersGruppeIngoing { Name = "U16 Updated", StartingAge = 15, EndingAge = 16 };
|
||||||
|
var updatedGroup = new AltersGruppe { Id = "123", Name = "U16 Updated", StartingAge = 15, EndingAge = 16 };
|
||||||
|
|
||||||
|
_mockService.Setup(s => s.UpdateAsync("123", It.IsAny<AltersGruppe>())).ReturnsAsync(updatedGroup);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.Update("123", ingoingGroup);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = Assert.IsType<OkObjectResult>(result);
|
||||||
|
var returnedGroup = Assert.IsType<AltersGruppe>(okResult.Value);
|
||||||
|
Assert.Equal("U16 Updated", returnedGroup.Name);
|
||||||
|
Assert.Equal(15, returnedGroup.StartingAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Update_NonExistingGroup_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoingGroup = new AltersGruppeIngoing { Name = "U16 Updated", StartingAge = 15, EndingAge = 16 };
|
||||||
|
_mockService.Setup(s => s.UpdateAsync("nonexistent", It.IsAny<AltersGruppe>())).ReturnsAsync((AltersGruppe?)null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.Update("nonexistent", ingoingGroup);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsType<NotFoundResult>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Delete_ExistingGroup_ReturnsNoContent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var group = new AltersGruppe { Id = "123", Name = "U18", StartingAge = 16, EndingAge = 18 };
|
||||||
|
_mockService.Setup(s => s.DeleteAsync("123")).ReturnsAsync(group);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.Delete("123");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsType<NoContentResult>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Delete_NonExistingGroup_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_mockService.Setup(s => s.DeleteAsync("nonexistent")).ReturnsAsync((AltersGruppe?)null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.Delete("nonexistent");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsType<NotFoundResult>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Create_ServiceIsCalled_WithCorrectParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoingGroup = new AltersGruppeIngoing { Name = "U20", StartingAge = 18, EndingAge = 20 };
|
||||||
|
var createdGroup = new AltersGruppe { Id = "new-id", Name = "U20", StartingAge = 18, EndingAge = 20 };
|
||||||
|
|
||||||
|
_mockService.Setup(s => s.CreateAsync(It.IsAny<AltersGruppe>())).ReturnsAsync(createdGroup);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _controller.Create(ingoingGroup);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_mockService.Verify(s => s.CreateAsync(It.Is<AltersGruppe>(g =>
|
||||||
|
g.Name == "U20" &&
|
||||||
|
g.StartingAge == 18 &&
|
||||||
|
g.EndingAge == 20
|
||||||
|
)), Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
118
API.Tests/Mappers/AltersgruppeMapperTests.cs
Normal file
118
API.Tests/Mappers/AltersgruppeMapperTests.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
using API.Models.Internal.Altersgruppen;
|
||||||
|
|
||||||
|
namespace API.Tests.Mappers;
|
||||||
|
|
||||||
|
public class AltersgruppeMapperTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_MapsAllProperties()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoing = new AltersGruppeIngoing
|
||||||
|
{
|
||||||
|
Name = "U14",
|
||||||
|
StartingAge = 12,
|
||||||
|
EndingAge = 14
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ingoing.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("U14", result.Name);
|
||||||
|
Assert.Equal(12, result.StartingAge);
|
||||||
|
Assert.Equal(14, result.EndingAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_GeneratesNewId()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoing = new AltersGruppeIngoing
|
||||||
|
{
|
||||||
|
Name = "U16",
|
||||||
|
StartingAge = 14,
|
||||||
|
EndingAge = 16
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ingoing.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result.Id);
|
||||||
|
Assert.NotEmpty(result.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_GeneratesUniqueIds()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoing1 = new AltersGruppeIngoing { Name = "U10", StartingAge = 8, EndingAge = 10 };
|
||||||
|
var ingoing2 = new AltersGruppeIngoing { Name = "U12", StartingAge = 10, EndingAge = 12 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result1 = ingoing1.ToInternalFromIngoing();
|
||||||
|
var result2 = ingoing2.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotEqual(result1.Id, result2.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_WithZeroAges_MapsCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoing = new AltersGruppeIngoing
|
||||||
|
{
|
||||||
|
Name = "Beginners",
|
||||||
|
StartingAge = 0,
|
||||||
|
EndingAge = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ingoing.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, result.StartingAge);
|
||||||
|
Assert.Equal(0, result.EndingAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_WithNegativeAges_MapsCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange - testing edge case, though business logic might not allow this
|
||||||
|
var ingoing = new AltersGruppeIngoing
|
||||||
|
{
|
||||||
|
Name = "Invalid",
|
||||||
|
StartingAge = -1,
|
||||||
|
EndingAge = -5
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ingoing.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(-1, result.StartingAge);
|
||||||
|
Assert.Equal(-5, result.EndingAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_WithLargeAges_MapsCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoing = new AltersGruppeIngoing
|
||||||
|
{
|
||||||
|
Name = "Seniors",
|
||||||
|
StartingAge = 60,
|
||||||
|
EndingAge = 100
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ingoing.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(60, result.StartingAge);
|
||||||
|
Assert.Equal(100, result.EndingAge);
|
||||||
|
}
|
||||||
|
}
|
||||||
123
API.Tests/Mappers/RegistrationKeyMapperTests.cs
Normal file
123
API.Tests/Mappers/RegistrationKeyMapperTests.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
using API.Models.Internal.User;
|
||||||
|
|
||||||
|
namespace API.Tests.Mappers;
|
||||||
|
|
||||||
|
public class RegistrationKeyMapperTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_MapsLinkedRole()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoing = new RegistrationKeyIngoing
|
||||||
|
{
|
||||||
|
LinkedRole = "Admin"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ingoing.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("Admin", result.LinkedRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_NullLinkedRole_MapsCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoing = new RegistrationKeyIngoing
|
||||||
|
{
|
||||||
|
LinkedRole = null
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ingoing.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Null(result.LinkedRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_GeneratesNewId()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoing = new RegistrationKeyIngoing
|
||||||
|
{
|
||||||
|
LinkedRole = "User"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ingoing.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result.Id);
|
||||||
|
Assert.NotEmpty(result.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_GeneratesUniqueIds()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoing1 = new RegistrationKeyIngoing { LinkedRole = "Admin" };
|
||||||
|
var ingoing2 = new RegistrationKeyIngoing { LinkedRole = "User" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result1 = ingoing1.ToInternalFromIngoing();
|
||||||
|
var result2 = ingoing2.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotEqual(result1.Id, result2.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_SetsCreatedDate()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoing = new RegistrationKeyIngoing
|
||||||
|
{
|
||||||
|
LinkedRole = "Moderator"
|
||||||
|
};
|
||||||
|
var beforeCreation = DateTime.UtcNow;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ingoing.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var afterCreation = DateTime.UtcNow;
|
||||||
|
Assert.True(result.Created >= beforeCreation.AddSeconds(-1));
|
||||||
|
Assert.True(result.Created <= afterCreation.AddSeconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_EmptyLinkedRole_MapsCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoing = new RegistrationKeyIngoing
|
||||||
|
{
|
||||||
|
LinkedRole = ""
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ingoing.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("", result.LinkedRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToInternalFromIngoing_WhitespaceLinkedRole_MapsCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var ingoing = new RegistrationKeyIngoing
|
||||||
|
{
|
||||||
|
LinkedRole = " "
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ingoing.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(" ", result.LinkedRole);
|
||||||
|
}
|
||||||
|
}
|
||||||
169
API.Tests/Services/AgeGroupServiceTests.cs
Normal file
169
API.Tests/Services/AgeGroupServiceTests.cs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
using API.Database;
|
||||||
|
using API.Models.Internal.Altersgruppen;
|
||||||
|
using API.Repository.AgeGroupRepo;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace API.Tests.Services;
|
||||||
|
|
||||||
|
public class AgeGroupServiceTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContext _context;
|
||||||
|
private readonly AgeGroupService _service;
|
||||||
|
|
||||||
|
public AgeGroupServiceTests()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||||
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
_context = new ApplicationDbContext(options);
|
||||||
|
_service = new AgeGroupService(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_context.Database.EnsureDeleted();
|
||||||
|
_context.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAllAsync_EmptyDatabase_ReturnsEmptyList()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = await _service.GetAllAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Empty(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAllAsync_WithData_ReturnsAllGroups()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var group1 = new AltersGruppe { Name = "U10", StartingAge = 8, EndingAge = 10 };
|
||||||
|
var group2 = new AltersGruppe { Name = "U12", StartingAge = 10, EndingAge = 12 };
|
||||||
|
await _context.Altersgruppen.AddRangeAsync(group1, group2);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.GetAllAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, result.Count);
|
||||||
|
Assert.Contains(result, g => g.Name == "U10");
|
||||||
|
Assert.Contains(result, g => g.Name == "U12");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAsync_ExistingId_ReturnsGroup()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var group = new AltersGruppe { Name = "U14", StartingAge = 12, EndingAge = 14 };
|
||||||
|
await _context.Altersgruppen.AddAsync(group);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.GetAsync(group.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("U14", result.Name);
|
||||||
|
Assert.Equal(12, result.StartingAge);
|
||||||
|
Assert.Equal(14, result.EndingAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAsync_NonExistingId_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = await _service.GetAsync("nonexistent-id");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAsync_ValidGroup_AddsToDatabase()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var newGroup = new AltersGruppe { Name = "U16", StartingAge = 14, EndingAge = 16 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.CreateAsync(newGroup);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("U16", result.Name);
|
||||||
|
|
||||||
|
var dbGroup = await _context.Altersgruppen.FindAsync(result.Id);
|
||||||
|
Assert.NotNull(dbGroup);
|
||||||
|
Assert.Equal("U16", dbGroup.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAsync_ExistingGroup_UpdatesProperties()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var group = new AltersGruppe { Name = "U18", StartingAge = 16, EndingAge = 18 };
|
||||||
|
await _context.Altersgruppen.AddAsync(group);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var updatedGroup = new AltersGruppe { Name = "U18 Updated", StartingAge = 15, EndingAge = 18 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.UpdateAsync(group.Id, updatedGroup);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("U18 Updated", result.Name);
|
||||||
|
Assert.Equal(15, result.StartingAge);
|
||||||
|
Assert.Equal(18, result.EndingAge);
|
||||||
|
|
||||||
|
var dbGroup = await _context.Altersgruppen.FindAsync(group.Id);
|
||||||
|
Assert.NotNull(dbGroup);
|
||||||
|
Assert.Equal("U18 Updated", dbGroup.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAsync_NonExistingGroup_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var updatedGroup = new AltersGruppe { Name = "U18 Updated", StartingAge = 15, EndingAge = 18 };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.UpdateAsync("nonexistent-id", updatedGroup);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAsync_ExistingGroup_RemovesFromDatabase()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var group = new AltersGruppe { Name = "U20", StartingAge = 18, EndingAge = 20 };
|
||||||
|
await _context.Altersgruppen.AddAsync(group);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.DeleteAsync(group.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("U20", result.Name);
|
||||||
|
|
||||||
|
var dbGroup = await _context.Altersgruppen.FindAsync(group.Id);
|
||||||
|
Assert.Null(dbGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAsync_NonExistingGroup_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = await _service.DeleteAsync("nonexistent-id");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
214
API.Tests/Services/PlunkEmailSenderTests.cs
Normal file
214
API.Tests/Services/PlunkEmailSenderTests.cs
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Text.Json;
|
||||||
|
using API.Models.Outgoing;
|
||||||
|
using API.Services;
|
||||||
|
using Moq;
|
||||||
|
using Moq.Protected;
|
||||||
|
|
||||||
|
namespace API.Tests.Services;
|
||||||
|
|
||||||
|
public class PlunkEmailSenderTests
|
||||||
|
{
|
||||||
|
private const string TestSecretKey = "test-secret-key";
|
||||||
|
private const string PlunkApiUrl = "https://api.useplunk.com/v1/send";
|
||||||
|
|
||||||
|
private static HttpClient CreateMockHttpClient(HttpStatusCode statusCode, string responseContent = "{}")
|
||||||
|
{
|
||||||
|
var mockHandler = new Mock<HttpMessageHandler>();
|
||||||
|
|
||||||
|
mockHandler
|
||||||
|
.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>(
|
||||||
|
"SendAsync",
|
||||||
|
ItExpr.IsAny<HttpRequestMessage>(),
|
||||||
|
ItExpr.IsAny<CancellationToken>())
|
||||||
|
.ReturnsAsync(new HttpResponseMessage
|
||||||
|
{
|
||||||
|
StatusCode = statusCode,
|
||||||
|
Content = new StringContent(responseContent)
|
||||||
|
});
|
||||||
|
|
||||||
|
return new HttpClient(mockHandler.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (HttpClient client, Mock<HttpMessageHandler> handler) CreateMockHttpClientWithHandler(
|
||||||
|
HttpStatusCode statusCode, string responseContent = "{}")
|
||||||
|
{
|
||||||
|
var mockHandler = new Mock<HttpMessageHandler>();
|
||||||
|
|
||||||
|
mockHandler
|
||||||
|
.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>(
|
||||||
|
"SendAsync",
|
||||||
|
ItExpr.IsAny<HttpRequestMessage>(),
|
||||||
|
ItExpr.IsAny<CancellationToken>())
|
||||||
|
.ReturnsAsync(new HttpResponseMessage
|
||||||
|
{
|
||||||
|
StatusCode = statusCode,
|
||||||
|
Content = new StringContent(responseContent)
|
||||||
|
});
|
||||||
|
|
||||||
|
return (new HttpClient(mockHandler.Object), mockHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendEmailAsync_SuccessfulSend_DoesNotThrow()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpClient = CreateMockHttpClient(HttpStatusCode.OK);
|
||||||
|
var emailSender = new PlunkEmailSender(httpClient, TestSecretKey);
|
||||||
|
|
||||||
|
// Act & Assert - should not throw
|
||||||
|
await emailSender.SendEmailAsync("test@example.com", "Test Subject", "<p>Test Body</p>");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendEmailAsync_ApiReturnsError_ThrowsException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var errorMessage = "Invalid API key";
|
||||||
|
var httpClient = CreateMockHttpClient(HttpStatusCode.Unauthorized, errorMessage);
|
||||||
|
var emailSender = new PlunkEmailSender(httpClient, TestSecretKey);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<Exception>(() =>
|
||||||
|
emailSender.SendEmailAsync("test@example.com", "Test Subject", "<p>Test Body</p>"));
|
||||||
|
|
||||||
|
Assert.Contains("Fehler beim Senden der E-Mail über Plunk", exception.Message);
|
||||||
|
Assert.Contains(errorMessage, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendEmailAsync_ServerError_ThrowsException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpClient = CreateMockHttpClient(HttpStatusCode.InternalServerError, "Server error");
|
||||||
|
var emailSender = new PlunkEmailSender(httpClient, TestSecretKey);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await Assert.ThrowsAsync<Exception>(() =>
|
||||||
|
emailSender.SendEmailAsync("test@example.com", "Test Subject", "<p>Test Body</p>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendEmailAsync_BadRequest_ThrowsException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpClient = CreateMockHttpClient(HttpStatusCode.BadRequest, "Invalid request");
|
||||||
|
var emailSender = new PlunkEmailSender(httpClient, TestSecretKey);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await Assert.ThrowsAsync<Exception>(() =>
|
||||||
|
emailSender.SendEmailAsync("test@example.com", "Test Subject", "<p>Test Body</p>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendEmailAsync_SendsCorrectPayload()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockHandler = new Mock<HttpMessageHandler>();
|
||||||
|
HttpRequestMessage? capturedRequest = null;
|
||||||
|
|
||||||
|
mockHandler
|
||||||
|
.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>(
|
||||||
|
"SendAsync",
|
||||||
|
ItExpr.IsAny<HttpRequestMessage>(),
|
||||||
|
ItExpr.IsAny<CancellationToken>())
|
||||||
|
.Callback<HttpRequestMessage, CancellationToken>((request, _) => capturedRequest = request)
|
||||||
|
.ReturnsAsync(new HttpResponseMessage
|
||||||
|
{
|
||||||
|
StatusCode = HttpStatusCode.OK,
|
||||||
|
Content = new StringContent("{}")
|
||||||
|
});
|
||||||
|
|
||||||
|
var httpClient = new HttpClient(mockHandler.Object);
|
||||||
|
var emailSender = new PlunkEmailSender(httpClient, TestSecretKey);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await emailSender.SendEmailAsync("recipient@example.com", "My Subject", "<h1>Hello</h1>");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(capturedRequest);
|
||||||
|
Assert.Equal(HttpMethod.Post, capturedRequest.Method);
|
||||||
|
Assert.Equal(PlunkApiUrl, capturedRequest.RequestUri?.ToString());
|
||||||
|
|
||||||
|
var requestContent = await capturedRequest.Content!.ReadAsStringAsync();
|
||||||
|
var payload = JsonSerializer.Deserialize<PlunkEmailDto>(requestContent, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.NotNull(payload);
|
||||||
|
Assert.Equal("recipient@example.com", payload.To);
|
||||||
|
Assert.Equal("My Subject", payload.Subject);
|
||||||
|
Assert.Equal("<h1>Hello</h1>", payload.Body);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendEmailAsync_SendsCorrectAuthorizationHeader()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockHandler = new Mock<HttpMessageHandler>();
|
||||||
|
HttpRequestMessage? capturedRequest = null;
|
||||||
|
|
||||||
|
mockHandler
|
||||||
|
.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>(
|
||||||
|
"SendAsync",
|
||||||
|
ItExpr.IsAny<HttpRequestMessage>(),
|
||||||
|
ItExpr.IsAny<CancellationToken>())
|
||||||
|
.Callback<HttpRequestMessage, CancellationToken>((request, _) => capturedRequest = request)
|
||||||
|
.ReturnsAsync(new HttpResponseMessage
|
||||||
|
{
|
||||||
|
StatusCode = HttpStatusCode.OK,
|
||||||
|
Content = new StringContent("{}")
|
||||||
|
});
|
||||||
|
|
||||||
|
var httpClient = new HttpClient(mockHandler.Object);
|
||||||
|
var emailSender = new PlunkEmailSender(httpClient, "my-secret-api-key");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await emailSender.SendEmailAsync("test@example.com", "Subject", "Body");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(capturedRequest);
|
||||||
|
var authHeader = httpClient.DefaultRequestHeaders.Authorization;
|
||||||
|
Assert.NotNull(authHeader);
|
||||||
|
Assert.Equal("Bearer", authHeader.Scheme);
|
||||||
|
Assert.Equal("my-secret-api-key", authHeader.Parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendEmailAsync_SendsCorrectContentType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockHandler = new Mock<HttpMessageHandler>();
|
||||||
|
HttpRequestMessage? capturedRequest = null;
|
||||||
|
|
||||||
|
mockHandler
|
||||||
|
.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>(
|
||||||
|
"SendAsync",
|
||||||
|
ItExpr.IsAny<HttpRequestMessage>(),
|
||||||
|
ItExpr.IsAny<CancellationToken>())
|
||||||
|
.Callback<HttpRequestMessage, CancellationToken>((request, _) => capturedRequest = request)
|
||||||
|
.ReturnsAsync(new HttpResponseMessage
|
||||||
|
{
|
||||||
|
StatusCode = HttpStatusCode.OK,
|
||||||
|
Content = new StringContent("{}")
|
||||||
|
});
|
||||||
|
|
||||||
|
var httpClient = new HttpClient(mockHandler.Object);
|
||||||
|
var emailSender = new PlunkEmailSender(httpClient, TestSecretKey);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await emailSender.SendEmailAsync("test@example.com", "Subject", "Body");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(capturedRequest);
|
||||||
|
Assert.NotNull(capturedRequest.Content);
|
||||||
|
Assert.Equal("application/json", capturedRequest.Content.Headers.ContentType?.MediaType);
|
||||||
|
Assert.Equal("utf-8", capturedRequest.Content.Headers.ContentType?.CharSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
244
API.Tests/Services/RegistrationKeyServiceTests.cs
Normal file
244
API.Tests/Services/RegistrationKeyServiceTests.cs
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
using API.Database;
|
||||||
|
using API.Models.Internal.User;
|
||||||
|
using API.Repository.RegistrationKeyRepo;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace API.Tests.Services;
|
||||||
|
|
||||||
|
public class RegistrationKeyServiceTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContext _context;
|
||||||
|
private readonly RegistrationKeyService _service;
|
||||||
|
|
||||||
|
public RegistrationKeyServiceTests()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||||
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
_context = new ApplicationDbContext(options);
|
||||||
|
_service = new RegistrationKeyService(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_context.Database.EnsureDeleted();
|
||||||
|
_context.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAllAsync_EmptyDatabase_ReturnsEmptyList()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = await _service.GetAllAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Empty(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAllAsync_WithData_ReturnsAllKeys()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var key1 = new RegistrationKey { LinkedRole = "Admin" };
|
||||||
|
var key2 = new RegistrationKey { LinkedRole = "User" };
|
||||||
|
await _context.RegistrationKeys.AddRangeAsync(key1, key2);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.GetAllAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, result.Count);
|
||||||
|
Assert.Contains(result, k => k.LinkedRole == "Admin");
|
||||||
|
Assert.Contains(result, k => k.LinkedRole == "User");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAsync_ExistingId_ReturnsKey()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var key = new RegistrationKey { LinkedRole = "Moderator" };
|
||||||
|
await _context.RegistrationKeys.AddAsync(key);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.GetAsync(key.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("Moderator", result.LinkedRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAsync_NonExistingId_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = await _service.GetAsync("nonexistent-id");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAsync_ValidKey_AddsToDatabase()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var keyIngoing = new RegistrationKeyIngoing { LinkedRole = "Admin" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.CreateAsync(keyIngoing);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("Admin", result.LinkedRole);
|
||||||
|
Assert.NotNull(result.Id);
|
||||||
|
|
||||||
|
var dbKey = await _context.RegistrationKeys.FindAsync(result.Id);
|
||||||
|
Assert.NotNull(dbKey);
|
||||||
|
Assert.Equal("Admin", dbKey.LinkedRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAsync_NullRole_AddsToDatabase()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var keyIngoing = new RegistrationKeyIngoing { LinkedRole = null };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.CreateAsync(keyIngoing);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Null(result.LinkedRole);
|
||||||
|
|
||||||
|
var dbKey = await _context.RegistrationKeys.FindAsync(result.Id);
|
||||||
|
Assert.NotNull(dbKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAsync_ExistingKey_RemovesFromDatabase()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var key = new RegistrationKey { LinkedRole = "ToDelete" };
|
||||||
|
await _context.RegistrationKeys.AddAsync(key);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.DeleteAsync(key.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("ToDelete", result.LinkedRole);
|
||||||
|
|
||||||
|
var dbKey = await _context.RegistrationKeys.FindAsync(key.Id);
|
||||||
|
Assert.Null(dbKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAsync_NonExistingKey_ReturnsNull()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = await _service.DeleteAsync("nonexistent-id");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteOldRegistrationKeysAsync_ZeroDays_ReturnsZero()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var key = new RegistrationKey { LinkedRole = "Old", Created = DateTime.UtcNow.AddDays(-10) };
|
||||||
|
await _context.RegistrationKeys.AddAsync(key);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.DeleteOldRegistrationKeysAsync(0);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, result);
|
||||||
|
Assert.Single(await _context.RegistrationKeys.ToListAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteOldRegistrationKeysAsync_NegativeDays_ReturnsZero()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var key = new RegistrationKey { LinkedRole = "Old", Created = DateTime.UtcNow.AddDays(-10) };
|
||||||
|
await _context.RegistrationKeys.AddAsync(key);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.DeleteOldRegistrationKeysAsync(-5);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, result);
|
||||||
|
Assert.Single(await _context.RegistrationKeys.ToListAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteOldRegistrationKeysAsync_NoOldKeys_ReturnsZero()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var key = new RegistrationKey { LinkedRole = "New", Created = DateTime.UtcNow };
|
||||||
|
await _context.RegistrationKeys.AddAsync(key);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.DeleteOldRegistrationKeysAsync(7);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, result);
|
||||||
|
Assert.Single(await _context.RegistrationKeys.ToListAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteOldRegistrationKeysAsync_WithOldKeys_DeletesOldKeysOnly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var oldKey1 = new RegistrationKey { LinkedRole = "Old1", Created = DateTime.UtcNow.AddDays(-10) };
|
||||||
|
var oldKey2 = new RegistrationKey { LinkedRole = "Old2", Created = DateTime.UtcNow.AddDays(-15) };
|
||||||
|
var newKey = new RegistrationKey { LinkedRole = "New", Created = DateTime.UtcNow.AddDays(-2) };
|
||||||
|
|
||||||
|
await _context.RegistrationKeys.AddRangeAsync(oldKey1, oldKey2, newKey);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.DeleteOldRegistrationKeysAsync(7);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, result);
|
||||||
|
|
||||||
|
var remainingKeys = await _context.RegistrationKeys.ToListAsync();
|
||||||
|
Assert.Single(remainingKeys);
|
||||||
|
Assert.Equal("New", remainingKeys[0].LinkedRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteOldRegistrationKeysAsync_ExactCutoffDate_KeyNotDeleted()
|
||||||
|
{
|
||||||
|
// Arrange - key created exactly at cutoff should NOT be deleted
|
||||||
|
var keyAtCutoff = new RegistrationKey { LinkedRole = "AtCutoff", Created = DateTime.UtcNow.AddDays(-7) };
|
||||||
|
await _context.RegistrationKeys.AddAsync(keyAtCutoff);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _service.DeleteOldRegistrationKeysAsync(7);
|
||||||
|
|
||||||
|
// Assert - cutoff is < not <=, so exactly 7 days old should not be deleted
|
||||||
|
Assert.Equal(0, result);
|
||||||
|
Assert.Single(await _context.RegistrationKeys.ToListAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteOldRegistrationKeysAsync_EmptyDatabase_ReturnsZero()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = await _service.DeleteOldRegistrationKeysAsync(7);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.11" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
@@ -29,8 +30,4 @@
|
|||||||
<PackageReference Include="Ulid" Version="1.4.1" />
|
<PackageReference Include="Ulid" Version="1.4.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Services\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using API.Models.Ingoing.Altersgruppen;
|
using API.Models.Internal.Altersgruppen;
|
||||||
using API.Repository.AgeGroup;
|
using API.Repository.AgeGroupRepo;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace API.Controllers
|
namespace API.Controllers
|
||||||
|
|||||||
16
API/Controllers/AuthController.cs
Normal file
16
API/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using API.Models.Internal.User;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/account")]
|
||||||
|
public class AuthController(UserManager<User> userManager, SignInManager<User> signInManager, IEmailSender emailSender)
|
||||||
|
: ControllerBase
|
||||||
|
{
|
||||||
|
private readonly UserManager<User> _userManager = userManager;
|
||||||
|
private readonly SignInManager<User> _signInManager = signInManager;
|
||||||
|
private readonly IEmailSender _emailSender = emailSender;
|
||||||
|
}
|
||||||
21
API/Controllers/RegistrationKeyController.cs
Normal file
21
API/Controllers/RegistrationKeyController.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using API.Repository.RegistrationKeyRepo;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/registrationKey")]
|
||||||
|
public class RegistrationKeyController : ControllerBase
|
||||||
|
{
|
||||||
|
private IRegistrationKeyService _keyService;
|
||||||
|
private readonly RoleManager<IdentityRole> _roleManager;
|
||||||
|
|
||||||
|
public RegistrationKeyController(IRegistrationKeyService keyService, RoleManager<IdentityRole> roleManager)
|
||||||
|
{
|
||||||
|
_keyService = keyService;
|
||||||
|
_roleManager = roleManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
using API.Models.Internal.Altersgruppen;
|
using API.Models.Internal.Altersgruppen;
|
||||||
|
using API.Models.Internal.User;
|
||||||
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace API.Database
|
namespace API.Database
|
||||||
{
|
{
|
||||||
public class ApplicationDbContext : DbContext
|
public class ApplicationDbContext : IdentityDbContext<User>
|
||||||
{
|
{
|
||||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
|
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<AltersGruppe> Altersgruppen { get; set; }
|
public DbSet<AltersGruppe> Altersgruppen { get; set; }
|
||||||
|
public DbSet<RegistrationKey> RegistrationKeys { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
|||||||
13
API/Database/Configurations/UserConfiguration.cs
Normal file
13
API/Database/Configurations/UserConfiguration.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using API.Models.Internal.User;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace API.Database.Configurations;
|
||||||
|
|
||||||
|
public class UserConfiguration : IEntityTypeConfiguration<User>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<User> entity)
|
||||||
|
{
|
||||||
|
entity.Property(e => e.Ininitals).HasMaxLength(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
294
API/Migrations/20260123202637_InitialIdentity.Designer.cs
generated
Normal file
294
API/Migrations/20260123202637_InitialIdentity.Designer.cs
generated
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using API.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20260123202637_InitialIdentity")]
|
||||||
|
partial class InitialIdentity
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.11");
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Models.Internal.Altersgruppen.AltersGruppe", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(26)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("EndingAge")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("StartingAge")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Altersgruppen");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Models.Internal.User.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("AccessFailedCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EmailConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Ininitals")
|
||||||
|
.HasMaxLength(5)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("LockoutEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedEmail")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedUserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("PhoneNumberConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SecurityStamp")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("TwoFactorEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedEmail")
|
||||||
|
.HasDatabaseName("EmailIndex");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedUserName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("UserNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUsers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("RoleNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoleClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderKey")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderDisplayName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("LoginProvider", "ProviderKey");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserLogins", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "RoleId");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "LoginProvider", "Name");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserTokens", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
223
API/Migrations/20260123202637_InitialIdentity.cs
Normal file
223
API/Migrations/20260123202637_InitialIdentity.cs
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class InitialIdentity : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetRoles",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||||
|
NormalizedName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||||
|
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetUsers",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Ininitals = table.Column<string>(type: "TEXT", maxLength: 5, nullable: true),
|
||||||
|
UserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||||
|
NormalizedUserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||||
|
Email = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||||
|
NormalizedEmail = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||||
|
EmailConfirmed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
PasswordHash = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
SecurityStamp = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
PhoneNumber = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
PhoneNumberConfirmed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
TwoFactorEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
LockoutEnd = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
|
LockoutEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
AccessFailedCount = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetRoleClaims",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
RoleId = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
ClaimType = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
ClaimValue = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
|
||||||
|
column: x => x.RoleId,
|
||||||
|
principalTable: "AspNetRoles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetUserClaims",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
ClaimType = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
ClaimValue = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetUserLogins",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
LoginProvider = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
ProviderKey = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
ProviderDisplayName = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
UserId = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetUserRoles",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
RoleId = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
|
||||||
|
column: x => x.RoleId,
|
||||||
|
principalTable: "AspNetRoles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetUserTokens",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
LoginProvider = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Value = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AspNetRoleClaims_RoleId",
|
||||||
|
table: "AspNetRoleClaims",
|
||||||
|
column: "RoleId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "RoleNameIndex",
|
||||||
|
table: "AspNetRoles",
|
||||||
|
column: "NormalizedName",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AspNetUserClaims_UserId",
|
||||||
|
table: "AspNetUserClaims",
|
||||||
|
column: "UserId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AspNetUserLogins_UserId",
|
||||||
|
table: "AspNetUserLogins",
|
||||||
|
column: "UserId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AspNetUserRoles_RoleId",
|
||||||
|
table: "AspNetUserRoles",
|
||||||
|
column: "RoleId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "EmailIndex",
|
||||||
|
table: "AspNetUsers",
|
||||||
|
column: "NormalizedEmail");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "UserNameIndex",
|
||||||
|
table: "AspNetUsers",
|
||||||
|
column: "NormalizedUserName",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetRoleClaims");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetUserClaims");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetUserLogins");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetUserRoles");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetUserTokens");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetRoles");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetUsers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
using API.Database;
|
using API.Database;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
@@ -37,6 +38,253 @@ namespace API.Migrations
|
|||||||
|
|
||||||
b.ToTable("Altersgruppen");
|
b.ToTable("Altersgruppen");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Models.Internal.User.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("AccessFailedCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EmailConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Ininitals")
|
||||||
|
.HasMaxLength(5)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("LockoutEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedEmail")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedUserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("PhoneNumberConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SecurityStamp")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("TwoFactorEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedEmail")
|
||||||
|
.HasDatabaseName("EmailIndex");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedUserName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("UserNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUsers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("RoleNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoleClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderKey")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderDisplayName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("LoginProvider", "ProviderKey");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserLogins", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "RoleId");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "LoginProvider", "Name");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserTokens", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
using API.Models.Internal.Altersgruppen;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace API.Models.Ingoing.Altersgruppen
|
|
||||||
{
|
|
||||||
public class AltersGruppeIngoing
|
|
||||||
{
|
|
||||||
[Required]
|
|
||||||
public string Name { get; set; }
|
|
||||||
[Required]
|
|
||||||
public int StartingAge { get; set; }
|
|
||||||
[Required]
|
|
||||||
public int EndingAge { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class AltersgruppeMapper
|
|
||||||
{
|
|
||||||
public static AltersGruppe ToInternalFromIngoing(this AltersGruppeIngoing group)
|
|
||||||
{
|
|
||||||
return new AltersGruppe
|
|
||||||
{
|
|
||||||
Name = group.Name,
|
|
||||||
StartingAge = group.StartingAge,
|
|
||||||
EndingAge = group.EndingAge,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,15 +4,32 @@ namespace API.Models.Internal.Altersgruppen
|
|||||||
{
|
{
|
||||||
public class AltersGruppe
|
public class AltersGruppe
|
||||||
{
|
{
|
||||||
public string Id { get; set; }
|
public string Id { get; set; } = Ulid.NewUlid().ToString();
|
||||||
public string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
public int StartingAge { get; set; }
|
public int StartingAge { get; set; }
|
||||||
public int EndingAge { get; set; }
|
public int EndingAge { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
// Constructor für automatische ULID-Generierung
|
public class AltersGruppeIngoing
|
||||||
public AltersGruppe()
|
|
||||||
{
|
{
|
||||||
Id = Ulid.NewUlid().ToString();
|
[Required]
|
||||||
|
public string Name { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int StartingAge { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int EndingAge { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AltersgruppeMapper
|
||||||
|
{
|
||||||
|
public static AltersGruppe ToInternalFromIngoing(this AltersGruppeIngoing group)
|
||||||
|
{
|
||||||
|
return new AltersGruppe
|
||||||
|
{
|
||||||
|
Name = group.Name,
|
||||||
|
StartingAge = group.StartingAge,
|
||||||
|
EndingAge = group.EndingAge,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
API/Models/Internal/User/RegistrationKey.cs
Normal file
24
API/Models/Internal/User/RegistrationKey.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace API.Models.Internal.User;
|
||||||
|
|
||||||
|
public class RegistrationKey
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = Ulid.NewUlid().ToString();
|
||||||
|
public string? LinkedRole { get; set; }
|
||||||
|
public DateTime Created { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RegistrationKeyIngoing
|
||||||
|
{
|
||||||
|
public string? LinkedRole { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RegistrationKeyMapper
|
||||||
|
{
|
||||||
|
public static RegistrationKey ToInternalFromIngoing(this RegistrationKeyIngoing key)
|
||||||
|
{
|
||||||
|
return new RegistrationKey
|
||||||
|
{
|
||||||
|
LinkedRole = key.LinkedRole,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
8
API/Models/Internal/User/User.cs
Normal file
8
API/Models/Internal/User/User.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace API.Models.Internal.User;
|
||||||
|
|
||||||
|
public class User : IdentityUser
|
||||||
|
{
|
||||||
|
public string? Ininitals { get; set; }
|
||||||
|
}
|
||||||
9
API/Models/Outgoing/PlunkEmailDto.cs
Normal file
9
API/Models/Outgoing/PlunkEmailDto.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace API.Models.Outgoing;
|
||||||
|
|
||||||
|
public class PlunkEmailDto
|
||||||
|
{
|
||||||
|
public required string To { get; set; }
|
||||||
|
public required string Subject { get; set; }
|
||||||
|
public required string Body { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
using API.Database;
|
using API.Database;
|
||||||
using API.Repository.AgeGroup;
|
using API.Models.Internal.User;
|
||||||
|
using API.Repository.AgeGroupRepo;
|
||||||
|
using API.Services;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
var webRoot = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "wwwroot"));
|
var webRoot = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "wwwroot"));
|
||||||
@@ -14,18 +18,33 @@ builder.Services.AddControllers();
|
|||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
builder.Services.AddAuthorization();
|
||||||
|
builder.Services.AddAuthentication()
|
||||||
|
.AddCookie(IdentityConstants.ApplicationScheme);
|
||||||
|
|
||||||
|
builder.Services.AddIdentityCore<User>(options =>
|
||||||
|
{
|
||||||
|
options.SignIn.RequireConfirmedAccount = true;
|
||||||
|
options.User.RequireUniqueEmail = true;
|
||||||
|
}).AddEntityFrameworkStores<ApplicationDbContext>().AddRoles<IdentityRole>().AddDefaultTokenProviders();
|
||||||
|
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
var postgreConnection = builder.Configuration.GetConnectionString("PostgresConnection");
|
var postgresConnection = builder.Configuration.GetConnectionString("PostgresConnection");
|
||||||
if (!string.IsNullOrEmpty(postgreConnection))
|
if (!string.IsNullOrEmpty(postgresConnection))
|
||||||
{
|
{
|
||||||
// Nutze PostgresSQL
|
// Nutze PostgresSQL
|
||||||
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseNpgsql(postgreConnection));
|
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseNpgsql(postgresConnection));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlite("Data Source=app.db"));
|
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlite("Data Source=app.db"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adding Email Service (Plunk)
|
||||||
|
builder.Services.AddHttpClient<IEmailSender, PlunkEmailSender>();
|
||||||
|
|
||||||
// Adding Database Services
|
// Adding Database Services
|
||||||
builder.Services.AddScoped<IAgeGroupService, AgeGroupService>();
|
builder.Services.AddScoped<IAgeGroupService, AgeGroupService>();
|
||||||
|
|
||||||
@@ -54,7 +73,7 @@ app.UseDefaultFiles();
|
|||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
using API.Models.Internal.Altersgruppen;
|
using API.Models.Internal.Altersgruppen;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace API.Repository.AgeGroup
|
namespace API.Repository.AgeGroupRepo
|
||||||
{
|
{
|
||||||
public class AgeGroupService : IAgeGroupService
|
public class AgeGroupService : IAgeGroupService
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using API.Models.Internal.Altersgruppen;
|
using API.Models.Internal.Altersgruppen;
|
||||||
|
|
||||||
namespace API.Repository.AgeGroup
|
namespace API.Repository.AgeGroupRepo
|
||||||
{
|
{
|
||||||
public interface IAgeGroupService
|
public interface IAgeGroupService
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using API.Models.Internal.User;
|
||||||
|
|
||||||
|
namespace API.Repository.RegistrationKeyRepo;
|
||||||
|
|
||||||
|
public interface IRegistrationKeyService
|
||||||
|
{
|
||||||
|
public Task<List<RegistrationKey>> GetAllAsync();
|
||||||
|
public Task<RegistrationKey?> GetAsync(string id);
|
||||||
|
public Task<RegistrationKey> CreateAsync(RegistrationKeyIngoing key);
|
||||||
|
public Task<RegistrationKey?> DeleteAsync(string id);
|
||||||
|
|
||||||
|
public Task<int> DeleteOldRegistrationKeysAsync(int x);
|
||||||
|
}
|
||||||
76
API/Repository/RegistrationKeyRepo/RegistrationKeyService.cs
Normal file
76
API/Repository/RegistrationKeyRepo/RegistrationKeyService.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using API.Database;
|
||||||
|
using API.Models.Internal.User;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace API.Repository.RegistrationKeyRepo;
|
||||||
|
|
||||||
|
public class RegistrationKeyService : IRegistrationKeyService
|
||||||
|
{
|
||||||
|
private ApplicationDbContext _context;
|
||||||
|
|
||||||
|
public RegistrationKeyService(ApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<RegistrationKey>> GetAllAsync()
|
||||||
|
{
|
||||||
|
var allKeys = await _context.RegistrationKeys.ToListAsync();
|
||||||
|
|
||||||
|
return allKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RegistrationKey?> GetAsync(string id)
|
||||||
|
{
|
||||||
|
return await _context.RegistrationKeys.FindAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RegistrationKey> CreateAsync(RegistrationKeyIngoing key)
|
||||||
|
{
|
||||||
|
var internalKey = key.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
await _context.RegistrationKeys.AddAsync(internalKey);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return internalKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RegistrationKey?> DeleteAsync(string id)
|
||||||
|
{
|
||||||
|
var key = await _context.RegistrationKeys.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.RegistrationKeys.Remove(key);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> DeleteOldRegistrationKeysAsync(int x)
|
||||||
|
{
|
||||||
|
if (x <= 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cutoff = DateTime.UtcNow.AddDays(-x);
|
||||||
|
|
||||||
|
var oldKeys = await _context.RegistrationKeys
|
||||||
|
.Where(k => k.Created < cutoff)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (oldKeys.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.RegistrationKeys.RemoveRange(oldKeys);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return oldKeys.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
API/Services/PlunkEmailSender.cs
Normal file
36
API/Services/PlunkEmailSender.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using API.Models.Outgoing;
|
||||||
|
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||||
|
|
||||||
|
namespace API.Services;
|
||||||
|
|
||||||
|
public class PlunkEmailSender(HttpClient httpClient, string plunkSecretKey) : IEmailSender
|
||||||
|
{
|
||||||
|
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
|
||||||
|
{
|
||||||
|
var requestBody = new PlunkEmailDto()
|
||||||
|
{
|
||||||
|
To = email,
|
||||||
|
Subject = subject,
|
||||||
|
Body = htmlMessage
|
||||||
|
};
|
||||||
|
|
||||||
|
var jsonContent = new StringContent(
|
||||||
|
JsonSerializer.Serialize(requestBody),
|
||||||
|
Encoding.UTF8,
|
||||||
|
"application/json"
|
||||||
|
);
|
||||||
|
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", plunkSecretKey);
|
||||||
|
|
||||||
|
var response = await httpClient.PostAsync("https://api.useplunk.com/v1/send", jsonContent);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var error = await response.Content.ReadAsStringAsync();
|
||||||
|
throw new Exception($"Fehler beim Senden der E-Mail über Plunk: {error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
API/app.db
BIN
API/app.db
Binary file not shown.
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref, watch } from 'vue';
|
||||||
import { useTheme } from 'vuetify';
|
import { useTheme } from 'vuetify';
|
||||||
import { Visibility, routes } from './plugins/routesLayout';
|
import { Visibility, routes } from './plugins/routesLayout';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
@@ -25,6 +25,25 @@ onMounted(() => {
|
|||||||
);
|
);
|
||||||
showDrawer.value = localStorage.getItem('drawer')?.startsWith('Y') || false;
|
showDrawer.value = localStorage.getItem('drawer')?.startsWith('Y') || false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function changeWebsiteTitle(path: string) {
|
||||||
|
// Pfad als Parameter
|
||||||
|
let currentPageInfo = routes.find((x) => x.path === path);
|
||||||
|
if (currentPageInfo) {
|
||||||
|
document.title = 'Judoteam - Stadtlohn | ' + currentPageInfo.name;
|
||||||
|
} else {
|
||||||
|
document.title = 'Judoteam - Stadtlohn';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beobachte die Route auf Änderungen
|
||||||
|
watch(
|
||||||
|
() => route.path,
|
||||||
|
(newPath) => {
|
||||||
|
changeWebsiteTitle(newPath);
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -40,7 +59,10 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<v-app-bar-nav-icon @click="toggleDrawer()"></v-app-bar-nav-icon>
|
<v-app-bar-nav-icon
|
||||||
|
v-tooltip="!showDrawer ? 'Menü öffnen' : 'Menü schließen'"
|
||||||
|
@click="toggleDrawer()"
|
||||||
|
></v-app-bar-nav-icon>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-app-bar-title class="title" @click="$router.push({ name: 'Home' })"
|
<v-app-bar-title class="title" @click="$router.push({ name: 'Home' })"
|
||||||
@@ -49,7 +71,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<v-tooltip v-if="!$vuetify.display.mobile">
|
<v-tooltip v-if="!$vuetify.display.mobile">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<v-btn icon v-bind="props">
|
<v-btn icon v-bind="props" to="/login">
|
||||||
<v-icon>mdi-account</v-icon>
|
<v-icon>mdi-account</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
@@ -87,6 +109,21 @@ onMounted(() => {
|
|||||||
|
|
||||||
<v-main>
|
<v-main>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
|
<v-footer
|
||||||
|
class="d-flex align-center justify-center ga-2 flex-wrap flex-grow-1 py-3"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
v-for="link in routes.filter((x) => x.visible === Visibility.Footer)"
|
||||||
|
:key="link.path"
|
||||||
|
:to="link.path"
|
||||||
|
:text="link.name"
|
||||||
|
variant="text"
|
||||||
|
rounded
|
||||||
|
></v-btn>
|
||||||
|
<div class="flex-1-0-100 text-center mt-2">
|
||||||
|
{{ new Date().getFullYear() }} — <strong>Judoteam - Stadtlohn</strong>
|
||||||
|
</div>
|
||||||
|
</v-footer>
|
||||||
</v-main>
|
</v-main>
|
||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -24,6 +24,6 @@ const props = defineProps({
|
|||||||
padding: 2px;
|
padding: 2px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
border-radius: 0px 0px 8px 8px;
|
border-radius: 0px 0px var(--default-radius) var(--default-radius);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
:root{
|
||||||
|
--default-radius: 7px;
|
||||||
|
--red-color: #b62b2b
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import Home from '@/routes/Home.vue'
|
import Home from '@/routes/Home.vue'
|
||||||
import NotFound from '@/routes/404NotFound.vue'
|
import NotFound from '@/routes/404NotFound.vue'
|
||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
|
import Login from '@/routes/authentication/Login.vue';
|
||||||
|
|
||||||
export enum Visibility {
|
export enum Visibility {
|
||||||
Hidden,
|
Hidden,
|
||||||
|
Authenticated,
|
||||||
|
Unauthenticated,
|
||||||
Authorized,
|
Authorized,
|
||||||
Public
|
Public,
|
||||||
|
Footer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -14,8 +18,9 @@ export interface LayoutRoute {
|
|||||||
name: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
icon: string,
|
icon: string,
|
||||||
|
disableFooter?: boolean,
|
||||||
visible: Visibility,
|
visible: Visibility,
|
||||||
meta: RouteRecordRaw
|
meta?: RouteRecordRaw
|
||||||
}
|
}
|
||||||
|
|
||||||
export const routes: LayoutRoute[] = [
|
export const routes: LayoutRoute[] = [
|
||||||
@@ -31,9 +36,28 @@ export const routes: LayoutRoute[] = [
|
|||||||
component: Home
|
component: Home
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: "Login",
|
||||||
|
description: "Logge dich ein",
|
||||||
|
icon: "mdi-login",
|
||||||
|
visible: Visibility.Hidden,
|
||||||
|
meta: {
|
||||||
|
path: "/login",
|
||||||
|
name: 'Login',
|
||||||
|
component: Login
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/impressum",
|
||||||
|
name: "Impressum",
|
||||||
|
description: "Impressum der Anwendung",
|
||||||
|
icon: "mdi-file-document",
|
||||||
|
visible: Visibility.Footer
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/notFound",
|
path: "/notFound",
|
||||||
name: "Nicht Verfügbar",
|
name: "Nicht Gefunden",
|
||||||
description: "Diese Seite wurde nicht gefunden",
|
description: "Diese Seite wurde nicht gefunden",
|
||||||
icon: "mdi-information-outline",
|
icon: "mdi-information-outline",
|
||||||
visible: Visibility.Hidden,
|
visible: Visibility.Hidden,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
|
||||||
import { routes } from '@/plugins/routesLayout'
|
import { routes } from '@/plugins/routesLayout'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: routes.map(x => x.meta)
|
routes: routes.filter(x => x.meta !== undefined).map(x => x.meta) as RouteRecordRaw[]
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h1>404</h1>
|
<v-container fluid class="fill-height d-flex flex-column justify-center align-center">
|
||||||
|
<h1 class="error-title text-h1 font-weight-bold">404</h1>
|
||||||
|
<p class="error-message text-h5">Page not found</p>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
to="/"
|
||||||
|
class="text-blue text-decoration-none font-weight-medium mt-5"
|
||||||
|
>
|
||||||
|
Zurück zur Startseite
|
||||||
|
</router-link>
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -18,4 +28,7 @@
|
|||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ const tabItems = [
|
|||||||
<template>
|
<template>
|
||||||
<v-container fluid>
|
<v-container fluid>
|
||||||
<v-img class="rounded-lg" height="40vh" src="/static/images/startPage.png" cover />
|
<v-img class="rounded-lg" height="40vh" src="/static/images/startPage.png" cover />
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<v-container
|
<v-container
|
||||||
id="main"
|
id="main"
|
||||||
|
|||||||
9
GUI/src/routes/authentication/Login.vue
Normal file
9
GUI/src/routes/authentication/Login.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-container class="">
|
||||||
|
<h1>Potenzieller Login</h1>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.14.36518.9 d17.14
|
VisualStudioVersion = 17.14.36518.9 d17.14
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{98166726-DC3A-4D5B-889A-8B4428E28656}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{98166726-DC3A-4D5B-889A-8B4428E28656}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API.Tests", "API.Tests\API.Tests.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -15,6 +17,10 @@ Global
|
|||||||
{98166726-DC3A-4D5B-889A-8B4428E28656}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{98166726-DC3A-4D5B-889A-8B4428E28656}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{98166726-DC3A-4D5B-889A-8B4428E28656}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{98166726-DC3A-4D5B-889A-8B4428E28656}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{98166726-DC3A-4D5B-889A-8B4428E28656}.Release|Any CPU.Build.0 = Release|Any CPU
|
{98166726-DC3A-4D5B-889A-8B4428E28656}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
190
README.md
190
README.md
@@ -1 +1,189 @@
|
|||||||
# JudoWebsurf
|
# JudoWeb
|
||||||
|
|
||||||
|
Eine moderne Webanwendung für das Judoteam Stadtlohn - mit Vereinspräsentation und Verwaltungssystem.
|
||||||
|
|
||||||
|
## Projektbeschreibung
|
||||||
|
|
||||||
|
JudoWeb ist eine Full-Stack-Webanwendung, die zwei Hauptzwecke erfüllt:
|
||||||
|
|
||||||
|
- **Öffentliche Vereinswebsite**: Präsentation des Judoclubs mit Informationen zu Judo, Vereinswerten (Respekt, Disziplin, Selbstbehauptung) und Veranstaltungen
|
||||||
|
- **Verwaltungssystem**: Benutzerverwaltung, Registrierung und Altersgruppenmanagement
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Responsive Homepage mit Bildkarussell (Würfe, Haltetechniken, Stabilitätstraining)
|
||||||
|
- Veranstaltungsübersicht (Nikolausturnier, Wettkämpfe, Kinoabende)
|
||||||
|
- Benutzerauthentifizierung mit Registrierungsschlüsseln
|
||||||
|
- Altersgruppenmanagement
|
||||||
|
- Dark/Light Theme-Umschaltung
|
||||||
|
- Mobile-optimiertes Design
|
||||||
|
|
||||||
|
## Technologie-Stack
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- **.NET 9.0** mit ASP.NET Core Web API
|
||||||
|
- **Entity Framework Core 9** - ORM
|
||||||
|
- **ASP.NET Core Identity** - Authentifizierung
|
||||||
|
- **Swagger/Swashbuckle** - API-Dokumentation
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- **Vue.js 3** mit TypeScript
|
||||||
|
- **Vite** - Build-Tool und Entwicklungsserver
|
||||||
|
- **Vuetify 3** - Material Design Komponenten
|
||||||
|
- **Vue Router** - Client-seitiges Routing
|
||||||
|
|
||||||
|
### Datenbank
|
||||||
|
- **SQLite** (Entwicklung)
|
||||||
|
- **PostgreSQL** (Produktion)
|
||||||
|
|
||||||
|
### Zusätzliche Services
|
||||||
|
- **Plunk** - E-Mail-Versand
|
||||||
|
|
||||||
|
## Projektstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
JudoWeb/
|
||||||
|
├── API/ # Backend (.NET)
|
||||||
|
│ ├── Controllers/ # API-Endpunkte
|
||||||
|
│ │ ├── AgeGroupController.cs
|
||||||
|
│ │ ├── AuthController.cs
|
||||||
|
│ │ └── RegistrationKeyController.cs
|
||||||
|
│ ├── Models/
|
||||||
|
│ │ ├── Internal/ # Domain-Modelle
|
||||||
|
│ │ └── Outgoing/ # DTOs
|
||||||
|
│ ├── Database/ # EF Core Kontext
|
||||||
|
│ ├── Repository/ # Datenzugriff
|
||||||
|
│ ├── Services/ # Business-Logik
|
||||||
|
│ └── Migrations/ # Datenbank-Migrationen
|
||||||
|
│
|
||||||
|
├── GUI/ # Frontend (Vue.js)
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── routes/ # Seiten-Komponenten
|
||||||
|
│ │ ├── components/ # Wiederverwendbare Komponenten
|
||||||
|
│ │ ├── plugins/ # Vue-Plugins
|
||||||
|
│ │ └── router/ # Router-Konfiguration
|
||||||
|
│ └── public/static/ # Statische Assets
|
||||||
|
│
|
||||||
|
└── JudoWeb.sln # Visual Studio Solution
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation & Setup
|
||||||
|
|
||||||
|
### Voraussetzungen
|
||||||
|
|
||||||
|
- [.NET 9.0 SDK](https://dotnet.microsoft.com/download)
|
||||||
|
- [Node.js](https://nodejs.org/) (LTS-Version empfohlen)
|
||||||
|
- Optional: [Visual Studio 2022](https://visualstudio.microsoft.com/) oder [VS Code](https://code.visualstudio.com/)
|
||||||
|
|
||||||
|
### Frontend einrichten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd GUI
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend einrichten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd API
|
||||||
|
dotnet restore
|
||||||
|
```
|
||||||
|
|
||||||
|
## Entwicklung starten
|
||||||
|
|
||||||
|
### Frontend (mit Hot-Reload)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd GUI
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Entwicklungsserver startet unter `http://localhost:5173`
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd API
|
||||||
|
dotnet run
|
||||||
|
```
|
||||||
|
|
||||||
|
Die API ist verfügbar unter:
|
||||||
|
- HTTP: `http://localhost:5246`
|
||||||
|
- HTTPS: `https://localhost:7248`
|
||||||
|
- Swagger UI: `https://localhost:7248/swagger`
|
||||||
|
|
||||||
|
## Verfügbare Scripts
|
||||||
|
|
||||||
|
### Frontend (GUI/)
|
||||||
|
|
||||||
|
| Befehl | Beschreibung |
|
||||||
|
|--------|--------------|
|
||||||
|
| `npm run dev` | Startet Entwicklungsserver |
|
||||||
|
| `npm run build` | Erstellt Produktions-Build |
|
||||||
|
| `npm run test:unit` | Führt Unit-Tests aus |
|
||||||
|
| `npm run type-check` | TypeScript-Typprüfung |
|
||||||
|
| `npm run format` | Code-Formatierung mit Prettier |
|
||||||
|
|
||||||
|
### Backend (API/)
|
||||||
|
|
||||||
|
| Befehl | Beschreibung |
|
||||||
|
|--------|--------------|
|
||||||
|
| `dotnet run` | Startet Entwicklungsserver |
|
||||||
|
| `dotnet build` | Kompiliert das Projekt |
|
||||||
|
| `dotnet publish -c Release` | Erstellt Produktions-Build |
|
||||||
|
| `dotnet ef database update` | Wendet Migrationen an |
|
||||||
|
|
||||||
|
## Datenbank-Konfiguration
|
||||||
|
|
||||||
|
Die Anwendung wählt automatisch die Datenbank basierend auf der Konfiguration:
|
||||||
|
|
||||||
|
1. **PostgreSQL** (Produktion): Wenn `PostgresConnection` in `appsettings.json` oder als Umgebungsvariable gesetzt ist
|
||||||
|
2. **SQLite** (Entwicklung): Standardmäßig wird `app.db` verwendet
|
||||||
|
|
||||||
|
Datenbank-Migrationen werden beim Anwendungsstart automatisch ausgeführt.
|
||||||
|
|
||||||
|
### Beispiel PostgreSQL-Konfiguration
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"PostgresConnection": "Host=localhost;Database=judoweb;Username=user;Password=pass"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API-Endpunkte
|
||||||
|
|
||||||
|
| Endpunkt | Beschreibung |
|
||||||
|
|----------|--------------|
|
||||||
|
| `GET /api/ageGroups` | Alle Altersgruppen abrufen |
|
||||||
|
| `POST /api/ageGroups` | Neue Altersgruppe erstellen |
|
||||||
|
| `PUT /api/ageGroups/{id}` | Altersgruppe aktualisieren |
|
||||||
|
| `DELETE /api/ageGroups/{id}` | Altersgruppe löschen |
|
||||||
|
| `POST /api/account/register` | Benutzer registrieren |
|
||||||
|
| `POST /api/account/login` | Benutzer anmelden |
|
||||||
|
| `GET /api/registrationKey` | Registrierungsschlüssel abrufen |
|
||||||
|
|
||||||
|
Vollständige API-Dokumentation ist über Swagger UI verfügbar.
|
||||||
|
|
||||||
|
## Produktion
|
||||||
|
|
||||||
|
### Frontend bauen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd GUI
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Build wird in `../wwwroot/` ausgegeben und vom Backend als statische Dateien serviert.
|
||||||
|
|
||||||
|
### Backend publizieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd API
|
||||||
|
dotnet publish -c Release -o ./publish
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
Dieses Projekt ist proprietär und gehört dem Judoteam Stadtlohn.
|
||||||
|
|||||||
Reference in New Issue
Block a user