mirror of
https://github.com/alex289/CleanArchitecture.git
synced 2025-07-04 12:32:57 +00:00
Merge pull request #22 from alex289/feature/background_service
feat: Add background service
This commit is contained in:
commit
cb73084965
@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CleanArchitecture.Domain.Entities;
|
||||||
|
using CleanArchitecture.Domain.Enums;
|
||||||
|
using CleanArchitecture.Infrastructure.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CleanArchitecture.Api.BackgroundServices;
|
||||||
|
|
||||||
|
public sealed class SetInactiveUsersService : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly ILogger<SetInactiveUsersService> _logger;
|
||||||
|
|
||||||
|
public SetInactiveUsersService(
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
ILogger<SetInactiveUsersService> logger)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var scope = _serviceProvider.CreateScope();
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
|
IList<User> inactiveUsers = Array.Empty<User>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
inactiveUsers = await context.Users
|
||||||
|
.Where(user =>
|
||||||
|
user.LastLoggedinDate < DateTime.UtcNow.AddDays(-30) &&
|
||||||
|
user.Status == UserStatus.Active)
|
||||||
|
.Take(250)
|
||||||
|
.ToListAsync(stoppingToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error while retrieving users to set inactive");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var user in inactiveUsers)
|
||||||
|
{
|
||||||
|
user.SetInactive();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await context.SaveChangesAsync(stoppingToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error while setting users to inactive");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromDays(1), stoppingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
using CleanArchitecture.Api.BackgroundServices;
|
||||||
using CleanArchitecture.Api.Extensions;
|
using CleanArchitecture.Api.Extensions;
|
||||||
using CleanArchitecture.Application.Extensions;
|
using CleanArchitecture.Application.Extensions;
|
||||||
using CleanArchitecture.Application.gRPC;
|
using CleanArchitecture.Application.gRPC;
|
||||||
@ -48,6 +49,8 @@ builder.Services.AddCommandHandlers();
|
|||||||
builder.Services.AddNotificationHandlers();
|
builder.Services.AddNotificationHandlers();
|
||||||
builder.Services.AddApiUser();
|
builder.Services.AddApiUser();
|
||||||
|
|
||||||
|
builder.Services.AddHostedService<SetInactiveUsersService>();
|
||||||
|
|
||||||
builder.Services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblies(typeof(Program).Assembly); });
|
builder.Services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblies(typeof(Program).Assembly); });
|
||||||
|
|
||||||
builder.Services.AddLogging(x => x.AddSimpleConsole(console =>
|
builder.Services.AddLogging(x => x.AddSimpleConsole(console =>
|
||||||
|
@ -11,6 +11,7 @@ public sealed class UserViewModel
|
|||||||
public string FirstName { get; set; } = string.Empty;
|
public string FirstName { get; set; } = string.Empty;
|
||||||
public string LastName { get; set; } = string.Empty;
|
public string LastName { get; set; } = string.Empty;
|
||||||
public UserRole Role { get; set; }
|
public UserRole Role { get; set; }
|
||||||
|
public UserStatus Status { get; set; }
|
||||||
|
|
||||||
public static UserViewModel FromUser(User user)
|
public static UserViewModel FromUser(User user)
|
||||||
{
|
{
|
||||||
@ -20,7 +21,8 @@ public sealed class UserViewModel
|
|||||||
Email = user.Email,
|
Email = user.Email,
|
||||||
FirstName = user.FirstName,
|
FirstName = user.FirstName,
|
||||||
LastName = user.LastName,
|
LastName = user.LastName,
|
||||||
Role = user.Role
|
Role = user.Role,
|
||||||
|
Status = user.Status
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -69,6 +69,14 @@ public sealed class LoginUserCommandHandler : CommandHandlerBase,
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user.SetActive();
|
||||||
|
user.SetLastLoggedinDate(DateTimeOffset.Now);
|
||||||
|
|
||||||
|
if (!await CommitAsync())
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
return BuildToken(
|
return BuildToken(
|
||||||
user,
|
user,
|
||||||
_tokenSettings);
|
_tokenSettings);
|
||||||
|
@ -10,6 +10,8 @@ public class User : Entity
|
|||||||
public string LastName { get; private set; }
|
public string LastName { get; private set; }
|
||||||
public string Password { get; private set; }
|
public string Password { get; private set; }
|
||||||
public UserRole Role { get; private set; }
|
public UserRole Role { get; private set; }
|
||||||
|
public UserStatus Status { get; private set; }
|
||||||
|
public DateTimeOffset? LastLoggedinDate { get; private set; }
|
||||||
|
|
||||||
public string FullName => $"{FirstName}, {LastName}";
|
public string FullName => $"{FirstName}, {LastName}";
|
||||||
|
|
||||||
@ -23,7 +25,8 @@ public class User : Entity
|
|||||||
string firstName,
|
string firstName,
|
||||||
string lastName,
|
string lastName,
|
||||||
string password,
|
string password,
|
||||||
UserRole role) : base(id)
|
UserRole role,
|
||||||
|
UserStatus status = UserStatus.Active) : base(id)
|
||||||
{
|
{
|
||||||
Email = email;
|
Email = email;
|
||||||
TenantId = tenantId;
|
TenantId = tenantId;
|
||||||
@ -31,6 +34,7 @@ public class User : Entity
|
|||||||
LastName = lastName;
|
LastName = lastName;
|
||||||
Password = password;
|
Password = password;
|
||||||
Role = role;
|
Role = role;
|
||||||
|
Status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetEmail(string email)
|
public void SetEmail(string email)
|
||||||
@ -62,4 +66,19 @@ public class User : Entity
|
|||||||
{
|
{
|
||||||
TenantId = tenantId;
|
TenantId = tenantId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetLastLoggedinDate(DateTimeOffset lastLoggedinDate)
|
||||||
|
{
|
||||||
|
LastLoggedinDate = lastLoggedinDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetInactive()
|
||||||
|
{
|
||||||
|
Status = UserStatus.Inactive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetActive()
|
||||||
|
{
|
||||||
|
Status = UserStatus.Active;
|
||||||
|
}
|
||||||
}
|
}
|
7
CleanArchitecture.Domain/Enums/UserStatus.cs
Normal file
7
CleanArchitecture.Domain/Enums/UserStatus.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace CleanArchitecture.Domain.Enums;
|
||||||
|
|
||||||
|
public enum UserStatus
|
||||||
|
{
|
||||||
|
Active,
|
||||||
|
Inactive
|
||||||
|
}
|
138
CleanArchitecture.Infrastructure/Migrations/20230901064720_AddUserStatus.Designer.cs
generated
Normal file
138
CleanArchitecture.Infrastructure/Migrations/20230901064720_AddUserStatus.Designer.cs
generated
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
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("20230901064720_AddUserStatus")]
|
||||||
|
partial class AddUserStatus
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "7.0.10")
|
||||||
|
.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<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<bool>("Deleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("nvarchar(255)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Tenants");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = new Guid("b542bf25-134c-47a2-a0df-84ed14d03c4a"),
|
||||||
|
Deleted = false,
|
||||||
|
Name = "Admin Tenant"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CleanArchitecture.Domain.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<bool>("Deleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(320)
|
||||||
|
.HasColumnType("nvarchar(320)");
|
||||||
|
|
||||||
|
b.Property<string>("FirstName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LastLoggedinDate")
|
||||||
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("nvarchar(128)");
|
||||||
|
|
||||||
|
b.Property<int>("Role")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<Guid>("TenantId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = new Guid("7e3892c0-9374-49fa-a3fd-53db637a40ae"),
|
||||||
|
Deleted = false,
|
||||||
|
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.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Tenant");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CleanArchitecture.Domain.Entities.Tenant", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Users");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace CleanArchitecture.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddUserStatus : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||||
|
name: "LastLoggedinDate",
|
||||||
|
table: "Users",
|
||||||
|
type: "datetimeoffset",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "Status",
|
||||||
|
table: "Users",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "Users",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("7e3892c0-9374-49fa-a3fd-53db637a40ae"),
|
||||||
|
columns: new[] { "LastLoggedinDate", "Status" },
|
||||||
|
values: new object[] { null, 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "LastLoggedinDate",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Status",
|
||||||
|
table: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -71,6 +71,9 @@ namespace CleanArchitecture.Infrastructure.Migrations
|
|||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("nvarchar(100)");
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LastLoggedinDate")
|
||||||
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
b.Property<string>("LastName")
|
b.Property<string>("LastName")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
@ -84,6 +87,9 @@ namespace CleanArchitecture.Infrastructure.Migrations
|
|||||||
b.Property<int>("Role")
|
b.Property<int>("Role")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.HasColumnType("uniqueidentifier");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
@ -103,6 +109,7 @@ namespace CleanArchitecture.Infrastructure.Migrations
|
|||||||
LastName = "User",
|
LastName = "User",
|
||||||
Password = "$2a$12$Blal/uiFIJdYsCLTMUik/egLbfg3XhbnxBC6Sb5IKz2ZYhiU/MzL2",
|
Password = "$2a$12$Blal/uiFIJdYsCLTMUik/egLbfg3XhbnxBC6Sb5IKz2ZYhiU/MzL2",
|
||||||
Role = 0,
|
Role = 0,
|
||||||
|
Status = 0,
|
||||||
TenantId = new Guid("b542bf25-134c-47a2-a0df-84ed14d03c4a")
|
TenantId = new Guid("b542bf25-134c-47a2-a0df-84ed14d03c4a")
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user