-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[PM-28555] Create idempotent sproc for creating a My Items collection #6677
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 20 commits
f54a77d
dd39159
5fd5c51
63b4f1b
cecb022
3bbf375
25189b8
f5393fa
1c03ef1
d001aa4
a9b24d7
6adb30e
0a28dbf
184677d
b0a7884
a3369ab
868ecf0
ed49d7d
7577fa6
aad01ec
dc1dda6
8582ab3
5c56767
e2cd3c6
afaaea0
b6ed81e
dca7695
1f931bd
ce7d727
03690cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| ๏ปฟusing Bit.Infrastructure.EntityFramework.Models; | ||
| using Microsoft.EntityFrameworkCore; | ||
| using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||
|
|
||
| namespace Bit.Infrastructure.EntityFramework.AdminConsole.Configurations; | ||
|
|
||
| public class CollectionEntityTypeConfiguration : IEntityTypeConfiguration<Collection> | ||
| { | ||
| public void Configure(EntityTypeBuilder<Collection> builder) | ||
| { | ||
| builder | ||
| .Property(c => c.Id) | ||
| .ValueGeneratedNever(); | ||
|
|
||
| builder | ||
| .HasIndex(c => new { c.DefaultCollectionOwner, c.OrganizationId, c.Type }) | ||
| .IsUnique() | ||
| .HasFilter("[Type] = 1") | ||
| .HasDatabaseName("IX_Collection_DefaultCollectionOwner_OrganizationId_Type"); | ||
|
|
||
| // Configure FK with NO ACTION delete behavior to prevent cascade conflicts | ||
| // Cleanup is handled explicitly in OrganizationUserRepository.DeleteAsync | ||
| builder | ||
| .HasOne<OrganizationUser>() | ||
| .WithMany() | ||
| .HasForeignKey(c => c.DefaultCollectionOwner) | ||
| .OnDelete(DeleteBehavior.NoAction); | ||
|
|
||
| builder.ToTable(nameof(Collection)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -821,6 +821,63 @@ public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable | |
| 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, | ||
| DefaultCollectionOwner = 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) | ||
| { | ||
| // Check if the inner exception is a SqlException with error 2601 or 2627 | ||
| return ex.InnerException is Microsoft.Data.SqlClient.SqlException sqlEx | ||
|
||
| && (sqlEx.Number == 2601 || sqlEx.Number == 2627); | ||
| } | ||
|
|
||
| private async Task<HashSet<Guid>> GetOrgUserIdsWithDefaultCollectionAsync(DatabaseContext dbContext, Guid organizationId) | ||
| { | ||
| var results = await dbContext.OrganizationUsers | ||
|
|
@@ -861,8 +918,8 @@ private async Task<HashSet<Guid>> GetOrgUserIdsWithDefaultCollectionAsync(Databa | |
| CreationDate = DateTime.UtcNow, | ||
| RevisionDate = DateTime.UtcNow, | ||
| Type = CollectionType.DefaultUserCollection, | ||
| DefaultUserCollectionEmail = null | ||
|
|
||
| DefaultUserCollectionEmail = null, | ||
| DefaultCollectionOwner = orgUserId | ||
| }); | ||
|
|
||
| collectionUsers.Add(new CollectionUser | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!