diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 85264c47c..e7b87f7b5 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -18,7 +18,6 @@ - diff --git a/src/Core/Models/Table/Favorite.cs b/src/Core/Models/Table/Favorite.cs new file mode 100644 index 000000000..a238b2b8d --- /dev/null +++ b/src/Core/Models/Table/Favorite.cs @@ -0,0 +1,10 @@ +using System; + +namespace Bit.Core.Models.Table +{ + public class Favorite + { + public Guid CipherId { get; set; } + public Guid UserId { get; set; } + } +} diff --git a/src/Core/Models/Table/FolderCipher.cs b/src/Core/Models/Table/FolderCipher.cs new file mode 100644 index 000000000..955131480 --- /dev/null +++ b/src/Core/Models/Table/FolderCipher.cs @@ -0,0 +1,10 @@ +using System; + +namespace Bit.Core.Models.Table +{ + public class FolderCipher + { + public Guid CipherId { get; set; } + public Guid FolderId { get; set; } + } +} diff --git a/src/Core/Repositories/ICipherRepository.cs b/src/Core/Repositories/ICipherRepository.cs index ac57fbd37..ba483599c 100644 --- a/src/Core/Repositories/ICipherRepository.cs +++ b/src/Core/Repositories/ICipherRepository.cs @@ -19,6 +19,7 @@ namespace Bit.Core.Repositories Task ReplaceAsync(Cipher obj, IEnumerable subvaultIds); Task UpdatePartialAsync(Guid id, Guid userId, Guid? folderId, bool favorite); Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable ciphers); - Task CreateAsync(IEnumerable ciphers); + Task CreateAsync(IEnumerable ciphers, IEnumerable favorites, IEnumerable folders, + IEnumerable folderCiphers); } } diff --git a/src/Core/Repositories/SqlServer/CipherRepository.cs b/src/Core/Repositories/SqlServer/CipherRepository.cs index add9ff431..f91fa5c80 100644 --- a/src/Core/Repositories/SqlServer/CipherRepository.cs +++ b/src/Core/Repositories/SqlServer/CipherRepository.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Collections.Generic; using System.Data.SqlClient; using System.Threading.Tasks; -using DataTableProxy; using Bit.Core.Models.Table; using System.Data; using Dapper; @@ -198,8 +197,7 @@ namespace Bit.Core.Repositories.SqlServer using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction)) { bulkCopy.DestinationTableName = "#TempCipher"; - - var dataTable = ciphers.ToTable(new ClassMapping().AddAllPropertiesAsColumns()); + var dataTable = BuildCiphersTable(ciphers); bulkCopy.WriteToServer(dataTable); } @@ -209,12 +207,7 @@ namespace Bit.Core.Repositories.SqlServer UPDATE [dbo].[Cipher] SET - -- Do not update [UserId] - -- Do not update [FolderId] - -- Do not update [Type] - -- Do not update [Favorite] [Data] = TC.[Data], - -- Do not update [CreationDate] [RevisionDate] = TC.[RevisionDate] FROM [dbo].[Cipher] C @@ -244,19 +237,14 @@ namespace Bit.Core.Repositories.SqlServer return Task.FromResult(0); } - public Task CreateAsync(IEnumerable ciphers) + public Task CreateAsync(IEnumerable ciphers, IEnumerable favorites, IEnumerable folders, + IEnumerable folderCiphers) { - if(ciphers.Count() == 0) + if(!ciphers.Any()) { return Task.FromResult(0); } - // Generate new Ids for these new ciphers - foreach(var cipher in ciphers) - { - cipher.SetNewId(); - } - using(var connection = new SqlConnection(ConnectionString)) { connection.Open(); @@ -265,13 +253,47 @@ namespace Bit.Core.Repositories.SqlServer { try { - using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.FireTriggers, transaction)) + if(folders.Any()) + { + using(var bulkCopy = new SqlBulkCopy(connection, + SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.FireTriggers, transaction)) + { + bulkCopy.DestinationTableName = "[dbo].[Folder]"; + var dataTable = BuildFoldersTable(folders); + bulkCopy.WriteToServer(dataTable); + } + } + + using(var bulkCopy = new SqlBulkCopy(connection, + SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.FireTriggers, transaction)) { bulkCopy.DestinationTableName = "[dbo].[Cipher]"; - var dataTable = ciphers.ToTable(new ClassMapping().AddAllPropertiesAsColumns()); + var dataTable = BuildCiphersTable(ciphers); bulkCopy.WriteToServer(dataTable); } + if(folderCiphers.Any()) + { + using(var bulkCopy = new SqlBulkCopy(connection, + SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.FireTriggers, transaction)) + { + bulkCopy.DestinationTableName = "[dbo].[FolderCipher]"; + var dataTable = BuildFolderCiphersTable(folderCiphers); + bulkCopy.WriteToServer(dataTable); + } + } + + if(favorites.Any()) + { + using(var bulkCopy = new SqlBulkCopy(connection, + SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.FireTriggers, transaction)) + { + bulkCopy.DestinationTableName = "[dbo].[Favorite]"; + var dataTable = BuildFavoritesTable(favorites); + bulkCopy.WriteToServer(dataTable); + } + } + transaction.Commit(); } catch @@ -285,6 +307,160 @@ namespace Bit.Core.Repositories.SqlServer return Task.FromResult(0); } + private DataTable BuildCiphersTable(IEnumerable ciphers) + { + var c = ciphers.FirstOrDefault(); + if(c == null) + { + throw new ApplicationException("Must have some ciphers to bulk import."); + } + + var ciphersTable = new DataTable("CipherDataTable"); + + var idColumn = new DataColumn(nameof(c.Id), c.Id.GetType()); + ciphersTable.Columns.Add(idColumn); + var userIdColumn = new DataColumn(nameof(c.UserId), typeof(Guid)); + ciphersTable.Columns.Add(userIdColumn); + var organizationId = new DataColumn(nameof(c.OrganizationId), typeof(Guid)); + ciphersTable.Columns.Add(organizationId); + var typeColumn = new DataColumn(nameof(c.Type), typeof(short)); + ciphersTable.Columns.Add(typeColumn); + var dataColumn = new DataColumn(nameof(c.Data), typeof(string)); + ciphersTable.Columns.Add(dataColumn); + var creationDateColumn = new DataColumn(nameof(c.CreationDate), c.CreationDate.GetType()); + ciphersTable.Columns.Add(creationDateColumn); + var revisionDateColumn = new DataColumn(nameof(c.RevisionDate), c.RevisionDate.GetType()); + ciphersTable.Columns.Add(revisionDateColumn); + + var keys = new DataColumn[1]; + keys[0] = idColumn; + ciphersTable.PrimaryKey = keys; + + foreach(var cipher in ciphers) + { + var row = ciphersTable.NewRow(); + + row[idColumn] = cipher.Id; + row[userIdColumn] = cipher.UserId.HasValue ? (object)cipher.UserId.Value : DBNull.Value; + row[organizationId] = cipher.OrganizationId.HasValue ? (object)cipher.OrganizationId.Value : DBNull.Value; + row[typeColumn] = (short)cipher.Type; + row[dataColumn] = cipher.Data; + row[creationDateColumn] = cipher.CreationDate; + row[revisionDateColumn] = cipher.RevisionDate; + + ciphersTable.Rows.Add(row); + } + + return ciphersTable; + } + + private DataTable BuildFavoritesTable(IEnumerable favorites) + { + var f = favorites.FirstOrDefault(); + if(f == null) + { + throw new ApplicationException("Must have some favorites to bulk import."); + } + + var favoritesTable = new DataTable("FavoriteDataTable"); + + var userIdColumn = new DataColumn(nameof(f.UserId), f.UserId.GetType()); + favoritesTable.Columns.Add(userIdColumn); + var cipherIdColumn = new DataColumn(nameof(f.CipherId), f.CipherId.GetType()); + favoritesTable.Columns.Add(cipherIdColumn); + + var keys = new DataColumn[2]; + keys[0] = userIdColumn; + keys[1] = cipherIdColumn; + favoritesTable.PrimaryKey = keys; + + foreach(var favorite in favorites) + { + var row = favoritesTable.NewRow(); + + row[cipherIdColumn] = favorite.CipherId; + row[userIdColumn] = favorite.UserId; + + favoritesTable.Rows.Add(row); + } + + return favoritesTable; + } + + private DataTable BuildFolderCiphersTable(IEnumerable folderCiphers) + { + var f = folderCiphers.FirstOrDefault(); + if(f == null) + { + throw new ApplicationException("Must have some folderCiphers to bulk import."); + } + + var folderCiphersTable = new DataTable("FolderCipherDataTable"); + + var folderIdColumn = new DataColumn(nameof(f.FolderId), f.FolderId.GetType()); + folderCiphersTable.Columns.Add(folderIdColumn); + var cipherIdColumn = new DataColumn(nameof(f.CipherId), f.CipherId.GetType()); + folderCiphersTable.Columns.Add(cipherIdColumn); + + var keys = new DataColumn[2]; + keys[0] = folderIdColumn; + keys[1] = cipherIdColumn; + folderCiphersTable.PrimaryKey = keys; + + foreach(var folderCipher in folderCiphers) + { + var row = folderCiphersTable.NewRow(); + + row[folderIdColumn] = folderCipher.FolderId; + row[cipherIdColumn] = folderCipher.CipherId; + + folderCiphersTable.Rows.Add(row); + } + + return folderCiphersTable; + } + + private DataTable BuildFoldersTable(IEnumerable folders) + { + var f = folders.FirstOrDefault(); + if(f == null) + { + throw new ApplicationException("Must have some folders to bulk import."); + } + + var foldersTable = new DataTable("FolderDataTable"); + + var idColumn = new DataColumn(nameof(f.Id), f.Id.GetType()); + foldersTable.Columns.Add(idColumn); + var userIdColumn = new DataColumn(nameof(f.UserId), f.UserId.GetType()); + foldersTable.Columns.Add(userIdColumn); + var nameColumn = new DataColumn(nameof(f.Name), typeof(string)); + foldersTable.Columns.Add(nameColumn); + var creationDateColumn = new DataColumn(nameof(f.CreationDate), f.CreationDate.GetType()); + foldersTable.Columns.Add(creationDateColumn); + var revisionDateColumn = new DataColumn(nameof(f.RevisionDate), f.RevisionDate.GetType()); + foldersTable.Columns.Add(revisionDateColumn); + + var keys = new DataColumn[1]; + keys[0] = idColumn; + foldersTable.PrimaryKey = keys; + + foreach(var folder in folders) + { + var row = foldersTable.NewRow(); + + row[idColumn] = folder.Id; + row[userIdColumn] = folder.UserId; + row[nameColumn] = folder.Name; + row[creationDateColumn] = folder.CreationDate; + row[revisionDateColumn] = folder.RevisionDate; + + foldersTable.Rows.Add(row); + } + + return foldersTable; + } + public class CipherWithSubvaults : Cipher { public DataTable SubvaultIds { get; set; } diff --git a/src/Core/Services/Implementations/CipherService.cs b/src/Core/Services/Implementations/CipherService.cs index dda18c4f4..d665ebefe 100644 --- a/src/Core/Services/Implementations/CipherService.cs +++ b/src/Core/Services/Implementations/CipherService.cs @@ -186,15 +186,30 @@ namespace Bit.Core.Services List ciphers, IEnumerable> folderRelationships) { - // create all the folders - var folderTasks = new List(); + // Init. ids and build out favorites. + var favorites = new List(); + foreach(var cipher in ciphers) + { + cipher.SetNewId(); + + if(cipher.UserId.HasValue && cipher.Favorite) + { + favorites.Add(new Favorite + { + UserId = cipher.UserId.Value, + CipherId = cipher.Id + }); + } + } + + // Init. ids for folders foreach(var folder in folders) { - folderTasks.Add(_folderRepository.CreateAsync(folder)); + folder.SetNewId(); } - await Task.WhenAll(folderTasks); - // associate the newly created folders to the ciphers + // Create the folder associations based on the newly created folder ids + var folderCiphers = new List(); foreach(var relationship in folderRelationships) { var cipher = ciphers.ElementAtOrDefault(relationship.Key); @@ -205,11 +220,15 @@ namespace Bit.Core.Services continue; } - //cipher.FolderId = folder.Id; + folderCiphers.Add(new FolderCipher + { + FolderId = folder.Id, + CipherId = cipher.Id + }); } - // create all the ciphers - await _cipherRepository.CreateAsync(ciphers); + // Create it all + await _cipherRepository.CreateAsync(ciphers, favorites, folders, folderCiphers); // push var userId = folders.FirstOrDefault()?.UserId ?? ciphers.FirstOrDefault()?.UserId;