Stored Procedure for UpdateUserDeletionSettings and UserDeletionService (WORKING VERSION)
Sat Aug 03 2024 14:02:36 GMT+0000 (Coordinated Universal Time)
Saved by @iamkatmakhafola
//SQL QUERY CREATE PROCEDURE UpdateUserDeletionSettings @DeletionTimeValue INT, @DeletionTimeUnit NVARCHAR(20) AS BEGIN SET NOCOUNT ON; -- Define the deletion threshold DECLARE @DeletionThreshold DATETIME; SET @DeletionThreshold = CASE WHEN @DeletionTimeUnit = 'Minutes' THEN DATEADD(MINUTE, -@DeletionTimeValue, GETUTCDATE()) WHEN @DeletionTimeUnit = 'Hours' THEN DATEADD(HOUR, -@DeletionTimeValue, GETUTCDATE()) WHEN @DeletionTimeUnit = 'Days' THEN DATEADD(DAY, -@DeletionTimeValue, GETUTCDATE()) WHEN @DeletionTimeUnit = 'Weeks' THEN DATEADD(WEEK, -@DeletionTimeValue, GETUTCDATE()) WHEN @DeletionTimeUnit = 'Months' THEN DATEADD(MONTH, -@DeletionTimeValue, GETUTCDATE()) WHEN @DeletionTimeUnit = 'Years' THEN DATEADD(YEAR, -@DeletionTimeValue, GETUTCDATE()) ELSE GETUTCDATE() END; -- Delete users based on the threshold DELETE FROM dbo.AspNetUsers WHERE User_Status_ID = 2 AND DeactivatedAt < @DeletionThreshold; END //UserDeletionService using av_motion_api.Data; using av_motion_api.Models; using Microsoft.AspNetCore.Identity; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; namespace av_motion_api.Services { public class UserDeletionService : IHostedService, IDisposable { private readonly IServiceProvider _serviceProvider; private readonly IOptionsMonitor<DeletionSettings> _settings; private Timer _timer; private long _remainingIntervals; private const long MaxInterval = Int32.MaxValue - 2; public UserDeletionService(IServiceProvider serviceProvider, IOptionsMonitor<DeletionSettings> settings) { _serviceProvider = serviceProvider; _settings = settings; } public Task StartAsync(CancellationToken cancellationToken) { ScheduleDeletionTask(); _settings.OnChange(settings => ScheduleDeletionTask()); return Task.CompletedTask; } private void ScheduleDeletionTask() { var interval = GetTimeSpan(_settings.CurrentValue.DeletionTimeValue, _settings.CurrentValue.DeletionTimeUnit); _remainingIntervals = (long)Math.Ceiling(interval.TotalMilliseconds / MaxInterval); _timer?.Dispose(); _timer = new Timer(OnTimerElapsed, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(MaxInterval)); } private void OnTimerElapsed(object state) { if (--_remainingIntervals <= 0) { DeleteDeactivatedUsers(state); ScheduleDeletionTask(); // Reschedule for the next interval } } private void DeleteDeactivatedUsers(object state) { using (var scope = _serviceProvider.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService<AppDbContext>(); var deletionThreshold = DateTime.UtcNow.Subtract(GetTimeSpan(_settings.CurrentValue.DeletionTimeValue, _settings.CurrentValue.DeletionTimeUnit)); var connection = context.Database.GetDbConnection(); if (connection.State != System.Data.ConnectionState.Open) { connection.Open(); } using (var command = connection.CreateCommand()) { command.CommandText = "UpdateUserDeletionSettings"; command.CommandType = System.Data.CommandType.StoredProcedure; command.Parameters.Add(new SqlParameter("@DeletionTimeValue", _settings.CurrentValue.DeletionTimeValue)); command.Parameters.Add(new SqlParameter("@DeletionTimeUnit", _settings.CurrentValue.DeletionTimeUnit)); command.ExecuteNonQuery(); } } } private TimeSpan GetTimeSpan(int value, string unit) { return unit.ToLower() switch { "minutes" => TimeSpan.FromMinutes(value), "hours" => TimeSpan.FromHours(value), "days" => TimeSpan.FromDays(value), "weeks" => TimeSpan.FromDays(value * 7), "months" => TimeSpan.FromDays(value * 30), // Approximation "years" => TimeSpan.FromDays(value * 365), // Approximation _ => TimeSpan.FromMinutes(value), }; } public Task StopAsync(CancellationToken cancellationToken) { _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } public void Dispose() { _timer?.Dispose(); } } } //Program.cs --unchanged using av_motion_api.Data; using av_motion_api.Factory; using av_motion_api.Models; using av_motion_api.Interfaces; using av_motion_api.Services; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Logging; using System.Text; using Microsoft.AspNetCore.Authentication.JwtBearer; using System.Text.Json.Serialization; using Microsoft.Extensions.Configuration; // Ensure this is here using Microsoft.Extensions.Hosting; var builder = WebApplication.CreateBuilder(args); // Configure the app environment var configuration = builder.Configuration; builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()) //.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: false); .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true); // Add reloadOnChange builder.Host.ConfigureAppConfiguration((hostingContext, config) => { //config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); config.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true); // Add reloadOnChange config.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true); }); // Configure logging builder.Logging.ClearProviders(); builder.Logging.AddConsole(); builder.Logging.AddDebug(); // CORS if (builder.Environment.IsDevelopment()) { builder.Services.AddCors(options => { options.AddPolicy("AllowAll", policy => { policy.AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); }); }); } // Add services to the container builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; }); // SQL builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddScoped<IRepository, Repository>(); builder.Services.AddIdentity<User, Role>(options => { options.Password.RequireUppercase = false; options.Password.RequireLowercase = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequireDigit = true; options.User.RequireUniqueEmail = true; }) .AddRoles<Role>() .AddEntityFrameworkStores<AppDbContext>() .AddDefaultTokenProviders(); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddCookie() .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Tokens:Issuer"], ValidAudience = builder.Configuration["Tokens:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Tokens:Key"])) }; }); // Configure FormOptions for file uploads builder.Services.Configure<FormOptions>(o => { o.ValueLengthLimit = int.MaxValue; o.MultipartBodyLengthLimit = int.MaxValue; o.MemoryBufferThreshold = int.MaxValue; }); builder.Services.AddScoped<IUserClaimsPrincipalFactory<User>, AppUserClaimsPrincipalFactory>(); builder.Services.Configure<DataProtectionTokenProviderOptions>(options => options.TokenLifespan = TimeSpan.FromHours(3)); // Register the OrderStatusUpdater hosted service builder.Services.AddHostedService<OrderStatusUpdater>(); // Register the DeletionSettings configuration section builder.Services.Configure<DeletionSettings>(configuration.GetSection("DeletionSettings")); // Register the UserDeletionService hosted service builder.Services.AddHostedService<UserDeletionService>(); // Register ContractLinkingService //builder.Services.AddHostedService<ContractLinkingService>(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } // Use CORS app.UseCors("AllowAll"); app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Use(async (context, next) => { var logger = app.Services.GetRequiredService<ILogger<Program>>(); logger.LogInformation("Handling request: " + context.Request.Path); await next.Invoke(); logger.LogInformation("Finished handling request."); }); app.Run(); //appsettings.Development.json --unchanged { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Server=DESKTOP-9QI4G7U\\SQLEXPRESS;Database=AV_Motion;Trusted_Connection=True;TrustServerCertificate=True" }, "Tokens": { "Key": "hLKjZkF5q2wX3rQ$@1hy+VRv[&)0XhxJ<sk=yUpW{yE5CH@xh", "Issuer": "https://localhost:7185", "Audience": "http://localhost:4200" }, "SendGrid": { "ApiKey": "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", "SenderEmail": "datalungeteam43@gmail.com", "SenderName": "AVSFitnessAdmin@AVMotion.com", "ContractsDirectory": "C:\\Contracts\\AVS_Fitness" }, "DeletionSettings": { "DeletionTimeValue": 1, "DeletionTimeUnit": "Minutes" } } //AppDbContext --Add & run migration public DbSet<DeletionSettings> DeletionSettings { get; set; } // Configure the DeletionSettings entity builder.Entity<DeletionSettings>() .HasKey(ds => new { ds.DeletionTimeValue, ds.DeletionTimeUnit }); builder.Entity<DeletionSettings>() .Property(ds => ds.DeletionTimeValue) .IsRequired(); builder.Entity<DeletionSettings>() .Property(ds => ds.DeletionTimeUnit) .IsRequired() .HasMaxLength(50);
BACKEND FOR USERDELETION WITH
Comments