mirror of
https://github.com/bitwarden/server
synced 2026-02-12 14:33:49 +00:00
[PM-28555] Add idempotent sproc to create My Items collections (#6801)
* Add sproc to create multiple default collections. SqlBulkCopy implementation is overkill for most cases. This provides a lighter weight sproc implementation for smaller data sets. * DRY up collection arrangement * DRY up tests because bulk and non-bulk share same behavior * use EF native AddRange instead of bulk insert, because we expect smaller data sizes on self-host
This commit is contained in:
@@ -160,6 +160,21 @@ public static class DapperHelpers
|
||||
return ids.ToArrayTVP("GuidId");
|
||||
}
|
||||
|
||||
public static DataTable ToTwoGuidIdArrayTVP(this IEnumerable<(Guid id1, Guid id2)> values)
|
||||
{
|
||||
var table = new DataTable();
|
||||
table.SetTypeName("[dbo].[TwoGuidIdArray]");
|
||||
table.Columns.Add("Id1", typeof(Guid));
|
||||
table.Columns.Add("Id2", typeof(Guid));
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
table.Rows.Add(value.id1, value.id2);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
public static DataTable ToArrayTVP<T>(this IEnumerable<T> values, string columnName)
|
||||
{
|
||||
var table = new DataTable();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Data;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Collections;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
@@ -360,7 +361,45 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName)
|
||||
public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName)
|
||||
{
|
||||
organizationUserIds = organizationUserIds.ToList();
|
||||
if (!organizationUserIds.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var organizationUserCollectionIds = organizationUserIds
|
||||
.Select(ou => (ou, CoreHelpers.GenerateComb()))
|
||||
.ToTwoGuidIdArrayTVP();
|
||||
|
||||
await using var connection = new SqlConnection(ConnectionString);
|
||||
await connection.OpenAsync();
|
||||
await using var transaction = connection.BeginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
await connection.ExecuteAsync(
|
||||
"[dbo].[Collection_CreateDefaultCollections]",
|
||||
new
|
||||
{
|
||||
OrganizationId = organizationId,
|
||||
DefaultCollectionName = defaultCollectionName,
|
||||
OrganizationUserCollectionIds = organizationUserCollectionIds
|
||||
},
|
||||
commandType: CommandType.StoredProcedure,
|
||||
transaction: transaction);
|
||||
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName)
|
||||
{
|
||||
organizationUserIds = organizationUserIds.ToList();
|
||||
if (!organizationUserIds.Any())
|
||||
@@ -377,7 +416,8 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
|
||||
|
||||
var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection);
|
||||
|
||||
var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, missingDefaultCollectionUserIds, defaultCollectionName);
|
||||
var (collections, collectionUsers) =
|
||||
CollectionUtils.BuildDefaultUserCollections(organizationId, missingDefaultCollectionUserIds, defaultCollectionName);
|
||||
|
||||
if (!collectionUsers.Any() || !collections.Any())
|
||||
{
|
||||
@@ -387,11 +427,11 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
|
||||
await BulkResourceCreationService.CreateCollectionsAsync(connection, transaction, collections);
|
||||
await BulkResourceCreationService.CreateCollectionsUsersAsync(connection, transaction, collectionUsers);
|
||||
|
||||
transaction.Commit();
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
await transaction.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -421,40 +461,6 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
|
||||
return organizationUserIds.ToHashSet();
|
||||
}
|
||||
|
||||
private (List<CollectionUser> collectionUser, List<Collection> collection) BuildDefaultCollectionForUsers(Guid organizationId, IEnumerable<Guid> missingDefaultCollectionUserIds, string defaultCollectionName)
|
||||
{
|
||||
var collectionUsers = new List<CollectionUser>();
|
||||
var collections = new List<Collection>();
|
||||
|
||||
foreach (var orgUserId in missingDefaultCollectionUserIds)
|
||||
{
|
||||
var collectionId = CoreHelpers.GenerateComb();
|
||||
|
||||
collections.Add(new Collection
|
||||
{
|
||||
Id = collectionId,
|
||||
OrganizationId = organizationId,
|
||||
Name = defaultCollectionName,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
RevisionDate = DateTime.UtcNow,
|
||||
Type = CollectionType.DefaultUserCollection,
|
||||
DefaultUserCollectionEmail = null
|
||||
|
||||
});
|
||||
|
||||
collectionUsers.Add(new CollectionUser
|
||||
{
|
||||
CollectionId = collectionId,
|
||||
OrganizationUserId = orgUserId,
|
||||
ReadOnly = false,
|
||||
HidePasswords = false,
|
||||
Manage = true,
|
||||
});
|
||||
}
|
||||
|
||||
return (collectionUsers, collections);
|
||||
}
|
||||
|
||||
public class CollectionWithGroupsAndUsers : Collection
|
||||
{
|
||||
public CollectionWithGroupsAndUsers() { }
|
||||
|
||||
Reference in New Issue
Block a user