diff --git a/backend/src/Migrations/MigrationPath.cs b/backend/src/Migrations/MigrationPath.cs index 1252c82b8..93d77f55f 100644 --- a/backend/src/Migrations/MigrationPath.cs +++ b/backend/src/Migrations/MigrationPath.cs @@ -18,7 +18,7 @@ namespace Migrations { public sealed class MigrationPath : IMigrationPath { - private const int CurrentVersion = 22; + private const int CurrentVersion = 23; private readonly IServiceProvider serviceProvider; public MigrationPath(IServiceProvider serviceProvider) @@ -108,7 +108,8 @@ namespace Migrations } // Version 22: Introduce domain id. - if (version < 22) + // Version 23: Fix parent id. + if (version < 23) { yield return serviceProvider.GetRequiredService().ForAssets(); } diff --git a/backend/src/Migrations/Migrations/MongoDb/ConvertDocumentIds.cs b/backend/src/Migrations/Migrations/MongoDb/ConvertDocumentIds.cs index 621518227..3e4be3116 100644 --- a/backend/src/Migrations/Migrations/MongoDb/ConvertDocumentIds.cs +++ b/backend/src/Migrations/Migrations/MongoDb/ConvertDocumentIds.cs @@ -59,25 +59,25 @@ namespace Migrations.Migrations.MongoDb switch (scope) { case Scope.Assets: - await RebuildAsync(database, "States_Assets"); - await RebuildAsync(database, "States_AssetFolders", null, ConvertParentId); + await RebuildAsync(database, ConvertParentId, "States_Assets"); + await RebuildAsync(database, ConvertParentId, "States_AssetFolders"); break; case Scope.Contents: - await RebuildAsync(databaseContent, "State_Contents_All", "States_Contents_All2"); - await RebuildAsync(databaseContent, "State_Contents_Published", "States_Contents_Published2"); + await RebuildAsync(databaseContent, null, "State_Contents_All"); + await RebuildAsync(databaseContent, null, "State_Contents_Published"); break; } } - private static async Task RebuildAsync(IMongoDatabase database, string collectionNameOld, string? collectionNameNew = null, Action? extraAction = null) + private static async Task RebuildAsync(IMongoDatabase database, Action? extraAction, string collectionNameOld) { const int SizeOfBatch = 1000; const int SizeOfQueue = 10; - if (string.IsNullOrWhiteSpace(collectionNameNew)) - { - collectionNameNew = $"{collectionNameOld}2"; - } + string collectionNameNew; + + collectionNameNew = $"{collectionNameOld}2"; + collectionNameNew = collectionNameNew.Replace("State_", "States_"); var collectionOld = database.GetCollection(collectionNameOld); var collectionNew = database.GetCollection(collectionNameNew); @@ -151,7 +151,10 @@ namespace Migrations.Migrations.MongoDb private static void ConvertParentId(BsonDocument document) { - document["pi"] = document["pi"].AsGuid.ToString(); + if (document.Contains("pi")) + { + document["pi"] = document["pi"].AsGuid.ToString(); + } } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs index 2dd888695..fdbb315f5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs @@ -37,8 +37,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets new CreateIndexModel( Index .Ascending(x => x.IndexedAppId) - .Ascending(x => x.IsDeleted) - .Ascending(x => x.ParentId)) + .Ascending(x => x.ParentId) + .Ascending(x => x.IsDeleted)) }, ct); } @@ -46,10 +46,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { using (Profiler.TraceMethod("QueryAsyncByQuery")) { + var filter = BuildFilter(appId, parentId); + var assetFolderEntities = - await Collection - .Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.ParentId == parentId).SortBy(x => x.FolderName) - .ToListAsync(); + await Collection.Find(filter).SortBy(x => x.FolderName) + .ToListAsync(); return ResultList.Create(assetFolderEntities.Count, assetFolderEntities); } @@ -59,8 +60,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { using (Profiler.TraceMethod()) { + var filter = BuildFilter(appId, parentId); + var assetFolderEntities = - await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.ParentId == parentId).Only(x => x.Id) + await Collection.Find(filter).Only(x => x.Id) .ToListAsync(); return assetFolderEntities.Select(x => DomainId.Create(x[Fields.AssetFolderId].AsString)).ToList(); @@ -80,5 +83,31 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets return assetFolderEntity; } } + + private static FilterDefinition BuildFilter(DomainId appId, DomainId? parentId) + { + var filters = new List> + { + Filter.Eq(x => x.IndexedAppId, appId), + Filter.Eq(x => x.IsDeleted, false) + }; + + if (parentId.HasValue) + { + if (parentId == DomainId.Empty) + { + filters.Add( + Filter.Or( + Filter.Exists(x => x.ParentId, false), + Filter.Eq(x => x.ParentId, DomainId.Empty))); + } + else + { + filters.Add(Filter.Eq(x => x.ParentId, parentId.Value)); + } + } + + return Filter.And(filters); + } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs index 5c9a83ae9..09d9a1670 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs @@ -73,18 +73,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors } } - if (filters.Count > 1) - { - return Filter.And(filters); - } - else if (filters.Count == 1) - { - return filters[0]; - } - else - { - return new BsonDocument(); - } + return Filter.And(filters); } } } diff --git a/backend/src/Squidex.Domain.Users.MongoDb/MongoUser.cs b/backend/src/Squidex.Domain.Users.MongoDb/MongoUser.cs index 7a511b75b..16dc9b07b 100644 --- a/backend/src/Squidex.Domain.Users.MongoDb/MongoUser.cs +++ b/backend/src/Squidex.Domain.Users.MongoDb/MongoUser.cs @@ -25,7 +25,7 @@ namespace Squidex.Domain.Users.MongoDb [BsonRequired] [BsonElement] - public List Logins { get; set; } = new List(); + public List Logins { get; set; } = new List(); [BsonRequired] [BsonElement] @@ -38,7 +38,7 @@ namespace Squidex.Domain.Users.MongoDb internal void AddLogin(UserLoginInfo login) { - Logins.Add(new UserLoginInfo(login.LoginProvider, login.ProviderKey, login.ProviderDisplayName)); + Logins.Add(new UserLogin(login)); } internal void AddRole(string role) @@ -53,7 +53,7 @@ namespace Squidex.Domain.Users.MongoDb internal void AddClaims(IEnumerable claims) { - claims.Foreach((x, _) => AddClaim(x)); + claims.Foreach(x => AddClaim(x)); } internal void AddToken(string provider, string name, string value) @@ -66,24 +66,24 @@ namespace Squidex.Domain.Users.MongoDb Logins.RemoveAll(x => x.LoginProvider == provider && x.ProviderKey == providerKey); } - internal void RemoveRole(string role) + internal void RemoveClaim(Claim claim) { - Roles.Remove(role); + Claims.RemoveAll(x => x.Type == claim.Type && x.Value == claim.Value); } - internal void RemoveClaim(Claim claim) + internal void RemoveToken(string provider, string name) { - Claims.RemoveAll(x => x.Type == claim.Type && x.Value == claim.Value); + Tokens.RemoveAll(x => x.LoginProvider == provider && x.Name == name); } - internal void RemoveClaims(IEnumerable claims) + internal void RemoveRole(string role) { - claims.Foreach((x, _) => RemoveClaim(x)); + Roles.Remove(role); } - internal void RemoveToken(string provider, string name) + internal void RemoveClaims(IEnumerable claims) { - Tokens.RemoveAll(x => x.LoginProvider == provider && x.Name == name); + claims.Foreach(x => RemoveClaim(x)); } internal void ReplaceClaim(Claim existingClaim, Claim newClaim) @@ -104,4 +104,17 @@ namespace Squidex.Domain.Users.MongoDb public sealed class UserTokenInfo : IdentityUserToken { } + + public sealed class UserLogin : UserLoginInfo + { + public UserLogin(string provider, string providerKey, string displayName) + : base(provider, providerKey, displayName) + { + } + + public UserLogin(UserLoginInfo source) + : base(source.LoginProvider, source.ProviderKey, source.ProviderDisplayName) + { + } + } } diff --git a/backend/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs b/backend/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs index fc7bb3930..4cc12961c 100644 --- a/backend/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs +++ b/backend/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs @@ -66,14 +66,20 @@ namespace Squidex.Domain.Users.MongoDb cm.MapMember(x => x.Value); }); - BsonClassMap.RegisterClassMap(cm => + BsonClassMap.RegisterClassMap(cm => { - cm.MapConstructor(typeof(UserLoginInfo).GetConstructors().First()) + cm.MapConstructor(typeof(UserLogin).GetConstructors() + .First(x => + { + var parameters = x.GetParameters(); + + return parameters.Length == 3; + })) .SetArguments(new[] { - nameof(UserLoginInfo.LoginProvider), - nameof(UserLoginInfo.ProviderKey), - nameof(UserLoginInfo.ProviderDisplayName) + nameof(UserLogin.LoginProvider), + nameof(UserLogin.ProviderKey), + nameof(UserLogin.ProviderDisplayName) }); cm.AutoMap(); diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs index 00ef422da..0dfb21bbf 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs @@ -62,39 +62,39 @@ namespace Squidex.Infrastructure.MongoDb } } - public static IFindFluent Only(this IFindFluent find, - Expression> include) + public static IFindFluent Only(this IFindFluent find, + Expression> include) { - return find.Project(Builders.Projection.Include(include)); + return find.Project(Builders.Projection.Include(include)); } - public static IFindFluent Only(this IFindFluent find, - Expression> include1, - Expression> include2) + public static IFindFluent Only(this IFindFluent find, + Expression> include1, + Expression> include2) { - return find.Project(Builders.Projection.Include(include1).Include(include2)); + return find.Project(Builders.Projection.Include(include1).Include(include2)); } - public static IFindFluent Not(this IFindFluent find, - Expression> exclude) + public static IFindFluent Not(this IFindFluent find, + Expression> exclude) { - return find.Project(Builders.Projection.Exclude(exclude)); + return find.Project(Builders.Projection.Exclude(exclude)); } - public static IFindFluent Not(this IFindFluent find, - Expression> exclude1, - Expression> exclude2) + public static IFindFluent Not(this IFindFluent find, + Expression> exclude1, + Expression> exclude2) { - return find.Project(Builders.Projection.Exclude(exclude1).Exclude(exclude2)); + return find.Project(Builders.Projection.Exclude(exclude1).Exclude(exclude2)); } - public static async Task UpsertVersionedAsync(this IMongoCollection collection, TKey key, long oldVersion, long newVersion, Func, UpdateDefinition> updater) - where TEntity : IVersionedEntity + public static async Task UpsertVersionedAsync(this IMongoCollection collection, TKey key, long oldVersion, long newVersion, Func, UpdateDefinition> updater) + where T : IVersionedEntity where TKey : notnull { try { - var update = updater(Builders.Update.Set(x => x.Version, newVersion)); + var update = updater(Builders.Update.Set(x => x.Version, newVersion)); if (oldVersion > EtagVersion.Any) { @@ -113,7 +113,7 @@ namespace Squidex.Infrastructure.MongoDb if (existingVersion != null) { - var versionField = GetVersionField(); + var versionField = GetVersionField(); throw new InconsistentStateException(existingVersion[versionField].AsInt64, oldVersion, ex); } @@ -124,22 +124,22 @@ namespace Squidex.Infrastructure.MongoDb } } - public static async Task UpsertVersionedAsync(this IMongoCollection collection, TKey key, long oldVersion, long newVersion, TEntity doc) - where TEntity : IVersionedEntity + public static async Task UpsertVersionedAsync(this IMongoCollection collection, TKey key, long oldVersion, long newVersion, T document) + where T : IVersionedEntity where TKey : notnull { try { - doc.DocumentId = key; - doc.Version = newVersion; + document.DocumentId = key; + document.Version = newVersion; if (oldVersion > EtagVersion.Any) { - await collection.ReplaceOneAsync(x => x.DocumentId.Equals(key) && x.Version == oldVersion, doc, UpsertReplace); + await collection.ReplaceOneAsync(x => x.DocumentId.Equals(key) && x.Version == oldVersion, document, UpsertReplace); } else { - await collection.ReplaceOneAsync(x => x.DocumentId.Equals(key), doc, UpsertReplace); + await collection.ReplaceOneAsync(x => x.DocumentId.Equals(key), document, UpsertReplace); } } catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey) @@ -150,7 +150,7 @@ namespace Squidex.Infrastructure.MongoDb if (existingVersion != null) { - var versionField = GetVersionField(); + var versionField = GetVersionField(); throw new InconsistentStateException(existingVersion[versionField].AsInt64, oldVersion, ex); } @@ -161,14 +161,14 @@ namespace Squidex.Infrastructure.MongoDb } } - private static string GetVersionField() - where TEntity : IVersionedEntity + private static string GetVersionField() + where T : IVersionedEntity where TKey : notnull { - return BsonClassMap.LookupClassMap(typeof(TEntity)).GetMemberMap(nameof(IVersionedEntity.Version)).ElementName; + return BsonClassMap.LookupClassMap(typeof(T)).GetMemberMap(nameof(IVersionedEntity.Version)).ElementName; } - public static async Task ForEachPipedAsync(this IAsyncCursorSource source, Func processor, CancellationToken cancellationToken = default) + public static async Task ForEachPipedAsync(this IAsyncCursorSource source, Func processor, CancellationToken cancellationToken = default) { using (var cursor = await source.ToCursorAsync(cancellationToken)) { @@ -176,14 +176,14 @@ namespace Squidex.Infrastructure.MongoDb } } - public static async Task ForEachPipedAsync(this IAsyncCursor source, Func processor, CancellationToken cancellationToken = default) + public static async Task ForEachPipedAsync(this IAsyncCursor source, Func processor, CancellationToken cancellationToken = default) { using (var selfToken = new CancellationTokenSource()) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(selfToken.Token, cancellationToken)) { var actionBlock = - new ActionBlock(async x => + new ActionBlock(async x => { if (!combined.IsCancellationRequested) { diff --git a/backend/src/Squidex.Infrastructure/CollectionExtensions.cs b/backend/src/Squidex.Infrastructure/CollectionExtensions.cs index c25e56fca..ddee81e66 100644 --- a/backend/src/Squidex.Infrastructure/CollectionExtensions.cs +++ b/backend/src/Squidex.Infrastructure/CollectionExtensions.cs @@ -263,6 +263,11 @@ namespace Squidex.Infrastructure return result; } + public static void Foreach(this IEnumerable collection, Action action) + { + collection.Foreach((x, i) => action(x)); + } + public static void Foreach(this IEnumerable collection, Action action) { var index = 0; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs index d224396b6..d13da211a 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs @@ -327,7 +327,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb private string _Q(ClrQuery query) { var rendered = - query.AdjustToModel(schemaDef).BuildFilter().Filter! + query.AdjustToModel(schemaDef).BuildFilter()! .Render(Serializer, Registry).ToString(); return rendered;