feat: add locked new-year rollover wizard for season transitions
Promote returning students, assign officers, and clear last season's data after an automatic SQLite backup, with Docker volume path docs fixed for /app/Data. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
using Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace WebApp.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Creates SQLite database backups via VACUUM INTO.
|
||||
/// </summary>
|
||||
public class DatabaseBackupService : IDatabaseBackupService
|
||||
{
|
||||
private readonly AppDbContext _context;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
private readonly ILogger<DatabaseBackupService> _logger;
|
||||
|
||||
public DatabaseBackupService(
|
||||
AppDbContext context,
|
||||
IWebHostEnvironment environment,
|
||||
ILogger<DatabaseBackupService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_environment = environment;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<string> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user