From a1d57f6f49f66895002099ab2b209868d558ae37 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Mon, 13 Nov 2017 20:55:04 +0100 Subject: [PATCH] App State fixes --- .../Apps/MongoAppEntity.cs | 53 ----- .../Apps/MongoAppRepository.cs | 70 ------- .../Apps/MongoAppRepository_EventHandling.cs | 143 ------------- .../Contents/MongoContentRepository.cs | 15 +- .../MongoContentRepository_EventHandling.cs | 4 +- .../Schemas/MongoSchemaEntity.cs | 74 ------- .../Schemas/MongoSchemaRepository.cs | 73 ------- .../MongoSchemaRepository_EventHandling.cs | 188 ------------------ .../Apps/IAppEntity.cs | 2 + .../Apps/Repositories/IAppRepository.cs | 23 --- .../Apps/Services/IAppProvider.cs | 20 -- .../Implementations/CachingAppProvider.cs | 121 ----------- .../Contents/ContentQueryService.cs | 13 +- .../Contents/GraphQL/CachingGraphQLService.cs | 49 +---- src/Squidex.Domain.Apps.Read/EntityMapper.cs | 6 +- .../{IAppState.cs => IAppProvider.cs} | 16 +- .../Schemas/ISchemaEventConsumer.cs | 16 -- .../Schemas/Repositories/ISchemaRepository.cs | 23 --- .../Schemas/Services/ISchemaProvider.cs | 20 -- .../Implementations/CachingSchemaProvider.cs | 131 ------------ .../{ => Orleans}/AppStateEventConsumer.cs | 10 +- .../State/Orleans/Grains/IAppStateGrain.cs | 18 +- .../State/Orleans/Grains/IAppUserGrain.cs | 7 +- .../Grains/Implementations/AppStateGrain.cs | 59 ++++-- .../Implementations/AppStateGrainState.cs | 45 ++++- .../Grains/Implementations/AppUserGrain.cs | 20 +- .../AppUserGrainState.cs} | 14 +- .../Grains/Implementations/JsonAppEntity.cs | 3 + .../Implementations/JsonSchemaEntity.cs | 1 + .../State/Orleans/OrleansAppProvider.cs | 89 +++++++++ .../State/Orleans/OrleansAppState.cs | 71 ------- .../Apps/AppCommandMiddleware.cs | 1 + .../Apps/Guards/GuardApp.cs | 5 +- .../Contents/ContentCommandMiddleware.cs | 8 +- .../Contents/ContentOperationContext.cs | 13 +- .../Rules/Guards/GuardRule.cs | 10 +- .../Rules/Guards/RuleTriggerValidator.cs | 18 +- .../Rules/RuleCommandMiddleware.cs | 15 +- .../Schemas/Guards/GuardSchema.cs | 6 +- .../Schemas/SchemaCommandMiddleware.cs | 13 +- .../Orleans/Grains/IEventConsumerGrain.cs | 3 +- .../Grains/IEventConsumerRegistryGrain.cs | 3 +- .../Implementation/EventConsumerGrain.cs | 5 +- .../EventConsumerRegistryGrain.cs | 5 +- .../Json/Orleans/IJsonValue.cs} | 9 +- src/Squidex.Infrastructure/Json/Orleans/J.cs | 49 +++++ .../Json/Orleans/JsonExternalSerializer.cs | 78 ++++++++ .../Json/Orleans/JsonState.cs | 73 +++++++ .../Reflection/ReflectionExtensions.cs | 5 + .../Reflection/SimpleCopier.cs | 85 ++++++++ .../Reflection/SimpleMapper.cs | 114 +++++------ src/Squidex/Config/Domain/ReadServices.cs | 27 +-- src/Squidex/Config/Domain/StoreServices.cs | 21 +- .../Config/Identity/LazyClientStore.cs | 12 +- src/Squidex/Config/Orleans/ClientServices.cs | 12 +- .../Config/Orleans/CustomJsonSerializer.cs | 22 ++ .../Orleans/CustomMongoDbStorageProvider.cs | 23 +++ src/Squidex/Config/Orleans/SiloExtensions.cs | 9 + src/Squidex/Config/Orleans/SiloServices.cs | 14 +- .../Controllers/Api/Apps/AppsController.cs | 10 +- .../Api/Assets/AssetsController.cs | 4 +- .../EventConsumersController.cs | 2 +- .../Api/Schemas/SchemasController.cs | 14 +- .../ContentApi/ContentSwaggerController.cs | 11 +- src/Squidex/Controllers/ControllerBase.cs | 7 +- src/Squidex/Pipeline/AppApiFilter.cs | 8 +- .../EnrichWithSchemaIdCommandMiddleware.cs | 14 +- src/Squidex/Program.cs | 2 +- .../Apps/CachingAppProviderTests.cs | 131 ------------ .../Contents/GraphQLTests.cs | 3 +- .../Contents/ODataQueryTests.cs | 2 +- .../Apps/AppCommandMiddlewareTests.cs | 3 +- .../Apps/Guards/GuardAppTests.cs | 5 +- .../Contents/ContentCommandMiddlewareTests.cs | 5 +- 74 files changed, 762 insertions(+), 1514 deletions(-) delete mode 100644 src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs delete mode 100644 src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs delete mode 100644 src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs delete mode 100644 src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaEntity.cs delete mode 100644 src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs delete mode 100644 src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Apps/Repositories/IAppRepository.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Apps/Services/IAppProvider.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs rename src/Squidex.Domain.Apps.Read/{IAppState.cs => IAppProvider.cs} (54%) delete mode 100644 src/Squidex.Domain.Apps.Read/Schemas/ISchemaEventConsumer.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Schemas/Repositories/ISchemaRepository.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Schemas/Services/ISchemaProvider.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs rename src/Squidex.Domain.Apps.Read/State/{ => Orleans}/AppStateEventConsumer.cs (86%) rename src/Squidex.Domain.Apps.Read/State/Orleans/Grains/{EventMessage.cs => Implementations/AppUserGrainState.cs} (53%) create mode 100644 src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppProvider.cs delete mode 100644 src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppState.cs rename src/{Squidex.Domain.Apps.Read/Apps/IAppEventConsumer.cs => Squidex.Infrastructure/Json/Orleans/IJsonValue.cs} (67%) create mode 100644 src/Squidex.Infrastructure/Json/Orleans/J.cs create mode 100644 src/Squidex.Infrastructure/Json/Orleans/JsonExternalSerializer.cs create mode 100644 src/Squidex.Infrastructure/Json/Orleans/JsonState.cs create mode 100644 src/Squidex.Infrastructure/Reflection/SimpleCopier.cs create mode 100644 src/Squidex/Config/Orleans/CustomJsonSerializer.cs create mode 100644 src/Squidex/Config/Orleans/CustomMongoDbStorageProvider.cs delete mode 100644 tests/Squidex.Domain.Apps.Read.Tests/Apps/CachingAppProviderTests.cs diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs deleted file mode 100644 index 799321285..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs +++ /dev/null @@ -1,53 +0,0 @@ -// ========================================================================== -// MongoAppEntity.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using MongoDB.Bson.Serialization.Attributes; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Infrastructure.MongoDb; - -namespace Squidex.Domain.Apps.Read.MongoDb.Apps -{ - public sealed class MongoAppEntity : MongoEntity, IAppEntity - { - [BsonRequired] - [BsonElement] - public string Name { get; set; } - - [BsonRequired] - [BsonElement] - public long Version { get; set; } - - [BsonIgnoreIfDefault] - [BsonElement] - public string PlanId { get; set; } - - [BsonIgnoreIfDefault] - [BsonElement] - public string PlanOwner { get; set; } - - [BsonIgnoreIfDefault] - [BsonElement] - public string[] ContributorIds { get; set; } - - [BsonRequired] - [BsonElement] - [BsonJson] - public AppClients Clients { get; set; } - - [BsonRequired] - [BsonElement] - [BsonJson] - public AppContributors Contributors { get; set; } - - [BsonRequired] - [BsonElement] - [BsonJson] - public LanguagesConfig LanguagesConfig { get; set; } - } -} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs deleted file mode 100644 index f55b7e494..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs +++ /dev/null @@ -1,70 +0,0 @@ -// ========================================================================== -// MongoAppRepository.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 MongoDB.Driver; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Apps.Repositories; -using Squidex.Infrastructure.MongoDb; - -namespace Squidex.Domain.Apps.Read.MongoDb.Apps -{ - public partial class MongoAppRepository : MongoRepositoryBase, IAppRepository, IAppEventConsumer - { - public MongoAppRepository(IMongoDatabase database) - : base(database) - { - } - - protected override string CollectionName() - { - return "Projections_Apps"; - } - - protected override async Task SetupCollectionAsync(IMongoCollection collection) - { - await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name)); - await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ContributorIds)); - } - - protected override MongoCollectionSettings CollectionSettings() - { - return new MongoCollectionSettings { WriteConcern = WriteConcern.WMajority }; - } - - public async Task> QueryAllAsync(string subjectId) - { - var appEntities = - await Collection.Find(s => s.ContributorIds.Contains(subjectId)) - .ToListAsync(); - - return appEntities; - } - - public async Task FindAppAsync(Guid id) - { - var appEntity = - await Collection.Find(s => s.Id == id) - .FirstOrDefaultAsync(); - - return appEntity; - } - - public async Task FindAppAsync(string name) - { - var appEntity = - await Collection.Find(s => s.Name == name) - .FirstOrDefaultAsync(); - - return appEntity; - } - } -} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs deleted file mode 100644 index 51c627fec..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs +++ /dev/null @@ -1,143 +0,0 @@ -// ========================================================================== -// MongoAppRepository_EventHandling.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Linq; -using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Apps; -using Squidex.Domain.Apps.Events.Apps.Utils; -using Squidex.Infrastructure; -using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.Dispatching; -using Squidex.Infrastructure.Reflection; - -namespace Squidex.Domain.Apps.Read.MongoDb.Apps -{ - public partial class MongoAppRepository - { - public string Name - { - get { return GetType().Name; } - } - - public string EventsFilter - { - get { return "^app-"; } - } - - public Task On(Envelope @event) - { - return this.DispatchActionAsync(@event.Payload, @event.Headers); - } - - protected Task On(AppCreated @event, EnvelopeHeaders headers) - { - return Collection.CreateAsync(@event, headers, a => - { - SimpleMapper.Map(@event, a); - - a.Clients = new AppClients(); - a.Contributors = new AppContributors(); - - a.LanguagesConfig = LanguagesConfig.Build(Language.EN); - }); - } - - protected Task On(AppPlanChanged @event, EnvelopeHeaders headers) - { - return UpdateAppAsync(@event, headers, a => - { - SimpleMapper.Map(@event, a); - }); - } - - protected Task On(AppClientAttached @event, EnvelopeHeaders headers) - { - return UpdateAppAsync(@event, headers, a => - { - a.Clients.Apply(@event); - }); - } - - protected Task On(AppClientRevoked @event, EnvelopeHeaders headers) - { - return UpdateAppAsync(@event, headers, a => - { - a.Clients.Apply(@event); - }); - } - - protected Task On(AppClientRenamed @event, EnvelopeHeaders headers) - { - return UpdateAppAsync(@event, headers, a => - { - a.Clients.Apply(@event); - }); - } - - protected Task On(AppClientUpdated @event, EnvelopeHeaders headers) - { - return UpdateAppAsync(@event, headers, a => - { - a.Clients.Apply(@event); - }); - } - - protected Task On(AppContributorRemoved @event, EnvelopeHeaders headers) - { - return UpdateAppAsync(@event, headers, a => - { - a.Contributors.Apply(@event); - }); - } - - protected Task On(AppContributorAssigned @event, EnvelopeHeaders headers) - { - return UpdateAppAsync(@event, headers, a => - { - a.Contributors.Apply(@event); - }); - } - - protected Task On(AppLanguageAdded @event, EnvelopeHeaders headers) - { - return UpdateAppAsync(@event, headers, a => - { - a.LanguagesConfig.Apply(@event); - }); - } - - protected Task On(AppLanguageRemoved @event, EnvelopeHeaders headers) - { - return UpdateAppAsync(@event, headers, a => - { - a.LanguagesConfig.Apply(@event); - }); - } - - protected Task On(AppLanguageUpdated @event, EnvelopeHeaders headers) - { - return UpdateAppAsync(@event, headers, a => - { - a.LanguagesConfig.Apply(@event); - }); - } - - private async Task UpdateAppAsync(AppEvent @event, EnvelopeHeaders headers, Action updater) - { - await Collection.UpdateAsync(@event, headers, a => - { - updater(a); - - a.ContributorIds = a.Contributors.Keys.ToArray(); - }); - } - } -} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs index 5ca896110..b0d763553 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs @@ -19,7 +19,6 @@ using Squidex.Domain.Apps.Read.Contents; using Squidex.Domain.Apps.Read.Contents.Repositories; using Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors; using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.Schemas.Services; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; @@ -29,7 +28,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents { private const string Prefix = "Projections_Content_"; private readonly IMongoDatabase database; - private readonly ISchemaProvider schemas; + private readonly IAppProvider appProvider; protected static FilterDefinitionBuilder Filter { @@ -63,13 +62,13 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents } } - public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemas) + public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider) { Guard.NotNull(database, nameof(database)); - Guard.NotNull(schemas, nameof(schemas)); + Guard.NotNull(appProvider, nameof(appProvider)); this.database = database; - this.schemas = schemas; + this.appProvider = appProvider; } public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery) @@ -177,11 +176,11 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents return contentEntity; } - private async Task ForSchemaAsync(Guid appId, Guid schemaId, Func, ISchemaEntity, Task> action) + private async Task ForSchemaAsync(NamedId appId, Guid schemaId, Func, ISchemaEntity, Task> action) { - var collection = GetCollection(appId); + var collection = GetCollection(appId.Id); - var schema = await schemas.FindSchemaByIdAsync(schemaId, true); + var schema = await appProvider.GetSchemaAsync(appId.Name, schemaId, true); if (schema == null) { diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs index 077fa6cd1..fabf77afc 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents protected Task On(ContentCreated @event, EnvelopeHeaders headers) { - return ForSchemaAsync(@event.AppId.Id, @event.SchemaId.Id, (collection, schema) => + return ForSchemaAsync(@event.AppId, @event.SchemaId.Id, (collection, schema) => { return collection.CreateAsync(@event, headers, content => { @@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents protected Task On(ContentUpdated @event, EnvelopeHeaders headers) { - return ForSchemaAsync(@event.AppId.Id, @event.SchemaId.Id, (collection, schema) => + return ForSchemaAsync(@event.AppId, @event.SchemaId.Id, (collection, schema) => { var idData = @event.Data?.ToIdModel(schema.SchemaDef, true); diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaEntity.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaEntity.cs deleted file mode 100644 index 7efc0059b..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaEntity.cs +++ /dev/null @@ -1,74 +0,0 @@ -// ========================================================================== -// MongoSchemaEntity.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; - -namespace Squidex.Domain.Apps.Read.MongoDb.Schemas -{ - public sealed class MongoSchemaEntity : MongoEntity, ISchemaEntity - { - [BsonRequired] - [BsonElement] - public string Name { get; set; } - - [BsonRequired] - [BsonElement] - public long Version { get; set; } - - [BsonRequired] - [BsonElement] - public Guid AppId { get; set; } - - [BsonRequired] - [BsonElement] - public RefToken CreatedBy { get; set; } - - [BsonRequired] - [BsonElement] - public RefToken LastModifiedBy { get; set; } - - [BsonRequired] - [BsonElement] - public bool IsPublished { get; set; } - - [BsonRequired] - [BsonElement] - public bool IsDeleted { get; set; } - - [BsonIgnoreIfNull] - [BsonElement] - public string ScriptQuery { get; set; } - - [BsonIgnoreIfNull] - [BsonElement] - public string ScriptCreate { get; set; } - - [BsonIgnoreIfNull] - [BsonElement] - public string ScriptUpdate { get; set; } - - [BsonIgnoreIfNull] - [BsonElement] - public string ScriptDelete { get; set; } - - [BsonIgnoreIfNull] - [BsonElement] - public string ScriptChange { get; set; } - - [BsonRequired] - [BsonElement] - [BsonJson] - public Schema SchemaDef { get; set; } - } -} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs deleted file mode 100644 index f17434021..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs +++ /dev/null @@ -1,73 +0,0 @@ -// ========================================================================== -// MongoSchemaRepository.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 MongoDB.Driver; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.Schemas.Repositories; -using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; - -namespace Squidex.Domain.Apps.Read.MongoDb.Schemas -{ - public partial class MongoSchemaRepository : MongoRepositoryBase, ISchemaRepository, ISchemaEventConsumer - { - private readonly FieldRegistry registry; - - public MongoSchemaRepository(IMongoDatabase database, FieldRegistry registry) - : base(database) - { - Guard.NotNull(registry, nameof(registry)); - - this.registry = registry; - } - - protected override string CollectionName() - { - return "Projections_Schemas"; - } - - protected override Task SetupCollectionAsync(IMongoCollection collection) - { - return Task.WhenAll( - collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name)), - collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Ascending(x => x.IsDeleted).Ascending(x => x.Name))); - } - - public async Task> QueryAllAsync(Guid appId) - { - var schemaEntities = - await Collection.Find(s => s.AppId == appId && !s.IsDeleted) - .ToListAsync(); - - return schemaEntities.OfType().ToList(); - } - - public async Task FindSchemaAsync(Guid appId, string name) - { - var schemaEntity = - await Collection.Find(s => s.AppId == appId && !s.IsDeleted && s.Name == name) - .FirstOrDefaultAsync(); - - return schemaEntity; - } - - public async Task FindSchemaAsync(Guid schemaId) - { - var schemaEntity = - await Collection.Find(s => s.Id == schemaId) - .FirstOrDefaultAsync(); - - return schemaEntity; - } - } -} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs deleted file mode 100644 index 772620b38..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs +++ /dev/null @@ -1,188 +0,0 @@ -// ========================================================================== -// MongoSchemaRepository_EventHandling.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Schemas; -using Squidex.Domain.Apps.Events.Schemas.Old; -using Squidex.Domain.Apps.Events.Schemas.Utils; -using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.Dispatching; -using Squidex.Infrastructure.Reflection; - -#pragma warning disable CS0612 // Type or member is obsolete - -namespace Squidex.Domain.Apps.Read.MongoDb.Schemas -{ - public partial class MongoSchemaRepository - { - public string Name - { - get { return GetType().Name; } - } - - public string EventsFilter - { - get { return "^schema-"; } - } - - public Task On(Envelope @event) - { - return this.DispatchActionAsync(@event.Payload, @event.Headers); - } - - protected Task On(SchemaCreated @event, EnvelopeHeaders headers) - { - return Collection.CreateAsync(@event, headers, s => - { - s.SchemaDef = SchemaEventDispatcher.Create(@event, registry); - - SimpleMapper.Map(@event, s); - }); - } - - protected Task On(FieldDeleted @event, EnvelopeHeaders headers) - { - return UpdateSchemaAsync(@event, headers, s => - { - s.SchemaDef.Apply(@event); - }); - } - - protected Task On(FieldLocked @event, EnvelopeHeaders headers) - { - return UpdateSchemaAsync(@event, headers, s => - { - s.SchemaDef.Apply(@event); - }); - } - - protected Task On(FieldHidden @event, EnvelopeHeaders headers) - { - return UpdateSchemaAsync(@event, headers, s => - { - s.SchemaDef.Apply(@event); - }); - } - - protected Task On(FieldShown @event, EnvelopeHeaders headers) - { - return UpdateSchemaAsync(@event, headers, s => - { - s.SchemaDef.Apply(@event); - }); - } - - protected Task On(FieldDisabled @event, EnvelopeHeaders headers) - { - return UpdateSchemaAsync(@event, headers, s => - { - s.SchemaDef.Apply(@event); - }); - } - - protected Task On(FieldEnabled @event, EnvelopeHeaders headers) - { - return UpdateSchemaAsync(@event, headers, s => - { - s.SchemaDef.Apply(@event); - }); - } - - protected Task On(FieldUpdated @event, EnvelopeHeaders headers) - { - return UpdateSchemaAsync(@event, headers, s => - { - s.SchemaDef.Apply(@event); - }); - } - - protected Task On(SchemaFieldsReordered @event, EnvelopeHeaders headers) - { - return UpdateSchemaAsync(@event, headers, s => - { - s.SchemaDef.Apply(@event); - }); - } - - protected Task On(SchemaUpdated @event, EnvelopeHeaders headers) - { - return UpdateSchemaAsync(@event, headers, s => - { - s.SchemaDef.Apply(@event); - }); - } - - protected Task On(SchemaPublished @event, EnvelopeHeaders headers) - { - return UpdateSchemaAsync(@event, headers, s => - { - s.SchemaDef.Apply(@event); - }); - } - - protected Task On(SchemaUnpublished @event, EnvelopeHeaders headers) - { - return UpdateSchemaAsync(@event, headers, s => - { - s.SchemaDef.Apply(@event); - }); - } - - protected Task On(FieldAdded @event, EnvelopeHeaders headers) - { - return UpdateSchemaAsync(@event, headers, s => - { - s.SchemaDef.Apply(@event, registry); - }); - } - - protected Task On(ScriptsConfigured @event, EnvelopeHeaders headers) - { - return Collection.UpdateAsync(@event, headers, s => - { - SimpleMapper.Map(@event, s); - }); - } - - protected Task On(SchemaDeleted @event, EnvelopeHeaders headers) - { - return Collection.UpdateAsync(@event, headers, s => - { - s.IsDeleted = true; - }); - } - - protected Task On(WebhookAdded @event, EnvelopeHeaders headers) - { - return Collection.UpdateAsync(@event, headers, s => - { - /* NOOP */ - }); - } - - protected Task On(WebhookDeleted @event, EnvelopeHeaders headers) - { - return Collection.UpdateAsync(@event, headers, s => - { - /* NOOP */ - }); - } - - private Task UpdateSchemaAsync(SquidexEvent @event, EnvelopeHeaders headers, Action updater) - { - return Collection.UpdateAsync(@event, headers, s => - { - updater(s); - - s.IsPublished = s.SchemaDef.IsPublished; - }); - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs b/src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs index f4cb66f7c..df1743d69 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs @@ -12,6 +12,8 @@ namespace Squidex.Domain.Apps.Read.Apps { public interface IAppEntity : IEntity, IEntityWithVersion { + string Etag { get; } + string Name { get; } string PlanId { get; } diff --git a/src/Squidex.Domain.Apps.Read/Apps/Repositories/IAppRepository.cs b/src/Squidex.Domain.Apps.Read/Apps/Repositories/IAppRepository.cs deleted file mode 100644 index 67fef0148..000000000 --- a/src/Squidex.Domain.Apps.Read/Apps/Repositories/IAppRepository.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ========================================================================== -// IAppRepository.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Squidex.Domain.Apps.Read.Apps.Repositories -{ - public interface IAppRepository - { - Task> QueryAllAsync(string subjectId); - - Task FindAppAsync(Guid appId); - - Task FindAppAsync(string name); - } -} diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/IAppProvider.cs b/src/Squidex.Domain.Apps.Read/Apps/Services/IAppProvider.cs deleted file mode 100644 index 632dd46e4..000000000 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/IAppProvider.cs +++ /dev/null @@ -1,20 +0,0 @@ -// ========================================================================== -// IAppProvider.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Threading.Tasks; - -namespace Squidex.Domain.Apps.Read.Apps.Services -{ - public interface IAppProvider - { - Task FindAppByIdAsync(Guid id); - - Task FindAppByNameAsync(string name); - } -} diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs b/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs deleted file mode 100644 index becf14af4..000000000 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs +++ /dev/null @@ -1,121 +0,0 @@ -// ========================================================================== -// CachingAppProvider.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Memory; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Read.Apps.Repositories; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; -using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.Tasks; - -namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations -{ - public class CachingAppProvider : CachingProviderBase, IAppProvider, IEventConsumer - { - private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(30); - private readonly IAppRepository repository; - - public string Name - { - get { return GetType().Name; } - } - - public string EventsFilter - { - get { return string.Empty; } - } - - public CachingAppProvider(IMemoryCache cache, IAppRepository repository) - : base(cache) - { - Guard.NotNull(cache, nameof(cache)); - - this.repository = repository; - } - - public async Task FindAppByIdAsync(Guid appId) - { - var cacheKey = BuildIdCacheKey(appId); - - if (!Cache.TryGetValue(cacheKey, out IAppEntity result)) - { - result = await repository.FindAppAsync(appId); - - Cache.Set(cacheKey, result, CacheDuration); - - if (result != null) - { - Cache.Set(BuildNameCacheKey(result.Name), result, CacheDuration); - } - } - - return result; - } - - public async Task FindAppByNameAsync(string name) - { - Guard.NotNullOrEmpty(name, nameof(name)); - - var cacheKey = BuildNameCacheKey(name); - - if (!Cache.TryGetValue(cacheKey, out IAppEntity result)) - { - result = await repository.FindAppAsync(name); - - Cache.Set(cacheKey, result, CacheDuration); - - if (result != null) - { - Cache.Set(BuildIdCacheKey(result.Id), result, CacheDuration); - } - } - - return result; - } - - public Task On(Envelope @event) - { - void Remove(NamedId id) - { - var cacheKeyById = BuildIdCacheKey(id.Id); - var cacheKeyByName = BuildNameCacheKey(id.Name); - - Cache.Remove(cacheKeyById); - Cache.Remove(cacheKeyByName); - - Cache.Invalidate(cacheKeyById); - Cache.Invalidate(cacheKeyByName); - } - - if (@event.Payload is AppEvent appEvent) - { - Remove(appEvent.AppId); - } - - return TaskHelper.Done; - } - - private static string BuildNameCacheKey(string name) - { - return $"App_Ids_{name}"; - } - - private static string BuildIdCacheKey(Guid schemaId) - { - return $"App_Names_{schemaId}"; - } - - public Task ClearAsync() - { - return TaskHelper.Done; - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs index f2a8263c3..46bb4848e 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs @@ -20,7 +20,6 @@ using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Contents.Edm; using Squidex.Domain.Apps.Read.Contents.Repositories; using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.Schemas.Services; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Security; @@ -30,23 +29,23 @@ namespace Squidex.Domain.Apps.Read.Contents public sealed class ContentQueryService : IContentQueryService { private readonly IContentRepository contentRepository; - private readonly ISchemaProvider schemas; + private readonly IAppProvider appProvider; private readonly IScriptEngine scriptEngine; private readonly EdmModelBuilder modelBuilder; public ContentQueryService( IContentRepository contentRepository, - ISchemaProvider schemas, + IAppProvider appProvider, IScriptEngine scriptEngine, EdmModelBuilder modelBuilder) { Guard.NotNull(contentRepository, nameof(contentRepository)); Guard.NotNull(scriptEngine, nameof(scriptEngine)); Guard.NotNull(modelBuilder, nameof(modelBuilder)); - Guard.NotNull(schemas, nameof(schemas)); + Guard.NotNull(appProvider, nameof(appProvider)); this.contentRepository = contentRepository; - this.schemas = schemas; + this.appProvider = appProvider; this.scriptEngine = scriptEngine; this.modelBuilder = modelBuilder; } @@ -156,12 +155,12 @@ namespace Squidex.Domain.Apps.Read.Contents if (Guid.TryParse(schemaIdOrName, out var id)) { - schema = await schemas.FindSchemaByIdAsync(id); + schema = await appProvider.GetSchemaAsync(app.Name, id); } if (schema == null) { - schema = await schemas.FindSchemaByNameAsync(app.Id, schemaIdOrName); + schema = await appProvider.GetSchemaAsync(app.Name, schemaIdOrName); } if (schema == null) diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs b/src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs index f20d7cf4c..17d31bb7d 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs +++ b/src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs @@ -11,65 +11,36 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; -using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Assets.Repositories; -using Squidex.Domain.Apps.Read.Schemas.Repositories; using Squidex.Infrastructure; -using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Read.Contents.GraphQL { - public sealed class CachingGraphQLService : CachingProviderBase, IGraphQLService, IEventConsumer + public sealed class CachingGraphQLService : CachingProviderBase, IGraphQLService { private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(60); private readonly IContentQueryService contentQuery; private readonly IGraphQLUrlGenerator urlGenerator; private readonly IAssetRepository assetRepository; - private readonly ISchemaRepository schemaRepository; - - public string Name - { - get { return GetType().Name; } - } - - public string EventsFilter - { - get { return "^(schema-)|(apps-)"; } - } + private readonly IAppProvider appProvider; public CachingGraphQLService(IMemoryCache cache, + IAppProvider appProvider, IAssetRepository assetRepository, IContentQueryService contentQuery, - IGraphQLUrlGenerator urlGenerator, - ISchemaRepository schemaRepository) + IGraphQLUrlGenerator urlGenerator) : base(cache) { - Guard.NotNull(schemaRepository, nameof(schemaRepository)); + Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(assetRepository, nameof(assetRepository)); Guard.NotNull(contentQuery, nameof(urlGenerator)); Guard.NotNull(contentQuery, nameof(contentQuery)); + this.appProvider = appProvider; this.assetRepository = assetRepository; this.contentQuery = contentQuery; this.urlGenerator = urlGenerator; - this.schemaRepository = schemaRepository; - } - - public Task ClearAsync() - { - return TaskHelper.Done; - } - - public Task On(Envelope @event) - { - if (@event.Payload is AppEvent appEvent) - { - Cache.Remove(CreateCacheKey(appEvent.AppId.Id)); - } - - return TaskHelper.Done; } public async Task<(object Data, object[] Errors)> QueryAsync(IAppEntity app, ClaimsPrincipal user, GraphQLQuery query) @@ -90,13 +61,13 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL private async Task GetModelAsync(IAppEntity app) { - var cacheKey = CreateCacheKey(app.Id); + var cacheKey = CreateCacheKey(app.Id, app.Etag); var modelContext = Cache.Get(cacheKey); if (modelContext == null) { - var allSchemas = await schemaRepository.QueryAllAsync(app.Id); + var allSchemas = await appProvider.GetSchemasAsync(app.Name); modelContext = new GraphQLModel(app, allSchemas.Where(x => x.IsPublished), urlGenerator); @@ -106,9 +77,9 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL return modelContext; } - private static object CreateCacheKey(Guid appId) + private static object CreateCacheKey(Guid appId, string etag) { - return $"GraphQLModel_{appId}"; + return $"GraphQLModel_{appId}_{etag}"; } } } diff --git a/src/Squidex.Domain.Apps.Read/EntityMapper.cs b/src/Squidex.Domain.Apps.Read/EntityMapper.cs index 01d396637..2efae1cfd 100644 --- a/src/Squidex.Domain.Apps.Read/EntityMapper.cs +++ b/src/Squidex.Domain.Apps.Read/EntityMapper.cs @@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Read { public static class EntityMapper { - public static T Create(SquidexEvent @event, EnvelopeHeaders headers, Action updater) where T : IEntity, new() + public static T Create(SquidexEvent @event, EnvelopeHeaders headers, Action updater = null) where T : IEntity, new() { var entity = new T(); @@ -29,13 +29,13 @@ namespace Squidex.Domain.Apps.Read return entity.Update(@event, headers, updater); } - public static T Update(this T entity, SquidexEvent @event, EnvelopeHeaders headers, Action updater) where T : IEntity, new() + public static T Update(this T entity, SquidexEvent @event, EnvelopeHeaders headers, Action updater = null) where T : IEntity, new() { SetVersion(headers, entity); SetLastModified(headers, entity); SetLastModifiedBy(@event, entity); - updater(entity); + updater?.Invoke(entity); return entity; } diff --git a/src/Squidex.Domain.Apps.Read/IAppState.cs b/src/Squidex.Domain.Apps.Read/IAppProvider.cs similarity index 54% rename from src/Squidex.Domain.Apps.Read/IAppState.cs rename to src/Squidex.Domain.Apps.Read/IAppProvider.cs index dffe6944c..296f8ff2d 100644 --- a/src/Squidex.Domain.Apps.Read/IAppState.cs +++ b/src/Squidex.Domain.Apps.Read/IAppProvider.cs @@ -1,5 +1,5 @@ // ========================================================================== -// IAppState.cs +// IApps.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -15,17 +15,19 @@ using Squidex.Domain.Apps.Read.Schemas; namespace Squidex.Domain.Apps.Read { - public interface IAppState + public interface IAppProvider { - Task GetAppAsync(Guid appId); + Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(string appName, Guid id); - Task GetSchemaAsync(Guid appId, Guid id, bool provideDeleted = false); + Task GetAppAsync(string appName); - Task GetSchemaAsync(Guid appId, string name, bool provideDeleted = false); + Task GetSchemaAsync(string appName, Guid id, bool provideDeleted = false); - Task> GetSchemasAsync(Guid appId); + Task GetSchemaAsync(string appName, string name, bool provideDeleted = false); - Task> GetRulesAsync(Guid appId); + Task> GetSchemasAsync(string appName); + + Task> GetRulesAsync(string appName); Task> GetUserApps(string userId); } diff --git a/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEventConsumer.cs b/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEventConsumer.cs deleted file mode 100644 index f94bdee68..000000000 --- a/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEventConsumer.cs +++ /dev/null @@ -1,16 +0,0 @@ -// ========================================================================== -// ISchemaEventConsumer.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using Squidex.Infrastructure.CQRS.Events; - -namespace Squidex.Domain.Apps.Read.Schemas -{ - public interface ISchemaEventConsumer : IEventConsumer - { - } -} diff --git a/src/Squidex.Domain.Apps.Read/Schemas/Repositories/ISchemaRepository.cs b/src/Squidex.Domain.Apps.Read/Schemas/Repositories/ISchemaRepository.cs deleted file mode 100644 index 147e4d1a9..000000000 --- a/src/Squidex.Domain.Apps.Read/Schemas/Repositories/ISchemaRepository.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ========================================================================== -// ISchemaRepository.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Squidex.Domain.Apps.Read.Schemas.Repositories -{ - public interface ISchemaRepository - { - Task> QueryAllAsync(Guid appId); - - Task FindSchemaAsync(Guid appId, string name); - - Task FindSchemaAsync(Guid schemaId); - } -} diff --git a/src/Squidex.Domain.Apps.Read/Schemas/Services/ISchemaProvider.cs b/src/Squidex.Domain.Apps.Read/Schemas/Services/ISchemaProvider.cs deleted file mode 100644 index 6d50efe1a..000000000 --- a/src/Squidex.Domain.Apps.Read/Schemas/Services/ISchemaProvider.cs +++ /dev/null @@ -1,20 +0,0 @@ -// ========================================================================== -// ISchemaProvider.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Threading.Tasks; - -namespace Squidex.Domain.Apps.Read.Schemas.Services -{ - public interface ISchemaProvider - { - Task FindSchemaByIdAsync(Guid id, bool provideDeleted = false); - - Task FindSchemaByNameAsync(Guid appId, string name); - } -} diff --git a/src/Squidex.Domain.Apps.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs b/src/Squidex.Domain.Apps.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs deleted file mode 100644 index ab0a609bd..000000000 --- a/src/Squidex.Domain.Apps.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs +++ /dev/null @@ -1,131 +0,0 @@ -// ========================================================================== -// CachingSchemaProvider.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Memory; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Read.Schemas.Repositories; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; -using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.Tasks; - -namespace Squidex.Domain.Apps.Read.Schemas.Services.Implementations -{ - public class CachingSchemaProvider : CachingProviderBase, ISchemaProvider, IEventConsumer - { - private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10); - private readonly ISchemaRepository repository; - - public string Name - { - get { return GetType().Name; } - } - - public string EventsFilter - { - get { return string.Empty; } - } - - public CachingSchemaProvider(IMemoryCache cache, ISchemaRepository repository) - : base(cache) - { - Guard.NotNull(repository, nameof(repository)); - - this.repository = repository; - } - - public async Task FindSchemaByIdAsync(Guid id, bool provideDeleted = false) - { - var cacheKey = BuildIdCacheKey(id); - - if (!Cache.TryGetValue(cacheKey, out ISchemaEntity result)) - { - result = await repository.FindSchemaAsync(id); - - Cache.Set(cacheKey, result, CacheDuration); - - if (result != null) - { - Cache.Set(BuildNameCacheKey(result.AppId, result.Name), result, CacheDuration); - } - } - - if (result != null && result.IsDeleted && !provideDeleted) - { - result = null; - } - - return result; - } - - public async Task FindSchemaByNameAsync(Guid appId, string name) - { - Guard.NotNullOrEmpty(name, nameof(name)); - - var cacheKey = BuildNameCacheKey(appId, name); - - if (!Cache.TryGetValue(cacheKey, out ISchemaEntity result)) - { - result = await repository.FindSchemaAsync(appId, name); - - Cache.Set(cacheKey, result, CacheDuration); - - if (result != null) - { - Cache.Set(BuildIdCacheKey(result.Id), result, CacheDuration); - } - } - - if (result != null && result.IsDeleted) - { - result = null; - } - - return result; - } - - public Task On(Envelope @event) - { - void Remove(NamedId appId, NamedId schemaId) - { - var cacheKeyById = BuildIdCacheKey(schemaId.Id); - var cacheKeyByName = BuildNameCacheKey(appId.Id, schemaId.Name); - - Cache.Remove(cacheKeyById); - Cache.Remove(cacheKeyByName); - - Cache.Invalidate(cacheKeyById); - Cache.Invalidate(cacheKeyByName); - } - - if (@event.Payload is SchemaEvent schemaEvent) - { - Remove(schemaEvent.AppId, schemaEvent.SchemaId); - } - - return TaskHelper.Done; - } - - private static string BuildNameCacheKey(Guid appId, string name) - { - return $"Schema_Ids_{appId}_{name}"; - } - - private static string BuildIdCacheKey(Guid schemaId) - { - return $"Schema_Names_{schemaId}"; - } - - public Task ClearAsync() - { - return TaskHelper.Done; - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/State/AppStateEventConsumer.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs similarity index 86% rename from src/Squidex.Domain.Apps.Read/State/AppStateEventConsumer.cs rename to src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs index d3706dd1e..9ca3a6203 100644 --- a/src/Squidex.Domain.Apps.Read/State/AppStateEventConsumer.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs @@ -15,7 +15,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Tasks; -namespace Squidex.Domain.Apps.Read.State +namespace Squidex.Domain.Apps.Read.State.Orleans { public sealed class AppStateEventConsumer : IEventConsumer { @@ -47,23 +47,23 @@ namespace Squidex.Domain.Apps.Read.State { if (@event.Payload is AppEvent appEvent) { - var appGrain = factory.GetGrain(appEvent.AppId.Id); + var appGrain = factory.GetGrain(appEvent.AppId.Name); - await appGrain.HandleAsync(new EventMessage { Event = @event }); + await appGrain.HandleAsync(@event); } if (@event.Payload is AppContributorAssigned contributorAssigned) { var userGrain = factory.GetGrain(contributorAssigned.ContributorId); - await userGrain.AddSchemaAsync(contributorAssigned.AppId.Id); + await userGrain.AddAppAsync(contributorAssigned.AppId.Name); } if (@event.Payload is AppContributorRemoved contributorRemoved) { var userGrain = factory.GetGrain(contributorRemoved.ContributorId); - await userGrain.RemoveSchemaAsync(contributorRemoved.AppId.Id); + await userGrain.RemoveAppAsync(contributorRemoved.AppId.Name); } } } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppStateGrain.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppStateGrain.cs index 339bdd942..e3bfe27ee 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppStateGrain.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppStateGrain.cs @@ -13,21 +13,25 @@ using Orleans; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Rules; using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.Json.Orleans; namespace Squidex.Domain.Apps.Read.State.Orleans.Grains { - public interface IAppStateGrain : IGrainWithGuidKey + public interface IAppStateGrain : IGrainWithStringKey { - Task GetAppAsync(); + Task> GetAppWithSchemaAsync(Guid id); - Task GetSchemaAsync(Guid id, bool provideDeleted = false); + Task> GetAppAsync(); - Task GetSchemaAsync(string name, bool provideDeleted = false); + Task> GetSchemaAsync(Guid id, bool provideDeleted = false); - Task> GetSchemasAsync(); + Task> GetSchemaAsync(string name, bool provideDeleted = false); - Task> GetRulesAsync(); + Task>> GetSchemasAsync(); - Task HandleAsync(EventMessage message); + Task>> GetRulesAsync(); + + Task HandleAsync(J> message); } } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppUserGrain.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppUserGrain.cs index 354687e8d..68a1db147 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppUserGrain.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppUserGrain.cs @@ -6,7 +6,6 @@ // All rights reserved. // ========================================================================== -using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans; @@ -15,10 +14,10 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains { public interface IAppUserGrain : IGrainWithStringKey { - Task> GetSchemaIdsAsync(); + Task> GetSchemaNamesAsync(); - Task AddSchemaAsync(Guid schemaId); + Task AddAppAsync(string appName); - Task RemoveSchemaAsync(Guid schemaId); + Task RemoveAppAsync(string appName); } } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrain.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrain.cs index ef15cc471..80086c4e3 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrain.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrain.cs @@ -8,8 +8,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; +using Newtonsoft.Json; using Orleans; using Orleans.Providers; using Squidex.Domain.Apps.Core.Schemas; @@ -17,59 +17,78 @@ using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Rules; using Squidex.Domain.Apps.Read.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.Json.Orleans; namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations { [StorageProvider(ProviderName = "Default")] - public sealed class AppStateGrain : Grain, IAppStateGrain + public sealed class AppStateGrain : Grain>, IAppStateGrain { private readonly FieldRegistry fieldRegistry; + private readonly JsonSerializer serializer; - public AppStateGrain(FieldRegistry fieldRegistry) + public AppStateGrain(FieldRegistry fieldRegistry, JsonSerializer serializer) { Guard.NotNull(fieldRegistry, nameof(fieldRegistry)); + Guard.NotNull(serializer, nameof(serializer)); this.fieldRegistry = fieldRegistry; + this.serializer = serializer; } - public Task GetAppAsync() + public override Task OnActivateAsync() { - var value = State.App; + State.SetSerializer(serializer); - return Task.FromResult(value); + return base.OnActivateAsync(); } - public Task> GetRulesAsync() + public Task> GetAppWithSchemaAsync(Guid id) { - var value = State.Rules.Values.OfType().ToList(); + var schema = State.Value.FindSchema(x => x.Id == id && !x.IsDeleted); - return Task.FromResult(value); + return J<(IAppEntity AppEntity, ISchemaEntity SchemaEntity)>.AsTask((State.Value.GetApp(), schema)); } - public Task> GetSchemasAsync() + public Task> GetAppAsync() { - var value = State.Schemas.Values.Where(x => !x.IsDeleted).OfType().ToList(); + var value = State.Value.GetApp(); - return Task.FromResult(value); + return J.AsTask(value); } - public Task GetSchemaAsync(Guid id, bool provideDeleted = false) + public Task>> GetRulesAsync() { - var value = State.Schemas.Values.FirstOrDefault(x => x.Id == id && (!x.IsDeleted || provideDeleted)); + var value = State.Value.FindRules(); - return Task.FromResult(value); + return J>.AsTask(value); } - public Task GetSchemaAsync(string name, bool provideDeleted = false) + public Task>> GetSchemasAsync() { - var value = State.Schemas.Values.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted)); + var value = State.Value.FindSchemas(x => !x.IsDeleted); - return Task.FromResult(value); + return J>.AsTask(value); } - public Task HandleAsync(EventMessage message) + public Task> GetSchemaAsync(Guid id, bool provideDeleted = false) { - State.Apply(message.Event, fieldRegistry); + var value = State.Value.FindSchema(x => x.Id == id && (!x.IsDeleted || provideDeleted)); + + return J.AsTask(value); + } + + public Task> GetSchemaAsync(string name, bool provideDeleted = false) + { + var value = State.Value.FindSchema(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted)); + + return J.AsTask(value); + } + + public Task HandleAsync(J> message) + { + State = State.Update(v => v.Apply(message, fieldRegistry)); return WriteStateAsync(); } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrainState.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrainState.cs index 4825ae43a..ff7223c8b 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrainState.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrainState.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Schemas; @@ -18,6 +19,9 @@ using Squidex.Domain.Apps.Events.Rules.Utils; using Squidex.Domain.Apps.Events.Schemas; using Squidex.Domain.Apps.Events.Schemas.Old; using Squidex.Domain.Apps.Events.Schemas.Utils; +using Squidex.Domain.Apps.Read.Apps; +using Squidex.Domain.Apps.Read.Rules; +using Squidex.Domain.Apps.Read.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Reflection; @@ -37,6 +41,26 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations [JsonProperty] public Dictionary Schemas { get; set; } + public IAppEntity GetApp() + { + return App; + } + + public ISchemaEntity FindSchema(Func filter) + { + return Schemas.Values.FirstOrDefault(filter); + } + + public List FindRules() + { + return Rules.Values.OfType().ToList(); + } + + public List FindSchemas(Func filter) + { + return Schemas.Values.Where(filter).OfType().ToList(); + } + public void Reset() { Rules = new Dictionary(); @@ -54,7 +78,7 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations App = EntityMapper.Create(@event, envelope.Headers, a => { - SimpleMapper.Map(envelope, a); + SimpleMapper.Map(@event, a); a.Clients = new AppClients(); a.Contributors = new AppContributors(); @@ -68,7 +92,7 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations case AppPlanChanged @event: App.Update(@event, envelope.Headers, a => { - SimpleMapper.Map(envelope, a); + SimpleMapper.Map(@event, a); }); break; @@ -258,19 +282,18 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations break; case WebhookAdded @event: - Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s => - { - /* NOOP */ - }); + Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers); break; case WebhookDeleted @event: - Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers, s => - { - /* NOOP */ - }); + Schemas[@event.SchemaId.Id].Update(@event, envelope.Headers); break; } + + if (App != null) + { + App.Etag = Guid.NewGuid().ToString(); + } } } -} +} \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrain.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrain.cs index 326bd03a0..10c0eff38 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrain.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrain.cs @@ -6,36 +6,34 @@ // All rights reserved. // ========================================================================== -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Orleans; using Orleans.Providers; -using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations { [StorageProvider(ProviderName = "Default")] - public sealed class AppUserGrain : Grain>, IAppUserGrain + public sealed class AppUserGrain : Grain, IAppUserGrain { - public Task AddSchemaAsync(Guid schemaId) + public Task AddAppAsync(string appName) { - State.Add(schemaId); + State.AppNames.Add(appName); - return TaskHelper.Done; + return WriteStateAsync(); } - public Task RemoveSchemaAsync(Guid schemaId) + public Task RemoveAppAsync(string appName) { - State.Remove(schemaId); + State.AppNames.Remove(appName); - return TaskHelper.Done; + return WriteStateAsync(); } - public Task> GetSchemaIdsAsync() + public Task> GetSchemaNamesAsync() { - return Task.FromResult(State.ToList()); + return Task.FromResult(State.AppNames.ToList()); } } } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/EventMessage.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrainState.cs similarity index 53% rename from src/Squidex.Domain.Apps.Read/State/Orleans/Grains/EventMessage.cs rename to src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrainState.cs index e4b552d38..53984019b 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/EventMessage.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppUserGrainState.cs @@ -1,19 +1,19 @@ // ========================================================================== -// EventMessage.cs +// AppUserGrainState.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -using Orleans.Concurrency; -using Squidex.Infrastructure.CQRS.Events; +using System.Collections.Generic; +using Newtonsoft.Json; -namespace Squidex.Domain.Apps.Read.State.Orleans.Grains +namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations { - [Immutable] - public sealed class EventMessage + public sealed class AppUserGrainState { - public Envelope Event { get; set; } + [JsonProperty] + public HashSet AppNames { get; set; } = new HashSet(); } } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonAppEntity.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonAppEntity.cs index 88359f66c..6dd326d57 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonAppEntity.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonAppEntity.cs @@ -20,6 +20,9 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations [JsonProperty] public string PlanId { get; set; } + [JsonProperty] + public string Etag { get; set; } + [JsonProperty] public string PlanOwner { get; set; } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonSchemaEntity.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonSchemaEntity.cs index 9615b357e..d038aed0f 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonSchemaEntity.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonSchemaEntity.cs @@ -49,6 +49,7 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations [JsonProperty] public Schema SchemaDef { get; set; } + [JsonIgnore] public bool IsPublished { get { return SchemaDef.IsPublished; } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppProvider.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppProvider.cs new file mode 100644 index 000000000..210fc614c --- /dev/null +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppProvider.cs @@ -0,0 +1,89 @@ +// ========================================================================== +// OrleansApps.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 Orleans; +using Squidex.Domain.Apps.Read.Apps; +using Squidex.Domain.Apps.Read.Rules; +using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Read.State.Orleans.Grains; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Read.State.Orleans +{ + public sealed class OrleansAppProvider : IAppProvider + { + private readonly IGrainFactory factory; + + public OrleansAppProvider(IGrainFactory factory) + { + Guard.NotNull(factory, nameof(factory)); + + this.factory = factory; + } + + public async Task GetAppAsync(string appName) + { + var result = await factory.GetGrain(appName).GetAppAsync(); + + return result.Value; + } + + public async Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(string appName, Guid id) + { + var result = await factory.GetGrain(appName).GetAppWithSchemaAsync(id); + + return result.Value; + } + + public async Task> GetRulesAsync(string appName) + { + var result = await factory.GetGrain(appName).GetRulesAsync(); + + return result.Value; + } + + public async Task GetSchemaAsync(string appName, Guid id, bool provideDeleted = false) + { + var result = await factory.GetGrain(appName).GetSchemaAsync(id, provideDeleted); + + return result.Value; + } + + public async Task GetSchemaAsync(string appName, string name, bool provideDeleted = false) + { + var result = await factory.GetGrain(appName).GetSchemaAsync(name, provideDeleted); + + return result.Value; + } + + public async Task> GetSchemasAsync(string appName) + { + var result = await factory.GetGrain(appName).GetSchemasAsync(); + + return result.Value; + } + + public async Task> GetUserApps(string userId) + { + var schemaIds = await factory.GetGrain(userId).GetSchemaNamesAsync(); + + var tasks = + schemaIds + .Select(x => factory.GetGrain(x)) + .Select(x => x.GetAppAsync()); + + var apps = await Task.WhenAll(tasks); + + return apps.Select(a => a.Value).Where(a => a != null).ToList(); + } + } +} diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppState.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppState.cs deleted file mode 100644 index ff6eb4aa4..000000000 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/OrleansAppState.cs +++ /dev/null @@ -1,71 +0,0 @@ -// ========================================================================== -// OrleansAppState.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 Orleans; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Rules; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Read.State.Orleans.Grains -{ - public sealed class OrleansAppState : IAppState - { - private readonly IGrainFactory factory; - - public OrleansAppState(IGrainFactory factory) - { - Guard.NotNull(factory, nameof(factory)); - - this.factory = factory; - } - - public Task GetAppAsync(Guid appId) - { - return factory.GetGrain(appId).GetAppAsync(); - } - - public Task> GetRulesAsync(Guid appId) - { - return factory.GetGrain(appId).GetRulesAsync(); - } - - public Task GetSchemaAsync(Guid appId, Guid id, bool provideDeleted = false) - { - return factory.GetGrain(appId).GetSchemaAsync(id, provideDeleted); - } - - public Task GetSchemaAsync(Guid appId, string name, bool provideDeleted = false) - { - return factory.GetGrain(appId).GetSchemaAsync(name, provideDeleted); - } - - public Task> GetSchemasAsync(Guid appId) - { - return factory.GetGrain(appId).GetSchemasAsync(); - } - - public async Task> GetUserApps(string userId) - { - var schemaIds = await factory.GetGrain(userId).GetSchemaIdsAsync(); - - var tasks = - schemaIds - .Select(x => factory.GetGrain(x)) - .Select(x => x.GetAppAsync()); - - var apps = await Task.WhenAll(tasks); - - return apps.Where(a => a != null).ToList(); - } - } -} diff --git a/src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs b/src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs index 03f5957bd..02042a262 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs @@ -8,6 +8,7 @@ using System; using System.Threading.Tasks; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Write.Apps.Commands; using Squidex.Domain.Apps.Write.Apps.Guards; diff --git a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardApp.cs b/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardApp.cs index c87e8ffda..89540ff13 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardApp.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardApp.cs @@ -9,6 +9,7 @@ using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Write.Apps.Commands; using Squidex.Infrastructure; @@ -17,13 +18,13 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { public static class GuardApp { - public static Task CanCreate(CreateApp command, IAppProvider apps) + public static Task CanCreate(CreateApp command, IAppProvider appProvider) { Guard.NotNull(command, nameof(command)); return Validate.It(() => "Cannot create app.", async error => { - if (await apps.FindAppByNameAsync(command.Name) != null) + if (await appProvider.GetAppAsync(command.Name) != null) { error(new ValidationError($"An app with name '{command.Name}' already exists", nameof(command.Name))); } diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs b/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs index d64143b2f..9716179e9 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs @@ -9,10 +9,9 @@ using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Scripting; -using Squidex.Domain.Apps.Read.Apps.Services; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Read.Assets.Repositories; using Squidex.Domain.Apps.Read.Contents.Repositories; -using Squidex.Domain.Apps.Read.Schemas.Services; using Squidex.Domain.Apps.Write.Contents.Commands; using Squidex.Domain.Apps.Write.Contents.Guards; using Squidex.Infrastructure; @@ -27,26 +26,22 @@ namespace Squidex.Domain.Apps.Write.Contents private readonly IAppProvider appProvider; private readonly IAssetRepository assetRepository; private readonly IContentRepository contentRepository; - private readonly ISchemaProvider schemas; private readonly IScriptEngine scriptEngine; public ContentCommandMiddleware( IAggregateHandler handler, IAppProvider appProvider, IAssetRepository assetRepository, - ISchemaProvider schemas, IScriptEngine scriptEngine, IContentRepository contentRepository) { Guard.NotNull(handler, nameof(handler)); - Guard.NotNull(schemas, nameof(schemas)); Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(scriptEngine, nameof(scriptEngine)); Guard.NotNull(assetRepository, nameof(assetRepository)); Guard.NotNull(contentRepository, nameof(contentRepository)); this.handler = handler; - this.schemas = schemas; this.appProvider = appProvider; this.scriptEngine = scriptEngine; this.assetRepository = assetRepository; @@ -154,7 +149,6 @@ namespace Squidex.Domain.Apps.Write.Contents content, command, appProvider, - schemas, scriptEngine, assetRepository, message); diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs b/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs index 5487f957e..3817eacfe 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs @@ -13,12 +13,11 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core.EnrichContent; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.ValidateContent; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Read.Assets.Repositories; using Squidex.Domain.Apps.Read.Contents.Repositories; using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.Schemas.Services; using Squidex.Domain.Apps.Write.Contents.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Tasks; @@ -43,25 +42,21 @@ namespace Squidex.Domain.Apps.Write.Contents ContentDomainObject content, ContentCommand command, IAppProvider appProvider, - ISchemaProvider schemas, IScriptEngine scriptEngine, IAssetRepository assetRepository, Func message) { - var taskForApp = appProvider.FindAppByIdAsync(command.AppId.Id); - var taskForSchema = schemas.FindSchemaByIdAsync(command.SchemaId.Id); - - await Task.WhenAll(taskForApp, taskForSchema); + var (appEntity, schemaEntity) = await appProvider.GetAppWithSchemaAsync(command.AppId.Name, command.SchemaId.Id); var context = new ContentOperationContext(); - context.appEntity = taskForApp.Result; + context.appEntity = appEntity; context.assetRepository = assetRepository; context.contentRepository = contentRepository; context.content = content; context.command = command; context.message = message; - context.schemaEntity = taskForSchema.Result; + context.schemaEntity = schemaEntity; context.scriptEngine = scriptEngine; return context; diff --git a/src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs b/src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs index 47a6cc129..5848f9f1e 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs +++ b/src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Read.Schemas.Services; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Write.Rules.Commands; using Squidex.Infrastructure; @@ -16,7 +16,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards { public static class GuardRule { - public static Task CanCreate(CreateRule command, ISchemaProvider schemas) + public static Task CanCreate(CreateRule command, IAppProvider appProvider) { Guard.NotNull(command, nameof(command)); @@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards } else { - var errors = await RuleTriggerValidator.ValidateAsync(command.Trigger, schemas); + var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Name, command.Trigger, appProvider); errors.Foreach(error); } @@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards }); } - public static Task CanUpdate(UpdateRule command, ISchemaProvider schemas) + public static Task CanUpdate(UpdateRule command, IAppProvider appProvider) { Guard.NotNull(command, nameof(command)); @@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards if (command.Trigger != null) { - var errors = await RuleTriggerValidator.ValidateAsync(command.Trigger, schemas); + var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Name, command.Trigger, appProvider); errors.Foreach(error); } diff --git a/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs b/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs index f9594c26f..48dfec259 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs +++ b/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs @@ -6,31 +6,33 @@ // All rights reserved. // ========================================================================== +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Triggers; -using Squidex.Domain.Apps.Read.Schemas.Services; +using Squidex.Domain.Apps.Read; +using Squidex.Domain.Apps.Read.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Write.Rules.Guards { public sealed class RuleTriggerValidator : IRuleTriggerVisitor>> { - public ISchemaProvider Schemas { get; } + public Func> SchemaProvider { get; } - public RuleTriggerValidator(ISchemaProvider schemas) + public RuleTriggerValidator(Func> schemaProvider) { - Schemas = schemas; + SchemaProvider = schemaProvider; } - public static Task> ValidateAsync(RuleTrigger action, ISchemaProvider schemas) + public static Task> ValidateAsync(string appName, RuleTrigger action, IAppProvider appProvider) { Guard.NotNull(action, nameof(action)); - Guard.NotNull(schemas, nameof(schemas)); + Guard.NotNull(appProvider, nameof(appProvider)); - var visitor = new RuleTriggerValidator(schemas); + var visitor = new RuleTriggerValidator(x => appProvider.GetSchemaAsync(appName, x)); return action.Accept(visitor); } @@ -41,7 +43,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards { var schemaErrors = await Task.WhenAll( trigger.Schemas.Select(async s => - await Schemas.FindSchemaByIdAsync(s.SchemaId) == null + await SchemaProvider(s.SchemaId) == null ? new ValidationError($"Schema {s.SchemaId} does not exist.", nameof(trigger.Schemas)) : null)); diff --git a/src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs b/src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs index b0b2ed082..7a29bd301 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs @@ -8,7 +8,7 @@ using System; using System.Threading.Tasks; -using Squidex.Domain.Apps.Read.Schemas.Services; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Write.Rules.Commands; using Squidex.Domain.Apps.Write.Rules.Guards; using Squidex.Infrastructure; @@ -20,22 +20,23 @@ namespace Squidex.Domain.Apps.Write.Rules public class RuleCommandMiddleware : ICommandMiddleware { private readonly IAggregateHandler handler; - private readonly ISchemaProvider schemas; + private readonly IAppProvider appProvider; - public RuleCommandMiddleware(IAggregateHandler handler, ISchemaProvider schemas) + public RuleCommandMiddleware(IAggregateHandler handler, IAppProvider appProvider) { Guard.NotNull(handler, nameof(handler)); - Guard.NotNull(schemas, nameof(schemas)); + Guard.NotNull(appProvider, nameof(appProvider)); this.handler = handler; - this.schemas = schemas; + + this.appProvider = appProvider; } protected Task On(CreateRule command, CommandContext context) { return handler.CreateAsync(context, async w => { - await GuardRule.CanCreate(command, schemas); + await GuardRule.CanCreate(command, appProvider); w.Create(command); }); @@ -45,7 +46,7 @@ namespace Squidex.Domain.Apps.Write.Rules { return handler.UpdateAsync(context, async c => { - await GuardRule.CanUpdate(command, schemas); + await GuardRule.CanUpdate(command, appProvider); c.Update(command); }); diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs b/src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs index 6502794b5..b9006a315 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs @@ -10,7 +10,7 @@ using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Schemas.Services; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Write.Schemas.Commands; using Squidex.Infrastructure; @@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards { public static class GuardSchema { - public static Task CanCreate(CreateSchema command, ISchemaProvider schemas) + public static Task CanCreate(CreateSchema command, IAppProvider appProvider) { Guard.NotNull(command, nameof(command)); @@ -29,7 +29,7 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards error(new ValidationError("Name must be a valid slug.", nameof(command.Name))); } - if (await schemas.FindSchemaByNameAsync(command.AppId.Id, command.Name) != null) + if (await appProvider.GetSchemaAsync(command.AppId.Name, command.Name) != null) { error(new ValidationError($"A schema with name '{command.Name}' already exists", nameof(command.Name))); } diff --git a/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs b/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs index 397622cb4..a651a264f 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs @@ -9,7 +9,7 @@ using System; using System.Linq; using System.Threading.Tasks; -using Squidex.Domain.Apps.Read.Schemas.Services; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Write.Schemas.Commands; using Squidex.Domain.Apps.Write.Schemas.Guards; using Squidex.Infrastructure; @@ -20,23 +20,24 @@ namespace Squidex.Domain.Apps.Write.Schemas { public class SchemaCommandMiddleware : ICommandMiddleware { - private readonly ISchemaProvider schemas; + private readonly IAppProvider appProvider; private readonly IAggregateHandler handler; - public SchemaCommandMiddleware(IAggregateHandler handler, ISchemaProvider schemas) + public SchemaCommandMiddleware(IAggregateHandler handler, IAppProvider appProvider) { Guard.NotNull(handler, nameof(handler)); - Guard.NotNull(schemas, nameof(schemas)); + Guard.NotNull(appProvider, nameof(appProvider)); this.handler = handler; - this.schemas = schemas; + + this.appProvider = appProvider; } protected Task On(CreateSchema command, CommandContext context) { return handler.CreateAsync(context, async s => { - await GuardSchema.CanCreate(command, schemas); + await GuardSchema.CanCreate(command, appProvider); s.Create(command); diff --git a/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/IEventConsumerGrain.cs b/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/IEventConsumerGrain.cs index 24091ade3..159155bcf 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/IEventConsumerGrain.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/IEventConsumerGrain.cs @@ -8,12 +8,13 @@ using System.Threading.Tasks; using Orleans; +using Orleans.Concurrency; namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains { public interface IEventConsumerGrain : IGrainWithStringKey, IEventSubscriber { - Task GetStateAsync(); + Task> GetStateAsync(); Task ActivateAsync(); diff --git a/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/IEventConsumerRegistryGrain.cs b/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/IEventConsumerRegistryGrain.cs index aa96490b8..15cc35e12 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/IEventConsumerRegistryGrain.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/IEventConsumerRegistryGrain.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Orleans; +using Orleans.Concurrency; namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains { @@ -22,6 +23,6 @@ namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains Task ResetAsync(string consumerName); - Task> GetConsumersAsync(); + Task>> GetConsumersAsync(); } } diff --git a/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/Implementation/EventConsumerGrain.cs b/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/Implementation/EventConsumerGrain.cs index 2ec816fc8..cfd475a79 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/Implementation/EventConsumerGrain.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/Implementation/EventConsumerGrain.cs @@ -9,6 +9,7 @@ using System; using System.Threading.Tasks; using Orleans; +using Orleans.Concurrency; using Orleans.Providers; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Tasks; @@ -170,9 +171,9 @@ namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation return dispatcher.StartNew(() => this.HandleClosedAsync(subscription)).Unwrap(); } - public Task GetStateAsync() + public Task> GetStateAsync() { - return Task.FromResult(State.ToInfo(this.GetPrimaryKeyString())); + return Task.FromResult(new Immutable(State.ToInfo(this.GetPrimaryKeyString()))); } private Task DoAndUpdateStateAsync(Action action) diff --git a/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/Implementation/EventConsumerRegistryGrain.cs b/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/Implementation/EventConsumerRegistryGrain.cs index 5ff0da2b3..eae74b083 100644 --- a/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/Implementation/EventConsumerRegistryGrain.cs +++ b/src/Squidex.Infrastructure/CQRS/Events/Orleans/Grains/Implementation/EventConsumerRegistryGrain.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using Orleans; +using Orleans.Concurrency; using Orleans.Runtime; using Squidex.Infrastructure.Tasks; @@ -48,14 +49,14 @@ namespace Squidex.Infrastructure.CQRS.Events.Orleans.Grains.Implementation return Task.WhenAll(tasks); } - public Task> GetConsumersAsync() + public Task>> GetConsumersAsync() { var tasks = eventConsumers .Select(c => GrainFactory.GetGrain(c.Name)) .Select(c => c.GetStateAsync()); - return Task.WhenAll(tasks).ContinueWith(x => x.Result.ToList()); + return Task.WhenAll(tasks).ContinueWith(x => new Immutable>(x.Result.Select(r => r.Value).ToList())); } public Task ReceiveReminder(string reminderName, TickStatus status) diff --git a/src/Squidex.Domain.Apps.Read/Apps/IAppEventConsumer.cs b/src/Squidex.Infrastructure/Json/Orleans/IJsonValue.cs similarity index 67% rename from src/Squidex.Domain.Apps.Read/Apps/IAppEventConsumer.cs rename to src/Squidex.Infrastructure/Json/Orleans/IJsonValue.cs index c34d229cf..fd1ba7c05 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/IAppEventConsumer.cs +++ b/src/Squidex.Infrastructure/Json/Orleans/IJsonValue.cs @@ -1,16 +1,15 @@ // ========================================================================== -// IAppEventConsumer.cs +// IJsonValue.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -using Squidex.Infrastructure.CQRS.Events; - -namespace Squidex.Domain.Apps.Read.Apps +namespace Squidex.Infrastructure.Json.Orleans { - public interface IAppEventConsumer : IEventConsumer + public interface IJsonValue { + object Value { get; } } } diff --git a/src/Squidex.Infrastructure/Json/Orleans/J.cs b/src/Squidex.Infrastructure/Json/Orleans/J.cs new file mode 100644 index 000000000..97256ee7f --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Orleans/J.cs @@ -0,0 +1,49 @@ +// ========================================================================== +// J.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace Squidex.Infrastructure.Json.Orleans +{ + public struct J : IJsonValue + { + private readonly T value; + + public T Value + { + get { return value; } + } + + object IJsonValue.Value + { + get { return Value; } + } + + [JsonConstructor] + public J(T value) + { + this.value = value; + } + + public static implicit operator T(J value) + { + return value.Value; + } + + public static implicit operator J(T d) + { + return new J(d); + } + + public static Task> AsTask(T value) + { + return Task.FromResult>(value); + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Orleans/JsonExternalSerializer.cs b/src/Squidex.Infrastructure/Json/Orleans/JsonExternalSerializer.cs new file mode 100644 index 000000000..160821faa --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Orleans/JsonExternalSerializer.cs @@ -0,0 +1,78 @@ +// ========================================================================== +// JsonExternalSerializer.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.IO; +using System.Linq; +using System.Text; +using Newtonsoft.Json; +using Orleans.Runtime; +using Orleans.Serialization; + +namespace Squidex.Infrastructure.Json.Orleans +{ + public class JsonExternalSerializer : IExternalSerializer + { + private readonly JsonSerializer jsonSerializer; + + public JsonExternalSerializer(JsonSerializer jsonSerializer) + { + Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); + + this.jsonSerializer = jsonSerializer; + } + + public void Initialize(Logger logger) + { + } + + public bool IsSupportedType(Type itemType) + { + return itemType.GetInterfaces().Contains(typeof(IJsonValue)); + } + + public object DeepCopy(object source, ICopyContext context) + { + return source; + } + + public object Deserialize(Type expectedType, IDeserializationContext context) + { + var outLength = context.StreamReader.ReadInt(); + var outBytes = context.StreamReader.ReadBytes(outLength); + + var json = Encoding.Default.GetString(outBytes); + + var stream = new MemoryStream(outBytes); + + using (var reader = new JsonTextReader(new StreamReader(stream))) + { + return jsonSerializer.Deserialize(reader, expectedType); + } + } + + public void Serialize(object item, ISerializationContext context, Type expectedType) + { + var stream = new MemoryStream(); + + using (var writer = new JsonTextWriter(new StreamWriter(stream))) + { + jsonSerializer.Serialize(writer, item); + + writer.Flush(); + } + + var outBytes = stream.ToArray(); + + var json = Encoding.Default.GetString(outBytes, 0, outBytes.Length); + + context.StreamWriter.Write(outBytes.Length); + context.StreamWriter.Write(outBytes); + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Orleans/JsonState.cs b/src/Squidex.Infrastructure/Json/Orleans/JsonState.cs new file mode 100644 index 000000000..22031485d --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Orleans/JsonState.cs @@ -0,0 +1,73 @@ +// ========================================================================== +// JsonState.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Squidex.Infrastructure.Json.Orleans +{ + public sealed class JsonState where TValue : class, new() + { + private JsonSerializer serializer = JsonSerializer.CreateDefault(); + + private TValue value; + + [JsonProperty] + public JToken Json { get; set; } + + [JsonIgnore] + public TValue Value + { + get + { + if (value == null) + { + value = ReadState(); + } + + return value; + } + set + { + this.value = value; + } + } + + public void SetSerializer(JsonSerializer serializer) + { + if (serializer != null) + { + this.serializer = serializer; + } + } + + public JsonState Update(Action updater) + { + var value = ReadState(); + + updater(value); + + var json = JObject.FromObject(value, serializer); + + return new JsonState { serializer = serializer, Json = json, Value = value }; + } + + private TValue ReadState() + { + if (Json != null) + { + return Json.ToObject(serializer); + } + else + { + return new TValue(); + } + } + } +} diff --git a/src/Squidex.Infrastructure/Reflection/ReflectionExtensions.cs b/src/Squidex.Infrastructure/Reflection/ReflectionExtensions.cs index 9cde81872..3f9f65703 100644 --- a/src/Squidex.Infrastructure/Reflection/ReflectionExtensions.cs +++ b/src/Squidex.Infrastructure/Reflection/ReflectionExtensions.cs @@ -64,5 +64,10 @@ namespace Squidex.Infrastructure.Reflection return flattenProperties.ToArray(); } + + public static bool Implements(this Type type) + { + return type.GetInterfaces().Contains(typeof(T)); + } } } diff --git a/src/Squidex.Infrastructure/Reflection/SimpleCopier.cs b/src/Squidex.Infrastructure/Reflection/SimpleCopier.cs new file mode 100644 index 000000000..112a32b2a --- /dev/null +++ b/src/Squidex.Infrastructure/Reflection/SimpleCopier.cs @@ -0,0 +1,85 @@ +// ========================================================================== +// SimpleCopier.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; + +#pragma warning disable RECS0108 // Warns about static fields in generic types + +namespace Squidex.Infrastructure.Reflection +{ + public static class SimpleCopier + { + private struct PropertyMapper + { + private readonly IPropertyAccessor accessor; + private readonly Func converter; + + public PropertyMapper(IPropertyAccessor accessor, Func converter) + { + this.accessor = accessor; + this.converter = converter; + } + + public void MapProperty(object source, object target) + { + var value = converter(accessor.Get(source)); + + accessor.Set(target, value); + } + } + + private static class ClassCopier where T : class, new() + { + private static readonly List Mappers = new List(); + + static ClassCopier() + { + var type = typeof(T); + + foreach (var property in type.GetPublicProperties()) + { + if (!property.CanWrite || !property.CanRead) + { + continue; + } + + var accessor = new PropertyAccessor(type, property); + + if (property.PropertyType.Implements()) + { + Mappers.Add(new PropertyMapper(accessor, x => x == null ? x : ((ICloneable)x).Clone())); + } + else + { + Mappers.Add(new PropertyMapper(accessor, x => x)); + } + } + } + + public static T Copy(T source) + { + var destination = new T(); + + foreach (var mapper in Mappers) + { + mapper.MapProperty(source, destination); + } + + return destination; + } + } + + public static T Clone(T source) where T : class, new() + { + Guard.NotNull(source, nameof(source)); + + return ClassCopier.Copy(source); + } + } +} diff --git a/src/Squidex.Infrastructure/Reflection/SimpleMapper.cs b/src/Squidex.Infrastructure/Reflection/SimpleMapper.cs index 065e3e26d..a38c522c1 100644 --- a/src/Squidex.Infrastructure/Reflection/SimpleMapper.cs +++ b/src/Squidex.Infrastructure/Reflection/SimpleMapper.cs @@ -21,13 +21,16 @@ namespace Squidex.Infrastructure.Reflection { private readonly Type targetType; - public ConversionPropertyMapper(IPropertyAccessor srcAccessor, IPropertyAccessor dstAccessor, Type targetType) - : base(srcAccessor, dstAccessor) + public ConversionPropertyMapper( + IPropertyAccessor sourceAccessor, + IPropertyAccessor targetAccessor, + Type targetType) + : base(sourceAccessor, targetAccessor) { this.targetType = targetType; } - public override void MapProperty(object source, object destination, CultureInfo culture) + public override void MapProperty(object source, object target, CultureInfo culture) { var value = GetValue(source); @@ -41,7 +44,7 @@ namespace Squidex.Infrastructure.Reflection { converted = Convert.ChangeType(value, targetType, culture); - SetValue(destination, converted); + SetValue(target, converted); } catch (InvalidCastException) { @@ -49,7 +52,7 @@ namespace Squidex.Infrastructure.Reflection { converted = value.ToString(); - SetValue(destination, converted); + SetValue(target, converted); } } } @@ -57,91 +60,78 @@ namespace Squidex.Infrastructure.Reflection private class PropertyMapper { - private readonly IPropertyAccessor srcAccessor; - private readonly IPropertyAccessor dstAccessor; + private readonly IPropertyAccessor sourceAccessor; + private readonly IPropertyAccessor targetAccessor; - public PropertyMapper(IPropertyAccessor srcAccessor, IPropertyAccessor dstAccessor) + public PropertyMapper(IPropertyAccessor sourceAccessor, IPropertyAccessor targetAccessor) { - this.srcAccessor = srcAccessor; - this.dstAccessor = dstAccessor; + this.sourceAccessor = sourceAccessor; + this.targetAccessor = targetAccessor; } - public virtual void MapProperty(object source, object destination, CultureInfo culture) + public virtual void MapProperty(object source, object target, CultureInfo culture) { var value = GetValue(source); - SetValue(destination, value); + SetValue(target, value); } protected void SetValue(object destination, object value) { - dstAccessor.Set(destination, value); + targetAccessor.Set(destination, value); } protected object GetValue(object source) { - return srcAccessor.Get(source); + return sourceAccessor.Get(source); } } - private static class ClassMapper - where TSource : class - where TDestination : class + private static class ClassMapper where TSource : class where TTarget : class { - private static readonly PropertyMapper[] Mappers; - - private static readonly Type[] Convertibles = - { - typeof(bool), - typeof(byte), - typeof(char), - typeof(decimal), - typeof(float), - typeof(double), - typeof(short), - typeof(int), - typeof(long), - typeof(string) - }; + private static readonly List Mappers = new List(); static ClassMapper() { - var dstType = typeof(TDestination); - var srcType = typeof(TSource); + var sourceClassType = typeof(TSource); + var sourceProperties = + sourceClassType.GetPublicProperties() + .Where(x => x.CanRead).ToList(); - var destinationProperties = dstType.GetPublicProperties(); + var targetClassType = typeof(TTarget); + var targetProperties = + targetClassType.GetPublicProperties() + .Where(x => x.CanWrite).ToList(); - var newMappers = new List(); - - foreach (var srcProperty in srcType.GetPublicProperties().Where(x => x.CanRead)) + foreach (var sourceProperty in sourceProperties) { - var dstProperty = destinationProperties.FirstOrDefault(x => x.Name == srcProperty.Name); + var targetProperty = targetProperties.FirstOrDefault(x => x.Name == sourceProperty.Name); - if (dstProperty == null || !dstProperty.CanWrite) + if (targetProperty == null) { continue; } - var srcPropertyType = srcProperty.PropertyType; - var dstPropertyType = dstProperty.PropertyType; + var sourceType = sourceProperty.PropertyType; + var targetType = targetProperty.PropertyType; - if (srcPropertyType == dstPropertyType) + if (sourceType == targetType) { - newMappers.Add(new PropertyMapper(new PropertyAccessor(srcType, srcProperty), new PropertyAccessor(dstType, dstProperty))); + Mappers.Add(new PropertyMapper( + new PropertyAccessor(sourceClassType, sourceProperty), + new PropertyAccessor(targetClassType, targetProperty))); } - else + else if (targetType.Implements() && sourceType.Implements()) { - if (Convertibles.Contains(dstPropertyType)) - { - newMappers.Add(new ConversionPropertyMapper(new PropertyAccessor(srcType, srcProperty), new PropertyAccessor(dstType, dstProperty), dstPropertyType)); - } + Mappers.Add(new ConversionPropertyMapper( + new PropertyAccessor(sourceClassType, sourceProperty), + new PropertyAccessor(targetClassType, targetProperty), + targetType)); } } - - Mappers = newMappers.ToArray(); } - public static TDestination MapClass(TSource source, TDestination destination, CultureInfo culture) + public static TTarget MapClass(TSource source, TTarget destination, CultureInfo culture) { foreach (var mapper in Mappers) { @@ -152,29 +142,29 @@ namespace Squidex.Infrastructure.Reflection } } - public static TDestination Map(TSource source) + public static TTarget Map(TSource source) where TSource : class - where TDestination : class, new() + where TTarget : class, new() { - return Map(source, new TDestination(), CultureInfo.CurrentCulture); + return Map(source, new TTarget(), CultureInfo.CurrentCulture); } - public static TDestination Map(TSource source, TDestination destination) + public static TTarget Map(TSource source, TTarget target) where TSource : class - where TDestination : class + where TTarget : class { - return Map(source, destination, CultureInfo.CurrentCulture); + return Map(source, target, CultureInfo.CurrentCulture); } - public static TDestination Map(TSource source, TDestination destination, CultureInfo culture) + public static TTarget Map(TSource source, TTarget target, CultureInfo culture) where TSource : class - where TDestination : class + where TTarget : class { Guard.NotNull(source, nameof(source)); Guard.NotNull(culture, nameof(culture)); - Guard.NotNull(destination, nameof(destination)); + Guard.NotNull(target, nameof(target)); - return ClassMapper.MapClass(source, destination, culture); + return ClassMapper.MapClass(source, target, culture); } } } diff --git a/src/Squidex/Config/Domain/ReadServices.cs b/src/Squidex/Config/Domain/ReadServices.cs index 4fe5def8f..02fe4a954 100644 --- a/src/Squidex/Config/Domain/ReadServices.cs +++ b/src/Squidex/Config/Domain/ReadServices.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules.ActionHandlers; using Squidex.Domain.Apps.Core.HandleRules.Triggers; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Read.Apps.Services.Implementations; @@ -23,9 +24,7 @@ using Squidex.Domain.Apps.Read.Contents.GraphQL; using Squidex.Domain.Apps.Read.History; using Squidex.Domain.Apps.Read.Rules; using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.Schemas.Services; -using Squidex.Domain.Apps.Read.Schemas.Services.Implementations; -using Squidex.Domain.Apps.Read.State; +using Squidex.Domain.Apps.Read.State.Orleans; using Squidex.Domain.Users; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; @@ -55,15 +54,9 @@ namespace Squidex.Config.Domain services.AddSingleton() .As(); - services.AddSingleton() - .As(); - services.AddSingleton() .As(); - services.AddSingleton() - .As(); - services.AddSingleton() .As(); @@ -85,6 +78,9 @@ namespace Squidex.Config.Domain services.AddSingleton() .As(); + services.AddSingleton() + .As(); + services.AddSingleton() .As(); @@ -100,19 +96,6 @@ namespace Squidex.Config.Domain services.AddSingleton(c => new CompoundEventConsumer(c.GetServices().ToArray())) .As(); - services.AddSingleton(c => - new CompoundEventConsumer( - c.GetServices().OfType() - .Concat(c.GetRequiredService()).ToArray())) - .As(); - - services.AddSingleton(c => - new CompoundEventConsumer( - c.GetServices().OfType() - .Concat(c.GetRequiredService()) - .Concat(c.GetRequiredService()).ToArray())) - .As(); - services.AddSingleton(c => { var allEventConsumers = c.GetServices(); diff --git a/src/Squidex/Config/Domain/StoreServices.cs b/src/Squidex/Config/Domain/StoreServices.cs index 7e2f64f3b..d05763f0a 100644 --- a/src/Squidex/Config/Domain/StoreServices.cs +++ b/src/Squidex/Config/Domain/StoreServices.cs @@ -11,24 +11,17 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Apps.Repositories; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Read.Assets; using Squidex.Domain.Apps.Read.Assets.Repositories; using Squidex.Domain.Apps.Read.Contents.Repositories; using Squidex.Domain.Apps.Read.History; using Squidex.Domain.Apps.Read.History.Repositories; -using Squidex.Domain.Apps.Read.MongoDb.Apps; using Squidex.Domain.Apps.Read.MongoDb.Assets; using Squidex.Domain.Apps.Read.MongoDb.Contents; using Squidex.Domain.Apps.Read.MongoDb.History; using Squidex.Domain.Apps.Read.MongoDb.Rules; -using Squidex.Domain.Apps.Read.MongoDb.Schemas; using Squidex.Domain.Apps.Read.Rules.Repositories; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.Schemas.Repositories; -using Squidex.Domain.Apps.Read.Schemas.Services; using Squidex.Domain.Users; using Squidex.Domain.Users.MongoDb; using Squidex.Domain.Users.MongoDb.Infrastructure; @@ -74,7 +67,7 @@ namespace Squidex.Config.Domain .As() .As(); - services.AddSingleton(c => new MongoContentRepository(mongoContentDatabase, c.GetService())) + services.AddSingleton(c => new MongoContentRepository(mongoContentDatabase, c.GetService())) .As() .As(); @@ -87,16 +80,6 @@ namespace Squidex.Config.Domain .As() .As(); - services.AddSingleton(c => new MongoAppRepository(mongoDatabase)) - .As() - .As() - .As(); - - services.AddSingleton(c => new MongoSchemaRepository(mongoDatabase, c.GetRequiredService())) - .As() - .As() - .As(); - services.AddSingleton(c => new MongoAssetStatsRepository(mongoDatabase)) .As() .As() diff --git a/src/Squidex/Config/Identity/LazyClientStore.cs b/src/Squidex/Config/Identity/LazyClientStore.cs index 2fb3a33ca..9aa0df67c 100644 --- a/src/Squidex/Config/Identity/LazyClientStore.cs +++ b/src/Squidex/Config/Identity/LazyClientStore.cs @@ -14,22 +14,22 @@ using IdentityServer4.Models; using IdentityServer4.Stores; using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Read.Apps.Services; +using Squidex.Domain.Apps.Read; using Squidex.Infrastructure; namespace Squidex.Config.Identity { public class LazyClientStore : IClientStore { - private readonly IAppProvider appProvider; + private readonly IAppProvider appState; private readonly Dictionary staticClients = new Dictionary(StringComparer.OrdinalIgnoreCase); - public LazyClientStore(IOptions urlsOptions, IAppProvider appProvider) + public LazyClientStore(IOptions urlsOptions, IAppProvider appState) { Guard.NotNull(urlsOptions, nameof(urlsOptions)); - Guard.NotNull(appProvider, nameof(appProvider)); + Guard.NotNull(appState, nameof(appState)); - this.appProvider = appProvider; + this.appState = appState; CreateStaticClients(urlsOptions); } @@ -50,7 +50,7 @@ namespace Squidex.Config.Identity return null; } - var app = await appProvider.FindAppByNameAsync(token[0]); + var app = await appState.GetAppAsync(token[0]); var appClient = app?.Clients.GetOrDefault(token[1]); diff --git a/src/Squidex/Config/Orleans/ClientServices.cs b/src/Squidex/Config/Orleans/ClientServices.cs index 94359b1d2..5dcfc8460 100644 --- a/src/Squidex/Config/Orleans/ClientServices.cs +++ b/src/Squidex/Config/Orleans/ClientServices.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Orleans; using Orleans.Runtime.Configuration; @@ -24,8 +25,10 @@ namespace Squidex.Config.Orleans services.AddSingleton(c => { + var configuration = ClientConfiguration.LocalhostSilo(); + var client = new ClientBuilder() - .UseConfiguration(ClientConfiguration.LocalhostSilo()) + .UseConfiguration(ClientConfiguration.LocalhostSilo().WithJsonSerializer()) .AddApplicationPartsFromReferences(typeof(AppStateGrain).Assembly) .AddApplicationPartsFromReferences(typeof(EventConsumerGrain).Assembly) .AddApplicationPartsFromReferences(typeof(XmlRepositoryGrain).Assembly) @@ -36,5 +39,12 @@ namespace Squidex.Config.Orleans return client; }); } + + public static ClientConfiguration WithJsonSerializer(this ClientConfiguration config) + { + config.SerializationProviders.Add(typeof(CustomJsonSerializer).GetTypeInfo()); + + return config; + } } } diff --git a/src/Squidex/Config/Orleans/CustomJsonSerializer.cs b/src/Squidex/Config/Orleans/CustomJsonSerializer.cs new file mode 100644 index 000000000..7fd8334ce --- /dev/null +++ b/src/Squidex/Config/Orleans/CustomJsonSerializer.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// CustomJsonSerializer.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Newtonsoft.Json; +using Squidex.Config.Domain; +using Squidex.Infrastructure.Json.Orleans; + +namespace Squidex.Config.Orleans +{ + public class CustomJsonSerializer : JsonExternalSerializer + { + public CustomJsonSerializer() + : base(JsonSerializer.Create(SerializationServices.DefaultJsonSettings)) + { + } + } +} diff --git a/src/Squidex/Config/Orleans/CustomMongoDbStorageProvider.cs b/src/Squidex/Config/Orleans/CustomMongoDbStorageProvider.cs new file mode 100644 index 000000000..c643a0fd4 --- /dev/null +++ b/src/Squidex/Config/Orleans/CustomMongoDbStorageProvider.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// CustomMongoDbStorageProvider.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Newtonsoft.Json; +using Orleans.Providers; +using Orleans.Providers.MongoDB.StorageProviders; +using Squidex.Config.Domain; + +namespace Squidex.Config.Orleans +{ + public sealed class CustomMongoDbStorageProvider : MongoStorageProvider + { + protected override JsonSerializerSettings ReturnSerializerSettings(IProviderRuntime providerRuntime, IProviderConfiguration config) + { + return SerializationServices.DefaultJsonSettings; + } + } +} \ No newline at end of file diff --git a/src/Squidex/Config/Orleans/SiloExtensions.cs b/src/Squidex/Config/Orleans/SiloExtensions.cs index f251d2343..3a3c03668 100644 --- a/src/Squidex/Config/Orleans/SiloExtensions.cs +++ b/src/Squidex/Config/Orleans/SiloExtensions.cs @@ -6,8 +6,10 @@ // All rights reserved. // ========================================================================== +using System.Reflection; using Microsoft.Extensions.Configuration; using Orleans.Hosting; +using Orleans.Runtime.Configuration; namespace Squidex.Config.Orleans { @@ -22,5 +24,12 @@ namespace Squidex.Config.Orleans return builder; } + + public static ClusterConfiguration WithJsonSerializer(this ClusterConfiguration config) + { + config.Globals.SerializationProviders.Add(typeof(CustomJsonSerializer).GetTypeInfo()); + + return config; + } } } diff --git a/src/Squidex/Config/Orleans/SiloServices.cs b/src/Squidex/Config/Orleans/SiloServices.cs index efb66dcd9..8fd6cb792 100644 --- a/src/Squidex/Config/Orleans/SiloServices.cs +++ b/src/Squidex/Config/Orleans/SiloServices.cs @@ -9,26 +9,14 @@ using System.Linq; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; using Orleans; -using Orleans.Providers; -using Orleans.Providers.MongoDB.StorageProviders; using Orleans.Runtime.Configuration; -using Squidex.Config.Domain; using Squidex.Infrastructure.CQRS.Events.Orleans.Grains; namespace Squidex.Config.Orleans { public static class SiloServices { - public sealed class CustomMongoDbStorageProvider : MongoStorageProvider - { - protected override JsonSerializerSettings ReturnSerializerSettings(IProviderRuntime providerRuntime, IProviderConfiguration config) - { - return SerializationServices.DefaultJsonSettings; - } - } - public static void AddAppSiloServices(this IServiceCollection services, IConfiguration config) { var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration"); @@ -54,7 +42,7 @@ namespace Squidex.Config.Orleans clusterConfiguration.AddMongoDBStorageProvider("Default", c => { c.ConnectionString = mongoConfiguration; - c.CollectionPrefix = "Orleans_"; + c.CollectionPrefix = "States_"; c.DatabaseName = mongoDatabaseName; c.UseJsonFormat = true; }); diff --git a/src/Squidex/Controllers/Api/Apps/AppsController.cs b/src/Squidex/Controllers/Api/Apps/AppsController.cs index 31ddaa735..e101b85af 100644 --- a/src/Squidex/Controllers/Api/Apps/AppsController.cs +++ b/src/Squidex/Controllers/Api/Apps/AppsController.cs @@ -13,7 +13,7 @@ using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Squidex.Controllers.Api.Apps.Models; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Read.Apps.Repositories; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Write.Apps.Commands; using Squidex.Infrastructure.CQRS.Commands; @@ -31,15 +31,15 @@ namespace Squidex.Controllers.Api.Apps [SwaggerTag(nameof(Apps))] public sealed class AppsController : ControllerBase { - private readonly IAppRepository appRepository; + private readonly IAppProvider appProvider; private readonly IAppPlansProvider appPlansProvider; public AppsController(ICommandBus commandBus, - IAppRepository appRepository, + IAppProvider appProvider, IAppPlansProvider appPlansProvider) : base(commandBus) { - this.appRepository = appRepository; + this.appProvider = appProvider; this.appPlansProvider = appPlansProvider; } @@ -61,7 +61,7 @@ namespace Squidex.Controllers.Api.Apps { var subject = HttpContext.User.OpenIdSubject(); - var apps = await appRepository.QueryAllAsync(subject); + var apps = await appProvider.GetUserApps(subject); var response = apps.Select(a => { diff --git a/src/Squidex/Controllers/Api/Assets/AssetsController.cs b/src/Squidex/Controllers/Api/Assets/AssetsController.cs index 54c0ae939..a95887bef 100644 --- a/src/Squidex/Controllers/Api/Assets/AssetsController.cs +++ b/src/Squidex/Controllers/Api/Assets/AssetsController.cs @@ -102,8 +102,8 @@ namespace Squidex.Controllers.Api.Assets } } - var taskForItems = assetRepository.QueryAsync(AppId, mimeTypeList, idsList, query, take, skip); - var taskForCount = assetRepository.CountAsync(AppId, mimeTypeList, idsList, query); + var taskForItems = assetRepository.QueryAsync(App.Id, mimeTypeList, idsList, query, take, skip); + var taskForCount = assetRepository.CountAsync(App.Id, mimeTypeList, idsList, query); await Task.WhenAll(taskForItems, taskForCount); diff --git a/src/Squidex/Controllers/Api/EventConsumers/EventConsumersController.cs b/src/Squidex/Controllers/Api/EventConsumers/EventConsumersController.cs index f020395aa..edd14846a 100644 --- a/src/Squidex/Controllers/Api/EventConsumers/EventConsumersController.cs +++ b/src/Squidex/Controllers/Api/EventConsumers/EventConsumersController.cs @@ -38,7 +38,7 @@ namespace Squidex.Controllers.Api.EventConsumers { var entities = await eventConsumerRegistryGrain.GetConsumersAsync(); - var models = entities.Select(x => SimpleMapper.Map(x, new EventConsumerDto())).ToList(); + var models = entities.Value.Select(x => SimpleMapper.Map(x, new EventConsumerDto())).ToList(); return Ok(models); } diff --git a/src/Squidex/Controllers/Api/Schemas/SchemasController.cs b/src/Squidex/Controllers/Api/Schemas/SchemasController.cs index b31a203db..8e218395b 100644 --- a/src/Squidex/Controllers/Api/Schemas/SchemasController.cs +++ b/src/Squidex/Controllers/Api/Schemas/SchemasController.cs @@ -15,8 +15,8 @@ using NSwag.Annotations; using Squidex.Controllers.Api.Schemas.Models; using Squidex.Controllers.Api.Schemas.Models.Converters; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.Schemas.Repositories; using Squidex.Domain.Apps.Write.Schemas.Commands; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.Reflection; @@ -33,12 +33,12 @@ namespace Squidex.Controllers.Api.Schemas [SwaggerTag(nameof(Schemas))] public sealed class SchemasController : ControllerBase { - private readonly ISchemaRepository schemaRepository; + private readonly IAppProvider appProvider; - public SchemasController(ICommandBus commandBus, ISchemaRepository schemaRepository) + public SchemasController(ICommandBus commandBus, IAppProvider appProvider) : base(commandBus) { - this.schemaRepository = schemaRepository; + this.appProvider = appProvider; } /// @@ -56,7 +56,7 @@ namespace Squidex.Controllers.Api.Schemas [ApiCosts(0)] public async Task GetSchemas(string app) { - var schemas = await schemaRepository.QueryAllAsync(AppId); + var schemas = await appProvider.GetSchemasAsync(AppName); var response = schemas.Select(s => s.ToModel()).ToList(); @@ -83,11 +83,11 @@ namespace Squidex.Controllers.Api.Schemas if (Guid.TryParse(name, out var id)) { - entity = await schemaRepository.FindSchemaAsync(id); + entity = await appProvider.GetSchemaAsync(AppName, id); } else { - entity = await schemaRepository.FindSchemaAsync(AppId, name); + entity = await appProvider.GetSchemaAsync(AppName, name); } if (entity == null || entity.IsDeleted) diff --git a/src/Squidex/Controllers/ContentApi/ContentSwaggerController.cs b/src/Squidex/Controllers/ContentApi/ContentSwaggerController.cs index b8a2899ab..fe4d9fc84 100644 --- a/src/Squidex/Controllers/ContentApi/ContentSwaggerController.cs +++ b/src/Squidex/Controllers/ContentApi/ContentSwaggerController.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Squidex.Controllers.ContentApi.Generator; -using Squidex.Domain.Apps.Read.Schemas.Repositories; +using Squidex.Domain.Apps.Read; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Pipeline; @@ -21,13 +21,14 @@ namespace Squidex.Controllers.ContentApi [SwaggerIgnore] public sealed class ContentSwaggerController : ControllerBase { - private readonly ISchemaRepository schemaRepository; + private readonly IAppProvider appProvider; private readonly SchemasSwaggerGenerator schemasSwaggerGenerator; - public ContentSwaggerController(ICommandBus commandBus, ISchemaRepository schemaRepository, SchemasSwaggerGenerator schemasSwaggerGenerator) + public ContentSwaggerController(ICommandBus commandBus, IAppProvider appProvider, SchemasSwaggerGenerator schemasSwaggerGenerator) : base(commandBus) { - this.schemaRepository = schemaRepository; + this.appProvider = appProvider; + this.schemasSwaggerGenerator = schemasSwaggerGenerator; } @@ -46,7 +47,7 @@ namespace Squidex.Controllers.ContentApi [ApiCosts(0)] public async Task GetSwagger(string app) { - var schemas = await schemaRepository.QueryAllAsync(App.Id); + var schemas = await appProvider.GetSchemasAsync(AppName); var swaggerDocument = await schemasSwaggerGenerator.Generate(App, schemas); diff --git a/src/Squidex/Controllers/ControllerBase.cs b/src/Squidex/Controllers/ControllerBase.cs index db2c972e1..4522d1162 100644 --- a/src/Squidex/Controllers/ControllerBase.cs +++ b/src/Squidex/Controllers/ControllerBase.cs @@ -41,12 +41,9 @@ namespace Squidex.Controllers } } - protected Guid AppId + protected string AppName { - get - { - return App.Id; - } + get { return App.Name; } } } } diff --git a/src/Squidex/Pipeline/AppApiFilter.cs b/src/Squidex/Pipeline/AppApiFilter.cs index 5181cf552..8adc14336 100644 --- a/src/Squidex/Pipeline/AppApiFilter.cs +++ b/src/Squidex/Pipeline/AppApiFilter.cs @@ -17,13 +17,13 @@ namespace Squidex.Pipeline { public sealed class AppApiFilter : IAsyncActionFilter { - private readonly IAppProvider appProvider; + private readonly Domain.Apps.Read.IAppProvider appState; private readonly IAppPlansProvider appPlanProvider; private readonly IUsageTracker usageTracker; - public AppApiFilter(IAppProvider appProvider, IAppPlansProvider appPlanProvider, IUsageTracker usageTracker) + public AppApiFilter(Domain.Apps.Read.IAppProvider appState, IAppPlansProvider appPlanProvider, IUsageTracker usageTracker) { - this.appProvider = appProvider; + this.appState = appState; this.appPlanProvider = appPlanProvider; this.usageTracker = usageTracker; @@ -35,7 +35,7 @@ namespace Squidex.Pipeline if (!string.IsNullOrWhiteSpace(appName)) { - var app = await appProvider.FindAppByNameAsync(appName); + var app = await appState.GetAppAsync(appName); if (app == null) { diff --git a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs index b8b2395f6..fbceb8398 100644 --- a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs +++ b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs @@ -1,5 +1,5 @@ // ========================================================================== -// EnrichWithSchemaIdHandler.cs +// EnrichWithSchemaIdCommandMiddleware.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -9,8 +9,8 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.Schemas.Services; using Squidex.Domain.Apps.Write; using Squidex.Domain.Apps.Write.Schemas; using Squidex.Infrastructure; @@ -20,12 +20,12 @@ namespace Squidex.Pipeline.CommandMiddlewares { public sealed class EnrichWithSchemaIdCommandMiddleware : ICommandMiddleware { - private readonly ISchemaProvider schemas; + private readonly IAppProvider appProvider; private readonly IActionContextAccessor actionContextAccessor; - public EnrichWithSchemaIdCommandMiddleware(ISchemaProvider schemas, IActionContextAccessor actionContextAccessor) + public EnrichWithSchemaIdCommandMiddleware(IAppProvider appProvider, IActionContextAccessor actionContextAccessor) { - this.schemas = schemas; + this.appProvider = appProvider; this.actionContextAccessor = actionContextAccessor; } @@ -44,11 +44,11 @@ namespace Squidex.Pipeline.CommandMiddlewares if (Guid.TryParse(schemaName, out var id)) { - schema = await schemas.FindSchemaByIdAsync(id); + schema = await appProvider.GetSchemaAsync(schemaCommand.AppId.Name, id); } else { - schema = await schemas.FindSchemaByNameAsync(schemaCommand.AppId.Id, schemaName); + schema = await appProvider.GetSchemaAsync(schemaCommand.AppId.Name, schemaName); } if (schema == null) diff --git a/src/Squidex/Program.cs b/src/Squidex/Program.cs index 6ebe15b58..e916793d9 100644 --- a/src/Squidex/Program.cs +++ b/src/Squidex/Program.cs @@ -27,7 +27,7 @@ namespace Squidex .AddApplicationPartsFromReferences(typeof(AppStateGrain).Assembly) .AddApplicationPartsFromReferences(typeof(EventConsumerGrain).Assembly) .AddApplicationPartsFromReferences(typeof(XmlRepositoryGrain).Assembly) - .UseConfiguration(ClusterConfiguration.LocalhostPrimarySilo(33333)) + .UseConfiguration(ClusterConfiguration.LocalhostPrimarySilo(33333).WithJsonSerializer()) .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureServices((context, services) => { diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Apps/CachingAppProviderTests.cs b/tests/Squidex.Domain.Apps.Read.Tests/Apps/CachingAppProviderTests.cs deleted file mode 100644 index 7f9a5313d..000000000 --- a/tests/Squidex.Domain.Apps.Read.Tests/Apps/CachingAppProviderTests.cs +++ /dev/null @@ -1,131 +0,0 @@ -// ========================================================================== -// CachingAppProviderTests.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using FakeItEasy; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Options; -using Squidex.Domain.Apps.Events.Apps; -using Squidex.Domain.Apps.Read.Apps.Repositories; -using Squidex.Domain.Apps.Read.Apps.Services.Implementations; -using Squidex.Infrastructure; -using Squidex.Infrastructure.CQRS.Events; -using Xunit; - -namespace Squidex.Domain.Apps.Read.Apps -{ - public class CachingAppProviderTests - { - private readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); - private readonly IAppRepository repository = A.Fake(); - private readonly CachingAppProvider sut; - private readonly IAppEntity appV1 = A.Dummy(); - private readonly IAppEntity appV2 = A.Dummy(); - private readonly NamedId appId = new NamedId(Guid.NewGuid(), "my-app"); - - public CachingAppProviderTests() - { - A.CallTo(() => appV1.Id).Returns(appId.Id); - A.CallTo(() => appV1.Name).Returns(appId.Name); - - A.CallTo(() => appV2.Id).Returns(appId.Id); - A.CallTo(() => appV2.Name).Returns(appId.Name); - - sut = new CachingAppProvider(cache, repository); - } - - [Fact] - public void Should_return_empty_for_events_filter() - { - Assert.Equal(string.Empty, sut.EventsFilter); - } - - [Fact] - public void Should_return_empty_for_name() - { - Assert.Equal(typeof(CachingAppProvider).Name, sut.Name); - } - - [Fact] - public void Should_do_nothing_when_clearing() - { - Assert.NotNull(sut.ClearAsync()); - } - - [Fact] - public async Task Should_also_retrieve_app_by_name_if_retrieved_by_id_before() - { - A.CallTo(() => repository.FindAppAsync(appId.Id)) - .Returns(appV1); - - await ProvideAppByIdAsync(appV1); - await ProvideAppByNameAsync(appV1); - - A.CallTo(() => repository.FindAppAsync(appId.Id)).MustHaveHappened(); - A.CallTo(() => repository.FindAppAsync(appId.Name)).MustNotHaveHappened(); - } - - [Fact] - public async Task Should_also_retrieve_app_by_id_if_retrieved_by_name_before() - { - A.CallTo(() => repository.FindAppAsync(appId.Name)) - .Returns(appV1); - - await ProvideAppByNameAsync(appV1); - await ProvideAppByIdAsync(appV1); - - A.CallTo(() => repository.FindAppAsync(appId.Id)).MustNotHaveHappened(); - A.CallTo(() => repository.FindAppAsync(appId.Name)).MustHaveHappened(); - } - - [Fact] - public async Task Should_clear_cache_for_id_after_update_event() - { - A.CallTo(() => repository.FindAppAsync(appId.Id)) - .Returns(appV2); - A.CallTo(() => repository.FindAppAsync(appId.Id)) - .Returns(appV1).Once(); - - await ProvideAppByIdAsync(appV1); - - sut.On(Envelope.Create(new AppLanguageAdded { AppId = appId }).To()).Wait(); - - await ProvideAppByIdAsync(appV2); - - A.CallTo(() => repository.FindAppAsync(appId.Id)).MustHaveHappened(Repeated.Exactly.Times(2)); - } - - [Fact] - public async Task Should_clear_cache_for_name_after_update_event() - { - A.CallTo(() => repository.FindAppAsync(appId.Name)) - .Returns(appV2); - A.CallTo(() => repository.FindAppAsync(appId.Name)) - .Returns(appV1).Once(); - - await ProvideAppByNameAsync(appV1); - - sut.On(Envelope.Create(new AppLanguageAdded { AppId = appId }).To()).Wait(); - - await ProvideAppByNameAsync(appV2); - - A.CallTo(() => repository.FindAppAsync(appId.Name)).MustHaveHappened(Repeated.Exactly.Times(2)); - } - - private async Task ProvideAppByIdAsync(IAppEntity app) - { - Assert.Equal(app, await sut.FindAppByIdAsync(appId.Id)); - } - - private async Task ProvideAppByNameAsync(IAppEntity app) - { - Assert.Equal(app, await sut.FindAppByNameAsync(appId.Name)); - } - } -} diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs b/tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs index a32193a3d..4efc295d0 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs +++ b/tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs @@ -17,6 +17,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NodaTime.Extensions; using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Read.Apps; @@ -77,7 +78,7 @@ namespace Squidex.Domain.Apps.Read.Contents new GeolocationFieldProperties())); A.CallTo(() => app.Id).Returns(appId); - A.CallTo(() => app.PartitionResolver).Returns(x => InvariantPartitioning.Instance); + A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.Build(Language.DE)); A.CallTo(() => schema.Id).Returns(schemaId); A.CallTo(() => schema.Name).Returns(schemaDef.Name); diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs b/tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs index f256b7d47..f7ffe79eb 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs +++ b/tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs @@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Read.Contents var app = A.Dummy(); A.CallTo(() => app.Id).Returns(Guid.NewGuid()); A.CallTo(() => app.Version).Returns(3); - A.CallTo(() => app.PartitionResolver).Returns(languagesConfig.ToResolver()); + A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig); edmModel = builder.BuildEdmModel(schema, app); } diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandMiddlewareTests.cs index fcbaa5d40..c582046fb 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandMiddlewareTests.cs @@ -9,6 +9,7 @@ using System; using System.Threading.Tasks; using FakeItEasy; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Read.Apps.Services.Implementations; @@ -37,7 +38,7 @@ namespace Squidex.Domain.Apps.Write.Apps { app = new AppDomainObject(AppId, -1); - A.CallTo(() => appProvider.FindAppByNameAsync(AppName)) + A.CallTo(() => appProvider.GetAppAsync(AppName)) .Returns((IAppEntity)null); A.CallTo(() => userResolver.FindByIdAsync(contributorId)) diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppTests.cs index 758187e8d..891c014ef 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppTests.cs +++ b/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppTests.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Write.Apps.Commands; @@ -26,7 +27,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards public GuardAppTests() { - A.CallTo(() => apps.FindAppByNameAsync("new-app")) + A.CallTo(() => apps.GetAppAsync("new-app")) .Returns(Task.FromResult(null)); A.CallTo(() => users.FindByIdAsync(A.Ignored)) @@ -39,7 +40,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards [Fact] public Task CanCreate_should_throw_exception_if_name_already_in_use() { - A.CallTo(() => apps.FindAppByNameAsync("new-app")) + A.CallTo(() => apps.GetAppAsync("new-app")) .Returns(A.Fake()); var command = new CreateApp { Name = "new-app" }; diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs index 3ea088770..e70d9a977 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs @@ -15,8 +15,8 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Scripting; +using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Read.Assets.Repositories; using Squidex.Domain.Apps.Read.Contents.Repositories; using Squidex.Domain.Apps.Read.Schemas; @@ -73,9 +73,8 @@ namespace Squidex.Domain.Apps.Write.Contents sut = new ContentCommandMiddleware(Handler, appProvider, A.Dummy(), schemas, scriptEngine, A.Dummy()); A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig); - A.CallTo(() => app.PartitionResolver).Returns(languagesConfig.ToResolver()); - A.CallTo(() => appProvider.FindAppByIdAsync(AppId)).Returns(app); + A.CallTo(() => appProvider.GetAppAsync(AppName)).Returns(app); A.CallTo(() => schema.SchemaDef).Returns(schemaDef); A.CallTo(() => schema.ScriptCreate).Returns("");