diff --git a/CleanArchitecture.Application/Queries/Tenants/GetAll/GetAllTenantsQueryHandler.cs b/CleanArchitecture.Application/Queries/Tenants/GetAll/GetAllTenantsQueryHandler.cs
index 9f46f18..940cf53 100644
--- a/CleanArchitecture.Application/Queries/Tenants/GetAll/GetAllTenantsQueryHandler.cs
+++ b/CleanArchitecture.Application/Queries/Tenants/GetAll/GetAllTenantsQueryHandler.cs
@@ -33,8 +33,8 @@ public sealed class GetAllTenantsQueryHandler :
var tenantsQuery = _tenantRepository
.GetAllNoTracking()
.IgnoreQueryFilters()
- .Include(x => x.Users.Where(y => request.IncludeDeleted || !y.Deleted))
- .Where(x => request.IncludeDeleted || !x.Deleted);
+ .Include(x => x.Users.Where(y => request.IncludeDeleted || y.DeletedAt == null))
+ .Where(x => request.IncludeDeleted || x.DeletedAt == null );
if (!string.IsNullOrWhiteSpace(request.SearchTerm))
{
diff --git a/CleanArchitecture.Application/Queries/Users/GetAll/GetAllUsersQueryHandler.cs b/CleanArchitecture.Application/Queries/Users/GetAll/GetAllUsersQueryHandler.cs
index 7de025a..71c85de 100644
--- a/CleanArchitecture.Application/Queries/Users/GetAll/GetAllUsersQueryHandler.cs
+++ b/CleanArchitecture.Application/Queries/Users/GetAll/GetAllUsersQueryHandler.cs
@@ -33,7 +33,7 @@ public sealed class GetAllUsersQueryHandler :
var usersQuery = _userRepository
.GetAllNoTracking()
.IgnoreQueryFilters()
- .Where(x => request.IncludeDeleted || !x.Deleted);
+ .Where(x => request.IncludeDeleted || x.DeletedAt == null);
if (!string.IsNullOrWhiteSpace(request.SearchTerm))
{
diff --git a/CleanArchitecture.Application/gRPC/TenantsApiImplementation.cs b/CleanArchitecture.Application/gRPC/TenantsApiImplementation.cs
index 97679ac..c91a6b4 100644
--- a/CleanArchitecture.Application/gRPC/TenantsApiImplementation.cs
+++ b/CleanArchitecture.Application/gRPC/TenantsApiImplementation.cs
@@ -40,7 +40,7 @@ public sealed class TenantsApiImplementation : TenantsApi.TenantsApiBase
{
Id = tenant.Id.ToString(),
Name = tenant.Name,
- IsDeleted = tenant.Deleted
+ DeletedAt = tenant.DeletedAt == null ? "": tenant.DeletedAt.ToString()
})
.ToListAsync();
diff --git a/CleanArchitecture.Application/gRPC/UsersApiImplementation.cs b/CleanArchitecture.Application/gRPC/UsersApiImplementation.cs
index 6255920..6c24cf2 100644
--- a/CleanArchitecture.Application/gRPC/UsersApiImplementation.cs
+++ b/CleanArchitecture.Application/gRPC/UsersApiImplementation.cs
@@ -42,7 +42,7 @@ public sealed class UsersApiImplementation : UsersApi.UsersApiBase
Email = user.Email,
FirstName = user.FirstName,
LastName = user.LastName,
- IsDeleted = user.Deleted
+ DeletedAt = user.DeletedAt == null ? "": user.DeletedAt.ToString()
})
.ToListAsync();
diff --git a/CleanArchitecture.Domain/Entities/Entity.cs b/CleanArchitecture.Domain/Entities/Entity.cs
index 41c7b54..df7d938 100644
--- a/CleanArchitecture.Domain/Entities/Entity.cs
+++ b/CleanArchitecture.Domain/Entities/Entity.cs
@@ -5,7 +5,7 @@ namespace CleanArchitecture.Domain.Entities;
public abstract class Entity
{
public Guid Id { get; private set; }
- public bool Deleted { get; private set; }
+ public DateTimeOffset? DeletedAt { get; private set; }
protected Entity(Guid id)
{
@@ -24,11 +24,11 @@ public abstract class Entity
public void Delete()
{
- Deleted = true;
+ DeletedAt = DateTimeOffset.UtcNow;
}
public void Undelete()
{
- Deleted = false;
+ DeletedAt = null;
}
}
\ No newline at end of file
diff --git a/CleanArchitecture.Infrastructure/Database/DbContextUtility.cs b/CleanArchitecture.Infrastructure/Database/DbContextUtility.cs
index 29900c4..56f8492 100644
--- a/CleanArchitecture.Infrastructure/Database/DbContextUtility.cs
+++ b/CleanArchitecture.Infrastructure/Database/DbContextUtility.cs
@@ -9,17 +9,17 @@ public partial class ApplicationDbContext
{
public static class DbContextUtility
{
- public const string IsDeletedProperty = "Deleted";
+ public const string IsDeletedProperty = "DeletedAt";
public static readonly MethodInfo PropertyMethod = typeof(EF)
.GetMethod(nameof(EF.Property), BindingFlags.Static | BindingFlags.Public)
- !.MakeGenericMethod(typeof(bool));
+ !.MakeGenericMethod(typeof(DateTimeOffset?));
public static LambdaExpression GetIsDeletedRestriction(Type type)
{
var parm = Expression.Parameter(type, "it");
var prop = Expression.Call(PropertyMethod, parm, Expression.Constant(IsDeletedProperty));
- var condition = Expression.MakeBinary(ExpressionType.Equal, prop, Expression.Constant(false));
+ var condition = Expression.MakeBinary(ExpressionType.Equal, prop, Expression.Constant(null));
var lambda = Expression.Lambda(condition, parm);
return lambda;
}
diff --git a/CleanArchitecture.Infrastructure/Migrations/20241208214605_AddDeletedTimestamp.Designer.cs b/CleanArchitecture.Infrastructure/Migrations/20241208214605_AddDeletedTimestamp.Designer.cs
new file mode 100644
index 0000000..0e544c0
--- /dev/null
+++ b/CleanArchitecture.Infrastructure/Migrations/20241208214605_AddDeletedTimestamp.Designer.cs
@@ -0,0 +1,136 @@
+//
+using System;
+using CleanArchitecture.Infrastructure.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace CleanArchitecture.Infrastructure.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20241208214605_AddDeletedTimestamp")]
+ partial class AddDeletedTimestamp
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.0")
+ .HasAnnotation("Proxies:ChangeTracking", false)
+ .HasAnnotation("Proxies:CheckEquality", false)
+ .HasAnnotation("Proxies:LazyLoading", true)
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("CleanArchitecture.Domain.Entities.Tenant", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DeletedAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("nvarchar(255)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Tenants");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("b542bf25-134c-47a2-a0df-84ed14d03c4a"),
+ Name = "Admin Tenant"
+ });
+ });
+
+ modelBuilder.Entity("CleanArchitecture.Domain.Entities.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DeletedAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasMaxLength(320)
+ .HasColumnType("nvarchar(320)");
+
+ b.Property("FirstName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("LastLoggedinDate")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("LastName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("Role")
+ .HasColumnType("int");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId");
+
+ b.ToTable("Users");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("7e3892c0-9374-49fa-a3fd-53db637a40ae"),
+ Email = "admin@email.com",
+ FirstName = "Admin",
+ LastName = "User",
+ Password = "$2a$12$Blal/uiFIJdYsCLTMUik/egLbfg3XhbnxBC6Sb5IKz2ZYhiU/MzL2",
+ Role = 0,
+ Status = 0,
+ TenantId = new Guid("b542bf25-134c-47a2-a0df-84ed14d03c4a")
+ });
+ });
+
+ modelBuilder.Entity("CleanArchitecture.Domain.Entities.User", b =>
+ {
+ b.HasOne("CleanArchitecture.Domain.Entities.Tenant", "Tenant")
+ .WithMany("Users")
+ .HasForeignKey("TenantId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Tenant");
+ });
+
+ modelBuilder.Entity("CleanArchitecture.Domain.Entities.Tenant", b =>
+ {
+ b.Navigation("Users");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/CleanArchitecture.Infrastructure/Migrations/20241208214605_AddDeletedTimestamp.cs b/CleanArchitecture.Infrastructure/Migrations/20241208214605_AddDeletedTimestamp.cs
new file mode 100644
index 0000000..71ef3e6
--- /dev/null
+++ b/CleanArchitecture.Infrastructure/Migrations/20241208214605_AddDeletedTimestamp.cs
@@ -0,0 +1,95 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace CleanArchitecture.Infrastructure.Migrations
+{
+ ///
+ public partial class AddDeletedTimestamp : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "DeletedAt",
+ table: "Users",
+ type: "datetimeoffset",
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "DeletedAt",
+ table: "Tenants",
+ type: "datetimeoffset",
+ nullable: true);
+
+ migrationBuilder.Sql("UPDATE Users SET DeletedAt = SYSDATETIMEOFFSET() WHERE Deleted = 1");
+ migrationBuilder.Sql("UPDATE Tenants SET DeletedAt = SYSDATETIMEOFFSET() WHERE Deleted = 1");
+
+ migrationBuilder.DropColumn(
+ name: "Deleted",
+ table: "Users");
+
+ migrationBuilder.DropColumn(
+ name: "Deleted",
+ table: "Tenants");
+
+ migrationBuilder.UpdateData(
+ table: "Tenants",
+ keyColumn: "Id",
+ keyValue: new Guid("b542bf25-134c-47a2-a0df-84ed14d03c4a"),
+ column: "DeletedAt",
+ value: null);
+
+ migrationBuilder.UpdateData(
+ table: "Users",
+ keyColumn: "Id",
+ keyValue: new Guid("7e3892c0-9374-49fa-a3fd-53db637a40ae"),
+ column: "DeletedAt",
+ value: null);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "Deleted",
+ table: "Users",
+ type: "bit",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.AddColumn(
+ name: "Deleted",
+ table: "Tenants",
+ type: "bit",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.Sql("UPDATE Users SET Deleted = true WHERE DeletedAt IS NOT NULL");
+ migrationBuilder.Sql("UPDATE Tenants SET Deleted = true WHERE DeletedAt IS NOT NULL");
+
+ migrationBuilder.DropColumn(
+ name: "DeletedAt",
+ table: "Users");
+
+ migrationBuilder.DropColumn(
+ name: "DeletedAt",
+ table: "Tenants");
+
+ migrationBuilder.UpdateData(
+ table: "Tenants",
+ keyColumn: "Id",
+ keyValue: new Guid("b542bf25-134c-47a2-a0df-84ed14d03c4a"),
+ column: "Deleted",
+ value: false);
+
+ migrationBuilder.UpdateData(
+ table: "Users",
+ keyColumn: "Id",
+ keyValue: new Guid("7e3892c0-9374-49fa-a3fd-53db637a40ae"),
+ column: "Deleted",
+ value: false);
+ }
+ }
+}
diff --git a/CleanArchitecture.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/CleanArchitecture.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs
index cdbe611..1d17fb5 100644
--- a/CleanArchitecture.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs
+++ b/CleanArchitecture.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -17,7 +17,7 @@ namespace CleanArchitecture.Infrastructure.Migrations
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "7.0.11")
+ .HasAnnotation("ProductVersion", "9.0.0")
.HasAnnotation("Proxies:ChangeTracking", false)
.HasAnnotation("Proxies:CheckEquality", false)
.HasAnnotation("Proxies:LazyLoading", true)
@@ -31,8 +31,8 @@ namespace CleanArchitecture.Infrastructure.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
- b.Property("Deleted")
- .HasColumnType("bit");
+ b.Property("DeletedAt")
+ .HasColumnType("datetimeoffset");
b.Property("Name")
.IsRequired()
@@ -47,7 +47,6 @@ namespace CleanArchitecture.Infrastructure.Migrations
new
{
Id = new Guid("b542bf25-134c-47a2-a0df-84ed14d03c4a"),
- Deleted = false,
Name = "Admin Tenant"
});
});
@@ -58,8 +57,8 @@ namespace CleanArchitecture.Infrastructure.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
- b.Property("Deleted")
- .HasColumnType("bit");
+ b.Property("DeletedAt")
+ .HasColumnType("datetimeoffset");
b.Property("Email")
.IsRequired()
@@ -103,7 +102,6 @@ namespace CleanArchitecture.Infrastructure.Migrations
new
{
Id = new Guid("7e3892c0-9374-49fa-a3fd-53db637a40ae"),
- Deleted = false,
Email = "admin@email.com",
FirstName = "Admin",
LastName = "User",
diff --git a/CleanArchitecture.Proto/Tenants/Models.proto b/CleanArchitecture.Proto/Tenants/Models.proto
index c98c121..db17dba 100644
--- a/CleanArchitecture.Proto/Tenants/Models.proto
+++ b/CleanArchitecture.Proto/Tenants/Models.proto
@@ -5,7 +5,7 @@ option csharp_namespace = "CleanArchitecture.Proto.Tenants";
message Tenant {
string id = 1;
string name = 2;
- bool isDeleted = 3;
+ optional string deletedAt = 3;
}
message GetTenantsByIdsResult {
diff --git a/CleanArchitecture.Proto/Users/Models.proto b/CleanArchitecture.Proto/Users/Models.proto
index c372420..f8e2e98 100644
--- a/CleanArchitecture.Proto/Users/Models.proto
+++ b/CleanArchitecture.Proto/Users/Models.proto
@@ -7,7 +7,7 @@ message GrpcUser {
string firstName = 3;
string lastName = 4;
string email = 5;
- bool isDeleted = 6;
+ optional string deletedAt = 6;
}
message GetUsersByIdsResult {
diff --git a/CleanArchitecture.Shared/Tenants/TenantViewModel.cs b/CleanArchitecture.Shared/Tenants/TenantViewModel.cs
index 97cd79f..bc015d9 100644
--- a/CleanArchitecture.Shared/Tenants/TenantViewModel.cs
+++ b/CleanArchitecture.Shared/Tenants/TenantViewModel.cs
@@ -4,4 +4,5 @@ namespace CleanArchitecture.Shared.Tenants;
public sealed record TenantViewModel(
Guid Id,
- string Name);
\ No newline at end of file
+ string Name,
+ DateTimeOffset? DeletedAt);
\ No newline at end of file
diff --git a/CleanArchitecture.Shared/Users/UserViewModel.cs b/CleanArchitecture.Shared/Users/UserViewModel.cs
index d08d316..b94c55b 100644
--- a/CleanArchitecture.Shared/Users/UserViewModel.cs
+++ b/CleanArchitecture.Shared/Users/UserViewModel.cs
@@ -7,4 +7,4 @@ public sealed record UserViewModel(
string Email,
string FirstName,
string LastName,
- bool IsDeleted);
\ No newline at end of file
+ DateTimeOffset? DeletedAt);
\ No newline at end of file
diff --git a/CleanArchitecture.gRPC/Contexts/TenantsContext.cs b/CleanArchitecture.gRPC/Contexts/TenantsContext.cs
index f6038e4..6b3e7d0 100644
--- a/CleanArchitecture.gRPC/Contexts/TenantsContext.cs
+++ b/CleanArchitecture.gRPC/Contexts/TenantsContext.cs
@@ -27,6 +27,7 @@ public sealed class TenantsContext : ITenantsContext
return result.Tenants.Select(tenant => new TenantViewModel(
Guid.Parse(tenant.Id),
- tenant.Name));
+ tenant.Name,
+ string.IsNullOrWhiteSpace(tenant.DeletedAt) ? null : DateTimeOffset.Parse(tenant.DeletedAt)));
}
}
\ No newline at end of file
diff --git a/CleanArchitecture.gRPC/Contexts/UsersContext.cs b/CleanArchitecture.gRPC/Contexts/UsersContext.cs
index a6b02d2..03b27c9 100644
--- a/CleanArchitecture.gRPC/Contexts/UsersContext.cs
+++ b/CleanArchitecture.gRPC/Contexts/UsersContext.cs
@@ -30,6 +30,6 @@ public sealed class UsersContext : IUsersContext
user.Email,
user.FirstName,
user.LastName,
- user.IsDeleted));
+ string.IsNullOrWhiteSpace(user.DeletedAt) ? null : DateTimeOffset.Parse(user.DeletedAt)));
}
}
\ No newline at end of file