From f6f7cb8b3612f9828cd09f9111eeee43128fc31a Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 24 May 2026 08:54:06 -0400 Subject: [PATCH] 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. --- .../Import/BundleImporter.cs | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/ScadaLink.Transport/Import/BundleImporter.cs b/src/ScadaLink.Transport/Import/BundleImporter.cs index b1741b9..a95c465 100644 --- a/src/ScadaLink.Transport/Import/BundleImporter.cs +++ b/src/ScadaLink.Transport/Import/BundleImporter.cs @@ -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; }