From 7ef5e186bd9d4ceadee011f6a4d0222c52869585 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 16 Mar 2026 09:40:16 +0100 Subject: [PATCH] Update import duplicate check to include more fields (#773) --- .../Components/ImportServiceCard.razor | 76 +++++++++++++++++-- .../AliasVault.ImportExport.csproj | 1 + 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceCard.razor b/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceCard.razor index b6deb003b..c4f130ad2 100644 --- a/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceCard.razor +++ b/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceCard.razor @@ -1104,18 +1104,80 @@ /// /// Detects and removes duplicates from the import list, and also detects folders. + /// Duplicates are identified by matching title, folder path, username, password, and notes (when present). + /// For each field: if both items have values, they must match; if both are null/empty, they match; otherwise no match. /// private async Task DetectAndRemoveDuplicates() { - var existingItems = await ItemService.LoadAllAsync(); + var context = await DbService.GetDbContextAsync(); + + // Load all items with folder navigation included + var existingItems = await context.Items + .Include(x => x.FieldValues.Where(fv => !fv.IsDeleted)) + .ThenInclude(fv => fv.FieldDefinition) + .Include(x => x.Folder) + .Where(x => !x.IsDeleted && x.DeletedAt == null) + .ToListAsync(); + var duplicates = ImportedCredentials.Where(imported => existingItems.Any(existing => - existing.Name != null && existing.Name.Equals(imported.ServiceName, StringComparison.OrdinalIgnoreCase) && - ItemService.GetFieldValue(existing, FieldKey.LoginUsername) != null && - ItemService.GetFieldValue(existing, FieldKey.LoginUsername)!.Equals(imported.Username, StringComparison.OrdinalIgnoreCase) && - ItemService.GetFieldValue(existing, FieldKey.LoginPassword) != null && - ItemService.GetFieldValue(existing, FieldKey.LoginPassword)!.Equals(imported.Password, StringComparison.OrdinalIgnoreCase) - )).ToList(); + { + // Title must match (case-insensitive) + if (existing.Name == null || !existing.Name.Equals(imported.ServiceName, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Folder path must match (case-insensitive) + var existingFolderPath = existing.Folder?.Name ?? string.Empty; + var importedFolderPath = imported.FolderPath ?? string.Empty; + if (!existingFolderPath.Equals(importedFolderPath, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Helper function to compare nullable fields + bool FieldsMatch(string? existingValue, string? importedValue) + { + bool existingHasValue = !string.IsNullOrEmpty(existingValue); + bool importedHasValue = !string.IsNullOrEmpty(importedValue); + + // If both have values, they must match (case-insensitive) + if (existingHasValue && importedHasValue) + { + return existingValue!.Equals(importedValue, StringComparison.OrdinalIgnoreCase); + } + + // If both are empty/null, they match + if (!existingHasValue && !importedHasValue) + { + return true; + } + + // If only one has a value, they don't match + return false; + } + + // Username must match + if (!FieldsMatch(ItemService.GetFieldValue(existing, FieldKey.LoginUsername), imported.Username)) + { + return false; + } + + // Password must match + if (!FieldsMatch(ItemService.GetFieldValue(existing, FieldKey.LoginPassword), imported.Password)) + { + return false; + } + + // Notes must match + if (!FieldsMatch(ItemService.GetFieldValue(existing, FieldKey.NotesContent), imported.Notes)) + { + return false; + } + + return true; + })).ToList(); DuplicateCredentialsCount = duplicates.Count; diff --git a/apps/server/Utilities/AliasVault.ImportExport/AliasVault.ImportExport.csproj b/apps/server/Utilities/AliasVault.ImportExport/AliasVault.ImportExport.csproj index 7b01f9d5d..3dba352f3 100644 --- a/apps/server/Utilities/AliasVault.ImportExport/AliasVault.ImportExport.csproj +++ b/apps/server/Utilities/AliasVault.ImportExport/AliasVault.ImportExport.csproj @@ -17,6 +17,7 @@ +