using Data;
using Microsoft.EntityFrameworkCore;
namespace WebApp.Services;
///
/// Creates SQLite database backups via VACUUM INTO.
///
public class DatabaseBackupService : IDatabaseBackupService
{
private readonly AppDbContext _context;
private readonly IWebHostEnvironment _environment;
private readonly ILogger _logger;
public DatabaseBackupService(
AppDbContext context,
IWebHostEnvironment environment,
ILogger logger)
{
_context = context;
_environment = environment;
_logger = logger;
}
public async Task CreatePreRolloverBackupAsync(CancellationToken cancellationToken = default)
{
var backupsDir = Path.Combine(_environment.ContentRootPath, "Data", "backups");
Directory.CreateDirectory(backupsDir);
var fileName = $"pre-rollover-{DateTime.Now:yyyyMMdd-HHmmss}.db";
var backupPath = Path.Combine(backupsDir, fileName);
// Path is server-generated (never user input); escape single quotes for SQLite string literal.
var escapedPath = backupPath.Replace("'", "''", StringComparison.Ordinal);
#pragma warning disable EF1002 // Path is fully server-controlled; VACUUM INTO cannot use parameters.
await _context.Database.ExecuteSqlRawAsync($"VACUUM INTO '{escapedPath}'", cancellationToken);
#pragma warning restore EF1002
if (!File.Exists(backupPath))
{
throw new InvalidOperationException(
$"Database backup was requested but the file was not created at '{backupPath}'.");
}
_logger.LogInformation("Created pre-rollover database backup at {BackupPath}", backupPath);
return backupPath;
}
}