feat(domain): V008 migration + Usuario with-methods + DomainException hierarchy [UDT-008]
This commit is contained in:
@@ -12,6 +12,11 @@ public sealed class Usuario
|
||||
public string PermisosJson { get; }
|
||||
public bool Activo { get; }
|
||||
|
||||
// UDT-008: new properties
|
||||
public DateTime? FechaModificacion { get; }
|
||||
public DateTime? UltimoLogin { get; }
|
||||
public bool MustChangePassword { get; }
|
||||
|
||||
public Usuario(
|
||||
int id,
|
||||
string username,
|
||||
@@ -21,7 +26,10 @@ public sealed class Usuario
|
||||
string? email,
|
||||
string rol,
|
||||
string permisosJson,
|
||||
bool activo)
|
||||
bool activo,
|
||||
DateTime? fechaModificacion = null,
|
||||
DateTime? ultimoLogin = null,
|
||||
bool mustChangePassword = false)
|
||||
{
|
||||
Id = id;
|
||||
Username = username;
|
||||
@@ -32,11 +40,14 @@ public sealed class Usuario
|
||||
Rol = rol;
|
||||
PermisosJson = permisosJson;
|
||||
Activo = activo;
|
||||
FechaModificacion = fechaModificacion;
|
||||
UltimoLogin = ultimoLogin;
|
||||
MustChangePassword = mustChangePassword;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating a new user (no Id — DB assigns via IDENTITY).
|
||||
/// Defaults: Activo=true, PermisosJson="[]".
|
||||
/// Defaults: Activo=true, PermisosJson="[]", MustChangePassword=false.
|
||||
/// </summary>
|
||||
public static Usuario ForCreation(
|
||||
string username,
|
||||
@@ -55,6 +66,87 @@ public sealed class Usuario
|
||||
email: email,
|
||||
rol: rol,
|
||||
permisosJson: "[]",
|
||||
activo: true);
|
||||
activo: true,
|
||||
fechaModificacion: null,
|
||||
ultimoLogin: null,
|
||||
mustChangePassword: false);
|
||||
}
|
||||
|
||||
// ── UDT-008: copy-with factory methods ────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance with updated profile fields.
|
||||
/// Sets FechaModificacion = UtcNow. Username and PasswordHash are immutable.
|
||||
/// </summary>
|
||||
public Usuario WithUpdatedProfile(string nombre, string apellido, string? email, string rol, bool activo)
|
||||
=> new(
|
||||
id: Id,
|
||||
username: Username,
|
||||
passwordHash: PasswordHash,
|
||||
nombre: nombre,
|
||||
apellido: apellido,
|
||||
email: email,
|
||||
rol: rol,
|
||||
permisosJson: PermisosJson,
|
||||
activo: activo,
|
||||
fechaModificacion: DateTime.UtcNow,
|
||||
ultimoLogin: UltimoLogin,
|
||||
mustChangePassword: MustChangePassword);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance with a new password hash and mustChangePassword flag.
|
||||
/// Sets FechaModificacion = UtcNow.
|
||||
/// </summary>
|
||||
public Usuario WithNewPasswordHash(string hash, bool mustChangePassword)
|
||||
=> new(
|
||||
id: Id,
|
||||
username: Username,
|
||||
passwordHash: hash,
|
||||
nombre: Nombre,
|
||||
apellido: Apellido,
|
||||
email: Email,
|
||||
rol: Rol,
|
||||
permisosJson: PermisosJson,
|
||||
activo: Activo,
|
||||
fechaModificacion: DateTime.UtcNow,
|
||||
ultimoLogin: UltimoLogin,
|
||||
mustChangePassword: mustChangePassword);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance with only the MustChangePassword flag changed.
|
||||
/// Sets FechaModificacion = UtcNow.
|
||||
/// </summary>
|
||||
public Usuario WithMustChangePassword(bool value)
|
||||
=> new(
|
||||
id: Id,
|
||||
username: Username,
|
||||
passwordHash: PasswordHash,
|
||||
nombre: Nombre,
|
||||
apellido: Apellido,
|
||||
email: Email,
|
||||
rol: Rol,
|
||||
permisosJson: PermisosJson,
|
||||
activo: Activo,
|
||||
fechaModificacion: DateTime.UtcNow,
|
||||
ultimoLogin: UltimoLogin,
|
||||
mustChangePassword: value);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance with only UltimoLogin updated.
|
||||
/// Does NOT touch FechaModificacion.
|
||||
/// </summary>
|
||||
public Usuario WithUltimoLogin(DateTime utcNow)
|
||||
=> new(
|
||||
id: Id,
|
||||
username: Username,
|
||||
passwordHash: PasswordHash,
|
||||
nombre: Nombre,
|
||||
apellido: Apellido,
|
||||
email: Email,
|
||||
rol: Rol,
|
||||
permisosJson: PermisosJson,
|
||||
activo: Activo,
|
||||
fechaModificacion: FechaModificacion,
|
||||
ultimoLogin: utcNow,
|
||||
mustChangePassword: MustChangePassword);
|
||||
}
|
||||
|
||||
11
src/api/SIGCM2.Domain/Exceptions/CannotSelfResetException.cs
Normal file
11
src/api/SIGCM2.Domain/Exceptions/CannotSelfResetException.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace SIGCM2.Domain.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Thrown when an admin attempts to reset their own password via the admin reset endpoint.
|
||||
/// Admin must use the self-service change password endpoint instead.
|
||||
/// </summary>
|
||||
public sealed class CannotSelfResetException : DomainException
|
||||
{
|
||||
public CannotSelfResetException()
|
||||
: base("Un administrador no puede resetear su propia contraseña. Use el endpoint de cambio de contraseña propio.") { }
|
||||
}
|
||||
10
src/api/SIGCM2.Domain/Exceptions/DomainException.cs
Normal file
10
src/api/SIGCM2.Domain/Exceptions/DomainException.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace SIGCM2.Domain.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all domain-level exceptions in SIGCM2.
|
||||
/// </summary>
|
||||
public abstract class DomainException : Exception
|
||||
{
|
||||
protected DomainException(string message) : base(message) { }
|
||||
protected DomainException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace SIGCM2.Domain.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Thrown when an operation would remove the last active admin from the system,
|
||||
/// causing a lockout condition.
|
||||
/// </summary>
|
||||
public sealed class LastAdminLockoutException : DomainException
|
||||
{
|
||||
public LastAdminLockoutException()
|
||||
: base("No se puede desactivar o cambiar el rol del último administrador activo.") { }
|
||||
}
|
||||
15
src/api/SIGCM2.Domain/Exceptions/UsuarioNotFoundException.cs
Normal file
15
src/api/SIGCM2.Domain/Exceptions/UsuarioNotFoundException.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace SIGCM2.Domain.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Thrown when a requested user does not exist in the system.
|
||||
/// </summary>
|
||||
public sealed class UsuarioNotFoundException : DomainException
|
||||
{
|
||||
public int Id { get; }
|
||||
|
||||
public UsuarioNotFoundException(int id)
|
||||
: base($"El usuario con id '{id}' no existe.")
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user