using PlotLine.Data; using PlotLine.Models; using PlotLine.ViewModels; namespace PlotLine.Services; public interface IProjectRestorePointService { Task GetAsync(int projectId); Task CreateManualAsync(int projectId, string? restorePointName, string? notes); Task DownloadAsync(int projectRestorePointId, int projectId); } public sealed class ProjectRestorePointService( IProjectRepository projects, IProjectExportService projectExports, IProjectRestorePointRepository restorePoints, ILogger logger) : IProjectRestorePointService { private const string ManualRestorePointType = "Manual"; public async Task 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 CreateManualAsync(int projectId, string? restorePointName, string? notes) { var export = await projectExports.ExportAsync(projectId); if (export is null) { logger.LogWarning("Manual restore point requested for missing project {ProjectID}.", projectId); return null; } var name = string.IsNullOrWhiteSpace(restorePointName) ? $"Manual restore point - {DateTime.Now:yyyy-MM-dd HH:mm}" : restorePointName.Trim(); var created = await restorePoints.CreateAsync(projectId, name, ManualRestorePointType, export.Json, Clean(notes)); logger.LogInformation("Manual restore point {ProjectRestorePointID} created for project {ProjectID} with {JsonSizeBytes} JSON bytes.", created.ProjectRestorePointID, projectId, created.JsonSizeBytes); return created; } public async Task 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 }; } 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"; } }