mirror of
https://github.com/bitwarden/server.git
synced 2026-01-31 14:13:18 +08:00
Move new sproc to separate PR
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@@ -77,10 +79,20 @@ public class AutomaticallyConfirmOrganizationUserCommand(IOrganizationUserReposi
|
||||
return;
|
||||
}
|
||||
|
||||
await collectionRepository.UpsertDefaultCollectionAsync(
|
||||
request.Organization!.Id,
|
||||
request.OrganizationUser!.Id,
|
||||
request.DefaultUserCollectionName);
|
||||
await collectionRepository.CreateAsync(
|
||||
new Collection
|
||||
{
|
||||
OrganizationId = request.Organization!.Id,
|
||||
Name = request.DefaultUserCollectionName,
|
||||
Type = CollectionType.DefaultUserCollection,
|
||||
DefaultCollectionOwnerId = request.OrganizationUserId
|
||||
},
|
||||
groups: null,
|
||||
[new CollectionAccessSelection
|
||||
{
|
||||
Id = request.OrganizationUser!.Id,
|
||||
Manage = true
|
||||
}]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@@ -296,10 +297,22 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
||||
return;
|
||||
}
|
||||
|
||||
await _collectionRepository.UpsertDefaultCollectionAsync(
|
||||
organizationUser.OrganizationId,
|
||||
organizationUser.Id,
|
||||
defaultUserCollectionName);
|
||||
var defaultCollection = new Collection
|
||||
{
|
||||
OrganizationId = organizationUser.OrganizationId,
|
||||
Name = defaultUserCollectionName,
|
||||
Type = CollectionType.DefaultUserCollection,
|
||||
DefaultCollectionOwnerId = organizationUser.Id
|
||||
};
|
||||
var collectionUser = new CollectionAccessSelection
|
||||
{
|
||||
Id = organizationUser.Id,
|
||||
ReadOnly = false,
|
||||
HidePasswords = false,
|
||||
Manage = true
|
||||
};
|
||||
|
||||
await _collectionRepository.CreateAsync(defaultCollection, groups: null, users: [collectionUser]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -71,14 +71,4 @@ public interface ICollectionRepository : IRepository<Collection, Guid>
|
||||
/// <param name="defaultCollectionName">The encrypted string to use as the default collection name.</param>
|
||||
/// <returns></returns>
|
||||
Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a default user collection for the specified organization user if they do not already have one.
|
||||
/// This operation is idempotent - calling it multiple times will not create duplicate collections.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The Organization ID.</param>
|
||||
/// <param name="organizationUserId">The Organization User ID to create/find a default collection for.</param>
|
||||
/// <param name="defaultCollectionName">The encrypted string to use as the default collection name.</param>
|
||||
/// <returns>True if a new collection was created; false if the user already had a default collection.</returns>
|
||||
Task<bool> UpsertDefaultCollectionAsync(Guid organizationId, Guid organizationUserId, string defaultCollectionName);
|
||||
}
|
||||
|
||||
@@ -396,30 +396,6 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpsertDefaultCollectionAsync(Guid organizationId, Guid organizationUserId, string defaultCollectionName)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var collectionId = CoreHelpers.GenerateComb();
|
||||
var now = DateTime.UtcNow;
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@CollectionId", collectionId);
|
||||
parameters.Add("@OrganizationId", organizationId);
|
||||
parameters.Add("@OrganizationUserId", organizationUserId);
|
||||
parameters.Add("@Name", defaultCollectionName);
|
||||
parameters.Add("@CreationDate", now);
|
||||
parameters.Add("@RevisionDate", now);
|
||||
parameters.Add("@WasCreated", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||
|
||||
await connection.ExecuteAsync(
|
||||
$"[{Schema}].[Collection_UpsertDefaultCollection]",
|
||||
parameters,
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return parameters.Get<bool>("@WasCreated");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HashSet<Guid>> GetOrgUserIdsWithDefaultCollectionAsync(SqlConnection connection, SqlTransaction transaction, Guid organizationId)
|
||||
{
|
||||
const string sql = @"
|
||||
|
||||
@@ -821,74 +821,6 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> UpsertDefaultCollectionAsync(Guid organizationId, Guid organizationUserId, string defaultCollectionName)
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
|
||||
try
|
||||
{
|
||||
// Create new default collection
|
||||
var collectionId = CoreHelpers.GenerateComb();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var collection = new Collection
|
||||
{
|
||||
Id = collectionId,
|
||||
OrganizationId = organizationId,
|
||||
Name = defaultCollectionName,
|
||||
ExternalId = null,
|
||||
CreationDate = now,
|
||||
RevisionDate = now,
|
||||
Type = CollectionType.DefaultUserCollection,
|
||||
DefaultUserCollectionEmail = null,
|
||||
DefaultCollectionOwnerId = organizationUserId
|
||||
};
|
||||
|
||||
var collectionUser = new CollectionUser
|
||||
{
|
||||
CollectionId = collectionId,
|
||||
OrganizationUserId = organizationUserId,
|
||||
ReadOnly = false,
|
||||
HidePasswords = false,
|
||||
Manage = true
|
||||
};
|
||||
|
||||
await dbContext.Collections.AddAsync(collection);
|
||||
await dbContext.CollectionUsers.AddAsync(collectionUser);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
// Bump user account revision dates
|
||||
await dbContext.UserBumpAccountRevisionDateByCollectionIdAsync(collectionId, organizationId);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (DbUpdateException ex) when (IsUniqueConstraintViolation(ex))
|
||||
{
|
||||
// Collection already exists, return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsUniqueConstraintViolation(DbUpdateException ex)
|
||||
{
|
||||
switch (ex.InnerException)
|
||||
{
|
||||
// Check if the inner exception is a SQL Server unique constraint violation (error 2601 or 2627)
|
||||
case Microsoft.Data.SqlClient.SqlException { Number: 2601 or 2627 }:
|
||||
// Check if the inner exception is a PostgreSQL unique constraint violation (SQLSTATE 23505)
|
||||
case Npgsql.PostgresException { SqlState: "23505" }:
|
||||
// Check if the inner exception is a SQLite unique constraint violation (SQLITE_CONSTRAINT = 19)
|
||||
case Microsoft.Data.Sqlite.SqliteException { SqliteErrorCode: 19 }:
|
||||
// Check if the inner exception is a MySQL unique constraint violation (ER_DUP_ENTRY = 1062)
|
||||
case MySqlConnector.MySqlException { ErrorCode: MySqlConnector.MySqlErrorCode.DuplicateKeyEntry }:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HashSet<Guid>> GetOrgUserIdsWithDefaultCollectionAsync(DatabaseContext dbContext, Guid organizationId)
|
||||
{
|
||||
var results = await dbContext.OrganizationUsers
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
-- This procedure prevents duplicate "My Items" collections for users using
|
||||
-- a filtered unique constraint on (DefaultCollectionOwnerId, OrganizationId, Type) WHERE Type = 1.
|
||||
|
||||
CREATE PROCEDURE [dbo].[Collection_UpsertDefaultCollection]
|
||||
@CollectionId UNIQUEIDENTIFIER,
|
||||
@OrganizationId UNIQUEIDENTIFIER,
|
||||
@OrganizationUserId UNIQUEIDENTIFIER,
|
||||
@Name VARCHAR(MAX),
|
||||
@CreationDate DATETIME2(7),
|
||||
@RevisionDate DATETIME2(7),
|
||||
@WasCreated BIT OUTPUT
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
BEGIN TRANSACTION
|
||||
|
||||
BEGIN TRY
|
||||
SET @WasCreated = 1
|
||||
|
||||
-- Insert Collection with DefaultCollectionOwnerId populated for constraint enforcement
|
||||
INSERT INTO [dbo].[Collection]
|
||||
(
|
||||
[Id],
|
||||
[OrganizationId],
|
||||
[Name],
|
||||
[ExternalId],
|
||||
[CreationDate],
|
||||
[RevisionDate],
|
||||
[DefaultUserCollectionEmail],
|
||||
[Type],
|
||||
[DefaultCollectionOwnerId]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@CollectionId,
|
||||
@OrganizationId,
|
||||
@Name,
|
||||
NULL, -- ExternalId
|
||||
@CreationDate,
|
||||
@RevisionDate,
|
||||
NULL, -- DefaultUserCollectionEmail
|
||||
1, -- CollectionType.DefaultUserCollection
|
||||
@OrganizationUserId
|
||||
)
|
||||
|
||||
-- Insert CollectionUser
|
||||
INSERT INTO [dbo].[CollectionUser]
|
||||
(
|
||||
[CollectionId],
|
||||
[OrganizationUserId],
|
||||
[ReadOnly],
|
||||
[HidePasswords],
|
||||
[Manage]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@CollectionId,
|
||||
@OrganizationUserId,
|
||||
0, -- ReadOnly = false
|
||||
0, -- HidePasswords = false
|
||||
1 -- Manage = true
|
||||
)
|
||||
|
||||
-- Bump user account revision dates
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @CollectionId, @OrganizationId
|
||||
|
||||
COMMIT TRANSACTION
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
-- Check if error is unique constraint violation (error 2601 or 2627)
|
||||
IF ERROR_NUMBER() IN (2601, 2627)
|
||||
BEGIN
|
||||
-- Collection already exists, return gracefully
|
||||
SET @WasCreated = 0
|
||||
IF @@TRANCOUNT > 0
|
||||
ROLLBACK TRANSACTION
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
-- Unexpected error, rollback and re-throw
|
||||
IF @@TRANCOUNT > 0
|
||||
ROLLBACK TRANSACTION
|
||||
THROW
|
||||
END
|
||||
END CATCH
|
||||
END
|
||||
@@ -9,6 +9,7 @@ using Bit.Core.AdminConsole.Utilities.v2;
|
||||
using Bit.Core.AdminConsole.Utilities.v2.Validation;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@@ -202,10 +203,15 @@ public class AutomaticallyConfirmUsersCommandTests
|
||||
|
||||
await sutProvider.GetDependency<ICollectionRepository>()
|
||||
.Received(1)
|
||||
.UpsertDefaultCollectionAsync(
|
||||
organization.Id,
|
||||
organizationUser.Id,
|
||||
defaultCollectionName);
|
||||
.CreateAsync(
|
||||
Arg.Is<Collection>(c =>
|
||||
c.OrganizationId == organization.Id &&
|
||||
c.Name == defaultCollectionName &&
|
||||
c.Type == CollectionType.DefaultUserCollection &&
|
||||
c.DefaultCollectionOwnerId == request.OrganizationUserId),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(groups => groups == null),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(access =>
|
||||
access.FirstOrDefault(x => x.Id == organizationUser.Id && x.Manage) != null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -247,10 +253,9 @@ public class AutomaticallyConfirmUsersCommandTests
|
||||
|
||||
await sutProvider.GetDependency<ICollectionRepository>()
|
||||
.DidNotReceive()
|
||||
.UpsertDefaultCollectionAsync(
|
||||
Arg.Any<Guid>(),
|
||||
Arg.Any<Guid>(),
|
||||
Arg.Any<string>());
|
||||
.CreateAsync(Arg.Any<Collection>(),
|
||||
Arg.Any<IEnumerable<CollectionAccessSelection>>(),
|
||||
Arg.Any<IEnumerable<CollectionAccessSelection>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -286,10 +291,9 @@ public class AutomaticallyConfirmUsersCommandTests
|
||||
|
||||
var collectionException = new Exception("Collection creation failed");
|
||||
sutProvider.GetDependency<ICollectionRepository>()
|
||||
.UpsertDefaultCollectionAsync(
|
||||
Arg.Any<Guid>(),
|
||||
Arg.Any<Guid>(),
|
||||
Arg.Any<string>())
|
||||
.CreateAsync(Arg.Any<Collection>(),
|
||||
Arg.Any<IEnumerable<CollectionAccessSelection>>(),
|
||||
Arg.Any<IEnumerable<CollectionAccessSelection>>())
|
||||
.ThrowsAsync(collectionException);
|
||||
|
||||
// Act
|
||||
|
||||
@@ -12,6 +12,7 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Repositories;
|
||||
@@ -493,10 +494,16 @@ public class ConfirmOrganizationUserCommandTests
|
||||
|
||||
await sutProvider.GetDependency<ICollectionRepository>()
|
||||
.Received(1)
|
||||
.UpsertDefaultCollectionAsync(
|
||||
organization.Id,
|
||||
orgUser.Id,
|
||||
collectionName);
|
||||
.CreateAsync(
|
||||
Arg.Is<Collection>(c =>
|
||||
c.Name == collectionName &&
|
||||
c.OrganizationId == organization.Id &&
|
||||
c.Type == CollectionType.DefaultUserCollection &&
|
||||
c.DefaultCollectionOwnerId == orgUser.Id),
|
||||
Arg.Any<IEnumerable<CollectionAccessSelection>>(),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(cu =>
|
||||
cu.Single().Id == orgUser.Id &&
|
||||
cu.Single().Manage));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository;
|
||||
|
||||
public class UpsertDefaultCollectionTests
|
||||
{
|
||||
[Theory, DatabaseData]
|
||||
public async Task UpsertDefaultCollectionAsync_ShouldCreateCollection_WhenUserDoesNotHaveDefaultCollection(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IUserRepository userRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
ICollectionRepository collectionRepository)
|
||||
{
|
||||
// Arrange
|
||||
var organization = await organizationRepository.CreateTestOrganizationAsync();
|
||||
var user = await userRepository.CreateTestUserAsync();
|
||||
var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user);
|
||||
var defaultCollectionName = $"My Items - {organization.Id}";
|
||||
|
||||
// Act
|
||||
var wasCreated = await collectionRepository.UpsertDefaultCollectionAsync(
|
||||
organization.Id,
|
||||
orgUser.Id,
|
||||
defaultCollectionName);
|
||||
|
||||
// Assert
|
||||
Assert.True(wasCreated);
|
||||
|
||||
var collectionDetails = await collectionRepository.GetManyByUserIdAsync(user.Id);
|
||||
var defaultCollection = collectionDetails.SingleOrDefault(c =>
|
||||
c.OrganizationId == organization.Id &&
|
||||
c.Type == CollectionType.DefaultUserCollection);
|
||||
|
||||
Assert.NotNull(defaultCollection);
|
||||
Assert.Equal(defaultCollectionName, defaultCollection.Name);
|
||||
Assert.True(defaultCollection.Manage);
|
||||
Assert.False(defaultCollection.ReadOnly);
|
||||
Assert.False(defaultCollection.HidePasswords);
|
||||
}
|
||||
|
||||
[Theory, DatabaseData]
|
||||
public async Task UpsertDefaultCollectionAsync_ShouldReturnFalse_WhenUserAlreadyHasDefaultCollection(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IUserRepository userRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
ICollectionRepository collectionRepository)
|
||||
{
|
||||
// Arrange
|
||||
var organization = await organizationRepository.CreateTestOrganizationAsync();
|
||||
var user = await userRepository.CreateTestUserAsync();
|
||||
var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user);
|
||||
var defaultCollectionName = $"My Items - {organization.Id}";
|
||||
|
||||
// Create initial collection
|
||||
var firstWasCreated = await collectionRepository.UpsertDefaultCollectionAsync(
|
||||
organization.Id,
|
||||
orgUser.Id,
|
||||
defaultCollectionName);
|
||||
|
||||
// Act - Call again with same parameters
|
||||
var secondWasCreated = await collectionRepository.UpsertDefaultCollectionAsync(
|
||||
organization.Id,
|
||||
orgUser.Id,
|
||||
defaultCollectionName);
|
||||
|
||||
// Assert
|
||||
Assert.True(firstWasCreated);
|
||||
Assert.False(secondWasCreated);
|
||||
|
||||
// Verify only one default collection exists
|
||||
var collectionDetails = await collectionRepository.GetManyByUserIdAsync(user.Id);
|
||||
var defaultCollections = collectionDetails.Where(c =>
|
||||
c.OrganizationId == organization.Id &&
|
||||
c.Type == CollectionType.DefaultUserCollection).ToList();
|
||||
|
||||
Assert.Single(defaultCollections);
|
||||
}
|
||||
|
||||
[Theory, DatabaseData]
|
||||
public async Task UpsertDefaultCollectionAsync_ShouldBeIdempotent_WhenCalledMultipleTimes(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IUserRepository userRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
ICollectionRepository collectionRepository)
|
||||
{
|
||||
// Arrange
|
||||
var organization = await organizationRepository.CreateTestOrganizationAsync();
|
||||
var user = await userRepository.CreateTestUserAsync();
|
||||
var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user);
|
||||
var defaultCollectionName = $"My Items - {organization.Id}";
|
||||
|
||||
// Act - Call method 5 times
|
||||
var tasks = Enumerable.Range(1, 5).Select(i => collectionRepository.UpsertDefaultCollectionAsync(
|
||||
organization.Id,
|
||||
orgUser.Id,
|
||||
defaultCollectionName));
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
// Assert
|
||||
Assert.Single(results, r => r); // First call should create successfully; all other results are implicitly false
|
||||
|
||||
// Verify only one collection exists
|
||||
var collectionDetails = await collectionRepository.GetManyByUserIdAsync(user.Id);
|
||||
Assert.Single(collectionDetails, c =>
|
||||
c.OrganizationId == organization.Id &&
|
||||
c.Type == CollectionType.DefaultUserCollection);
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
-- Create the idempotent stored procedure for creating default collections
|
||||
-- This procedure prevents duplicate "My Items" collections for users using
|
||||
-- a filtered unique constraint on (DefaultCollectionOwnerId, OrganizationId, Type) WHERE Type = 1.
|
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Collection_UpsertDefaultCollection]
|
||||
@CollectionId UNIQUEIDENTIFIER,
|
||||
@OrganizationId UNIQUEIDENTIFIER,
|
||||
@OrganizationUserId UNIQUEIDENTIFIER,
|
||||
@Name VARCHAR(MAX),
|
||||
@CreationDate DATETIME2(7),
|
||||
@RevisionDate DATETIME2(7),
|
||||
@WasCreated BIT OUTPUT
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
BEGIN TRANSACTION
|
||||
|
||||
BEGIN TRY
|
||||
SET @WasCreated = 1
|
||||
|
||||
-- Insert Collection with DefaultCollectionOwnerId populated for constraint enforcement
|
||||
INSERT INTO [dbo].[Collection]
|
||||
(
|
||||
[Id],
|
||||
[OrganizationId],
|
||||
[Name],
|
||||
[ExternalId],
|
||||
[CreationDate],
|
||||
[RevisionDate],
|
||||
[DefaultUserCollectionEmail],
|
||||
[Type],
|
||||
[DefaultCollectionOwnerId]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@CollectionId,
|
||||
@OrganizationId,
|
||||
@Name,
|
||||
NULL, -- ExternalId
|
||||
@CreationDate,
|
||||
@RevisionDate,
|
||||
NULL, -- DefaultUserCollectionEmail
|
||||
1, -- CollectionType.DefaultUserCollection
|
||||
@OrganizationUserId
|
||||
)
|
||||
|
||||
-- Insert CollectionUser
|
||||
INSERT INTO [dbo].[CollectionUser]
|
||||
(
|
||||
[CollectionId],
|
||||
[OrganizationUserId],
|
||||
[ReadOnly],
|
||||
[HidePasswords],
|
||||
[Manage]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@CollectionId,
|
||||
@OrganizationUserId,
|
||||
0, -- ReadOnly = false
|
||||
0, -- HidePasswords = false
|
||||
1 -- Manage = true
|
||||
)
|
||||
|
||||
-- Bump user account revision dates
|
||||
EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @CollectionId, @OrganizationId
|
||||
|
||||
COMMIT TRANSACTION
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
-- Check if error is unique constraint violation (error 2601 or 2627)
|
||||
IF ERROR_NUMBER() IN (2601, 2627)
|
||||
BEGIN
|
||||
-- Collection already exists, return gracefully
|
||||
SET @WasCreated = 0
|
||||
IF @@TRANCOUNT > 0
|
||||
ROLLBACK TRANSACTION
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
-- Unexpected error, rollback and re-throw
|
||||
IF @@TRANCOUNT > 0
|
||||
ROLLBACK TRANSACTION
|
||||
THROW
|
||||
END
|
||||
END CATCH
|
||||
END
|
||||
GO
|
||||
Reference in New Issue
Block a user