feat(Backend): update API gateway and websocket hubs

This commit is contained in:
2026-08-09 21:32:52 +02:00
parent fdf4b6efcb
commit a9553e9fbf
66 changed files with 188878 additions and 0 deletions
@@ -0,0 +1,142 @@
using System;
using System.Security.Claims;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using FinlyticBackend.Services;
using FinlyticCore.Models.Auth;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace FinlyticBackend.Controllers;
/// <summary>
/// DTO for admin password reset requests (AOT-compliant).
/// </summary>
public record ResetPasswordAdminDto(
[property: JsonPropertyName("newPassword")] string NewPassword
);
[ApiController]
[Route("api/v1/admin")]
[Authorize(Roles = "Admin")]
[EnableCors("AllowAll")]
public class AdminUserController : ControllerBase
{
private readonly IUserService _userService;
public AdminUserController(IUserService userService)
{
_userService = userService;
}
/// <summary>
/// Creates a new user account by an administrator.
/// </summary>
[HttpPost("users")]
public async Task<IActionResult> CreateUser([FromBody] CreateUserRequestDto request, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(request.Email) || string.IsNullOrWhiteSpace(request.Password))
{
return BadRequest(new { error = "Email and Password are required." });
}
var createdUser = await _userService.CreateUserByAdminAsync(request, cancellationToken);
if (createdUser == null)
{
return Conflict(new { error = $"User with email '{request.Email}' already exists." });
}
return StatusCode(201, createdUser);
}
/// <summary>
/// Retrieves a list of all registered users.
/// </summary>
[HttpGet("users")]
public async Task<IActionResult> GetAllUsers(CancellationToken cancellationToken)
{
var users = await _userService.GetAllUsersAsync(cancellationToken);
return Ok(users);
}
/// <summary>
/// Updates an existing user account.
/// </summary>
[HttpPut("users/{id:guid}")]
public async Task<IActionResult> UpdateUser(Guid id, [FromBody] UpdateUserRequestDto request, CancellationToken cancellationToken)
{
var currentAdminId = GetCurrentUserId();
// Schutz: Ein Admin sollte sich nicht selbst abwerten oder deaktivieren können
if (currentAdminId.HasValue && currentAdminId.Value == id)
{
if (request.IsActive == false)
{
return BadRequest(new { error = "You cannot deactivate your own admin account." });
}
if (!string.IsNullOrWhiteSpace(request.Role) && !request.Role.Equals("Admin", StringComparison.OrdinalIgnoreCase))
{
return BadRequest(new { error = "You cannot revoke your own Admin role." });
}
}
var updated = await _userService.UpdateUserAsync(id, request, cancellationToken);
if (updated == null)
{
return NotFound(new { error = $"User with ID '{id}' not found." });
}
return Ok(updated);
}
/// <summary>
/// Deactivates a user account.
/// </summary>
[HttpDelete("users/{id:guid}")]
public async Task<IActionResult> DeactivateUser(Guid id, CancellationToken cancellationToken)
{
var currentAdminId = GetCurrentUserId();
if (currentAdminId.HasValue && currentAdminId.Value == id)
{
return BadRequest(new { error = "You cannot deactivate your own admin account." });
}
bool deactivated = await _userService.DeactivateUserAsync(id, cancellationToken);
if (!deactivated)
{
return NotFound(new { error = $"User with ID '{id}' not found." });
}
return Ok(new { message = $"User '{id}' deactivated successfully." });
}
/// <summary>
/// Resets a user's password and forces password change on next login.
/// </summary>
[HttpPost("users/{id:guid}/reset-password")]
public async Task<IActionResult> ResetPassword(Guid id, [FromBody] ResetPasswordAdminDto request, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(request?.NewPassword))
{
return BadRequest(new { error = "NewPassword is required." });
}
bool reset = await _userService.ResetPasswordAsync(id, request.NewPassword, cancellationToken);
if (!reset)
{
return NotFound(new { error = $"User with ID '{id}' not found." });
}
return Ok(new { message = $"Password for user '{id}' has been reset successfully. They must change it upon next login." });
}
private Guid? GetCurrentUserId()
{
var claimVal = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub");
return Guid.TryParse(claimVal, out var parsed) ? parsed : null;
}
}