UDT-008: Gestión completa de usuarios #11
@@ -0,0 +1,34 @@
|
|||||||
|
-- V008: Add MustChangePassword column + IX_Usuario_Activo_Rol index
|
||||||
|
-- Idempotent: re-runnable without errors.
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
SET ANSI_NULLS ON;
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- Add MustChangePassword column (idempotent via COL_LENGTH check)
|
||||||
|
IF COL_LENGTH('dbo.Usuario', 'MustChangePassword') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Usuario
|
||||||
|
ADD MustChangePassword BIT NOT NULL
|
||||||
|
CONSTRAINT DF_Usuario_MustChangePassword DEFAULT(0);
|
||||||
|
PRINT 'Column MustChangePassword added to dbo.Usuario.';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
PRINT 'Column MustChangePassword already exists — skipping.';
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- Compound index for listado filtrado (Activo + Rol) and anti-lockout guard
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM sys.indexes
|
||||||
|
WHERE name = 'IX_Usuario_Activo_Rol'
|
||||||
|
AND object_id = OBJECT_ID('dbo.Usuario')
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_Usuario_Activo_Rol
|
||||||
|
ON dbo.Usuario(Activo, Rol)
|
||||||
|
INCLUDE (Id, Username, Email, UltimoLogin, FechaModificacion);
|
||||||
|
PRINT 'Index IX_Usuario_Activo_Rol created.';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
PRINT 'Index IX_Usuario_Activo_Rol already exists — skipping.';
|
||||||
|
GO
|
||||||
@@ -12,6 +12,11 @@ public sealed class Usuario
|
|||||||
public string PermisosJson { get; }
|
public string PermisosJson { get; }
|
||||||
public bool Activo { get; }
|
public bool Activo { get; }
|
||||||
|
|
||||||
|
// UDT-008: new properties
|
||||||
|
public DateTime? FechaModificacion { get; }
|
||||||
|
public DateTime? UltimoLogin { get; }
|
||||||
|
public bool MustChangePassword { get; }
|
||||||
|
|
||||||
public Usuario(
|
public Usuario(
|
||||||
int id,
|
int id,
|
||||||
string username,
|
string username,
|
||||||
@@ -21,7 +26,10 @@ public sealed class Usuario
|
|||||||
string? email,
|
string? email,
|
||||||
string rol,
|
string rol,
|
||||||
string permisosJson,
|
string permisosJson,
|
||||||
bool activo)
|
bool activo,
|
||||||
|
DateTime? fechaModificacion = null,
|
||||||
|
DateTime? ultimoLogin = null,
|
||||||
|
bool mustChangePassword = false)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Username = username;
|
Username = username;
|
||||||
@@ -32,11 +40,14 @@ public sealed class Usuario
|
|||||||
Rol = rol;
|
Rol = rol;
|
||||||
PermisosJson = permisosJson;
|
PermisosJson = permisosJson;
|
||||||
Activo = activo;
|
Activo = activo;
|
||||||
|
FechaModificacion = fechaModificacion;
|
||||||
|
UltimoLogin = ultimoLogin;
|
||||||
|
MustChangePassword = mustChangePassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Factory for creating a new user (no Id — DB assigns via IDENTITY).
|
/// Factory for creating a new user (no Id — DB assigns via IDENTITY).
|
||||||
/// Defaults: Activo=true, PermisosJson="[]".
|
/// Defaults: Activo=true, PermisosJson="[]", MustChangePassword=false.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Usuario ForCreation(
|
public static Usuario ForCreation(
|
||||||
string username,
|
string username,
|
||||||
@@ -55,6 +66,87 @@ public sealed class Usuario
|
|||||||
email: email,
|
email: email,
|
||||||
rol: rol,
|
rol: rol,
|
||||||
permisosJson: "[]",
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ using SIGCM2.Domain.Entities;
|
|||||||
|
|
||||||
namespace SIGCM2.Application.Tests.Domain;
|
namespace SIGCM2.Application.Tests.Domain;
|
||||||
|
|
||||||
|
// ── UDT-008 tests ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public class UsuarioTests
|
public class UsuarioTests
|
||||||
{
|
{
|
||||||
// Happy path: constructor sets all properties correctly
|
// Happy path: constructor sets all properties correctly
|
||||||
@@ -69,4 +71,172 @@ public class UsuarioTests
|
|||||||
var usuario = new Usuario(2, "inactive", "$2a$12$hash", "Old", "User", null, "consulta", "[]", false);
|
var usuario = new Usuario(2, "inactive", "$2a$12$hash", "Old", "User", null, "consulta", "[]", false);
|
||||||
Assert.False(usuario.Activo);
|
Assert.False(usuario.Activo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── UDT-008: new properties ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ForCreation_Defaults_MustChangePassword_False()
|
||||||
|
{
|
||||||
|
var u = Usuario.ForCreation("user", "hash", "A", "B", null, "cajero");
|
||||||
|
Assert.False(u.MustChangePassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ForCreation_Defaults_FechaModificacion_Null()
|
||||||
|
{
|
||||||
|
var u = Usuario.ForCreation("user", "hash", "A", "B", null, "cajero");
|
||||||
|
Assert.Null(u.FechaModificacion);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ForCreation_Defaults_UltimoLogin_Null()
|
||||||
|
{
|
||||||
|
var u = Usuario.ForCreation("user", "hash", "A", "B", null, "cajero");
|
||||||
|
Assert.Null(u.UltimoLogin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── UDT-008: WithUpdatedProfile ──────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithUpdatedProfile_Returns_NewInstance()
|
||||||
|
{
|
||||||
|
var u = MakeUsuario();
|
||||||
|
var updated = u.WithUpdatedProfile("NuevoNombre", "NuevoApellido", "new@x.com", "cajero", true);
|
||||||
|
Assert.NotSame(u, updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithUpdatedProfile_Sets_Fields_Correctly()
|
||||||
|
{
|
||||||
|
var u = MakeUsuario();
|
||||||
|
var updated = u.WithUpdatedProfile("Pedro", "Gómez", "p@g.com", "cajero", false);
|
||||||
|
Assert.Equal("Pedro", updated.Nombre);
|
||||||
|
Assert.Equal("Gómez", updated.Apellido);
|
||||||
|
Assert.Equal("p@g.com", updated.Email);
|
||||||
|
Assert.Equal("cajero", updated.Rol);
|
||||||
|
Assert.False(updated.Activo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithUpdatedProfile_Sets_FechaModificacion_To_UtcNow()
|
||||||
|
{
|
||||||
|
var before = DateTime.UtcNow.AddSeconds(-1);
|
||||||
|
var u = MakeUsuario();
|
||||||
|
var updated = u.WithUpdatedProfile("A", "B", null, "admin", true);
|
||||||
|
Assert.NotNull(updated.FechaModificacion);
|
||||||
|
Assert.True(updated.FechaModificacion >= before);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithUpdatedProfile_Preserves_Immutable_Fields()
|
||||||
|
{
|
||||||
|
var u = MakeUsuario();
|
||||||
|
var updated = u.WithUpdatedProfile("X", "Y", null, "cajero", true);
|
||||||
|
Assert.Equal(u.Id, updated.Id);
|
||||||
|
Assert.Equal(u.Username, updated.Username);
|
||||||
|
Assert.Equal(u.PasswordHash, updated.PasswordHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── UDT-008: WithNewPasswordHash ─────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithNewPasswordHash_Returns_NewInstance()
|
||||||
|
{
|
||||||
|
var u = MakeUsuario();
|
||||||
|
var updated = u.WithNewPasswordHash("newhash", mustChangePassword: false);
|
||||||
|
Assert.NotSame(u, updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithNewPasswordHash_Sets_Hash_And_MustChange()
|
||||||
|
{
|
||||||
|
var u = MakeUsuario();
|
||||||
|
var updated = u.WithNewPasswordHash("newhash", mustChangePassword: true);
|
||||||
|
Assert.Equal("newhash", updated.PasswordHash);
|
||||||
|
Assert.True(updated.MustChangePassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithNewPasswordHash_Clears_MustChange_When_False()
|
||||||
|
{
|
||||||
|
var u = MakeUsuario(mustChangePassword: true);
|
||||||
|
var updated = u.WithNewPasswordHash("hash2", mustChangePassword: false);
|
||||||
|
Assert.False(updated.MustChangePassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithNewPasswordHash_Sets_FechaModificacion()
|
||||||
|
{
|
||||||
|
var before = DateTime.UtcNow.AddSeconds(-1);
|
||||||
|
var u = MakeUsuario();
|
||||||
|
var updated = u.WithNewPasswordHash("hash2", false);
|
||||||
|
Assert.NotNull(updated.FechaModificacion);
|
||||||
|
Assert.True(updated.FechaModificacion >= before);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── UDT-008: WithUltimoLogin ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithUltimoLogin_Returns_NewInstance()
|
||||||
|
{
|
||||||
|
var u = MakeUsuario();
|
||||||
|
var updated = u.WithUltimoLogin(DateTime.UtcNow);
|
||||||
|
Assert.NotSame(u, updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithUltimoLogin_Sets_UltimoLogin()
|
||||||
|
{
|
||||||
|
var ts = new DateTime(2026, 1, 15, 10, 0, 0, DateTimeKind.Utc);
|
||||||
|
var u = MakeUsuario();
|
||||||
|
var updated = u.WithUltimoLogin(ts);
|
||||||
|
Assert.Equal(ts, updated.UltimoLogin);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithUltimoLogin_Does_NOT_Touch_FechaModificacion()
|
||||||
|
{
|
||||||
|
var u = MakeUsuario();
|
||||||
|
var originalFecha = u.FechaModificacion;
|
||||||
|
var updated = u.WithUltimoLogin(DateTime.UtcNow);
|
||||||
|
Assert.Equal(originalFecha, updated.FechaModificacion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── UDT-008: WithMustChangePassword ──────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithMustChangePassword_Sets_Value_True()
|
||||||
|
{
|
||||||
|
var u = MakeUsuario(mustChangePassword: false);
|
||||||
|
var updated = u.WithMustChangePassword(true);
|
||||||
|
Assert.True(updated.MustChangePassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WithMustChangePassword_Sets_FechaModificacion()
|
||||||
|
{
|
||||||
|
var before = DateTime.UtcNow.AddSeconds(-1);
|
||||||
|
var u = MakeUsuario();
|
||||||
|
var updated = u.WithMustChangePassword(true);
|
||||||
|
Assert.NotNull(updated.FechaModificacion);
|
||||||
|
Assert.True(updated.FechaModificacion >= before);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static Usuario MakeUsuario(bool mustChangePassword = false)
|
||||||
|
=> new(
|
||||||
|
id: 1,
|
||||||
|
username: "testuser",
|
||||||
|
passwordHash: "$2a$12$hash",
|
||||||
|
nombre: "Test",
|
||||||
|
apellido: "User",
|
||||||
|
email: "test@x.com",
|
||||||
|
rol: "admin",
|
||||||
|
permisosJson: "[]",
|
||||||
|
activo: true,
|
||||||
|
fechaModificacion: null,
|
||||||
|
ultimoLogin: null,
|
||||||
|
mustChangePassword: mustChangePassword
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
|||||||
_connection = new SqlConnection(_connectionString);
|
_connection = new SqlConnection(_connectionString);
|
||||||
await _connection.OpenAsync();
|
await _connection.OpenAsync();
|
||||||
|
|
||||||
|
// V008: ensure MustChangePassword column and IX_Usuario_Activo_Rol exist in test DB
|
||||||
|
await EnsureV008SchemaAsync();
|
||||||
|
|
||||||
_respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions
|
_respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions
|
||||||
{
|
{
|
||||||
DbAdapter = DbAdapter.SqlServer,
|
DbAdapter = DbAdapter.SqlServer,
|
||||||
@@ -83,6 +86,38 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies V008 schema changes idempotently to the test database.
|
||||||
|
/// Mirrors V008__add_mustchangepassword_and_indexes.sql.
|
||||||
|
/// </summary>
|
||||||
|
private async Task EnsureV008SchemaAsync()
|
||||||
|
{
|
||||||
|
const string addColumn = """
|
||||||
|
IF COL_LENGTH('dbo.Usuario', 'MustChangePassword') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.Usuario
|
||||||
|
ADD MustChangePassword BIT NOT NULL
|
||||||
|
CONSTRAINT DF_Usuario_MustChangePassword DEFAULT(0);
|
||||||
|
END
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string addIndex = """
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM sys.indexes
|
||||||
|
WHERE name = 'IX_Usuario_Activo_Rol'
|
||||||
|
AND object_id = OBJECT_ID('dbo.Usuario')
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
CREATE INDEX IX_Usuario_Activo_Rol
|
||||||
|
ON dbo.Usuario(Activo, Rol)
|
||||||
|
INCLUDE (Id, Username, Email, UltimoLogin, FechaModificacion);
|
||||||
|
END
|
||||||
|
""";
|
||||||
|
|
||||||
|
await _connection.ExecuteAsync(addColumn);
|
||||||
|
await _connection.ExecuteAsync(addIndex);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SeedPermisosCanonicalAsync()
|
private async Task SeedPermisosCanonicalAsync()
|
||||||
{
|
{
|
||||||
const string sql = """
|
const string sql = """
|
||||||
@@ -183,11 +218,11 @@ public sealed class SqlTestFixture : IAsyncLifetime
|
|||||||
const string sql = """
|
const string sql = """
|
||||||
SET QUOTED_IDENTIFIER ON;
|
SET QUOTED_IDENTIFIER ON;
|
||||||
IF NOT EXISTS (SELECT 1 FROM dbo.Usuario WHERE Username = 'admin')
|
IF NOT EXISTS (SELECT 1 FROM dbo.Usuario WHERE Username = 'admin')
|
||||||
INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo)
|
INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo, MustChangePassword)
|
||||||
VALUES (
|
VALUES (
|
||||||
'admin',
|
'admin',
|
||||||
'$2a$12$rmq6tlSAQ8WXhR2CwLCSeuwCJKz/.8Eab95UQCUNfwe4dokeOqMcW',
|
'$2a$12$rmq6tlSAQ8WXhR2CwLCSeuwCJKz/.8Eab95UQCUNfwe4dokeOqMcW',
|
||||||
'Administrador', 'Sistema', 'admin', '["*"]', 1
|
'Administrador', 'Sistema', 'admin', '["*"]', 1, 0
|
||||||
);
|
);
|
||||||
""";
|
""";
|
||||||
await _connection.ExecuteAsync(sql);
|
await _connection.ExecuteAsync(sql);
|
||||||
|
|||||||
Reference in New Issue
Block a user