90 lines
3.5 KiB
C#
90 lines
3.5 KiB
C#
using System.Text;
|
|
using System.Text.Json;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.DataProtection;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using JobTrackerApi.Data;
|
|
|
|
namespace JobTrackerApi.Controllers
|
|
{
|
|
[ApiController]
|
|
[Route("api/backup")]
|
|
[Authorize(AuthenticationSchemes = "local")]
|
|
public class BackupController : ControllerBase
|
|
{
|
|
private readonly JobTrackerContext _db;
|
|
private readonly ILogger<BackupController> _logger;
|
|
private readonly IDataProtector _protector;
|
|
|
|
public BackupController(JobTrackerContext db, ILogger<BackupController> logger, IDataProtectionProvider dp)
|
|
{
|
|
_db = db;
|
|
_logger = logger;
|
|
_protector = dp.CreateProtector("JobTrackerApi.Backup.v1");
|
|
}
|
|
|
|
public sealed record BackupEnvelope(
|
|
string Version,
|
|
DateTime CreatedAt,
|
|
object Data
|
|
);
|
|
|
|
[HttpPost("encrypted")]
|
|
public async Task<IActionResult> Encrypted(CancellationToken cancellationToken)
|
|
{
|
|
var data = await BuildExport(cancellationToken);
|
|
var envelope = new BackupEnvelope("jtbackup.v1", DateTime.Now, data);
|
|
|
|
var jsonBytes = JsonSerializer.SerializeToUtf8Bytes(
|
|
envelope,
|
|
new JsonSerializerOptions { WriteIndented = true }
|
|
);
|
|
|
|
// Data Protection encrypts payload using the app's key ring.
|
|
// On Windows, keys are encrypted at rest for the current user.
|
|
var protectedText = _protector.Protect(Convert.ToBase64String(jsonBytes));
|
|
var cipher = Encoding.UTF8.GetBytes(protectedText);
|
|
var fileName = $"jobtracker_backup_{DateTime.Now:yyyyMMdd_HHmmss}.jtbackup";
|
|
|
|
_logger.LogInformation("Generated encrypted backup {FileName} ({Bytes} bytes).", fileName, cipher.Length);
|
|
|
|
return File(cipher, "application/octet-stream", fileName);
|
|
}
|
|
|
|
private async Task<object> BuildExport(CancellationToken cancellationToken)
|
|
{
|
|
// Avoid navigation cycles by exporting as plain graphs / DTOs.
|
|
var companies = await _db.Companies.AsNoTracking().OrderBy(c => c.Name).ToListAsync(cancellationToken);
|
|
var jobs = await _db.JobApplications.AsNoTracking().OrderByDescending(j => j.DateApplied).ToListAsync(cancellationToken);
|
|
var jobIds = jobs.Select(j => j.Id).ToList();
|
|
|
|
var correspondence = await _db.Correspondences.AsNoTracking()
|
|
.Where(c => jobIds.Contains(c.JobApplicationId))
|
|
.OrderBy(c => c.Date)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
var attachments = await _db.Attachments.AsNoTracking()
|
|
.Where(a => jobIds.Contains(a.JobApplicationId))
|
|
.OrderBy(a => a.UploadDate)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
var events = await _db.JobEvents.AsNoTracking()
|
|
.Where(e => jobIds.Contains(e.JobApplicationId))
|
|
.OrderBy(e => e.At)
|
|
.ToListAsync(cancellationToken);
|
|
var rules = await _db.RuleSettings.AsNoTracking().FirstOrDefaultAsync(cancellationToken);
|
|
|
|
return new
|
|
{
|
|
Companies = companies,
|
|
JobApplications = jobs,
|
|
Correspondence = correspondence,
|
|
Attachments = attachments,
|
|
Events = events,
|
|
Rules = rules
|
|
};
|
|
}
|
|
}
|
|
}
|