PlotDirector/PlotLine/Services/ProjectRestorePointService.cs
2026-06-05 21:14:53 +01:00

139 lines
5.6 KiB
C#

using PlotLine.Data;
using PlotLine.Models;
using PlotLine.ViewModels;
namespace PlotLine.Services;
public interface IProjectRestorePointService
{
Task<ProjectRestorePointListViewModel?> GetAsync(int projectId);
Task<ProjectRestorePoint?> CreateManualAsync(int projectId, string? restorePointName, string? notes);
Task<ProjectRestorePoint?> CreatePreRestoreAsync(int projectId, string sourceRestorePointName);
Task<ProjectRestorePointDownload?> DownloadAsync(int projectRestorePointId, int projectId);
Task<bool> HasReachedManualLimitAsync(int projectId);
Task<bool> DeleteAsync(int projectRestorePointId, int projectId);
}
public sealed class ProjectRestorePointService(
IProjectRepository projects,
IProjectExportService projectExports,
IProjectRestorePointRepository restorePoints,
ILogger<ProjectRestorePointService> logger) : IProjectRestorePointService
{
private const string ManualRestorePointType = "Manual";
private const string PreRestorePointType = "PreRestore";
private const int MaxRestorePointsPerProject = 10;
public const string LimitReachedMessage = "You have reached the limit of 10 restore points for this project. Delete an old restore point before creating a new one. You can download a restore point first if you want to keep a local backup copy.";
public async Task<ProjectRestorePointListViewModel?> GetAsync(int projectId)
{
var project = await projects.GetAsync(projectId);
if (project is null)
{
return null;
}
return new ProjectRestorePointListViewModel
{
Project = project,
RestorePoints = await restorePoints.ListAsync(projectId)
};
}
public async Task<ProjectRestorePoint?> CreateManualAsync(int projectId, string? restorePointName, string? notes)
{
if (await HasReachedManualLimitAsync(projectId))
{
logger.LogInformation("Manual restore point creation blocked for project {ProjectID} because the project already has {MaxRestorePointCount} restore points.",
projectId,
MaxRestorePointsPerProject);
return null;
}
var name = string.IsNullOrWhiteSpace(restorePointName)
? $"Manual restore point - {DateTime.Now:yyyy-MM-dd HH:mm}"
: restorePointName.Trim();
return await CreateAsync(projectId, name, ManualRestorePointType, Clean(notes));
}
public async Task<ProjectRestorePoint?> CreatePreRestoreAsync(int projectId, string sourceRestorePointName)
{
var name = $"Automatic Pre-Restore Backup - {DateTime.Now:yyyy-MM-dd HH:mm}";
var notes = $"Created automatically before restoring from \"{sourceRestorePointName}\".";
return await CreateAsync(projectId, name, PreRestorePointType, notes);
}
private async Task<ProjectRestorePoint?> CreateAsync(int projectId, string restorePointName, string restorePointType, string? notes)
{
var export = await projectExports.ExportAsync(projectId);
if (export is null)
{
logger.LogWarning("{RestorePointType} restore point requested for missing project {ProjectID}.", restorePointType, projectId);
return null;
}
var created = await restorePoints.CreateAsync(projectId, restorePointName, restorePointType, export.Json, notes);
logger.LogInformation("{RestorePointType} restore point {ProjectRestorePointID} created for project {ProjectID} with {JsonSizeBytes} JSON bytes.",
restorePointType,
created.ProjectRestorePointID,
projectId,
created.JsonSizeBytes);
return created;
}
public async Task<ProjectRestorePointDownload?> DownloadAsync(int projectRestorePointId, int projectId)
{
var restorePoint = await restorePoints.GetAsync(projectRestorePointId, projectId);
if (restorePoint?.JsonData is null)
{
logger.LogWarning("Restore point download requested for missing restore point {ProjectRestorePointID} in project {ProjectID}.", projectRestorePointId, projectId);
return null;
}
return new ProjectRestorePointDownload
{
FileName = BuildFileName(restorePoint.ProjectName, restorePoint.CreatedDateUtc),
ContentType = "application/json",
Json = restorePoint.JsonData
};
}
public async Task<bool> HasReachedManualLimitAsync(int projectId)
=> await restorePoints.CountAsync(projectId) >= MaxRestorePointsPerProject;
public async Task<bool> DeleteAsync(int projectRestorePointId, int projectId)
{
var deleted = await restorePoints.DeleteAsync(projectRestorePointId, projectId);
if (!deleted)
{
logger.LogWarning("Restore point delete failed for restore point {ProjectRestorePointID} in project {ProjectID}.", projectRestorePointId, projectId);
}
return deleted;
}
private static string? Clean(string? value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
private static string BuildFileName(string projectName, DateTime createdDateUtc)
{
var safeTitle = new string(projectName
.Select(c => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c)
.ToArray())
.Trim();
safeTitle = string.Join("_", safeTitle.Split(' ', StringSplitOptions.RemoveEmptyEntries));
if (string.IsNullOrWhiteSpace(safeTitle))
{
safeTitle = "PlotWeaver_Project";
}
return $"{safeTitle}_RestorePoint_{createdDateUtc:yyyy-MM-dd_HHmm}.plotweaver";
}
}