diff --git a/src/Squidex.Domain.Apps.Core/Apps/PermissionLevel.cs b/src/Squidex.Domain.Apps.Core/Apps/PermissionLevel.cs index c60b1f0eb..06ec9cb06 100644 --- a/src/Squidex.Domain.Apps.Core/Apps/PermissionLevel.cs +++ b/src/Squidex.Domain.Apps.Core/Apps/PermissionLevel.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Core.Apps { public enum PermissionLevel diff --git a/src/Squidex.Domain.Apps.Core/Identity/SquidexClaimTypes.cs b/src/Squidex.Domain.Apps.Core/Identity/SquidexClaimTypes.cs index 08004ca47..b0b071e50 100644 --- a/src/Squidex.Domain.Apps.Core/Identity/SquidexClaimTypes.cs +++ b/src/Squidex.Domain.Apps.Core/Identity/SquidexClaimTypes.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Core.Identity { public static class SquidexClaimTypes diff --git a/src/Squidex.Domain.Apps.Core/Identity/SquidexRoles.cs b/src/Squidex.Domain.Apps.Core/Identity/SquidexRoles.cs index 5ed5df31c..3031c17e4 100644 --- a/src/Squidex.Domain.Apps.Core/Identity/SquidexRoles.cs +++ b/src/Squidex.Domain.Apps.Core/Identity/SquidexRoles.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Core.Identity { public static class SquidexRoles diff --git a/src/Squidex.Domain.Apps.Core/Schemas/BooleanFieldEditor.cs b/src/Squidex.Domain.Apps.Core/Schemas/BooleanFieldEditor.cs index b82b6be70..6be38b6f9 100644 --- a/src/Squidex.Domain.Apps.Core/Schemas/BooleanFieldEditor.cs +++ b/src/Squidex.Domain.Apps.Core/Schemas/BooleanFieldEditor.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Core.Schemas { public enum BooleanFieldEditor diff --git a/src/Squidex.Domain.Apps.Core/Schemas/DateTimeCalculatedDefaultValue.cs b/src/Squidex.Domain.Apps.Core/Schemas/DateTimeCalculatedDefaultValue.cs index 4395ad2c0..0e71b0187 100644 --- a/src/Squidex.Domain.Apps.Core/Schemas/DateTimeCalculatedDefaultValue.cs +++ b/src/Squidex.Domain.Apps.Core/Schemas/DateTimeCalculatedDefaultValue.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Core.Schemas { public enum DateTimeCalculatedDefaultValue diff --git a/src/Squidex.Domain.Apps.Core/Schemas/DateTimeFieldEditor.cs b/src/Squidex.Domain.Apps.Core/Schemas/DateTimeFieldEditor.cs index d144cad0b..cb2be2f19 100644 --- a/src/Squidex.Domain.Apps.Core/Schemas/DateTimeFieldEditor.cs +++ b/src/Squidex.Domain.Apps.Core/Schemas/DateTimeFieldEditor.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Core.Schemas { public enum DateTimeFieldEditor diff --git a/src/Squidex.Domain.Apps.Core/Schemas/GeolocationFieldEditor.cs b/src/Squidex.Domain.Apps.Core/Schemas/GeolocationFieldEditor.cs index 2100ee084..d6f3acff7 100644 --- a/src/Squidex.Domain.Apps.Core/Schemas/GeolocationFieldEditor.cs +++ b/src/Squidex.Domain.Apps.Core/Schemas/GeolocationFieldEditor.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Core.Schemas { public enum GeolocationFieldEditor diff --git a/src/Squidex.Domain.Apps.Core/Schemas/NumberFieldEditor.cs b/src/Squidex.Domain.Apps.Core/Schemas/NumberFieldEditor.cs index 57c562cf7..28f164ef8 100644 --- a/src/Squidex.Domain.Apps.Core/Schemas/NumberFieldEditor.cs +++ b/src/Squidex.Domain.Apps.Core/Schemas/NumberFieldEditor.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Core.Schemas { public enum NumberFieldEditor diff --git a/src/Squidex.Domain.Apps.Core/Schemas/SchemaProperties.cs b/src/Squidex.Domain.Apps.Core/Schemas/SchemaProperties.cs index 2a5618c36..4731958e7 100644 --- a/src/Squidex.Domain.Apps.Core/Schemas/SchemaProperties.cs +++ b/src/Squidex.Domain.Apps.Core/Schemas/SchemaProperties.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Core.Schemas { public sealed class SchemaProperties : NamedElementPropertiesBase diff --git a/src/Squidex.Domain.Apps.Core/Schemas/StringFieldEditor.cs b/src/Squidex.Domain.Apps.Core/Schemas/StringFieldEditor.cs index 3af038292..1226fef19 100644 --- a/src/Squidex.Domain.Apps.Core/Schemas/StringFieldEditor.cs +++ b/src/Squidex.Domain.Apps.Core/Schemas/StringFieldEditor.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Core.Schemas { public enum StringFieldEditor diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj b/src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj index 833fd3e2e..668717ccb 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj @@ -24,9 +24,4 @@ - - - C:\Users\mail2\.nuget\packages\system.reflection.metadata\1.4.1\lib\netstandard1.1\System.Reflection.Metadata.dll - - diff --git a/src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs b/src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs index bd02ee85a..8a5e04edf 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Read.Apps { public interface IAppClientEntity diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/IAppLimitsPlan.cs b/src/Squidex.Domain.Apps.Read/Apps/Services/IAppLimitsPlan.cs index e6ef5ab03..3bc0a0012 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/IAppLimitsPlan.cs +++ b/src/Squidex.Domain.Apps.Read/Apps/Services/IAppLimitsPlan.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Read.Apps.Services { public interface IAppLimitsPlan diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppLimitsPlan.cs b/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppLimitsPlan.cs index e5e3d1476..0ed64ddcb 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppLimitsPlan.cs +++ b/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppLimitsPlan.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations { public sealed class ConfigAppLimitsPlan : IAppLimitsPlan diff --git a/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs b/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs index 0bd1a25c5..ac23e8fd4 100644 --- a/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Read.Assets { public interface IAssetEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion diff --git a/src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs b/src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs index 4eb2c50af..b80ed6b08 100644 --- a/src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs +++ b/src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Read { public interface IEntityWithVersion diff --git a/src/Squidex.Domain.Apps.Read/Schemas/WebhookResult.cs b/src/Squidex.Domain.Apps.Read/Schemas/WebhookResult.cs index db82786d5..df9360b3d 100644 --- a/src/Squidex.Domain.Apps.Read/Schemas/WebhookResult.cs +++ b/src/Squidex.Domain.Apps.Read/Schemas/WebhookResult.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Read.Schemas { public enum WebhookResult diff --git a/src/Squidex.Domain.Apps.Read/Users/ExternalLogin.cs b/src/Squidex.Domain.Apps.Read/Users/ExternalLogin.cs index 04afa5749..a618757ff 100644 --- a/src/Squidex.Domain.Apps.Read/Users/ExternalLogin.cs +++ b/src/Squidex.Domain.Apps.Read/Users/ExternalLogin.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Read.Users { public sealed class ExternalLogin diff --git a/src/Squidex.Domain.Apps.Read/Users/IRole.cs b/src/Squidex.Domain.Apps.Read/Users/IRole.cs index 2daea1d65..558e520e1 100644 --- a/src/Squidex.Domain.Apps.Read/Users/IRole.cs +++ b/src/Squidex.Domain.Apps.Read/Users/IRole.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Read.Users { public interface IRole diff --git a/src/Squidex.Domain.Apps.Read/Users/IRoleFactory.cs b/src/Squidex.Domain.Apps.Read/Users/IRoleFactory.cs index a54d681c1..930dea5ac 100644 --- a/src/Squidex.Domain.Apps.Read/Users/IRoleFactory.cs +++ b/src/Squidex.Domain.Apps.Read/Users/IRoleFactory.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Read.Users { public interface IRoleFactory diff --git a/src/Squidex.Domain.Apps.Read/Users/IUserFactory.cs b/src/Squidex.Domain.Apps.Read/Users/IUserFactory.cs index 53c298ec0..573b8715b 100644 --- a/src/Squidex.Domain.Apps.Read/Users/IUserFactory.cs +++ b/src/Squidex.Domain.Apps.Read/Users/IUserFactory.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Read.Users { public interface IUserFactory diff --git a/src/Squidex.Domain.Apps.Write/Assets/Commands/DeleteAsset.cs b/src/Squidex.Domain.Apps.Write/Assets/Commands/DeleteAsset.cs index ff27b0b63..a9cfd5cfe 100644 --- a/src/Squidex.Domain.Apps.Write/Assets/Commands/DeleteAsset.cs +++ b/src/Squidex.Domain.Apps.Write/Assets/Commands/DeleteAsset.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Assets.Commands { public sealed class DeleteAsset : AssetAggregateCommand diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/DeleteContent.cs b/src/Squidex.Domain.Apps.Write/Contents/Commands/DeleteContent.cs index 738fad329..210815c1f 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/DeleteContent.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/Commands/DeleteContent.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Contents.Commands { public class DeleteContent : ContentCommand diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs b/src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs index f49c799b7..d14f5f5f8 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Contents.Commands { public class PatchContent : ContentDataCommand diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/PublishContent.cs b/src/Squidex.Domain.Apps.Write/Contents/Commands/PublishContent.cs index 184a5644f..f6227ee79 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/PublishContent.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/Commands/PublishContent.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Contents.Commands { public class PublishContent : ContentCommand diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/UnpublishContent.cs b/src/Squidex.Domain.Apps.Write/Contents/Commands/UnpublishContent.cs index 5b169b4b4..c0a63beae 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/UnpublishContent.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/Commands/UnpublishContent.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Contents.Commands { public class UnpublishContent : ContentCommand diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs b/src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs index ce3820262..87b824b25 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Contents.Commands { public class UpdateContent : ContentDataCommand diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteField.cs b/src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteField.cs index 1ba90df08..3d4d987f4 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteField.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteField.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Schemas.Commands { public class DeleteField : FieldCommand diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteSchema.cs b/src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteSchema.cs index 841f9935e..66ee9e9ae 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteSchema.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteSchema.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Schemas.Commands { public class DeleteSchema : SchemaAggregateCommand diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/DisableField.cs b/src/Squidex.Domain.Apps.Write/Schemas/Commands/DisableField.cs index d916192e4..4fd5a0548 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/DisableField.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Commands/DisableField.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Schemas.Commands { public class DisableField : FieldCommand diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/EnableField.cs b/src/Squidex.Domain.Apps.Write/Schemas/Commands/EnableField.cs index dd9933c0c..678ab9781 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/EnableField.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Commands/EnableField.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Schemas.Commands { public class EnableField : FieldCommand diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/FieldCommand.cs b/src/Squidex.Domain.Apps.Write/Schemas/Commands/FieldCommand.cs index a9d3d05a1..4fe5f6b6f 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/FieldCommand.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Commands/FieldCommand.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Schemas.Commands { public class FieldCommand : SchemaAggregateCommand diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/HideField.cs b/src/Squidex.Domain.Apps.Write/Schemas/Commands/HideField.cs index 1dc20d1ab..31e92ab8a 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/HideField.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Commands/HideField.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Schemas.Commands { public class HideField : FieldCommand diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/PublishSchema.cs b/src/Squidex.Domain.Apps.Write/Schemas/Commands/PublishSchema.cs index d4d63d3da..1e04b79af 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/PublishSchema.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Commands/PublishSchema.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Schemas.Commands { public class PublishSchema : SchemaAggregateCommand diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/ShowField.cs b/src/Squidex.Domain.Apps.Write/Schemas/Commands/ShowField.cs index 48a8e2565..83b01c10c 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/ShowField.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Commands/ShowField.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Schemas.Commands { public class ShowField : FieldCommand diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/UnpublishSchema.cs b/src/Squidex.Domain.Apps.Write/Schemas/Commands/UnpublishSchema.cs index 387cdd34e..119ac26a1 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/UnpublishSchema.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Commands/UnpublishSchema.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Domain.Apps.Write.Schemas.Commands { public class UnpublishSchema : SchemaAggregateCommand diff --git a/src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs b/src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs new file mode 100644 index 000000000..765b76514 --- /dev/null +++ b/src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs @@ -0,0 +1,92 @@ +// ========================================================================== +// MongoRoleStore.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.MongoDB; +using MongoDB.Driver; + +namespace Squidex.Domain.Users.MongoDb +{ + public sealed class MongoRoleStore : + IRoleStore, + IRoleFactory + { + private readonly RoleStore innerStore; + + public MongoRoleStore(IMongoDatabase database) + { + var rolesCollection = database.GetCollection("Identity_Roles"); + + IndexChecks.EnsureUniqueIndexOnNormalizedRoleName(rolesCollection); + + innerStore = new RoleStore(rolesCollection); + } + + public void Dispose() + { + innerStore.Dispose(); + } + + public IRole Create(string name) + { + return new WrappedIdentityRole { Name = name }; + } + + public async Task FindByIdAsync(string roleId, CancellationToken cancellationToken) + { + return await innerStore.FindByIdAsync(roleId, cancellationToken); + } + + public async Task FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken) + { + return await innerStore.FindByNameAsync(normalizedRoleName, cancellationToken); + } + + public Task CreateAsync(IRole role, CancellationToken cancellationToken) + { + return innerStore.CreateAsync((WrappedIdentityRole)role, cancellationToken); + } + + public Task UpdateAsync(IRole role, CancellationToken cancellationToken) + { + return innerStore.UpdateAsync((WrappedIdentityRole)role, cancellationToken); + } + + public Task DeleteAsync(IRole role, CancellationToken cancellationToken) + { + return innerStore.DeleteAsync((WrappedIdentityRole)role, cancellationToken); + } + + public Task GetRoleIdAsync(IRole role, CancellationToken cancellationToken) + { + return innerStore.GetRoleIdAsync((WrappedIdentityRole)role, cancellationToken); + } + + public Task GetRoleNameAsync(IRole role, CancellationToken cancellationToken) + { + return innerStore.GetRoleNameAsync((WrappedIdentityRole)role, cancellationToken); + } + + public Task SetRoleNameAsync(IRole role, string roleName, CancellationToken cancellationToken) + { + return innerStore.SetRoleNameAsync((WrappedIdentityRole)role, roleName, cancellationToken); + } + + public Task GetNormalizedRoleNameAsync(IRole role, CancellationToken cancellationToken) + { + return innerStore.GetNormalizedRoleNameAsync((WrappedIdentityRole)role, cancellationToken); + } + + public Task SetNormalizedRoleNameAsync(IRole role, string normalizedName, CancellationToken cancellationToken) + { + return innerStore.SetNormalizedRoleNameAsync((WrappedIdentityRole)role, normalizedName, cancellationToken); + } + } +} diff --git a/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs b/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs new file mode 100644 index 000000000..33f5facd7 --- /dev/null +++ b/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs @@ -0,0 +1,329 @@ +// ========================================================================== +// MongoUserStore.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.MongoDB; +using MongoDB.Driver; +using Squidex.Shared.Users; + +namespace Squidex.Domain.Users.MongoDb +{ + public sealed class MongoUserStore : + IUserPasswordStore, + IUserRoleStore, + IUserLoginStore, + IUserSecurityStampStore, + IUserEmailStore, + IUserClaimStore, + IUserPhoneNumberStore, + IUserTwoFactorStore, + IUserLockoutStore, + IUserAuthenticationTokenStore, + IUserFactory, + IUserResolver, + IQueryableUserStore + { + private readonly UserStore innerStore; + + public MongoUserStore(IMongoDatabase database) + { + var usersCollection = database.GetCollection("Identity_Users"); + + IndexChecks.EnsureUniqueIndexOnNormalizedEmail(usersCollection); + IndexChecks.EnsureUniqueIndexOnNormalizedUserName(usersCollection); + + innerStore = new UserStore(usersCollection); + } + + public void Dispose() + { + innerStore.Dispose(); + } + + public IQueryable Users + { + get { return innerStore.Users; } + } + + public IUser Create(string email) + { + return new WrappedIdentityUser { Email = email, UserName = email }; + } + + public async Task FindByIdAsync(string userId) + { + return await innerStore.FindByIdAsync(userId, CancellationToken.None); + } + + public async Task FindByIdAsync(string userId, CancellationToken cancellationToken) + { + return await innerStore.FindByIdAsync(userId, cancellationToken); + } + + public async Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken) + { + return await innerStore.FindByEmailAsync(normalizedEmail, cancellationToken); + } + + public async Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken) + { + return await innerStore.FindByNameAsync(normalizedUserName, cancellationToken); + } + + public async Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) + { + return await innerStore.FindByLoginAsync(loginProvider, providerKey, cancellationToken); + } + + public async Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken) + { + return (await innerStore.GetUsersForClaimAsync(claim, cancellationToken)).OfType().ToList(); + } + + public async Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken) + { + return (await innerStore.GetUsersInRoleAsync(roleName, cancellationToken)).OfType().ToList(); + } + + public Task CreateAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.CreateAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task UpdateAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.UpdateAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task DeleteAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.DeleteAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task GetUserIdAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetUserIdAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task GetUserNameAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetUserNameAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetUserNameAsync(IUser user, string userName, CancellationToken cancellationToken) + { + return innerStore.SetUserNameAsync((WrappedIdentityUser)user, userName, cancellationToken); + } + + public Task GetNormalizedUserNameAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetNormalizedUserNameAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetNormalizedUserNameAsync(IUser user, string normalizedName, CancellationToken cancellationToken) + { + return innerStore.SetNormalizedUserNameAsync((WrappedIdentityUser)user, normalizedName, cancellationToken); + } + + public Task GetPasswordHashAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetPasswordHashAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetPasswordHashAsync(IUser user, string passwordHash, CancellationToken cancellationToken) + { + return innerStore.SetPasswordHashAsync((WrappedIdentityUser)user, passwordHash, cancellationToken); + } + + public Task AddToRoleAsync(IUser user, string roleName, CancellationToken cancellationToken) + { + return innerStore.AddToRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken); + } + + public Task RemoveFromRoleAsync(IUser user, string roleName, CancellationToken cancellationToken) + { + return innerStore.RemoveFromRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken); + } + + public Task> GetRolesAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetRolesAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task IsInRoleAsync(IUser user, string roleName, CancellationToken cancellationToken) + { + return innerStore.IsInRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken); + } + + public Task AddLoginAsync(IUser user, UserLoginInfo login, CancellationToken cancellationToken) + { + return innerStore.AddLoginAsync((WrappedIdentityUser)user, login, cancellationToken); + } + + public Task RemoveLoginAsync(IUser user, string loginProvider, string providerKey, CancellationToken cancellationToken) + { + return innerStore.RemoveLoginAsync((WrappedIdentityUser)user, loginProvider, providerKey, cancellationToken); + } + + public Task> GetLoginsAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetLoginsAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task GetSecurityStampAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetSecurityStampAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetSecurityStampAsync(IUser user, string stamp, CancellationToken cancellationToken) + { + return innerStore.SetSecurityStampAsync((WrappedIdentityUser)user, stamp, cancellationToken); + } + + public Task GetEmailAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetEmailAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetEmailAsync(IUser user, string email, CancellationToken cancellationToken) + { + return innerStore.SetEmailAsync((WrappedIdentityUser)user, email, cancellationToken); + } + + public Task GetEmailConfirmedAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetEmailConfirmedAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetEmailConfirmedAsync(IUser user, bool confirmed, CancellationToken cancellationToken) + { + return innerStore.SetEmailConfirmedAsync((WrappedIdentityUser)user, confirmed, cancellationToken); + } + + public Task GetNormalizedEmailAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetNormalizedEmailAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetNormalizedEmailAsync(IUser user, string normalizedEmail, CancellationToken cancellationToken) + { + return innerStore.SetNormalizedEmailAsync((WrappedIdentityUser)user, normalizedEmail, cancellationToken); + } + + public Task> GetClaimsAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetClaimsAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task AddClaimsAsync(IUser user, IEnumerable claims, CancellationToken cancellationToken) + { + return innerStore.AddClaimsAsync((WrappedIdentityUser)user, claims, cancellationToken); + } + + public Task ReplaceClaimAsync(IUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken) + { + return innerStore.ReplaceClaimAsync((WrappedIdentityUser)user, claim, newClaim, cancellationToken); + } + + public Task RemoveClaimsAsync(IUser user, IEnumerable claims, CancellationToken cancellationToken) + { + return innerStore.RemoveClaimsAsync((WrappedIdentityUser)user, claims, cancellationToken); + } + + public Task GetPhoneNumberAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetPhoneNumberAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetPhoneNumberAsync(IUser user, string phoneNumber, CancellationToken cancellationToken) + { + return innerStore.SetPhoneNumberAsync((WrappedIdentityUser)user, phoneNumber, cancellationToken); + } + + public Task GetPhoneNumberConfirmedAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetPhoneNumberConfirmedAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetPhoneNumberConfirmedAsync(IUser user, bool confirmed, CancellationToken cancellationToken) + { + return innerStore.SetPhoneNumberConfirmedAsync((WrappedIdentityUser)user, confirmed, cancellationToken); + } + + public Task GetTwoFactorEnabledAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetTwoFactorEnabledAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetTwoFactorEnabledAsync(IUser user, bool enabled, CancellationToken cancellationToken) + { + return innerStore.SetTwoFactorEnabledAsync((WrappedIdentityUser)user, enabled, cancellationToken); + } + + public Task GetLockoutEndDateAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetLockoutEndDateAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetLockoutEndDateAsync(IUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken) + { + return innerStore.SetLockoutEndDateAsync((WrappedIdentityUser)user, lockoutEnd, cancellationToken); + } + + public Task GetAccessFailedCountAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetAccessFailedCountAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task IncrementAccessFailedCountAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.IncrementAccessFailedCountAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task ResetAccessFailedCountAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.ResetAccessFailedCountAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task GetLockoutEnabledAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetLockoutEnabledAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetLockoutEnabledAsync(IUser user, bool enabled, CancellationToken cancellationToken) + { + return innerStore.SetLockoutEnabledAsync((WrappedIdentityUser)user, enabled, cancellationToken); + } + + public Task SetTokenAsync(IUser user, string loginProvider, string name, string value, CancellationToken cancellationToken) + { + return innerStore.SetTokenAsync((WrappedIdentityUser)user, loginProvider, name, value, cancellationToken); + } + + public Task RemoveTokenAsync(IUser user, string loginProvider, string name, CancellationToken cancellationToken) + { + return innerStore.RemoveTokenAsync((WrappedIdentityUser)user, loginProvider, name, cancellationToken); + } + + public Task GetTokenAsync(IUser user, string loginProvider, string name, CancellationToken cancellationToken) + { + return innerStore.GetTokenAsync((WrappedIdentityUser)user, loginProvider, name, cancellationToken); + } + + public Task HasPasswordAsync(IUser user, CancellationToken cancellationToken) + { + return Task.FromResult(!string.IsNullOrWhiteSpace(((WrappedIdentityUser)user).PasswordHash)); + } + } +} diff --git a/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj b/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj new file mode 100644 index 000000000..b6da5d210 --- /dev/null +++ b/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj @@ -0,0 +1,19 @@ + + + netstandard1.6 + + + full + True + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Squidex.Domain.Users.MongoDb/WrappedIdentityRole.cs b/src/Squidex.Domain.Users.MongoDb/WrappedIdentityRole.cs new file mode 100644 index 000000000..fee399a23 --- /dev/null +++ b/src/Squidex.Domain.Users.MongoDb/WrappedIdentityRole.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// WrappedIdentityRole.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Microsoft.AspNetCore.Identity.MongoDB; + +namespace Squidex.Domain.Users.MongoDb +{ + public sealed class WrappedIdentityRole : IdentityRole, IRole + { + } +} diff --git a/src/Squidex.Domain.Users.MongoDb/WrappedIdentityUser.cs b/src/Squidex.Domain.Users.MongoDb/WrappedIdentityUser.cs new file mode 100644 index 000000000..abbce010a --- /dev/null +++ b/src/Squidex.Domain.Users.MongoDb/WrappedIdentityUser.cs @@ -0,0 +1,46 @@ +// ========================================================================== +// WrappedIdentityUser.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Microsoft.AspNetCore.Identity.MongoDB; +using Squidex.Shared.Users; + +namespace Squidex.Domain.Users.MongoDb +{ + public sealed class WrappedIdentityUser : IdentityUser, IUser + { + public bool IsLocked + { + get { return LockoutEndDateUtc != null && LockoutEndDateUtc.Value > DateTime.UtcNow; } + } + + IReadOnlyList IUser.Claims + { + get { return Claims.Select(x => new Claim(x.Type, x.Value)).ToList(); } + } + + IReadOnlyList IUser.Logins + { + get { return Logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey, x.ProviderDisplayName)).ToList(); } + } + + public void UpdateEmail(string email) + { + Email = UserName = email; + } + + public void SetClaim(string type, string value) + { + Claims.RemoveAll(x => string.Equals(x.Type, type, StringComparison.OrdinalIgnoreCase)); + Claims.Add(new IdentityUserClaim { Type = type, Value = value }); + } + } +} diff --git a/src/Squidex.Domain.Users/AssetUserPictureStore.cs b/src/Squidex.Domain.Users/AssetUserPictureStore.cs new file mode 100644 index 000000000..00780a709 --- /dev/null +++ b/src/Squidex.Domain.Users/AssetUserPictureStore.cs @@ -0,0 +1,43 @@ +// ========================================================================== +// AssetUserPictureStore.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.IO; +using System.Threading.Tasks; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Assets; + +namespace Squidex.Domain.Users +{ + public sealed class AssetUserPictureStore : IUserPictureStore + { + private readonly IAssetStore assetStore; + + public AssetUserPictureStore(IAssetStore assetStore) + { + Guard.NotNull(assetStore, nameof(assetStore)); + + this.assetStore = assetStore; + } + + public Task UploadAsync(string userId, Stream stream) + { + return assetStore.UploadAsync(userId, 0, "picture", stream); + } + + public async Task DownloadAsync(string userId) + { + var memoryStream = new MemoryStream(); + + await assetStore.DownloadAsync(userId, 0, "picture", memoryStream); + + memoryStream.Position = 0; + + return memoryStream; + } + } +} diff --git a/src/Squidex.Domain.Users/IRole.cs b/src/Squidex.Domain.Users/IRole.cs new file mode 100644 index 000000000..9c3b55f09 --- /dev/null +++ b/src/Squidex.Domain.Users/IRole.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// IRole.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Domain.Users +{ + public interface IRole + { + string Name { get; } + } +} diff --git a/src/Squidex.Domain.Users/IRoleFactory.cs b/src/Squidex.Domain.Users/IRoleFactory.cs new file mode 100644 index 000000000..a9a03f3a5 --- /dev/null +++ b/src/Squidex.Domain.Users/IRoleFactory.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// IRoleFactory.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Domain.Users +{ + public interface IRoleFactory + { + IRole Create(string name); + } +} diff --git a/src/Squidex.Domain.Users/IUserFactory.cs b/src/Squidex.Domain.Users/IUserFactory.cs new file mode 100644 index 000000000..fef18423a --- /dev/null +++ b/src/Squidex.Domain.Users/IUserFactory.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// IUserFactory.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Shared.Users; + +namespace Squidex.Domain.Users +{ + public interface IUserFactory + { + IUser Create(string email); + } +} diff --git a/src/Squidex.Domain.Users/IUserPictureStore.cs b/src/Squidex.Domain.Users/IUserPictureStore.cs new file mode 100644 index 000000000..2f2abaac9 --- /dev/null +++ b/src/Squidex.Domain.Users/IUserPictureStore.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// IUserPictureStore.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.IO; +using System.Threading.Tasks; + +namespace Squidex.Domain.Users +{ + public interface IUserPictureStore + { + Task UploadAsync(string userId, Stream stream); + + Task DownloadAsync(string userId); + } +} diff --git a/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj b/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj new file mode 100644 index 000000000..2d3950684 --- /dev/null +++ b/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj @@ -0,0 +1,17 @@ + + + netstandard1.6 + + + full + True + + + + + + + + + + \ No newline at end of file diff --git a/src/Squidex.Domain.Users/UserExtensions.cs b/src/Squidex.Domain.Users/UserExtensions.cs new file mode 100644 index 000000000..1fe92e1f9 --- /dev/null +++ b/src/Squidex.Domain.Users/UserExtensions.cs @@ -0,0 +1,75 @@ +// ========================================================================== +// UserExtensions.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Linq; +using Squidex.Infrastructure; +using Squidex.Shared.Identity; +using Squidex.Shared.Users; + +// ReSharper disable InvertIf + +namespace Squidex.Domain.Users +{ + public static class UserExtensions + { + public static void UpdateDisplayName(this IUser user, string displayName) + { + user.SetClaim(SquidexClaimTypes.SquidexDisplayName, displayName); + } + + public static void SetPictureUrl(this IUser user, string pictureUrl) + { + user.SetClaim(SquidexClaimTypes.SquidexPictureUrl, pictureUrl); + } + + public static void SetPictureUrlToStore(this IUser user) + { + user.SetClaim(SquidexClaimTypes.SquidexPictureUrl, "store"); + } + + public static void SetPictureUrlFromGravatar(this IUser user, string email) + { + user.SetClaim(SquidexClaimTypes.SquidexPictureUrl, GravatarHelper.CreatePictureUrl(email)); + } + + public static bool IsPictureUrlStored(this IUser user) + { + return string.Equals(user.Claims.FirstOrDefault(x => x.Type == SquidexClaimTypes.SquidexPictureUrl)?.Value, "store", StringComparison.OrdinalIgnoreCase); + } + + public static string PictureUrl(this IUser user) + { + return user.Claims.FirstOrDefault(x => x.Type == SquidexClaimTypes.SquidexPictureUrl)?.Value; + } + + public static string DisplayName(this IUser user) + { + return user.Claims.FirstOrDefault(x => x.Type == SquidexClaimTypes.SquidexDisplayName)?.Value; + } + + public static string PictureNormalizedUrl(this IUser user) + { + var url = user.Claims.FirstOrDefault(x => x.Type == SquidexClaimTypes.SquidexPictureUrl)?.Value; + + if (!string.IsNullOrWhiteSpace(url) && Uri.IsWellFormedUriString(url, UriKind.Absolute) && url.Contains("gravatar")) + { + if (url.Contains("?")) + { + url += "&d=404"; + } + else + { + url += "?d=404"; + } + } + + return url; + } + } +} diff --git a/src/Squidex.Domain.Users/UserManagerExtensions.cs b/src/Squidex.Domain.Users/UserManagerExtensions.cs new file mode 100644 index 000000000..bc9032de1 --- /dev/null +++ b/src/Squidex.Domain.Users/UserManagerExtensions.cs @@ -0,0 +1,132 @@ +// ========================================================================== +// UserManagerExtensions.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Squidex.Infrastructure; +using Squidex.Shared.Users; + +// ReSharper disable ImplicitlyCapturedClosure +// ReSharper disable InvertIf +// ReSharper disable ReturnTypeCanBeEnumerable.Local + +namespace Squidex.Domain.Users +{ + public static class UserManagerExtensions + { + public static Task> QueryByEmailAsync(this UserManager userManager, string email = null, int take = 10, int skip = 0) + { + var users = QueryUsers(userManager, email).Skip(skip).Take(take).ToList(); + + return Task.FromResult>(users); + } + + public static Task CountByEmailAsync(this UserManager userManager, string email = null) + { + var count = QueryUsers(userManager, email).LongCount(); + + return Task.FromResult(count); + } + + private static IQueryable QueryUsers(UserManager userManager, string email = null) + { + var result = userManager.Users; + + if (!string.IsNullOrWhiteSpace(email)) + { + var upperEmail = email.ToUpperInvariant(); + + result = result.Where(x => x.NormalizedEmail.Contains(upperEmail)); + } + + return result; + } + + public static async Task CreateAsync(this UserManager userManager, IUserFactory factory, string email, string displayName, string password) + { + var user = factory.Create(email); + + user.UpdateDisplayName(displayName); + user.SetPictureUrlFromGravatar(email); + + await DoChecked(() => userManager.CreateAsync(user), "Cannot create user."); + + if (!string.IsNullOrWhiteSpace(password)) + { + await DoChecked(() => userManager.AddPasswordAsync(user, password), "Cannot create user."); + } + + return user; + } + + public static async Task UpdateAsync(this UserManager userManager, string id, string email, string displayName, string password) + { + var user = await userManager.FindByIdAsync(id); + + if (user == null) + { + throw new DomainObjectNotFoundException(id, typeof(IUser)); + } + + if (!string.IsNullOrWhiteSpace(email)) + { + user.UpdateEmail(email); + } + + if (!string.IsNullOrWhiteSpace(displayName)) + { + user.UpdateDisplayName(displayName); + } + + await DoChecked(() => userManager.UpdateAsync(user), "Cannot update user."); + + if (!string.IsNullOrWhiteSpace(password)) + { + await DoChecked(() => userManager.RemovePasswordAsync(user), "Cannot update user."); + await DoChecked(() => userManager.AddPasswordAsync(user, password), "Cannot update user."); + } + } + + public static async Task LockAsync(this UserManager userManager, string id) + { + var user = await userManager.FindByIdAsync(id); + + if (user == null) + { + throw new DomainObjectNotFoundException(id, typeof(IUser)); + } + + await DoChecked(() => userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddYears(100)), "Cannot lock user."); + } + + public static async Task UnlockAsync(this UserManager userManager, string id) + { + var user = await userManager.FindByIdAsync(id); + + if (user == null) + { + throw new DomainObjectNotFoundException(id, typeof(IUser)); + } + + await DoChecked(() => userManager.SetLockoutEndDateAsync(user, null), "Cannot unlock user."); + } + + private static async Task DoChecked(Func> action, string message) + { + var result = await action(); + + if (!result.Succeeded) + { + throw new ValidationException(message, result.Errors.Select(x => new ValidationError(x.Description)).ToArray()); + } + } + } +} diff --git a/src/Squidex.Infrastructure/Assets/ImageInfo.cs b/src/Squidex.Infrastructure/Assets/ImageInfo.cs index c4f01451f..85147ed58 100644 --- a/src/Squidex.Infrastructure/Assets/ImageInfo.cs +++ b/src/Squidex.Infrastructure/Assets/ImageInfo.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.Assets { public sealed class ImageInfo diff --git a/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult.cs b/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult.cs index cef2f5324..259b836df 100644 --- a/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult.cs +++ b/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.CQRS.Commands { public static class EntityCreatedResult diff --git a/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult_T.cs b/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult_T.cs index d704f1cbc..7b56d620c 100644 --- a/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult_T.cs +++ b/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult_T.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.CQRS.Commands { public sealed class EntityCreatedResult : EntitySavedResult diff --git a/src/Squidex.Infrastructure/CQRS/Commands/EntitySavedResult.cs b/src/Squidex.Infrastructure/CQRS/Commands/EntitySavedResult.cs index 2e7b280c7..deef50b7c 100644 --- a/src/Squidex.Infrastructure/CQRS/Commands/EntitySavedResult.cs +++ b/src/Squidex.Infrastructure/CQRS/Commands/EntitySavedResult.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.CQRS.Commands { public class EntitySavedResult diff --git a/src/Squidex.Infrastructure/CQRS/Commands/ICommand.cs b/src/Squidex.Infrastructure/CQRS/Commands/ICommand.cs index c87233bd4..fb3516b3b 100644 --- a/src/Squidex.Infrastructure/CQRS/Commands/ICommand.cs +++ b/src/Squidex.Infrastructure/CQRS/Commands/ICommand.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.CQRS.Commands { public interface ICommand diff --git a/src/Squidex.Infrastructure/CQRS/Events/CommonHeaders.cs b/src/Squidex.Infrastructure/CQRS/Events/CommonHeaders.cs index c561fc89e..617123666 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/CommonHeaders.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/CommonHeaders.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.CQRS.Events { public static class CommonHeaders diff --git a/src/Squidex.Infrastructure/CQRS/Events/EnvelopeHeaders.cs b/src/Squidex.Infrastructure/CQRS/Events/EnvelopeHeaders.cs index d00193b16..f95580f9d 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/EnvelopeHeaders.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/EnvelopeHeaders.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.CQRS.Events { public sealed class EnvelopeHeaders : PropertiesBag diff --git a/src/Squidex.Infrastructure/CQRS/Events/Envelope_1.cs b/src/Squidex.Infrastructure/CQRS/Events/Envelope_1.cs index 010d32c49..a6bddbeda 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/Envelope_1.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/Envelope_1.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.CQRS.Events { public class Envelope where TPayload : class diff --git a/src/Squidex.Infrastructure/CQRS/Events/IEvent.cs b/src/Squidex.Infrastructure/CQRS/Events/IEvent.cs index 863bfaf64..11915d798 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/IEvent.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/IEvent.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.CQRS.Events { public interface IEvent diff --git a/src/Squidex.Infrastructure/CQRS/Events/IEventConsumerInfo.cs b/src/Squidex.Infrastructure/CQRS/Events/IEventConsumerInfo.cs index ce89b7068..e05f9623a 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/IEventConsumerInfo.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/IEventConsumerInfo.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.CQRS.Events { public interface IEventConsumerInfo diff --git a/src/Squidex.Infrastructure/CQRS/Events/StoredEvent.cs b/src/Squidex.Infrastructure/CQRS/Events/StoredEvent.cs index 6890ee2f8..2029df12e 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/StoredEvent.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/StoredEvent.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.CQRS.Events { public sealed class StoredEvent diff --git a/src/Squidex.Infrastructure/Caching/IInvalidatingCache.cs b/src/Squidex.Infrastructure/Caching/IInvalidatingCache.cs index 0deefe866..4047b604a 100644 --- a/src/Squidex.Infrastructure/Caching/IInvalidatingCache.cs +++ b/src/Squidex.Infrastructure/Caching/IInvalidatingCache.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.Caching { public interface IInvalidatingCache diff --git a/src/Squidex.Infrastructure/IExternalSystem.cs b/src/Squidex.Infrastructure/IExternalSystem.cs index 4408e645c..8943f9de6 100644 --- a/src/Squidex.Infrastructure/IExternalSystem.cs +++ b/src/Squidex.Infrastructure/IExternalSystem.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure { public interface IExternalSystem diff --git a/src/Squidex.Infrastructure/Log/ILogAppender.cs b/src/Squidex.Infrastructure/Log/ILogAppender.cs index 596c6b8d6..99c2669ed 100644 --- a/src/Squidex.Infrastructure/Log/ILogAppender.cs +++ b/src/Squidex.Infrastructure/Log/ILogAppender.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.Log { public interface ILogAppender diff --git a/src/Squidex.Infrastructure/Log/ILogChannel.cs b/src/Squidex.Infrastructure/Log/ILogChannel.cs index 20ebee481..7824f10a9 100644 --- a/src/Squidex.Infrastructure/Log/ILogChannel.cs +++ b/src/Squidex.Infrastructure/Log/ILogChannel.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.Log { public interface ILogChannel diff --git a/src/Squidex.Infrastructure/Log/Internal/IConsole.cs b/src/Squidex.Infrastructure/Log/Internal/IConsole.cs index f6f4a9309..6186d4a60 100644 --- a/src/Squidex.Infrastructure/Log/Internal/IConsole.cs +++ b/src/Squidex.Infrastructure/Log/Internal/IConsole.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.Log.Internal { public interface IConsole diff --git a/src/Squidex.Infrastructure/Log/Internal/LogMessageEntry.cs b/src/Squidex.Infrastructure/Log/Internal/LogMessageEntry.cs index e106bb4c4..5c506116e 100644 --- a/src/Squidex.Infrastructure/Log/Internal/LogMessageEntry.cs +++ b/src/Squidex.Infrastructure/Log/Internal/LogMessageEntry.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.Log.Internal { public struct LogMessageEntry diff --git a/src/Squidex.Infrastructure/Log/SemanticLogLevel.cs b/src/Squidex.Infrastructure/Log/SemanticLogLevel.cs index aa724f5db..232811359 100644 --- a/src/Squidex.Infrastructure/Log/SemanticLogLevel.cs +++ b/src/Squidex.Infrastructure/Log/SemanticLogLevel.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.Log { public enum SemanticLogLevel diff --git a/src/Squidex.Infrastructure/Reflection/IPropertyAccessor.cs b/src/Squidex.Infrastructure/Reflection/IPropertyAccessor.cs index 26004bfd5..9efc49e13 100644 --- a/src/Squidex.Infrastructure/Reflection/IPropertyAccessor.cs +++ b/src/Squidex.Infrastructure/Reflection/IPropertyAccessor.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.Reflection { public interface IPropertyAccessor diff --git a/src/Squidex.Infrastructure/Security/OpenIdClaims.cs b/src/Squidex.Infrastructure/Security/OpenIdClaims.cs index 407af18e0..e01fb6d88 100644 --- a/src/Squidex.Infrastructure/Security/OpenIdClaims.cs +++ b/src/Squidex.Infrastructure/Security/OpenIdClaims.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Infrastructure.Security { public static class OpenIdClaims diff --git a/src/Squidex.Shared/Identity/SquidexClaimTypes.cs b/src/Squidex.Shared/Identity/SquidexClaimTypes.cs new file mode 100644 index 000000000..9a26f7ec4 --- /dev/null +++ b/src/Squidex.Shared/Identity/SquidexClaimTypes.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// SquidexClaimTypes.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Shared.Identity +{ + public static class SquidexClaimTypes + { + public static readonly string SquidexDisplayName = "urn:squidex:name"; + + public static readonly string SquidexPictureUrl = "urn:squidex:picture"; + + public static readonly string Prefix = "urn:squidex:"; + } +} diff --git a/src/Squidex.Shared/Identity/SquidexRoles.cs b/src/Squidex.Shared/Identity/SquidexRoles.cs new file mode 100644 index 000000000..f9ed773d8 --- /dev/null +++ b/src/Squidex.Shared/Identity/SquidexRoles.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// SquidexRoles.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Shared.Identity +{ + public static class SquidexRoles + { + public static readonly string Administrator = "ADMINISTRATOR"; + + public static readonly string AppOwner = "APP-OWNER"; + + public static readonly string AppEditor = "APP-EDITOR"; + + public static readonly string AppDeveloper = "APP-DEVELOPER"; + } +} diff --git a/src/Squidex.Shared/Squidex.Shared.csproj b/src/Squidex.Shared/Squidex.Shared.csproj new file mode 100644 index 000000000..c8bca6ac3 --- /dev/null +++ b/src/Squidex.Shared/Squidex.Shared.csproj @@ -0,0 +1,12 @@ + + + netstandard1.6 + + + full + True + + + + + \ No newline at end of file diff --git a/src/Squidex.Shared/Users/ExternalLogin.cs b/src/Squidex.Shared/Users/ExternalLogin.cs new file mode 100644 index 000000000..4f9c41b30 --- /dev/null +++ b/src/Squidex.Shared/Users/ExternalLogin.cs @@ -0,0 +1,32 @@ +// ========================================================================== +// ExternalLogin.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Shared.Users +{ + public sealed class ExternalLogin + { + public string LoginProvider { get; } + + public string ProviderKey { get; } + + public string ProviderDisplayName { get; } + + public ExternalLogin(string loginProvider, string providerKey, string providerDisplayName) + { + LoginProvider = loginProvider; + + ProviderKey = providerKey; + ProviderDisplayName = providerDisplayName; + + if (string.IsNullOrWhiteSpace(ProviderDisplayName)) + { + ProviderDisplayName = loginProvider; + } + } + } +} diff --git a/src/Squidex.Shared/Users/IUser.cs b/src/Squidex.Shared/Users/IUser.cs new file mode 100644 index 000000000..a283a23f0 --- /dev/null +++ b/src/Squidex.Shared/Users/IUser.cs @@ -0,0 +1,34 @@ +// ========================================================================== +// IUser.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Security.Claims; + +namespace Squidex.Shared.Users +{ + public interface IUser + { + bool IsLocked { get; } + + string Id { get; } + + string Email { get; } + + string NormalizedEmail { get; } + + IReadOnlyList Claims { get; } + + IReadOnlyList Logins { get; } + + void UpdateEmail(string email); + + void AddClaim(Claim claim); + + void SetClaim(string type, string value); + } +} diff --git a/src/Squidex.Shared/Users/IUserResolver.cs b/src/Squidex.Shared/Users/IUserResolver.cs new file mode 100644 index 000000000..20693a42c --- /dev/null +++ b/src/Squidex.Shared/Users/IUserResolver.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// IUserResolver.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; + +namespace Squidex.Shared.Users +{ + public interface IUserResolver + { + Task FindByIdAsync(string id); + } +} diff --git a/src/Squidex/Config/Constants.cs b/src/Squidex/Config/Constants.cs index 70b040482..f1a9f9385 100644 --- a/src/Squidex/Config/Constants.cs +++ b/src/Squidex/Config/Constants.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Config { public static class Constants diff --git a/src/Squidex/Config/Identity/MyIdentityOptions.cs b/src/Squidex/Config/Identity/MyIdentityOptions.cs index 0b5b4d16f..10d0fc99a 100644 --- a/src/Squidex/Config/Identity/MyIdentityOptions.cs +++ b/src/Squidex/Config/Identity/MyIdentityOptions.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Config.Identity { public sealed class MyIdentityOptions diff --git a/src/Squidex/Controllers/Api/Assets/Models/AssetsDto.cs b/src/Squidex/Controllers/Api/Assets/Models/AssetsDto.cs index 49157dc3f..0811b3f58 100644 --- a/src/Squidex/Controllers/Api/Assets/Models/AssetsDto.cs +++ b/src/Squidex/Controllers/Api/Assets/Models/AssetsDto.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Controllers.Api.Assets.Models { public sealed class AssetsDto diff --git a/src/Squidex/Controllers/Api/EventConsumers/Models/EventConsumerDto.cs b/src/Squidex/Controllers/Api/EventConsumers/Models/EventConsumerDto.cs index 13c7d3fbf..7fd83332f 100644 --- a/src/Squidex/Controllers/Api/EventConsumers/Models/EventConsumerDto.cs +++ b/src/Squidex/Controllers/Api/EventConsumers/Models/EventConsumerDto.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Controllers.Api.EventConsumers.Models { public sealed class EventConsumerDto diff --git a/src/Squidex/Controllers/Api/Plans/Models/PlanDto.cs b/src/Squidex/Controllers/Api/Plans/Models/PlanDto.cs index 9653a7b58..30c1191f9 100644 --- a/src/Squidex/Controllers/Api/Plans/Models/PlanDto.cs +++ b/src/Squidex/Controllers/Api/Plans/Models/PlanDto.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Controllers.Api.Plans.Models { public class PlanDto diff --git a/src/Squidex/Controllers/Api/Statistics/Models/CurrentCallsDto.cs b/src/Squidex/Controllers/Api/Statistics/Models/CurrentCallsDto.cs index 1a80a83aa..7082bf5d8 100644 --- a/src/Squidex/Controllers/Api/Statistics/Models/CurrentCallsDto.cs +++ b/src/Squidex/Controllers/Api/Statistics/Models/CurrentCallsDto.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Controllers.Api.Statistics.Models { public class CurrentCallsDto diff --git a/src/Squidex/Controllers/Api/Statistics/Models/CurrentStorageDto.cs b/src/Squidex/Controllers/Api/Statistics/Models/CurrentStorageDto.cs index 1bf5a3f87..85f5e91d9 100644 --- a/src/Squidex/Controllers/Api/Statistics/Models/CurrentStorageDto.cs +++ b/src/Squidex/Controllers/Api/Statistics/Models/CurrentStorageDto.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Controllers.Api.Statistics.Models { public class CurrentStorageDto diff --git a/src/Squidex/Controllers/Api/Users/Models/UsersDto.cs b/src/Squidex/Controllers/Api/Users/Models/UsersDto.cs index 803d2179e..fc0ca93fd 100644 --- a/src/Squidex/Controllers/Api/Users/Models/UsersDto.cs +++ b/src/Squidex/Controllers/Api/Users/Models/UsersDto.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Controllers.Api.Users.Models { public class UsersDto diff --git a/src/Squidex/Controllers/ContentApi/Models/ContentsDto.cs b/src/Squidex/Controllers/ContentApi/Models/ContentsDto.cs index 07349ac5f..d1d594ca3 100644 --- a/src/Squidex/Controllers/ContentApi/Models/ContentsDto.cs +++ b/src/Squidex/Controllers/ContentApi/Models/ContentsDto.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Controllers.ContentApi.Models { public class AssetsDto diff --git a/src/Squidex/Controllers/UI/ExternalProvider.cs b/src/Squidex/Controllers/UI/ExternalProvider.cs index 125534e0e..26574c5b1 100644 --- a/src/Squidex/Controllers/UI/ExternalProvider.cs +++ b/src/Squidex/Controllers/UI/ExternalProvider.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Controllers.UI { public class ExternalProvider diff --git a/src/Squidex/Pipeline/IAppTrackingWeightFeature.cs b/src/Squidex/Pipeline/IAppTrackingWeightFeature.cs index 595f1adbe..56274c735 100644 --- a/src/Squidex/Pipeline/IAppTrackingWeightFeature.cs +++ b/src/Squidex/Pipeline/IAppTrackingWeightFeature.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Pipeline { public interface IAppTrackingWeightFeature