//BACKEND //Controller using av_motion_api.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newtonsoft.Json.Linq; using NuGet.Configuration; namespace av_motion_api.Controllers { [Route("api/[controller]")] [ApiController] public class DeletionSettingsController : ControllerBase { private readonly IOptionsMonitor<DeletionSettings> _deletionSettings; //private readonly IConfiguration _configuration; private readonly IConfigurationRoot _configurationRoot; public DeletionSettingsController(IOptionsMonitor<DeletionSettings> deletionSettings, IConfiguration configuration) { _deletionSettings = deletionSettings; //_configuration = configuration; _configurationRoot = (IConfigurationRoot)configuration; } [HttpPost] [Route("UpdateDeletionTime")] public IActionResult UpdateDeletionTime([FromBody] DeletionSettings settings) { if (settings.DeletionTimeValue < 0) { return BadRequest(new { message = "Deletion time value must be non-negative" }); } var configurationFile = "appsettings.Development.json"; var configurationPath = Path.Combine(Directory.GetCurrentDirectory(), configurationFile); var json = System.IO.File.ReadAllText(configurationPath); dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json); if (jsonObj["DeletionSettings"] == null) { jsonObj["DeletionSettings"] = new Newtonsoft.Json.Linq.JObject(); } jsonObj["DeletionSettings"]["DeletionTimeValue"] = settings.DeletionTimeValue; jsonObj["DeletionSettings"]["DeletionTimeUnit"] = settings.DeletionTimeUnit; string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented); System.IO.File.WriteAllText(configurationPath, output); // Reload the configuration _configurationRoot.Reload(); return Ok(new { message = "Deletion time updated successfully" }); } [HttpGet] [Route("GetDeletionSettings")] public IActionResult GetDeletionSettings() { var settings = _deletionSettings.CurrentValue; if (settings == null) { return NotFound(new { message = "Deletion settings not found" }); } return Ok(settings); } } } //Model namespace av_motion_api.Models { public class DeletionSettings { public int DeletionTimeValue { get; set; } public string DeletionTimeUnit { get; set; } } } //Service using av_motion_api.Data; using av_motion_api.Models; using Microsoft.AspNetCore.Identity; 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 userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>(); var deletionThreshold = DateTime.UtcNow.Subtract(GetTimeSpan(_settings.CurrentValue.DeletionTimeValue, _settings.CurrentValue.DeletionTimeUnit)); var usersToDelete = context.Users .Where(u => u.User_Status_ID == 2 && u.DeactivatedAt < deletionThreshold) .ToList(); foreach (var user in usersToDelete) { userManager.DeleteAsync(user).Wait(); } context.SaveChanges(); } } 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 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 { "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": 6, "DeletionTimeUnit": "Months" } } //FRONTEND //Deletionsettings component //html <div class="view-deletion-settings-container"> <button class="btn btn" (click)="goBack()"> <i class="bi bi-arrow-left-circle header-icon"></i> </button> <h2>Deletion Settings</h2> <p><strong>Deletion Time:</strong> {{deletionSettings?.deletionTimeValue}} {{deletionSettings?.deletionTimeUnit}}</p> <button mat-raised-button color="primary" (click)="openEditModal()">Update Settings</button> </div> <!-- Edit Modal --> <div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="editModalLabel">Update Deletion Time</h5> </div> <div class="modal-body"> <form [formGroup]="deletionSettingsForm"> <div class="form-group"> <label for="deletionTime">Deletion Time:</label> <input type="number" id="deletionTime" formControlName="value" class="form-control" name="deletionTime"> </div> <div class="form-group"> <label for="timeUnit">Time Unit:</label> <select id="timeUnit" formControlName="unit" class="form-control" name="timeUnit"> <option value="Minutes">Minutes</option> <option value="Hours">Hours</option> <option value="Days">Days</option> <option value="Weeks">Weeks</option> <option value="Months">Months</option> <option value="Years">Years</option> </select> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-primary" (click)="saveDeletionTime()">Update</button> </div> </div> </div> </div> //ts import { CommonModule } from '@angular/common'; import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { UserService } from '../Services/userprofile.service'; import { DeletionSettings } from '../shared/deletionsettings'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Router, RouterLink } from '@angular/router'; import { Location } from '@angular/common'; declare var $: any; @Component({ selector: 'app-deletion-settings', standalone: true, imports: [CommonModule, ReactiveFormsModule, RouterLink, FormsModule], templateUrl: './deletion-settings.component.html', styleUrl: './deletion-settings.component.css' }) export class DeletionSettingsComponent implements OnInit { deletionSettings: DeletionSettings | undefined; deletionSettingsForm: FormGroup; constructor( private userService: UserService, private fb: FormBuilder, private snackBar: MatSnackBar, private router: Router, private location: Location ) { this.deletionSettingsForm = this.fb.group({ value: ['', [Validators.required, Validators.min(0)]], unit: ['', Validators.required] }); } ngOnInit(): void { this.loadDeletionSettings(); } loadDeletionSettings(): void { this.userService.getDeletionSettings().subscribe({ next: (settings) => { this.deletionSettings = settings; this.deletionSettingsForm.patchValue({ value: settings.deletionTimeValue, unit: settings.deletionTimeUnit }); }, error: (error) => { console.error('Error fetching deletion settings', error); } }); } openEditModal(): void { $('#editModal').modal('show'); } saveDeletionTime(): void { if (this.deletionSettingsForm.valid) { const settings: DeletionSettings = { deletionTimeValue: this.deletionSettingsForm.value.value, deletionTimeUnit: this.deletionSettingsForm.value.unit }; this.userService.updateDeletionTime(settings).subscribe({ next: (response) => { console.log("Deletion time", response ) this.snackBar.open('Deletion time updated successfully', 'Close', { duration: 5000 }); this.loadDeletionSettings(); $('#editModal').modal('hide'); }, error: (error) => { console.error('Error updating deletion time', error); this.snackBar.open('Failed to update deletion time. Please try again', 'Close', { duration: 5000 }); this.loadDeletionSettings(); $('#editModal').modal('hide'); } }); } } goBack(): void { this.location.back(); } } //Service export class UserService { httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; constructor(private http: HttpClient) {} endPoint: string = "https://localhost:7185/api/"; getDeletionSettings(): Observable<DeletionSettings> { return this.http.get<DeletionSettings>(`${this.endPoint}DeletionSettings/GetDeletionSettings`); } updateDeletionTime(settings: DeletionSettings): Observable<any> { return this.http.post<any>(`${this.endPoint}DeletionSettings/UpdateDeletionTime`, settings, this.httpOptions); } //model export interface DeletionSettings { deletionTimeValue: number; deletionTimeUnit: string; } //routes { path:'deletion-settings', component:DeletionSettingsComponent },
Preview:
downloadDownload PNG
downloadDownload JPEG
downloadDownload SVG
Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!
Click to optimize width for Twitter