refactor(storage): scope exports by owner
CI and Deploy / test (pull_request) Successful in 5m24s
CI and Deploy / deploy (pull_request) Has been skipped

This commit is contained in:
cesnimda
2026-08-15 18:19:59 +02:00
parent 0d487123af
commit cdcc7163fa
17 changed files with 123 additions and 38 deletions
@@ -8,7 +8,7 @@ public sealed record CvPdfArtifact(string FileName, string StoragePath, byte[] B
public interface ICvPdfExporter
{
Task<CvPdfArtifact> ExportAsync(TailoredCvRenderResult renderResult, CancellationToken cancellationToken);
Task<CvPdfArtifact> ExportAsync(string ownerUserId, TailoredCvRenderResult renderResult, CancellationToken cancellationToken);
}
public sealed class PlaywrightCvPdfExporter : ICvPdfExporter
@@ -36,17 +36,18 @@ public sealed class PlaywrightCvPdfExporter : ICvPdfExporter
_retentionDays = Math.Clamp(configuration.GetValue("CvExports:RetainDays", 30), 1, 365);
}
public async Task<CvPdfArtifact> ExportAsync(TailoredCvRenderResult renderResult, CancellationToken cancellationToken)
public async Task<CvPdfArtifact> ExportAsync(string ownerUserId, TailoredCvRenderResult renderResult, CancellationToken cancellationToken)
{
var now = DateTimeOffset.UtcNow;
PruneExpiredExports(DateOnly.FromDateTime(now.UtcDateTime).AddDays(-_retentionDays));
var folder = Path.Combine(_paths.CvExportsRoot, now.ToString("yyyyMMdd"));
var folder = Path.Combine(_paths.GetOwnerCvExportsRoot(ownerUserId), now.ToString("yyyyMMdd"));
Directory.CreateDirectory(folder);
var fileName = string.IsNullOrWhiteSpace(renderResult.SuggestedFileName)
var suggestedFileName = string.IsNullOrWhiteSpace(renderResult.SuggestedFileName)
? $"tailored-cv-{now:yyyyMMddHHmmss}.pdf"
: renderResult.SuggestedFileName;
var storagePath = Path.Combine(folder, fileName);
: Path.GetFileName(renderResult.SuggestedFileName);
var fileName = string.IsNullOrWhiteSpace(suggestedFileName) ? $"tailored-cv-{now:yyyyMMddHHmmss}.pdf" : suggestedFileName;
var storagePath = Path.Combine(folder, $"{Guid.NewGuid():N}.pdf");
var tempRoot = Path.Combine(Path.GetTempPath(), "jobtracker-cv-pdf", Guid.NewGuid().ToString("n"));
var htmlPath = Path.Combine(tempRoot, "document.html");
@@ -118,19 +119,36 @@ public sealed class PlaywrightCvPdfExporter : ICvPdfExporter
foreach (var directory in Directory.EnumerateDirectories(_paths.CvExportsRoot))
{
var name = Path.GetFileName(directory);
if (!DateOnly.TryParseExact(name, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date) || date >= cutoff) continue;
if (DateOnly.TryParseExact(name, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var legacyDate))
{
TryDeleteExpired(directory, legacyDate, cutoff);
continue;
}
try
foreach (var datedDirectory in Directory.EnumerateDirectories(directory))
{
Directory.Delete(directory, recursive: true);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not prune expired CV export directory {Directory}", directory);
var datedName = Path.GetFileName(datedDirectory);
if (DateOnly.TryParseExact(datedName, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date))
{
TryDeleteExpired(datedDirectory, date, cutoff);
}
}
}
}
private void TryDeleteExpired(string directory, DateOnly date, DateOnly cutoff)
{
if (date >= cutoff) return;
try
{
Directory.Delete(directory, recursive: true);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not prune expired CV export directory {Directory}", directory);
}
}
private static IReadOnlyList<string> BuildArguments(string storagePath, string htmlPath)
{
return new[]