Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ COPY ./SimpleMailArchiver ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out
RUN dotnet publish SimpleMailArchiver/SimpleMailArchiver.csproj -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
Expand Down
60 changes: 55 additions & 5 deletions SimpleMailArchiver/SimpleMailArchiver/Pages/Browse.razor
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
@page "/"
@page "/browse"
@using System.Diagnostics
@using Radzen
@using Radzen.Blazor
@using SimpleMailArchiver.Data
@using SimpleMailArchiver.Services
@using SimpleMailArchiver.Shared

@inject ArchiveContext DbContext;
@inject DialogService DialogService;
@inject DeleteService DeleteService;
@inject ILogger<Browse> Logger;

<h3>Mail Archive</h3>

Expand Down Expand Up @@ -64,6 +66,7 @@
</HeaderTemplate>
<Template>
<RadzenButton Click="@(() => DisplayMessage(context))">Open</RadzenButton>
<RadzenButton ButtonStyle="ButtonStyle.Danger" Click="@(() => ConfirmDeleteMessage(context))">Delete</RadzenButton>
</Template>
</RadzenDataGridColumn>

Expand All @@ -78,11 +81,7 @@
{
await base.OnInitializedAsync();


_messages = DbContext.MailMessages;
var sw = Stopwatch.StartNew();
var folders = _messages.Count();
sw.Stop();
}

private void DisplayMessage(MailMessage message)
Expand All @@ -94,4 +93,55 @@
DialogService.Open<DisplayMail>("Mail", dict, new DialogOptions { Width = "1200px", Height = "800px" });
}

private async Task ConfirmDeleteMessage(MailMessage message)
{
var result = await DialogService.Confirm(
$"Are you sure you want to delete the message '{message.Subject}' from '{message.Sender}'? This action cannot be undone.",
"Delete Message",
new ConfirmOptions
{
OkButtonText = "Delete",
CancelButtonText = "Cancel",
}
);

if (result == true)
{
await DeleteMessage(message);
}
}

private async Task DeleteMessage(MailMessage message)
{
try
{
var result = await DeleteService.DeleteMessageAsync(message.Id);

if (result.IsSuccess)
{
Logger.LogInformation("Message {MessageId} deleted successfully", message.Id);

// Refresh the data
_messages = DbContext.MailMessages;
if (_messagesGrid is not null)
{
await _messagesGrid.Reload();
}

await DialogService.Alert("Message deleted successfully.", "Success");
}
else
{
var errorMessages = string.Join("\n", result.Errors.Select(e => e.Message));
Logger.LogWarning("Failed to delete message {MessageId}: {Errors}", message.Id, errorMessages);
await DialogService.Alert($"Failed to delete the message:\n{errorMessages}", "Error");
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error deleting message {MessageId}", message.Id);
await DialogService.Alert($"Error deleting message: {ex.Message}", "Error");
}
}

}
78 changes: 78 additions & 0 deletions SimpleMailArchiver/SimpleMailArchiver/Services/DeleteService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using FluentResults;
using Microsoft.EntityFrameworkCore;
using SimpleMailArchiver.Data;

namespace SimpleMailArchiver.Services;

public class DeleteService(
IDbContextFactory<ArchiveContext> dbContextFactory,
MailMessageHelperService messageHelperService,
ILogger<DeleteService> logger)
{
/// <summary>
/// Deletes a mail message from both the database and the file system.
/// </summary>
/// <param name="messageId">The ID of the message to delete</param>
/// <param name="token">Cancellation token</param>
/// <returns>Result indicating success or failure with error messages</returns>
public async Task<Result> DeleteMessageAsync(long messageId, CancellationToken token = default)
{
try
{
await using var context = await dbContextFactory.CreateDbContextAsync(token);

// Find the message
var message = await context.MailMessages.FirstOrDefaultAsync(m => m.Id == messageId, token);
if (message is null)
{
var errorMsg = $"Message with ID {messageId} not found";
logger.LogWarning("Message not found for deletion: {MessageId}", messageId);
return Result.Fail(errorMsg);
}

await using var transaction = await context.Database.BeginTransactionAsync(token);

try
{
// Get the EML file path
var emlPath = messageHelperService.GetEmlPath(message);

// Delete the EML file if it exists
if (!File.Exists(emlPath))
{
logger.LogWarning("EML file not found for message {MessageId}, path {Path}", messageId, emlPath);
return Result.Fail($"EML file not found for message {messageId}: {emlPath}");
}
else
{
File.Delete(emlPath);
logger.LogInformation("Deleted EML file for message {MessageId} at path {Path}", messageId,
emlPath);
}

// Delete the message from the database
context.MailMessages.Remove(message);
await context.SaveChangesAsync(token);

await transaction.CommitAsync(token);
logger.LogInformation("Successfully deleted message {MessageId} from database and file system",
messageId);
return Result.Ok();
}
catch (Exception ex)
{
await transaction.RollbackAsync(token);
var errorMsg = $"Error during message deletion: {ex.Message}";
logger.LogError(ex, "Error during message deletion, transaction rolled back for message {MessageId}",
messageId);
return Result.Fail(errorMsg);
}
}
catch (Exception ex)
{
var errorMsg = $"Failed to delete message {messageId}: {ex.Message}";
logger.LogError(ex, "Failed to delete message {MessageId}: {ExceptionMessage}", messageId, ex.Message);
return Result.Fail(errorMsg);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2025.2.4"/>
<PackageReference Include="FluentResults" Version="3.15.2"/>
<PackageReference Include="MailKit" Version="4.15.0"/>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.3"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.3"/>
Expand Down
1 change: 1 addition & 0 deletions SimpleMailArchiver/SimpleMailArchiver/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton(_appConfig);
services.AddSingleton<ApplicationContext>();
services.AddScoped<MailMessageHelperService>();
services.AddScoped<DeleteService>();

// Set up directories
if (!Directory.Exists(_appConfig.ArchiveBasePath))
Expand Down