mirror of
https://github.com/alex289/CleanArchitecture.git
synced 2025-06-30 02:31:08 +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.Application.Extensions;
|
||||
using CleanArchitecture.Application.gRPC;
|
||||
@ -48,6 +49,8 @@ builder.Services.AddCommandHandlers();
|
||||
builder.Services.AddNotificationHandlers();
|
||||
builder.Services.AddApiUser();
|
||||
|
||||
builder.Services.AddHostedService<SetInactiveUsersService>();
|
||||
|
||||
builder.Services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblies(typeof(Program).Assembly); });
|
||||
|
||||
builder.Services.AddLogging(x => x.AddSimpleConsole(console =>
|
||||
|
@ -11,6 +11,7 @@ public sealed class UserViewModel
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
public string LastName { get; set; } = string.Empty;
|
||||
public UserRole Role { get; set; }
|
||||
public UserStatus Status { get; set; }
|
||||
|
||||
public static UserViewModel FromUser(User user)
|
||||
{
|
||||
@ -20,7 +21,8 @@ public sealed class UserViewModel
|
||||
Email = user.Email,
|
||||
FirstName = user.FirstName,
|
||||
LastName = user.LastName,
|
||||
Role = user.Role
|
||||
Role = user.Role,
|
||||
Status = user.Status
|
||||
};
|
||||
}
|
||||
}
|
@ -68,6 +68,14 @@ public sealed class LoginUserCommandHandler : CommandHandlerBase,
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
user.SetActive();
|
||||
user.SetLastLoggedinDate(DateTimeOffset.Now);
|
||||
|
||||
if (!await CommitAsync())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return BuildToken(
|
||||
user,
|
||||
|
@ -10,6 +10,8 @@ public class User : Entity
|
||||
public string LastName { get; private set; }
|
||||
public string Password { 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}";
|
||||
|
||||
@ -23,7 +25,8 @@ public class User : Entity
|
||||
string firstName,
|
||||
string lastName,
|
||||
string password,
|
||||
UserRole role) : base(id)
|
||||
UserRole role,
|
||||
UserStatus status = UserStatus.Active) : base(id)
|
||||
{
|
||||
Email = email;
|
||||
TenantId = tenantId;
|
||||
@ -31,6 +34,7 @@ public class User : Entity
|
||||
LastName = lastName;
|
||||
Password = password;
|
||||
Role = role;
|
||||
Status = status;
|
||||
}
|
||||
|
||||
public void SetEmail(string email)
|
||||
@ -62,4 +66,19 @@ public class User : Entity
|
||||
{
|
||||
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)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<DateTimeOffset?>("LastLoggedinDate")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
@ -84,6 +87,9 @@ namespace CleanArchitecture.Infrastructure.Migrations
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
@ -103,6 +109,7 @@ namespace CleanArchitecture.Infrastructure.Migrations
|
||||
LastName = "User",
|
||||
Password = "$2a$12$Blal/uiFIJdYsCLTMUik/egLbfg3XhbnxBC6Sb5IKz2ZYhiU/MzL2",
|
||||
Role = 0,
|
||||
Status = 0,
|
||||
TenantId = new Guid("b542bf25-134c-47a2-a0df-84ed14d03c4a")
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user