PlotDirector/PlotLine/Services/AccountDeletionService.cs
2026-06-12 20:11:52 +01:00

114 lines
4.7 KiB
C#

using PlotLine.Data;
using PlotLine.Models;
namespace PlotLine.Services;
public sealed class AccountDeletionService(
IUserRepository users,
IProjectRepository projects,
ISubscriptionService subscriptions,
IHardDeleteFileCleanupService fileCleanup,
ILogger<AccountDeletionService> logger) : IAccountDeletionService
{
public async Task<AccountDeletionResult> HardDeleteAccountAsync(int userId)
{
var user = await users.GetByIdAsync(userId);
if (user is null)
{
logger.LogWarning("Account hard delete requested for missing user {UserID}.", userId);
return AccountDeletionResult.MissingUser();
}
if (await subscriptions.HasPaidBillingRiskAsync(userId))
{
logger.LogWarning("Account hard delete blocked for user {UserID}: paid billing risk exists.", userId);
return AccountDeletionResult.PaidSubscriptionBlocked();
}
var deletedProjects = 0;
var deletedFiles = 0;
var missingFiles = 0;
var failedFiles = 0;
var ownedProjects = await projects.ListOwnedForAccountDeletionAsync(userId);
logger.LogInformation("Account hard delete started for user {UserID} with {OwnedProjectCount} owned projects.", userId, ownedProjects.Count);
foreach (var project in ownedProjects)
{
IReadOnlyList<string> projectFilePaths;
try
{
projectFilePaths = await projects.ListHardDeleteFilePathsForAccountDeletionAsync(project.ProjectID, userId);
await projects.HardDeleteForAccountDeletionAsync(project.ProjectID, userId);
deletedProjects++;
logger.LogInformation("Account hard delete removed project {ProjectID} for user {UserID}. {FilePathCount} file paths queued for cleanup.",
project.ProjectID,
userId,
projectFilePaths.Count);
}
catch (Exception ex)
{
logger.LogError(ex, "Account hard delete failed while deleting project {ProjectID} for user {UserID}. Account deletion stopped.",
project.ProjectID,
userId);
return AccountDeletionResult.Failure("The account could not be deleted. No further account data was removed.");
}
var projectCleanup = fileCleanup.DeleteFiles("account project hard delete", project.ProjectID, projectFilePaths);
deletedFiles += projectCleanup.Deleted;
missingFiles += projectCleanup.Missing;
failedFiles += projectCleanup.Failed;
}
IReadOnlyList<string> userFilePaths;
try
{
userFilePaths = await users.ListHardDeleteFilePathsAsync(userId);
}
catch (Exception ex)
{
logger.LogError(ex, "Account hard delete could not list remaining user-level files for user {UserID}. Account deletion stopped.", userId);
return AccountDeletionResult.Failure("The account could not be deleted. No further account data was removed.");
}
var userCleanup = fileCleanup.DeleteFiles("account user file hard delete", userId, userFilePaths);
deletedFiles += userCleanup.Deleted;
missingFiles += userCleanup.Missing;
failedFiles += userCleanup.Failed;
try
{
var deleted = await users.HardDeleteAccountDataAsync(userId);
if (!deleted)
{
logger.LogError("Account hard delete failed to remove the AppUser row for user {UserID}.", userId);
return AccountDeletionResult.Failure("The account could not be deleted.");
}
}
catch (Exception ex)
{
logger.LogError(ex, "Account hard delete failed while removing remaining user data for user {UserID}.", userId);
return AccountDeletionResult.Failure("The account could not be deleted.");
}
if (failedFiles > 0)
{
logger.LogWarning("Account hard delete completed for user {UserID}, but file cleanup had {FailedFileCount} failures, {DeletedFileCount} deleted files, and {MissingFileCount} missing files.",
userId,
failedFiles,
deletedFiles,
missingFiles);
}
else
{
logger.LogInformation("Account hard delete completed for user {UserID}: {DeletedProjectCount} projects, {DeletedFileCount} files deleted, {MissingFileCount} files missing.",
userId,
deletedProjects,
deletedFiles,
missingFiles);
}
return AccountDeletionResult.Success(deletedProjects, deletedFiles, missingFiles, failedFiles);
}
}