diff --git a/backend/src/Squidex.Data.MongoDb/Domain/Users/MongoUserStore.cs b/backend/src/Squidex.Data.MongoDb/Domain/Users/MongoUserStore.cs index e8651a370..6ea88e642 100644 --- a/backend/src/Squidex.Data.MongoDb/Domain/Users/MongoUserStore.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Users/MongoUserStore.cs @@ -35,6 +35,7 @@ public sealed class MongoUserStore(IMongoDatabase database) : private const string InternalLoginProvider = "[AspNetUserStore]"; private const string AuthenticatorKeyTokenName = "AuthenticatorKey"; private const string RecoveryCodeTokenName = "RecoveryCodes"; + private IMongoCollection queryableCollection; static MongoUserStore() { @@ -51,11 +52,11 @@ public sealed class MongoUserStore(IMongoDatabase database) : parameters[1].Name == "value" && parameters[1].ParameterType == typeof(string); })) - .SetArguments(new[] - { + .SetArguments( + [ nameof(Claim.Type), nameof(Claim.Value), - }); + ]); cm.MapMember(x => x.Type); cm.MapMember(x => x.Value); @@ -70,12 +71,12 @@ public sealed class MongoUserStore(IMongoDatabase database) : return parameters.Length == 3; })) - .SetArguments(new[] - { + .SetArguments( + [ nameof(UserLogin.LoginProvider), nameof(UserLogin.ProviderKey), nameof(UserLogin.ProviderDisplayName), - }); + ]); cm.AutoMap(); }); @@ -121,6 +122,8 @@ public sealed class MongoUserStore(IMongoDatabase database) : cm.MapMember(x => x.TwoFactorEnabled) .SetIgnoreIfDefault(true); }); + + BsonSerializer.TryRegisterSerializer(new IdentityUserForwardingSerializer()); } protected override string CollectionName() @@ -165,7 +168,7 @@ public sealed class MongoUserStore(IMongoDatabase database) : public IQueryable Users { - get => Collection.AsQueryable(); + get => (queryableCollection ??= Database.GetCollection(CollectionName())).AsQueryable(); } public bool IsId(string id) @@ -181,7 +184,9 @@ public sealed class MongoUserStore(IMongoDatabase database) : public async Task FindByIdAsync(string userId, CancellationToken cancellationToken) { - var result = await Collection.Find(x => x.Id == userId).FirstOrDefaultAsync(cancellationToken); + var result = + await Collection.Find(x => x.Id == userId) + .FirstOrDefaultAsync(cancellationToken); return result; } @@ -189,7 +194,9 @@ public sealed class MongoUserStore(IMongoDatabase database) : public async Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken) { - var result = await Collection.Find(x => x.NormalizedEmail == normalizedEmail).FirstOrDefaultAsync(cancellationToken); + var result = + await Collection.Find(x => x.NormalizedEmail == normalizedEmail) + .FirstOrDefaultAsync(cancellationToken); return result; } @@ -197,7 +204,9 @@ public sealed class MongoUserStore(IMongoDatabase database) : public async Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken) { - var result = await Collection.Find(x => x.NormalizedEmail == normalizedUserName).FirstOrDefaultAsync(cancellationToken); + var result = + await Collection.Find(x => x.NormalizedEmail == normalizedUserName) + .FirstOrDefaultAsync(cancellationToken); return result; } @@ -205,7 +214,9 @@ public sealed class MongoUserStore(IMongoDatabase database) : public async Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) { - var result = await Collection.Find(Filter.ElemMatch(x => x.Logins, BuildFilter(loginProvider, providerKey))).FirstOrDefaultAsync(cancellationToken); + var result = + await Collection.Find(Filter.ElemMatch(x => x.Logins, BuildFilter(loginProvider, providerKey))) + .FirstOrDefaultAsync(cancellationToken); return result; } @@ -213,7 +224,9 @@ public sealed class MongoUserStore(IMongoDatabase database) : public async Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken) { - var result = await Collection.Find(x => x.Claims.Exists(y => y.Type == claim.Type && y.Value == claim.Value)).ToListAsync(cancellationToken); + var result = + await Collection.Find(x => x.Claims.Exists(y => y.Type == claim.Type && y.Value == claim.Value)) + .ToListAsync(cancellationToken); return result.OfType().ToList(); } @@ -221,7 +234,9 @@ public sealed class MongoUserStore(IMongoDatabase database) : public async Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken) { - var result = await Collection.Find(x => x.Roles.Contains(roleName)).ToListAsync(cancellationToken); + var result = + await Collection.Find(x => x.Roles.Contains(roleName)) + .ToListAsync(cancellationToken); return result.OfType().ToList(); } @@ -231,7 +246,8 @@ public sealed class MongoUserStore(IMongoDatabase database) : { user.Id = ObjectId.GenerateNewId().ToString(); - await Collection.InsertOneAsync((MongoUser)user, null, cancellationToken); + await Collection.InsertOneAsync((MongoUser)user, null, + cancellationToken); return IdentityResult.Success; } @@ -239,7 +255,8 @@ public sealed class MongoUserStore(IMongoDatabase database) : public async Task UpdateAsync(IdentityUser user, CancellationToken cancellationToken) { - await Collection.ReplaceOneAsync(x => x.Id == user.Id, (MongoUser)user, cancellationToken: cancellationToken); + await Collection.ReplaceOneAsync(x => x.Id == user.Id, (MongoUser)user, + cancellationToken: cancellationToken); return IdentityResult.Success; } @@ -247,7 +264,8 @@ public sealed class MongoUserStore(IMongoDatabase database) : public async Task DeleteAsync(IdentityUser user, CancellationToken cancellationToken) { - await Collection.DeleteOneAsync(x => x.Id == user.Id, null, cancellationToken); + await Collection.DeleteOneAsync(x => x.Id == user.Id, null, + cancellationToken); return IdentityResult.Success; } @@ -654,4 +672,29 @@ public sealed class MongoUserStore(IMongoDatabase database) : filter.Eq(x => x.LoginProvider, loginProvider), filter.Eq(x => x.ProviderKey, providerKey)); } + + private sealed class IdentityUserForwardingSerializer + : SerializerBase, IBsonDocumentSerializer + { + private static readonly IBsonSerializer MongoUserSerializer = + BsonSerializer.LookupSerializer(); + + public override IdentityUser Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) + { + args.NominalType = typeof(MongoUser); + return MongoUserSerializer.Deserialize(context, args); + } + + public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, IdentityUser value) + { + args.NominalType = typeof(MongoUser); + MongoUserSerializer.Serialize(context, args, (MongoUser)value); + } + + public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo serializationInfo) + { + return ((IBsonDocumentSerializer)MongoUserSerializer) + .TryGetMemberSerializationInfo(memberName, out serializationInfo); + } + } } diff --git a/backend/src/Squidex.Data.MongoDb/Squidex.Data.MongoDb.csproj b/backend/src/Squidex.Data.MongoDb/Squidex.Data.MongoDb.csproj index f7b00de2c..23ae20754 100644 --- a/backend/src/Squidex.Data.MongoDb/Squidex.Data.MongoDb.csproj +++ b/backend/src/Squidex.Data.MongoDb/Squidex.Data.MongoDb.csproj @@ -30,7 +30,7 @@ - + diff --git a/backend/src/Squidex.Web/Pipeline/UsagePipeWriter.cs b/backend/src/Squidex.Web/Pipeline/UsagePipeWriter.cs index b34f8a09d..8fe172772 100644 --- a/backend/src/Squidex.Web/Pipeline/UsagePipeWriter.cs +++ b/backend/src/Squidex.Web/Pipeline/UsagePipeWriter.cs @@ -13,6 +13,10 @@ public sealed class UsagePipeWriter(PipeWriter inner) : PipeWriter { private long bytesWritten; + public override bool CanGetUnflushedBytes => inner.CanGetUnflushedBytes; + + public override long UnflushedBytes => inner.UnflushedBytes; + public long BytesWritten { get => bytesWritten; diff --git a/backend/src/Squidex/Config/Web/WebServices.cs b/backend/src/Squidex/Config/Web/WebServices.cs index d30a47623..6d31af1c7 100644 --- a/backend/src/Squidex/Config/Web/WebServices.cs +++ b/backend/src/Squidex/Config/Web/WebServices.cs @@ -9,6 +9,7 @@ using GraphQL; using GraphQL.DI; using GraphQL.Server.Transports.AspNetCore; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.ResponseCompression; diff --git a/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj b/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj index 2a33ce6f7..82aee2ef0 100644 --- a/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj +++ b/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj @@ -21,7 +21,7 @@ - +