feat(backend): add admin evaluation history, engine, simulation, bot API controllers and global exception middleware

This commit is contained in:
2026-08-24 21:37:32 +02:00
parent 6974b2075b
commit 676496b77d
37 changed files with 88203 additions and 83075 deletions
+12 -55
View File
@@ -21,9 +21,6 @@ public interface IUserService
/// <summary>Authenticates user credentials and returns JWT response.</summary>
Task<AuthResponseDto?> AuthenticateAsync(LoginRequestDto request, CancellationToken cancellationToken = default);
/// <summary>Registers a new user account.</summary>
Task<AuthResponseDto?> RegisterUserAsync(RegisterRequestDto request, CancellationToken cancellationToken = default);
/// <summary>Changes the initial password for a user.</summary>
Task<bool> ChangeInitialPasswordAsync(Guid userId, string newPassword,
CancellationToken cancellationToken = default);
@@ -115,54 +112,6 @@ public class UserService : IUserService
};
}
/// <inheritdoc />
public async Task<AuthResponseDto?> RegisterUserAsync(RegisterRequestDto request,
CancellationToken cancellationToken = default)
{
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<BackendDbContext>();
string normalizedEmail = request.Email.Trim().ToLowerInvariant();
bool exists = await dbContext.Users.AnyAsync(u => u.Email.ToLower() == normalizedEmail, cancellationToken);
if (exists)
{
_logger.LogWarning("[{Channel}] Registration failed: Email '{Email}' is already taken.", "AuthChannel",
request.Email);
return null;
}
string passwordHash = BCrypt.Net.BCrypt.HashPassword(request.Password);
var newUser = new UserEntity
{
Email = normalizedEmail,
PasswordHash = passwordHash,
FullName = string.IsNullOrWhiteSpace(request.FullName) ? normalizedEmail.Split('@')[0] : request.FullName,
Role = "User",
ThemePreference = "fluent_dark", // Default Theme for FluentAvalonia
IsActive = true,
CreatedAt = DateTime.UtcNow,
LastLoginAt = DateTime.UtcNow
};
dbContext.Users.Add(newUser);
await dbContext.SaveChangesAsync(cancellationToken);
var (token, expiresAt) = _jwtTokenService.GenerateToken(newUser);
return new AuthResponseDto
{
Token = token,
UserId = newUser.Id,
Email = newUser.Email,
FullName = newUser.FullName,
Role = newUser.Role,
ThemePreference = newUser.ThemePreference,
FcmTokens = new List<string>(),
ExpiresAt = expiresAt
};
}
/// <inheritdoc />
public async Task<UserDto?> CreateUserByAdminAsync(CreateUserRequestDto request,
CancellationToken cancellationToken = default)
@@ -352,13 +301,21 @@ public class UserService : IUserService
if (!exists)
{
string password = !string.IsNullOrWhiteSpace(defaultAdminPassword)
? defaultAdminPassword
: "AdminDefaultPassword2026!";
// Fail-fast statt Fallback auf einen im Repo öffentlichen Literal (Rules.md §12): ein Gateway,
// das den Default-Admin mit einem repo-öffentlichen Passwort anlegt, ist schlimmer als eines,
// das nicht startet. Program.cs validiert dies bereits vor dem Aufruf; dieser Guard dient als
// Verteidigung in der Tiefe, falls die Methode von anderer Stelle aufgerufen wird.
if (string.IsNullOrWhiteSpace(defaultAdminPassword))
{
throw new InvalidOperationException(
"ADMIN:DefaultPassword (bzw. ADMIN__DefaultPassword) ist nicht konfiguriert. " +
"Das Seeding des Default-Admin-Accounts kann ohne explizit konfiguriertes Passwort nicht durchgeführt werden.");
}
var adminUser = new UserEntity
{
Email = adminEmail,
PasswordHash = BCrypt.Net.BCrypt.HashPassword(password),
PasswordHash = BCrypt.Net.BCrypt.HashPassword(defaultAdminPassword),
FullName = "System Administrator",
Role = "Admin",
ThemePreference = "fluent_dark",