fix(transport): wire TemplateFolder FK on imported templates

ApplyTemplatesAsync built the Template entity via BuildTemplate() but
never read the DTO's FolderName -- so every imported template landed
at the root regardless of which folder it lived in on the source
cluster. ApplyTemplateFoldersAsync had already flushed the folder
rows by that point; the FK just wasn't being set.

Resolve folder-name -> persisted FolderId from the same name table
(via _templateRepo.GetAllFoldersAsync after the folder pass), honour
TemplateFolder Rename resolutions, and set FolderId on Add /
Overwrite / Rename paths alike. The audit-row 'after' state now
includes FolderId so the action's effect is visible in the
configuration audit log.
This commit is contained in:
Joseph Doherty
2026-05-24 08:54:06 -04:00
parent 1361a39770
commit f6f7cb8b36

View File

@@ -822,9 +822,30 @@ public sealed class BundleImporter : IBundleImporter
var stubs = await _templateRepo.GetAllTemplatesAsync(ct).ConfigureAwait(false);
var byName = stubs.ToDictionary(t => t.Name, t => t, StringComparer.Ordinal);
// ApplyTemplateFoldersAsync has already flushed every imported folder
// (and target folders pre-exist), so name-keyed lookup resolves to the
// persisted FolderId every imported template should reference.
var folders = await _templateRepo.GetAllFoldersAsync(ct).ConfigureAwait(false);
var folderIdByName = folders.ToDictionary(f => f.Name, f => f.Id, StringComparer.Ordinal);
// Honour folder renames -- a TemplateFolder resolved as Rename was
// written under its new name; templates that reference the old name in
// the bundle DTO must map to the renamed folder, not be orphaned.
int? ResolveFolderId(string? folderName)
{
if (folderName is null) return null;
var folderResolution = ResolveOrDefault(map, "TemplateFolder", folderName);
var lookupName = (folderResolution.Action == ResolutionAction.Rename
&& !string.IsNullOrEmpty(folderResolution.RenameTo))
? folderResolution.RenameTo
: folderName;
return folderIdByName.TryGetValue(lookupName, out var fid) ? fid : (int?)null;
}
foreach (var dto in dtos)
{
var resolution = ResolveOrDefault(map, "Template", dto.Name);
var folderId = ResolveFolderId(dto.FolderName);
switch (resolution.Action)
{
case ResolutionAction.Skip:
@@ -834,17 +855,19 @@ public sealed class BundleImporter : IBundleImporter
{
var name = resolution.RenameTo ?? dto.Name;
var t = BuildTemplate(dto, overrideName: name);
t.FolderId = folderId;
await _templateRepo.AddTemplateAsync(t, ct).ConfigureAwait(false);
await _auditService.LogAsync(user, "Create", "Template", "0", name,
new { Name = name, dto.Description, RenamedFrom = dto.Name }, ct).ConfigureAwait(false);
new { Name = name, dto.Description, FolderId = folderId, RenamedFrom = dto.Name }, ct).ConfigureAwait(false);
summary.Renamed++;
break;
}
case ResolutionAction.Overwrite when byName.TryGetValue(dto.Name, out var ex):
ex.Description = dto.Description;
ex.FolderId = folderId;
await _templateRepo.UpdateTemplateAsync(ex, ct).ConfigureAwait(false);
await _auditService.LogAsync(user, "Update", "Template", ex.Id.ToString(), ex.Name,
new { ex.Name, ex.Description }, ct).ConfigureAwait(false);
new { ex.Name, ex.Description, ex.FolderId }, ct).ConfigureAwait(false);
summary.Overwritten++;
break;
case ResolutionAction.Add:
@@ -852,9 +875,10 @@ public sealed class BundleImporter : IBundleImporter
default:
{
var t = BuildTemplate(dto, overrideName: null);
t.FolderId = folderId;
await _templateRepo.AddTemplateAsync(t, ct).ConfigureAwait(false);
await _auditService.LogAsync(user, "Create", "Template", "0", t.Name,
new { t.Name, t.Description }, ct).ConfigureAwait(false);
new { t.Name, t.Description, t.FolderId }, ct).ConfigureAwait(false);
summary.Added++;
break;
}