Update Roles in UI and API
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 5m9s

This commit is contained in:
Leandro Hernan Rojas 2025-04-30 00:16:59 -03:00
parent bedee403b6
commit 508ab9de18
7 changed files with 286 additions and 169 deletions

View File

@ -142,7 +142,13 @@ namespace phronCare.API.Controllers
user.NormalizedEmail = model.Email.ToUpper();
user.TwoFactorEnabled = model.TwoFactorEnabled;
user.LockoutEnabled = model.LockoutEnabled;
// Campos personalizados
user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.PhoneNumber = model.PhoneNumber;
user.CompanyName = model.CompanyName;
user.Department = model.Department;
user.BirthDate = model.BirthDate;
var result = await _userManager.UpdateAsync(user);
if (result.Succeeded)

View File

@ -7,6 +7,14 @@
public string Email { get; set; } = string.Empty;
public bool TwoFactorEnabled { get; set; }
public bool LockoutEnabled { get; set; }
// Nuevos campos
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? PhoneNumber { get; set; }
public string? CompanyName { get; set; }
public string? Department { get; set; }
public DateTime? BirthDate { get; set; }
}
public class User
{
@ -24,5 +32,12 @@
public DateTimeOffset? LockoutEnd { get; set; }
public bool LockoutEnabled { get; set; }
public int AccessFailedCount { get; set; }
// Nuevos campos
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Company { get; set; }
public string? Department { get; set; }
public DateTime? BirthDate { get; set; }
}
}

View File

@ -1,71 +1,104 @@
@page "/registration"
@using System.Text.Json;
@using System.Collections.Generic
@using System.ComponentModel.DataAnnotations;
@using System.Text.Json
@using System.ComponentModel.DataAnnotations
@inject HttpClient httpClient
@inject IToastService toastService
@inject NavigationManager navigation
<h1>Registro de Usuario</h1>
<EditForm Model="user" OnValidSubmit="RegistrarUsuario">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group">
<label for="InputUserName">Nombre de Usuario:</label>
<div class="col-md-4">
<InputText id="InputUserName" @bind-Value="user.UserName" class="form-control" />
</div>
<div class="card mt-4" style="zoom:90%">
<div class="card-header text-center">
<h3 class="card-title">Registro de Usuario</h3>
</div>
<div class="form-group">
<label for="InputEmail">Correo Electrónico:</label>
<div class="col-md-4">
<InputText id="InputEmail" @bind-Value="user.EmailAddress" class="form-control" />
</div>
<div class="card-body">
<EditForm Model="user" OnValidSubmit="RegistrarUsuario">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="row">
<div class="col-md-6">
<label for="InputUserName">Nombre de Usuario:</label>
<InputText id="InputUserName" @bind-Value="user.UserName" class="form-control" />
</div>
<div class="col-md-6">
<label for="InputEmail">Correo Electrónico:</label>
<InputText id="InputEmail" @bind-Value="user.EmailAddress" class="form-control" />
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<label for="InputPassword">Contraseña:</label>
<InputText id="InputPassword" @bind-Value="user.Password" type="password" class="form-control" />
</div>
<div class="col-md-6">
<label for="InputConfirmPassword">Confirmar Contraseña:</label>
<InputText id="InputConfirmPassword" @bind-Value="confirmPassword" type="password" class="form-control" />
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<label for="InputRole">Rol:</label>
<InputSelect @bind-Value="user.Role" class="form-control">
@foreach (var role in roles)
{
<option value="@role.Name">@role.Name</option>
}
</InputSelect>
</div>
<div class="col-md-6">
<label for="PhoneNumber">Teléfono:</label>
<InputText id="PhoneNumber" @bind-Value="user.PhoneNumber" class="form-control" />
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<label for="FirstName">Nombre:</label>
<InputText id="FirstName" @bind-Value="user.FirstName" class="form-control" />
</div>
<div class="col-md-6">
<label for="LastName">Apellido:</label>
<InputText id="LastName" @bind-Value="user.LastName" class="form-control" />
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<label for="Company">Empresa:</label>
<InputText id="Company" @bind-Value="user.Company" class="form-control" />
</div>
<div class="col-md-6">
<label for="Department">Departamento:</label>
<InputText id="Department" @bind-Value="user.Department" class="form-control" />
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<label for="BirthDate">Fecha de nacimiento:</label>
<InputDate id="BirthDate" @bind-Value="user.BirthDate" class="form-control" />
</div>
</div>
</EditForm>
</div>
<div class="form-group">
<label for="InputPassword">Contraseña:</label>
<div class="col-md-4">
<InputText id="InputPassword" @bind-Value="user.Password" type="password" class="form-control" />
</div>
<div class="card-footer d-flex justify-content-end">
<button type="submit" class="btn btn-primary me-2" @onclick="RegistrarUsuario">Registrar</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancelar</button>
</div>
<div class="form-group">
<label for="InputConfirmPassword">Confirmar Contraseña:</label>
<div class="col-md-4">
<InputText id="InputConfirmPassword" @bind-Value="confirmPassword" type="password" class="form-control" />
</div>
</div>
<div class="form-group">
<label for="InputRole">Rol:</label>
<div class="col-md-4">
<InputSelect @bind-Value="user.Role" class="form-control">
@foreach (var role in roles)
{
<option value="@role.Name">@role.Name</option>
}
</InputSelect>
</div>
</div>
<br />
<button type="submit" class="btn btn-primary">Registrar</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancelar</button>
</EditForm>
</div>
@code {
private User user = new User();
private string confirmPassword=string.Empty;
private User user = new();
private string confirmPassword = string.Empty;
public List<Role> roles = new List<Role>(); // Reemplaza con tu lista de roles reales
public List<Role> roles = new();
private const int USERNAME_VALID_LENGTH = 8;
protected override async Task OnInitializedAsync()
{
roles = await GetAllRoles();
@ -78,25 +111,26 @@
}
private async Task RegistrarUsuario()
{
{
if (user.UserName.Length < USERNAME_VALID_LENGTH)
{
toastService.ShowError($"longitud de nombre minima: {USERNAME_VALID_LENGTH} caracteres. ¡Inténtalo de nuevo!");
toastService.ShowError($"El nombre de usuario debe tener al menos {USERNAME_VALID_LENGTH} caracteres.");
}
else if (user.Password != confirmPassword)
{
toastService.ShowError("Las contraseñas no coinciden. ¡Inténtalo de nuevo!");
toastService.ShowError("Las contraseñas no coinciden.");
}
else if (string.IsNullOrEmpty(user.Role))
{
toastService.ShowWarning("Debes seleccionar un rol.");
toastService.ShowWarning("Debe seleccionar un rol.");
}
else
{
await CreateUser();
}
}
public async Task CreateUser()
private async Task CreateUser()
{
var requestContent = new StringContent(JsonSerializer.Serialize(user), System.Text.Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync("/api/Authentication/register/", requestContent);
@ -111,18 +145,26 @@
toastService.ShowError(errorResponse);
}
}
public void Cancel()
private void Cancel()
{
navigation.NavigateTo("/users");
}
public class User
{
[Required(ErrorMessage = "El Username es un dato obligatorio")] public string UserName { get; set; } = string.Empty;
[EmailAddress, Required(ErrorMessage = "El correo electronico es un dato obligatorio")] public string EmailAddress { get; set; } = string.Empty;
[Required(ErrorMessage = "La contraseña es un dato obligatorio")] public string Password { get; set; } = string.Empty;
[Required(ErrorMessage = "El rol es un dato obligatorio")] public string Role { get; set; } = string.Empty;
[Required] public string UserName { get; set; } = string.Empty;
[EmailAddress, Required] public string EmailAddress { get; set; } = string.Empty;
[Required] public string Password { get; set; } = string.Empty;
[Required] public string Role { get; set; } = string.Empty;
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? PhoneNumber { get; set; }
public string? Company { get; set; }
public string? Department { get; set; }
public DateTime? BirthDate { get; set; }
}
public class Role
{
public string Id { get; set; } = string.Empty;

View File

@ -1,5 +1,5 @@
@page "/roleform/edit/{id}"
@page "/roleform/create"
@page "/role/{id}"
@page "/role/create"
@using System.Net.Http.Headers;
@using System.Text.Json;
@ -9,8 +9,14 @@
@inject IToastService toastService
@inject AuthenticationStateProvider authenticationStateProvider
<h1>Editar Rol</h1>
@if (role.Id is null)
{
<h1>Crear Nuevo Rol</h1>
}
else
{
<h1>Editar Rol</h1>
}
@if (role is not null)
{
<EditForm Model="role" OnValidSubmit="UpsertRole">
@ -42,10 +48,8 @@
[Parameter]
public string id { get; set; } = string.Empty;
private Role? role;
protected override async Task OnInitializedAsync()
{
if ( id is not null)
{
role = await GetRole(id);
@ -55,6 +59,7 @@
role = new Role();
}
}
public async Task<Role?> GetRole(string roleId)
{
var customAuthStateProvider = (CustomAuthorizationProvider)authenticationStateProvider;
@ -75,7 +80,6 @@
};
Role role = JsonSerializer.Deserialize<Role>(jsonResponse, options) ?? new Role();
return role;
}
}
catch
@ -88,7 +92,7 @@
}
private async Task UpsertRole()
{
if(role is null)
if(role.Id is null)
{
await CreateRole();
}
@ -99,23 +103,8 @@
}
public async Task UpdateRole()
{
// var requestContent = new StringContent(JsonSerializer.Serialize(role), System.Text.Encoding.UTF8, "application/json");
// var response = await _httpClient.PutAsync("/api/Account/UpdateRole/"+role.Id, requestContent);
// if (response.IsSuccessStatusCode)
// {
// toastService.ShowSuccess("El registro fue actualizado correctamente!");
// Navigation.NavigateTo("/roles"); // Redirige a la página de roles después de la actualización
// }
// else
// {
// // Manejar errores de actualización
// var errorResponse = await response.Content.ReadAsStringAsync();
// toastService.ShowError(errorResponse);
// }
if (role == null)
{
// Maneja el caso en que role es null
toastService.ShowError("Role no está definido.");
return;
}
@ -135,10 +124,10 @@
toastService.ShowError(errorResponse);
}
}
public async Task CreateRole()
{
var newConcurrencyStamp = Guid.NewGuid().ToString();
var requestContent = new StringContent(JsonSerializer.Serialize(role), System.Text.Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("/api/Account/CreateRole/", requestContent);
var errorResponse = await response.Content.ReadAsStringAsync();

View File

@ -10,7 +10,7 @@
@inject AuthenticationStateProvider authenticationStateProvider
<h1>Lista de Roles</h1>
<a href="/roleform/create" class="btn btn-dark">Crear Nuevo Rol</a>
<a href="/role/create" class="btn btn-dark">Crear Nuevo Rol</a>
<br/>
@if (roles != null && roles.Count > 0)
{
@ -100,7 +100,7 @@ else
}
public void EditRole(string roleId)
{
navigation.NavigateTo($"/roleform/edit/{roleId}");
navigation.NavigateTo($"/role/{roleId}");
}
private void ConfirmDelete(string roleId)
{

View File

@ -1,54 +1,80 @@
@page "/userform/edit/{Id}"
@using System.Net.Http.Headers;
@using System.Text.Json;
@using System.Net.Http.Headers
@using System.Text.Json
@using Microsoft.AspNetCore.Components.Forms
@inject HttpClient _httpClient
@inject NavigationManager Navigation
@inject IToastService toastService
@inject AuthenticationStateProvider authenticationStateProvider
@inject IToastService toastService
<h1>Editar Usuario</h1>
@if (user is not null)
{
<EditForm Model="@user" OnSubmit="UpdateUser">
<div class="form-group">
<label for="Name">Id del Rol</label>
<div class="col-md-4">
<InputText disabled="1" @bind-Value="user.Id" id="Id" class="form-control" />
<div class="card mt-4" style="zoom: 90%">
<div class="card-header text-center">
<h3 class="card-title">Editar Usuario</h3>
</div>
@if (user is not null)
{
<EditForm Model="@user" OnValidSubmit="UpdateUser">
<div class="card-body">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="row">
<div class="col-sm-12 col-md-6">
<label>Nombre:</label>
<InputText @bind-Value="user.FirstName" class="form-control" />
</div>
<div class="col-sm-12 col-md-6">
<label>Apellido:</label>
<InputText @bind-Value="user.LastName" class="form-control" />
</div>
</div>
<div class="row mt-3">
<div class="col-sm-12 col-md-6">
<label>Teléfono:</label>
<InputText @bind-Value="user.PhoneNumber" class="form-control" />
</div>
<div class="col-sm-12 col-md-6">
<label>Empresa:</label>
<InputText @bind-Value="user.CompanyName" class="form-control" />
</div>
</div>
<div class="row mt-3">
<div class="col-sm-12 col-md-6">
<label>Departamento:</label>
<InputText @bind-Value="user.Department" class="form-control" />
</div>
<div class="col-sm-12 col-md-6">
<label>Fecha de nacimiento:</label>
<InputDate @bind-Value="user.BirthDate" class="form-control" />
</div>
</div>
<div class="row mt-4">
<div class="col-sm-12 col-md-6 d-flex align-items-center">
<div class="form-check form-switch">
<InputCheckbox id="TwoFactorEnabled" @bind-Value="user.TwoFactorEnabled" class="form-check-input" />
<label class="form-check-label ms-2" for="TwoFactorEnabled">Autenticación de dos factores</label>
</div>
</div>
<div class="col-sm-12 col-md-6 d-flex align-items-center">
<div class="form-check form-switch">
<InputCheckbox id="LockoutEnabled" @bind-Value="user.LockoutEnabled" class="form-check-input" />
<label class="form-check-label ms-2" for="LockoutEnabled">Bloqueo de cuenta</label>
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="Username">Nombre de usuario:</label>
<div class="col-md-4">
<InputText id="Username" @bind-Value="user.UserName" class="form-control" />
<div class="card-footer d-flex justify-content-end">
<button type="submit" class="btn btn-primary me-2">Guardar</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancelar</button>
</div>
</div>
<div class="form-group">
<label for="Username">Correo electrónico:</label>
<div class="col-md-4">
<InputText id="Email" @bind-Value="user.Email" class="form-control" />
</div>
</div>
<div class="form-group">
<label for="TwoFactorEnabled">Autenticación de dos factores:</label>
<div class="col-md-4">
<InputCheckbox id="TwoFactorEnabled" @bind-Value="user.TwoFactorEnabled"/>
</div>
</div>
<div class="form-group">
<label for="LockoutEnabled">Bloqueo de cuenta:</label>
<div class="col-md-4">
<InputCheckbox id="LockoutEnabled" @bind-Value="user.LockoutEnabled"/>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Guardar</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancelar</button>
</div>
</EditForm>
}
</EditForm>
}
</div>
@code {
[Parameter]
@ -57,12 +83,12 @@
protected override async Task OnInitializedAsync()
{
if (id is not null)
{
user = await GetUser(id);
}
}
public async Task<UserUpdate?> GetUser(string userId)
{
var customAuthStateProvider = (CustomAuthorizationProvider)authenticationStateProvider;
@ -77,28 +103,31 @@
if (response.IsSuccessStatusCode)
{
var jsonResponse = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var deserializedUser = JsonSerializer.Deserialize<User>(jsonResponse, options);
User user = deserializedUser ?? new User();
// user = JsonSerializer.Deserialize<User>(jsonResponse, options);
UserUpdate require = new UserUpdate();
require.Id = user.Id;
require.UserName = user.UserName;
require.Email = user.Email;
require.LockoutEnabled = user.LockoutEnabled;
require.TwoFactorEnabled = user.TwoFactorEnabled;
return require;
return new UserUpdate
{
Id = user.Id,
UserName = user.UserName,
Email = user.Email,
LockoutEnabled = user.LockoutEnabled,
TwoFactorEnabled = user.TwoFactorEnabled,
FirstName = user.FirstName,
LastName = user.LastName,
PhoneNumber = user.PhoneNumber,
CompanyName = user.CompanyName,
Department = user.Department,
BirthDate = user.BirthDate
};
}
}
catch
{
return null;
}
};
}
return null;
}
@ -125,10 +154,12 @@
toastService.ShowError(ex.Message);
}
}
public void Cancel()
{
Navigation.NavigateTo("/users");
}
public class User
{
public string Id { get; set; } = string.Empty;
@ -145,14 +176,27 @@
public DateTimeOffset? LockoutEnd { get; set; }
public bool LockoutEnabled { get; set; }
public int AccessFailedCount { get; set; }
// Campos personalizados
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? CompanyName { get; set; }
public string? Department { get; set; }
public DateTime? BirthDate { get; set; }
}
public class UserUpdate
{
public string Id { get; set; } = string.Empty;
public string UserName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? PhoneNumber { get; set; }
public string? CompanyName { get; set; }
public string? Department { get; set; }
public DateTime? BirthDate { get; set; }
public bool TwoFactorEnabled { get; set; }
public bool LockoutEnabled { get; set; }
}
}
}

View File

@ -10,41 +10,53 @@
@inject AuthenticationStateProvider authenticationStateProvider
<h1>Lista de Usuarios</h1>
<a href="/registration" class="btn btn-dark">Registrar usuario</a>
<br />
<a href="/registration" class="btn btn-dark mb-3">Registrar usuario</a>
@if (users != null && users.Count > 0)
{
<table class="table">
<thead>
<table class="table table-hover">
<thead class="table-secondary">
<tr>
<th>Id</th>
<th>Username</th>
<th>Email</th>
<th>Confirmed</th>
<th>2FA</th>
<th>Access Failed</th>
<th>Lockout</th>
<th>Actions</th>
<th class="text-center align-middle">Nombre completo</th>
<th class="text-center align-middle">Usuario</th>
<th class="text-center align-middle">Email</th>
<th class="text-center align-middle">Teléfono</th>
<th class="text-center align-middle">Empresa</th>
<th class="text-center align-middle">Departamento</th>
<th class="text-center align-middle">Verificado</th>
<th class="text-center align-middle">2FA</th>
<th class="text-center align-middle">#Intentos</th>
<th class="text-center align-middle">Lockout</th>
<th class="text-center align-middle">Acciones</th>
</tr>
</thead>
<tbody>
@foreach (var user in users)
{
<tr>
<td>@user.Id</td>
<td>@user.FullName</td>
<td>@user.UserName</td>
<td>@user.Email</td>
<td>@user.EmailConfirmed</td>
<td>@user.TwoFactorEnabled</td>
<td>@user.AccessFailedCount</td>
<td>@user.LockoutEnabled</td>
<td>@user.PhoneNumber</td>
<td>@user.CompanyName</td>
<td>@user.Department</td>
<td class="text-center align-middle">@(user.EmailConfirmed ? "✅" : "❌")</td>
<td class="text-center align-middle">@(user.TwoFactorEnabled ? "✅" : "❌")</td>
<td class="text-center align-middle">@user.AccessFailedCount</td>
<td class="text-center align-middle">@(user.LockoutEnabled ? "✅" : "❌")</td>
<td>
<button class="btn btn-primary btn-margin" @onclick="() => EditUser(user.Id)"> <span class="fa fa-pencil"></span> </button>
<button class="btn btn-sm btn-primary me-1" @onclick="() => EditUser(user.Id)">
<i class="fa fa-pencil"></i>
</button>
@if (user.UserName.ToLower() != "superdmin")
{
<button class="btn btn-danger btn-margin" @onclick="() => ConfirmDelete(user.Id)"> <span class="fa fa-trash"></span> </button>
<button class="btn btn-sm btn-danger me-1" @onclick="() => ConfirmDelete(user.Id)">
<i class="fa fa-trash"></i>
</button>
}
<button class="btn btn-warning btn-margin" @onclick="() => RecoveryPassword(user.Email)"> <span class="fa fa-user-secret"></span> </button>
<button class="btn btn-sm btn-warning" @onclick="() => RecoveryPassword(user.Email)">
<i class="fa fa-user-secret"></i>
</button>
</td>
</tr>
}
@ -53,8 +65,7 @@
}
else
{
<br />
<p>Cargando informacion...</p>
<p class="mt-3">Cargando información o no hay usuarios disponibles...</p>
}
@code {
@ -69,7 +80,7 @@ else
try
{
var response = await _httpClient.GetAsync("/api/Account/GetAllUsers");
Console.WriteLine(token.token);
//Console.WriteLine(token.token);
if (response.IsSuccessStatusCode)
{
var jsonResponse = await response.Content.ReadAsStringAsync();
@ -206,5 +217,15 @@ else
public DateTimeOffset? LockoutEnd { get; set; }
public bool LockoutEnabled { get; set; }
public int AccessFailedCount { get; set; }
// Nuevos campos
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string FullName { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string Department { get; set; } = string.Empty;
public string CompanyName { get; set; } = string.Empty;
public DateTime? BirthDate { get; set; }
}
}