mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-19 22:06:08 -04:00
Add email cleanup tasks (#221)
This commit is contained in:
@@ -89,11 +89,8 @@ public class EmailController(ILogger<VaultController> logger, IDbContextFactory<
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
// Delete associated attachments
|
||||
context.EmailAttachments.RemoveRange(email!.Attachments);
|
||||
|
||||
// Delete the email
|
||||
context.Emails.Remove(email);
|
||||
// Delete the email - attachments will be cascade deleted
|
||||
context.Emails.Remove(email!);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -41,6 +41,9 @@ builder.Services.AddSingleton<ServerSettingsService>();
|
||||
|
||||
// Define the tasks that will be executed by the TaskRunner.
|
||||
builder.Services.AddTransient<IMaintenanceTask, LogCleanupTask>();
|
||||
builder.Services.AddTransient<IMaintenanceTask, RefreshTokenCleanupTask>();
|
||||
builder.Services.AddTransient<IMaintenanceTask, EmailCleanupTask>();
|
||||
builder.Services.AddTransient<IMaintenanceTask, EmailQuotaCleanupTask>();
|
||||
|
||||
builder.Services.AddStatusHostedService<TaskRunnerWorker, AliasServerDbContext>(Assembly.GetExecutingAssembly().GetName().Name!);
|
||||
|
||||
|
||||
64
src/Services/AliasVault.TaskRunner/Tasks/EmailCleanupTask.cs
Normal file
64
src/Services/AliasVault.TaskRunner/Tasks/EmailCleanupTask.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="EmailCleanupTask.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.TaskRunner.Tasks;
|
||||
|
||||
using AliasServerDb;
|
||||
using AliasVault.Shared.Server.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// A maintenance task that deletes old emails based on server settings.
|
||||
/// </summary>
|
||||
public class EmailCleanupTask : IMaintenanceTask
|
||||
{
|
||||
private readonly ILogger<EmailCleanupTask> _logger;
|
||||
private readonly IDbContextFactory<AliasServerDbContext> _dbContextFactory;
|
||||
private readonly ServerSettingsService _settingsService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmailCleanupTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="dbContextFactory">The database context factory.</param>
|
||||
/// <param name="settingsService">The settings service.</param>
|
||||
public EmailCleanupTask(
|
||||
ILogger<EmailCleanupTask> logger,
|
||||
IDbContextFactory<AliasServerDbContext> dbContextFactory,
|
||||
ServerSettingsService settingsService)
|
||||
{
|
||||
_logger = logger;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Email Cleanup";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var settings = await _settingsService.GetAllSettingsAsync();
|
||||
if (settings.EmailRetentionDays <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
var cutoffDate = DateTime.UtcNow.AddDays(-settings.EmailRetentionDays);
|
||||
|
||||
// Delete the emails
|
||||
var emailsDeleted = await dbContext.Emails
|
||||
.Where(x => x.DateSystem < cutoffDate)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
_logger.LogWarning(
|
||||
"Deleted {EmailCount} emails older than {Days} days",
|
||||
emailsDeleted,
|
||||
settings.EmailRetentionDays);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="EmailQuotaCleanupTask.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.TaskRunner.Tasks;
|
||||
|
||||
using AliasServerDb;
|
||||
using AliasVault.Shared.Server.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// A maintenance task that enforces email quotas by deleting oldest emails when users exceed their limit.
|
||||
/// </summary>
|
||||
public class EmailQuotaCleanupTask : IMaintenanceTask
|
||||
{
|
||||
private readonly ILogger<EmailQuotaCleanupTask> _logger;
|
||||
private readonly IDbContextFactory<AliasServerDbContext> _dbContextFactory;
|
||||
private readonly ServerSettingsService _settingsService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmailQuotaCleanupTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="dbContextFactory">The database context factory.</param>
|
||||
/// <param name="settingsService">The settings service.</param>
|
||||
public EmailQuotaCleanupTask(
|
||||
ILogger<EmailQuotaCleanupTask> logger,
|
||||
IDbContextFactory<AliasServerDbContext> dbContextFactory,
|
||||
ServerSettingsService settingsService)
|
||||
{
|
||||
_logger = logger;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Email Quota Cleanup";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var settings = await _settingsService.GetAllSettingsAsync();
|
||||
if (settings.MaxEmailsPerUser <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
// Get all users with their email claims
|
||||
var userEmailClaims = await dbContext.UserEmailClaims
|
||||
.Select(c => new { c.UserId, c.Address })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var totalEmailsDeleted = 0;
|
||||
var usersProcessed = 0;
|
||||
|
||||
// Group email claims by user
|
||||
foreach (var userGroup in userEmailClaims.GroupBy(c => c.UserId))
|
||||
{
|
||||
var userAddresses = userGroup.Select(c => c.Address).ToList();
|
||||
|
||||
// Get total email count for this user
|
||||
var emailCount = await dbContext.Emails
|
||||
.Where(e => userAddresses.Contains(e.To))
|
||||
.CountAsync(cancellationToken);
|
||||
|
||||
if (emailCount > settings.MaxEmailsPerUser)
|
||||
{
|
||||
// Calculate how many emails need to be deleted
|
||||
var deleteCount = emailCount - settings.MaxEmailsPerUser;
|
||||
|
||||
// Delete the oldest emails - attachments will be cascade deleted
|
||||
var emailsDeleted = await dbContext.Emails
|
||||
.Where(e => userAddresses.Contains(e.To))
|
||||
.OrderBy(e => e.DateSystem)
|
||||
.Take(deleteCount)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
if (emailsDeleted > 0)
|
||||
{
|
||||
totalEmailsDeleted += emailsDeleted;
|
||||
usersProcessed++;
|
||||
_logger.LogWarning(
|
||||
"Deleted {EmailCount} emails for user {UserId} to maintain quota of {MaxEmails}",
|
||||
emailsDeleted,
|
||||
userGroup.Key,
|
||||
settings.MaxEmailsPerUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogWarning(
|
||||
"Deleted {TotalEmails} emails across {UserCount} users to maintain quota of {MaxEmails} max emails per user",
|
||||
totalEmailsDeleted,
|
||||
usersProcessed,
|
||||
settings.MaxEmailsPerUser);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="RefreshTokenCleanupTask.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.TaskRunner.Tasks;
|
||||
|
||||
using AliasServerDb;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// A maintenance task that deletes expired refresh tokens.
|
||||
/// </summary>
|
||||
public class RefreshTokenCleanupTask : IMaintenanceTask
|
||||
{
|
||||
private readonly ILogger<RefreshTokenCleanupTask> _logger;
|
||||
private readonly IDbContextFactory<AliasServerDbContext> _dbContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RefreshTokenCleanupTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="dbContextFactory">The database context factory.</param>
|
||||
public RefreshTokenCleanupTask(
|
||||
ILogger<RefreshTokenCleanupTask> logger,
|
||||
IDbContextFactory<AliasServerDbContext> dbContextFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Refresh Token Cleanup";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var cutoffDate = DateTime.UtcNow;
|
||||
var deletedCount = await dbContext.AliasVaultUserRefreshTokens
|
||||
.Where(x => x.ExpireDate < cutoffDate)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
if (deletedCount > 0)
|
||||
{
|
||||
_logger.LogWarning("Deleted {Count} expired refresh tokens", deletedCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,32 +13,32 @@ namespace AliasVault.Shared.Server.Models;
|
||||
public class ServerSettingsModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the general log retention days.
|
||||
/// Gets or sets the general log retention days. Defaults to 30.
|
||||
/// </summary>
|
||||
public int GeneralLogRetentionDays { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the auth log retention days.
|
||||
/// Gets or sets the auth log retention days. Defaults to 30.
|
||||
/// </summary>
|
||||
public int AuthLogRetentionDays { get; set; } = 90;
|
||||
public int AuthLogRetentionDays { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the email retention days.
|
||||
/// Gets or sets the email retention days. Defaults to 0 (disabled).
|
||||
/// </summary>
|
||||
public int EmailRetentionDays { get; set; } = 30;
|
||||
public int EmailRetentionDays { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the max emails per user.
|
||||
/// Gets or sets the max emails per user. Defaults to 0 (unlimited).
|
||||
/// </summary>
|
||||
public int MaxEmailsPerUser { get; set; } = 100;
|
||||
public int MaxEmailsPerUser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time when maintenance tasks are run (24h format).
|
||||
/// Gets or sets the time when maintenance tasks are run (24h format). Defaults to 00:00.
|
||||
/// </summary>
|
||||
public TimeOnly MaintenanceTime { get; set; } = new TimeOnly(0, 0);
|
||||
public TimeOnly MaintenanceTime { get; set; } = new(0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the task runner days.
|
||||
/// Gets or sets the task runner days. Defaults to all days of the week.
|
||||
/// </summary>
|
||||
public List<int> TaskRunnerDays { get; set; } = new() { 1, 2, 3, 4, 5, 6, 7 };
|
||||
public List<int> TaskRunnerDays { get; set; } = [1, 2, 3, 4, 5, 6, 7];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user