From b8d6590e8bec308f505c5e3761eee2c81abe6f84 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 6 Sep 2016 23:09:13 +0200 Subject: [PATCH] A lot progress --- .../Configurations/InfrastructureModule.cs | 13 +- src/PinkParrot/Configurations/ReadModule.cs | 3 +- src/PinkParrot/Configurations/Serializers.cs | 17 +- src/PinkParrot/Modules/Api/BaseController.cs | 23 --- .../Api/Schemas/SchemaFieldsController.cs | 2 +- .../Modules/Api/Schemas/SchemasController.cs | 41 +++-- .../{SchemaListModel.cs => SchemasDto.cs} | 2 +- src/PinkParrot/Modules/ControllerBase.cs | 40 ++++ .../EnrichWithAggregateIdHandler.cs | 31 +++- .../EnrichWithTenantIdHandler.cs | 20 +- src/PinkParrot/Pipeline/ITenantFeature.cs | 17 ++ src/PinkParrot/Pipeline/TenantMiddleware.cs | 45 +++++ src/PinkParrot/Startup.cs | 4 +- .../PinkParrot.Core/Schema/Json/SchemaDto.cs | 68 +++++++ .../PinkParrot.Core/Schema/ModelField.cs | 20 -- .../Schema/ModelFieldFactory.cs | 9 +- .../Schema/ModelField_Generic.cs | 5 +- .../PinkParrot.Core/Schema/ModelSchema.cs | 59 +++--- .../PinkParrot.Core/Schema/NumberField.cs | 4 +- .../Schema/NumberFieldProperties.cs | 27 ++- .../CQRS/Commands/IDomainObjectRepository.cs | 2 +- .../CQRS/Commands/InMemoryCommandBus.cs | 5 + .../CQRS/DomainObject.cs | 15 +- .../CQRS/EventStore/DefaultNameResolver.cs | 11 +- .../CQRS/EventStore/EventStoreBus.cs | 101 +++++----- .../EventStoreDomainObjectRepository.cs | 2 +- .../CQRS/EventStore/IStreamPositionStorage.cs | 6 +- .../CQRS/Events/IEventConsumer.cs | 5 +- .../Reflection/ReflectionExtensions.cs | 68 +++++++ .../Reflection/SimpleMapper.cs | 4 +- .../PinkParrot.Read/IEntity.cs | 14 -- .../PinkParrot.Read/IModelSchemaRM.cs | 11 -- .../Repositories/EntityWithSchema.cs | 26 +++ .../{Models => Repositories}/IEntity.cs | 2 +- .../Repositories/IModelSchemaEntity.cs | 14 ++ .../Repositories/IModelSchemaRepository.cs | 9 +- .../{Models => Repositories}/ITenantEntity.cs | 4 +- .../Implementations/EntityMapper.cs | 19 +- .../MongoModelSchemaEntity.cs} | 11 +- .../MongoModelSchemaListRepository.cs | 66 ------- .../MongoModelSchemaRepository.cs | 172 ++++++++++++++++++ .../Services/IModelSchemaProvider.cs | 2 +- .../Services/ITenantProvider.cs | 2 +- .../Implementations/ModelSchemaProvider.cs | 69 ++++++- .../Implementations/MongoPositions.cs | 5 +- .../MongoStreamPositionsStorage.cs | 31 +--- .../Implementations/TenantProvider.cs | 4 +- .../PinkParrot.Read/project.json | 1 + .../Schema/Commands/AddModelField.cs | 2 +- .../Schema/Commands/DeleteModelField.cs | 2 +- .../Schema/Commands/DeleteModelSchema.cs | 2 +- .../Schema/Commands/DisableModelField.cs | 4 +- .../Schema/Commands/EnableModelField.cs | 2 +- .../Schema/Commands/HideModelField.cs | 2 +- .../Schema/Commands/ShowModelField.cs | 2 +- .../Schema/Commands/UpdateModelField.cs | 2 +- .../Schema/Commands/UpdateModelSchema.cs | 2 +- .../Schema/ModelSchemaCommandHandler.cs | 2 +- .../Schema/ModelSchemaDomainObject.cs | 25 +-- .../PinkParrot.Write/TenantCommand.cs | 2 +- 60 files changed, 820 insertions(+), 360 deletions(-) delete mode 100644 src/PinkParrot/Modules/Api/BaseController.cs rename src/PinkParrot/Modules/Api/Schemas/{SchemaListModel.cs => SchemasDto.cs} (95%) create mode 100644 src/PinkParrot/Modules/ControllerBase.cs create mode 100644 src/PinkParrot/Pipeline/ITenantFeature.cs create mode 100644 src/PinkParrot/Pipeline/TenantMiddleware.cs create mode 100644 src/pinkparrot_core/PinkParrot.Core/Schema/Json/SchemaDto.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Reflection/ReflectionExtensions.cs delete mode 100644 src/pinkparrot_read/PinkParrot.Read/IEntity.cs delete mode 100644 src/pinkparrot_read/PinkParrot.Read/IModelSchemaRM.cs create mode 100644 src/pinkparrot_read/PinkParrot.Read/Repositories/EntityWithSchema.cs rename src/pinkparrot_read/PinkParrot.Read/{Models => Repositories}/IEntity.cs (92%) create mode 100644 src/pinkparrot_read/PinkParrot.Read/Repositories/IModelSchemaEntity.cs rename src/pinkparrot_read/PinkParrot.Read/{Models => Repositories}/ITenantEntity.cs (82%) rename src/pinkparrot_read/PinkParrot.Read/{Models/ModelSchemaListRM.cs => Repositories/Implementations/MongoModelSchemaEntity.cs} (80%) delete mode 100644 src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaListRepository.cs create mode 100644 src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaRepository.cs diff --git a/src/PinkParrot/Configurations/InfrastructureModule.cs b/src/PinkParrot/Configurations/InfrastructureModule.cs index a0dde6e87..c5a62eff8 100644 --- a/src/PinkParrot/Configurations/InfrastructureModule.cs +++ b/src/PinkParrot/Configurations/InfrastructureModule.cs @@ -1,5 +1,5 @@ // ========================================================================== -// InfrastructureDependencies.cs +// InfrastructureModule.cs // PinkParrot Headless CMS // ========================================================================== // Copyright (c) PinkParrot Group @@ -13,10 +13,12 @@ using EventStore.ClientAPI.SystemData; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; using PinkParrot.Infrastructure.CQRS.Autofac; using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.CQRS.EventStore; +using PinkParrot.Pipeline; using PinkParrot.Read.Services.Implementations; namespace PinkParrot.Configurations @@ -63,7 +65,7 @@ namespace PinkParrot.Configurations .As() .SingleInstance(); - builder.RegisterType() + builder.RegisterInstance(new DefaultNameResolver("pinkparrot")) .As() .SingleInstance(); @@ -89,7 +91,12 @@ namespace PinkParrot.Configurations { public static void UseAppEventBus(this IApplicationBuilder app) { - app.ApplicationServices.GetService(typeof(EventStoreBus)); + app.ApplicationServices.GetService().Subscribe("pinkparrot"); + } + + public static void UseAppTenants(this IApplicationBuilder app) + { + app.UseMiddleware(); } } } diff --git a/src/PinkParrot/Configurations/ReadModule.cs b/src/PinkParrot/Configurations/ReadModule.cs index 08d793128..1bbdd2f02 100644 --- a/src/PinkParrot/Configurations/ReadModule.cs +++ b/src/PinkParrot/Configurations/ReadModule.cs @@ -25,9 +25,10 @@ namespace PinkParrot.Configurations builder.RegisterType() .As() + .As() .SingleInstance(); - builder.RegisterType() + builder.RegisterType() .As() .As() .SingleInstance(); diff --git a/src/PinkParrot/Configurations/Serializers.cs b/src/PinkParrot/Configurations/Serializers.cs index 0ae7c7c56..cf4e1d146 100644 --- a/src/PinkParrot/Configurations/Serializers.cs +++ b/src/PinkParrot/Configurations/Serializers.cs @@ -7,15 +7,12 @@ // ========================================================================== using System.Reflection; +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using PinkParrot.Core.Schema; using PinkParrot.Infrastructure.CQRS.EventStore; using PinkParrot.Infrastructure.Json; -using IMvcBuilder = Microsoft.Extensions.DependencyInjection.IMvcBuilder; -using IServiceCollection = Microsoft.Extensions.DependencyInjection.IServiceCollection; -using MvcJsonMvcBuilderExtensions = Microsoft.Extensions.DependencyInjection.MvcJsonMvcBuilderExtensions; -using ServiceCollectionServiceExtensions = Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions; namespace PinkParrot.Configurations { @@ -40,18 +37,16 @@ namespace PinkParrot.Configurations public static void AddEventFormatter(this IServiceCollection services) { - var fieldFactory = - new ModelFieldFactory() - .AddFactory(id => new NumberField(id)); + var fieldFactory = new ModelFieldFactory(); - ServiceCollectionServiceExtensions.AddSingleton(services, t => CreateSettings()); - ServiceCollectionServiceExtensions.AddSingleton(services, fieldFactory); - ServiceCollectionServiceExtensions.AddSingleton(services); + services.AddSingleton(t => CreateSettings()); + services.AddSingleton(fieldFactory); + services.AddSingleton(); } public static void AddAppSerializers(this IMvcBuilder mvc) { - MvcJsonMvcBuilderExtensions.AddJsonOptions(mvc, options => + mvc.AddJsonOptions(options => { ConfigureJson(options.SerializerSettings); }); diff --git a/src/PinkParrot/Modules/Api/BaseController.cs b/src/PinkParrot/Modules/Api/BaseController.cs deleted file mode 100644 index cd7e35351..000000000 --- a/src/PinkParrot/Modules/Api/BaseController.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ========================================================================== -// BaseController.cs -// PinkParrot Headless CMS -// ========================================================================== -// Copyright (c) PinkParrot Group -// All rights reserved. -// ========================================================================== - -using Microsoft.AspNetCore.Mvc; -using PinkParrot.Infrastructure.CQRS.Commands; - -namespace PinkParrot.Modules.Api -{ - public abstract class BaseController : Controller - { - public ICommandBus CommandBus { get; } - - protected BaseController(ICommandBus commandBus) - { - CommandBus = commandBus; - } - } -} diff --git a/src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs b/src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs index f8dc11541..d7eb58d81 100644 --- a/src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs +++ b/src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs @@ -17,7 +17,7 @@ using Swashbuckle.SwaggerGen.Annotations; namespace PinkParrot.Modules.Api.Schemas { - public class SchemasFieldsController : BaseController + public class SchemasFieldsController : ControllerBase { public SchemasFieldsController(ICommandBus commandBus) : base(commandBus) diff --git a/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs b/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs index f53113173..2a439de86 100644 --- a/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs +++ b/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs @@ -12,10 +12,10 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using PinkParrot.Core.Schema; +using PinkParrot.Core.Schema.Json; using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.Reflection; using PinkParrot.Read.Repositories; -using PinkParrot.Read.Services; using PinkParrot.Write.Schema.Commands; using Swashbuckle.SwaggerGen.Annotations; @@ -23,17 +23,14 @@ using Swashbuckle.SwaggerGen.Annotations; namespace PinkParrot.Modules.Api.Schemas { - public class SchemasController : BaseController + public class SchemasController : ControllerBase { private readonly IModelSchemaRepository modelSchemaRepository; - private readonly ITenantProvider tenantProvider; - - public SchemasController(ICommandBus commandBus, ITenantProvider tenantProvider, IModelSchemaRepository modelSchemaRepository) + + public SchemasController(ICommandBus commandBus, IModelSchemaRepository modelSchemaRepository) : base(commandBus) { this.modelSchemaRepository = modelSchemaRepository; - - this.tenantProvider = tenantProvider; } /// @@ -42,12 +39,34 @@ namespace PinkParrot.Modules.Api.Schemas [HttpGet] [Route("schemas/")] [SwaggerOperation(Tags = new[] { "Schemas" })] - public async Task> Query() + [ProducesResponseType(typeof(List), 200)] + public async Task> Query() + { + var schemas = await modelSchemaRepository.QueryAllAsync(TenantId); + + return schemas.Select(s => SimpleMapper.Map(s, new SchemasDto())).ToList(); + } + + /// + /// Gets the schema with the specified name. + /// + /// The name of the schema. + /// Schema returned + /// Schema not found + [HttpGet] + [Route("schemas/{name}/")] + [SwaggerOperation(Tags = new[] { "Schemas" })] + [ProducesResponseType(typeof(SchemaDto), 200)] + public async Task Get(string name) { - var tenantId = await tenantProvider.ProvideTenantIdByDomainAsync(Request.Host.ToString()); - var schemes = await modelSchemaRepository.QueryAllAsync(tenantId); + var entity = await modelSchemaRepository.FindSchemaAsync(TenantId, name); + + if (entity == null) + { + return NotFound(); + } - return schemes.Select(s => SimpleMapper.Map(s, new SchemaListModel())).ToList(); + return Ok(SchemaDto.Create(entity.Schema)); } /// diff --git a/src/PinkParrot/Modules/Api/Schemas/SchemaListModel.cs b/src/PinkParrot/Modules/Api/Schemas/SchemasDto.cs similarity index 95% rename from src/PinkParrot/Modules/Api/Schemas/SchemaListModel.cs rename to src/PinkParrot/Modules/Api/Schemas/SchemasDto.cs index b1ca9bd0a..7ae7475a8 100644 --- a/src/PinkParrot/Modules/Api/Schemas/SchemaListModel.cs +++ b/src/PinkParrot/Modules/Api/Schemas/SchemasDto.cs @@ -11,7 +11,7 @@ using System.ComponentModel.DataAnnotations; namespace PinkParrot.Modules.Api.Schemas { - public class SchemaListModel + public class SchemasDto { [Required] public Guid Id { get; set; } diff --git a/src/PinkParrot/Modules/ControllerBase.cs b/src/PinkParrot/Modules/ControllerBase.cs new file mode 100644 index 000000000..3cc00c7c7 --- /dev/null +++ b/src/PinkParrot/Modules/ControllerBase.cs @@ -0,0 +1,40 @@ +// ========================================================================== +// ControllerBase.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using Microsoft.AspNetCore.Mvc; +using PinkParrot.Infrastructure.CQRS.Commands; +using PinkParrot.Pipeline; + +namespace PinkParrot.Modules +{ + public abstract class ControllerBase : Controller + { + public ICommandBus CommandBus { get; } + + protected ControllerBase(ICommandBus commandBus) + { + CommandBus = commandBus; + } + + public Guid TenantId + { + get + { + var tenantFeature = HttpContext.Features.Get(); + + if (tenantFeature == null) + { + throw new InvalidOperationException("Not in a tenant context"); + } + + return tenantFeature.TenantId; + } + } + } +} diff --git a/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithAggregateIdHandler.cs b/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithAggregateIdHandler.cs index 6d753d575..c3c57be97 100644 --- a/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithAggregateIdHandler.cs +++ b/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithAggregateIdHandler.cs @@ -9,8 +9,11 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Infrastructure; +using PinkParrot.Infrastructure; using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Read.Services; +using PinkParrot.Write; +using PinkParrot.Write.Schema; // ReSharper disable InvertIf @@ -32,16 +35,32 @@ namespace PinkParrot.Pipeline.CommandHandlers { var aggregateCommand = context.Command as IAggregateCommand; - if (aggregateCommand != null && aggregateCommand.AggregateId == Guid.Empty) + if (aggregateCommand == null || aggregateCommand.AggregateId != Guid.Empty) { - var routeValues = actionContextAccessor.ActionContext.RouteData.Values; + return false; + } + + var tenantCommand = context.Command as ITenantCommand; - if (routeValues.ContainsKey("name")) - { - var schemeName = routeValues["name"]; + if (tenantCommand == null) + { + return false; + } + + var routeValues = actionContextAccessor.ActionContext.RouteData.Values; - aggregateCommand.AggregateId = await modelSchemaProvider.FindSchemaIdByNameAsync(schemeName.ToString()); + if (routeValues.ContainsKey("name")) + { + var schemaName = routeValues["name"].ToString(); + + var id = await modelSchemaProvider.FindSchemaIdByNameAsync(tenantCommand.TenantId, schemaName); + + if (!id.HasValue) + { + throw new DomainObjectNotFoundException(schemaName, typeof(ModelSchemaDomainObject)); } + + aggregateCommand.AggregateId = id.Value; } return false; diff --git a/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithTenantIdHandler.cs b/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithTenantIdHandler.cs index 6b2502bca..3128dc7ae 100644 --- a/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithTenantIdHandler.cs +++ b/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithTenantIdHandler.cs @@ -6,10 +6,10 @@ // All rights reserved. // ========================================================================== +using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using PinkParrot.Infrastructure.CQRS.Commands; -using PinkParrot.Read.Services; using PinkParrot.Write; // ReSharper disable InvertIf @@ -18,28 +18,30 @@ namespace PinkParrot.Pipeline.CommandHandlers { public sealed class EnrichWithTenantIdHandler : ICommandHandler { - private readonly ITenantProvider tenantProvider; private readonly IHttpContextAccessor httpContextAccessor; - public EnrichWithTenantIdHandler(ITenantProvider tenantProvider, IHttpContextAccessor httpContextAccessor) + public EnrichWithTenantIdHandler(IHttpContextAccessor httpContextAccessor) { - this.tenantProvider = tenantProvider; - this.httpContextAccessor = httpContextAccessor; } - public async Task HandleAsync(CommandContext context) + public Task HandleAsync(CommandContext context) { var tenantCommand = context.Command as ITenantCommand; if (tenantCommand != null) { - var domain = httpContextAccessor.HttpContext.Request.Host.ToString(); + var tenantFeature = httpContextAccessor.HttpContext.Features.Get(); + + if (tenantFeature == null) + { + throw new InvalidOperationException("Cannot reslolve tenant"); + } - tenantCommand.TenantId = await tenantProvider.ProvideTenantIdByDomainAsync(domain); + tenantCommand.TenantId = tenantFeature.TenantId; } - return false; + return Task.FromResult(false); } } } diff --git a/src/PinkParrot/Pipeline/ITenantFeature.cs b/src/PinkParrot/Pipeline/ITenantFeature.cs new file mode 100644 index 000000000..465169d7a --- /dev/null +++ b/src/PinkParrot/Pipeline/ITenantFeature.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// ITenantFeature.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Pipeline +{ + public interface ITenantFeature + { + Guid TenantId { get; } + } +} diff --git a/src/PinkParrot/Pipeline/TenantMiddleware.cs b/src/PinkParrot/Pipeline/TenantMiddleware.cs new file mode 100644 index 000000000..dbeb1e105 --- /dev/null +++ b/src/PinkParrot/Pipeline/TenantMiddleware.cs @@ -0,0 +1,45 @@ +// ========================================================================== +// TenantMiddleware.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using PinkParrot.Read.Services; + +namespace PinkParrot.Pipeline +{ + public sealed class TenantMiddleware + { + private readonly ITenantProvider tenantProvider; + private readonly RequestDelegate next; + + public TenantMiddleware(RequestDelegate next, ITenantProvider tenantProvider) + { + this.next = next; + + this.tenantProvider = tenantProvider; + } + + private class TenantFeature : ITenantFeature + { + public Guid TenantId { get; set; } + } + + public async Task Invoke(HttpContext context) + { + var tenantId = await tenantProvider.ProvideTenantIdByDomainAsync(context.Request.Host.ToString()); + + if (tenantId.HasValue) + { + context.Features.Set(new TenantFeature { TenantId = tenantId.Value }); + } + + await next(context); + } + } +} diff --git a/src/PinkParrot/Startup.cs b/src/PinkParrot/Startup.cs index c8da389eb..384a27e23 100644 --- a/src/PinkParrot/Startup.cs +++ b/src/PinkParrot/Startup.cs @@ -25,14 +25,15 @@ namespace PinkParrot { services.AddMvc().AddAppSerializers(); services.AddRouting(); + services.AddMemoryCache(); services.AddAppSwagger(); services.AddEventFormatter(); var builder = new ContainerBuilder(); - builder.Populate(services); builder.RegisterModule(); builder.RegisterModule(); builder.RegisterModule(); + builder.Populate(services); return new AutofacServiceProvider(builder.Build()); } @@ -41,6 +42,7 @@ namespace PinkParrot { loggerFactory.AddConsole(); + app.UseAppTenants(); app.UseMvc(); app.UseStaticFiles(); app.UseAppSwagger(); diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/Json/SchemaDto.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/Json/SchemaDto.cs new file mode 100644 index 000000000..d1aeb6aed --- /dev/null +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/Json/SchemaDto.cs @@ -0,0 +1,68 @@ +// ========================================================================== +// SerializationModel.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using PinkParrot.Infrastructure; + +// ReSharper disable LoopCanBeConvertedToQuery + +namespace PinkParrot.Core.Schema.Json +{ + public class SchemaDto + { + private readonly ModelSchemaProperties properties; + private readonly ImmutableDictionary fields; + + [Required] + public ImmutableDictionary Fields + { + get { return fields; } + } + + [Required] + public ModelSchemaProperties Properties + { + get { return properties; } + } + + public SchemaDto(ModelSchemaProperties properties, ImmutableDictionary fields) + { + Guard.NotNull(fields, nameof(fields)); + Guard.NotNull(properties, nameof(properties)); + + this.properties = properties; + + this.fields = fields; + } + + public static SchemaDto Create(ModelSchema schema) + { + Guard.NotNull(schema, nameof(schema)); + + var fields = schema.Fields.ToDictionary(t => t.Key, t => t.Value.RawProperties).ToImmutableDictionary(); + + return new SchemaDto(schema.Properties, fields); + } + + public ModelSchema ToModelSchema(ModelFieldFactory factory) + { + Guard.NotNull(factory, nameof(factory)); + + var schema = ModelSchema.Create(properties); + + foreach (var field in fields) + { + schema = schema.AddField(field.Key, field.Value, factory); + } + + return schema; + } + } +} diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs index d5e11b2e6..3845a3ef4 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs @@ -80,41 +80,21 @@ namespace PinkParrot.Core.Schema public ModelField Hide() { - if (isHidden) - { - throw new DomainException($"The field '{Name} is already hidden."); - } - return Update(clone => clone.isHidden = true); } public ModelField Show() { - if (!isHidden) - { - throw new DomainException($"The field '{Name} is already visible."); - } - return Update(clone => clone.isHidden = false); } public ModelField Disable() { - if (isDisabled) - { - throw new DomainException($"The field '{Name} is already disabled."); - } - return Update(clone => clone.isDisabled = true); } public ModelField Enable() { - if (!isDisabled) - { - throw new DomainException($"The field '{Name} is already enabled."); - } - return Update(clone => clone.isDisabled = false); } diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs index 9ec7e86be..17259139a 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs @@ -14,14 +14,15 @@ namespace PinkParrot.Core.Schema { public class ModelFieldFactory { - private readonly Dictionary> factories = new Dictionary>(); + private readonly Dictionary> factories + = new Dictionary>(); public ModelFieldFactory() { - AddFactory(id => new NumberField(id)); + AddFactory((id, p) => new NumberField(id, (NumberFieldProperties)p)); } - public ModelFieldFactory AddFactory(Func factory) where T : ModelFieldProperties + public ModelFieldFactory AddFactory(Func factory) where T : ModelFieldProperties { Guard.NotNull(factory, nameof(factory)); @@ -41,7 +42,7 @@ namespace PinkParrot.Core.Schema throw new InvalidOperationException("Field type is not supported."); } - return factory(id); + return factory(id, properties); } } } diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField_Generic.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField_Generic.cs index 8a4079892..bbac67ea1 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField_Generic.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField_Generic.cs @@ -46,9 +46,12 @@ namespace PinkParrot.Core.Schema get { return properties; } } - protected ModelField(long id) + protected ModelField(long id, T properties) : base(id) { + Guard.NotNull(properties, nameof(properties)); + + this.properties = properties; } public override ModelField Configure(ModelFieldProperties newProperties, IList errors) diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs index ac34a616c..7ad79cb61 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs @@ -18,38 +18,39 @@ namespace PinkParrot.Core.Schema public sealed class ModelSchema { private readonly ModelSchemaProperties properties; - private readonly ImmutableDictionary fields; + private readonly ImmutableDictionary fieldsById; private readonly Dictionary fieldsByName; public ModelSchema(ModelSchemaProperties properties, ImmutableDictionary fields) { Guard.NotNull(fields, nameof(fields)); Guard.NotNull(properties, nameof(properties)); - - this.fields = fields; - + this.properties = properties; + fieldsById = fields; fieldsByName = fields.Values.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); } - public static ModelSchema Create(ModelSchemaProperties metadata) + public static ModelSchema Create(ModelSchemaProperties properties) { + Guard.NotNull(properties, nameof(properties)); + var errors = new List(); - metadata.Validate(errors); + properties.Validate(errors); if (errors.Any()) { throw new ValidationException("Failed to create a new model schema.", errors); } - return new ModelSchema(metadata, ImmutableDictionary.Empty); + return new ModelSchema(properties, ImmutableDictionary.Empty); } - public IReadOnlyDictionary Fields + public ImmutableDictionary Fields { - get { return fields; } + get { return fieldsById; } } public ModelSchemaProperties Properties @@ -61,14 +62,14 @@ namespace PinkParrot.Core.Schema { Guard.NotNull(newMetadata, nameof(newMetadata)); - return new ModelSchema(newMetadata, fields); + return new ModelSchema(newMetadata, fieldsById); } public ModelSchema AddField(long id, ModelFieldProperties fieldProperties, ModelFieldFactory factory) { var field = factory.CreateField(id, fieldProperties); - return SetField(field); + return ReplaceOrAddField(field); } public ModelSchema SetField(long fieldId, ModelFieldProperties fieldProperties) @@ -110,41 +111,35 @@ namespace PinkParrot.Core.Schema return UpdateField(fieldId, field => field.Show()); } - public ModelSchema SetField(ModelField field) + public ModelSchema DeleteField(long fieldId) { - Guard.NotNull(field, nameof(field)); - - if (fields.Values.Any(f => f.Name == field.Name && f.Id != field.Id)) - { - throw new ValidationException($"A field with name '{field.Name}' already exists."); - } - - return new ModelSchema(properties, fields.SetItem(field.Id, field)); + return new ModelSchema(properties, fieldsById.Remove(fieldId)); } - public ModelSchema DeleteField(long fieldId) + private ModelSchema UpdateField(long fieldId, Func updater) { - if (!fields.ContainsKey(fieldId)) + ModelField field; + + if (!fieldsById.TryGetValue(fieldId, out field)) { - throw new ValidationException($"A field with id {fieldId} does not exist."); + throw new DomainObjectNotFoundException(fieldId.ToString(), typeof(ModelField)); } - return new ModelSchema(properties, fields.Remove(fieldId)); + var newField = updater(field); + + return ReplaceOrAddField(newField); } - private ModelSchema UpdateField(long fieldId, Func updater) + private ModelSchema ReplaceOrAddField(ModelField field) { - ModelField field; + Guard.NotNull(field, nameof(field)); - if (!fields.TryGetValue(fieldId, out field)) + if (fieldsById.Values.Any(f => f.Name == field.Name && f.Id != field.Id)) { - throw new ValidationException($"Cannot update field with id '{fieldId}'.", - new ValidationError("Field does not exist.", "fieldId")); + throw new ValidationException($"A field with name '{field.Name}' already exists."); } - var newField = updater(field); - - return SetField(newField); + return new ModelSchema(properties, fieldsById.SetItem(field.Id, field)); } public async Task ValidateAsync(PropertiesBag data) diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs index 4ae37df87..ee38d4f61 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs @@ -27,8 +27,8 @@ namespace PinkParrot.Core.Schema get { return Properties.MinValue; } } - public NumberField(long id) - : base(id) + public NumberField(long id, NumberFieldProperties properties) + : base(id, properties) { } diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/NumberFieldProperties.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/NumberFieldProperties.cs index 1b415515a..0f9115f31 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/NumberFieldProperties.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/NumberFieldProperties.cs @@ -14,29 +14,52 @@ namespace PinkParrot.Core.Schema [TypeName("Number")] public sealed class NumberFieldProperties : ModelFieldProperties { + public double? DefaultValue { get; } + public double? MaxValue { get; } public double? MinValue { get; } + public string Placeholder { get; set; } + public NumberFieldProperties( bool isRequired, string name, string label, string hints, + string placeholder, double? minValue, - double? maxValue) + double? maxValue, + double? defaultValue) : base(isRequired, name, label, hints) { + Placeholder = placeholder; + MinValue = minValue; MaxValue = maxValue; + + DefaultValue = defaultValue; } protected override void ValidateCore(IList errors) { - if (MaxValue.HasValue && MinValue.HasValue && MinValue.Value > MaxValue.Value) + if (MaxValue.HasValue && MinValue.HasValue) { errors.Add(new ValidationError("MinValue cannot be larger than max value", "MinValue", "MaxValue")); } + + if (DefaultValue.HasValue) + { + if (MinValue.HasValue && DefaultValue.Value < MinValue.Value) + { + errors.Add(new ValidationError("DefaultValue must be larger than the min value.", "DefaultValue")); + } + + if (MaxValue.HasValue && DefaultValue.Value > MaxValue.Value) + { + errors.Add(new ValidationError("DefaultValue must be smaller than the max value.", "DefaultValue")); + } + } } } } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs index b2b719f93..b05b0c1c3 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs @@ -13,7 +13,7 @@ namespace PinkParrot.Infrastructure.CQRS.Commands { public interface IDomainObjectRepository { - Task GetByIdAsync(Guid id, int version = 0) where TDomainObject : class, IAggregate; + Task GetByIdAsync(Guid id, int version = int.MaxValue) where TDomainObject : class, IAggregate; Task SaveAsync(IAggregate domainObject, Guid commitId); } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs index e01559228..c2007365f 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs @@ -54,6 +54,11 @@ namespace PinkParrot.Infrastructure.CQRS.Commands context.MarkFailed(e); } } + + if (context.Exception != null) + { + throw context.Exception; + } } } } \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs index 407343a36..7f1421cc9 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs @@ -52,20 +52,15 @@ namespace PinkParrot.Infrastructure.CQRS { ApplyEvent(envelopeToAdd); } + else + { + version++; + } } protected void RaiseEvent(IEvent @event, bool disableApply = false) { - Guard.NotNull(@event, nameof(@event)); - - var envelopeToAdd = EnvelopeFactory.ForEvent(@event, this); - - uncomittedEvents.Add(envelopeToAdd); - - if (!disableApply) - { - ApplyEvent(envelopeToAdd); - } + RaiseEvent(EnvelopeFactory.ForEvent(@event, this), disableApply); } void IAggregate.ApplyEvent(Envelope @event) diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs index f3e0f7999..af0d0abf9 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs @@ -14,20 +14,17 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore public sealed class DefaultNameResolver : IStreamNameResolver { private readonly string prefix; - - public DefaultNameResolver() - : this(string.Empty) - { - } - + public DefaultNameResolver(string prefix) { + Guard.NotNullOrEmpty(prefix, nameof(prefix)); + this.prefix = prefix; } public string GetStreamName(Type aggregateType, Guid id) { - return string.Format(CultureInfo.InvariantCulture, "{0}{1}-{2}", prefix, char.ToLower(aggregateType.Name[0]) + aggregateType.Name.Substring(1), id); + return string.Format(CultureInfo.InvariantCulture, "{0}-{1}-{2}", prefix, char.ToLower(aggregateType.Name[0]) + aggregateType.Name.Substring(1), id); } } } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreBus.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreBus.cs index 59b6aaf9a..811c49e4a 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreBus.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreBus.cs @@ -8,8 +8,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using EventStore.ClientAPI; using EventStore.ClientAPI.SystemData; using Microsoft.Extensions.Logging; @@ -26,7 +26,8 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore private readonly IEnumerable catchConsumers; private readonly ILogger logger; private readonly IStreamPositionStorage positions; - private EventStoreAllCatchUpSubscription catchSubscription; + private EventStoreCatchUpSubscription catchSubscription; + private bool isLive; public EventStoreBus( ILogger logger, @@ -52,16 +53,19 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore this.credentials = credentials; this.liveConsumers = liveConsumers; this.catchConsumers = catchConsumers; - - Subscribe(); } - private void Subscribe() + public void Subscribe(string streamName = "$all") { - var position = positions.ReadPosition(); + Guard.NotNullOrEmpty(streamName, nameof(streamName)); - var now = DateTime.UtcNow; + if (catchSubscription != null) + { + return; + } + var position = positions.ReadPosition(); + logger.LogInformation($"Subscribing from: {0}", position); var settings = @@ -70,58 +74,63 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore true, true); - catchSubscription = connection.SubscribeToAllFrom(position, settings, (s, resolvedEvent) => + catchSubscription = + connection.SubscribeToStreamFrom(streamName, position, settings, + OnEvent, + OnLiveProcessingStarted, + userCredentials: credentials); + } + + private void OnEvent(EventStoreCatchUpSubscription subscription, ResolvedEvent resolvedEvent) + { + try { - var requireUpdate = false; + if (resolvedEvent.OriginalEvent.EventStreamId.StartsWith("$", StringComparison.OrdinalIgnoreCase)) + { + return; + } - Debug.WriteLine($"Last Position: {catchSubscription.LastProcessedPosition}"); - try + if (!liveConsumers.Any() && !catchConsumers.Any()) { - if (resolvedEvent.OriginalEvent.EventStreamId.StartsWith("$", StringComparison.OrdinalIgnoreCase)) - { - return; - } + return; + } - if (liveConsumers.Any() || catchConsumers.Any()) - { - requireUpdate = true; + var @event = formatter.Parse(resolvedEvent); - var @event = formatter.Parse(resolvedEvent); + if (isLive) + { + DispatchConsumers(liveConsumers, @event).Wait(); + } - if (resolvedEvent.Event.Created > now) - { - Dispatch(liveConsumers, @event); - } + DispatchConsumers(catchConsumers, @event).Wait(); + } + finally + { + positions.WritePosition(resolvedEvent.OriginalEventNumber); + } + } - Dispatch(catchConsumers, @event); - } + private void OnLiveProcessingStarted(EventStoreCatchUpSubscription subscription) + { + isLive = true; + } - requireUpdate = requireUpdate || catchSubscription.LastProcessedPosition.CommitPosition % 2 == 0; - } - finally - { - if (requireUpdate) - { - positions.WritePosition(catchSubscription.LastProcessedPosition); - } - } - }, userCredentials: credentials); + private Task DispatchConsumers(IEnumerable consumers, Envelope @event) + { + return Task.WhenAll(consumers.Select(c => DispatchConsumer(@event, c)).ToList()); } - private void Dispatch(IEnumerable consumers, Envelope @event) + private async Task DispatchConsumer(Envelope @event, IEventConsumer consumer) { - foreach (var consumer in consumers) + try { - try - { - consumer.On(@event); - } - catch (Exception ex) - { - var eventId = new EventId(10001, "EventConsumeFailed"); + await consumer.On(@event); + } + catch (Exception ex) + { + var eventId = new EventId(10001, "EventConsumeFailed"); - logger.LogError(eventId, ex, "'{0}' failed to handle event {1} ({2})", consumer, @event.Payload, @event.Headers.EventId()); - } + logger.LogError(eventId, ex, "'{0}' failed to handle event {1} ({2})", consumer, @event.Payload, @event.Headers.EventId()); } } } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs index 45149303f..483fa7a4f 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs @@ -51,7 +51,7 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore this.nameResolver = nameResolver; } - public async Task GetByIdAsync(Guid id, int version = 0) where TDomainObject : class, IAggregate + public async Task GetByIdAsync(Guid id, int version = int.MaxValue) where TDomainObject : class, IAggregate { Guard.GreaterThan(version, 0, nameof(version)); diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamPositionStorage.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamPositionStorage.cs index 54e7130f0..c039801ab 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamPositionStorage.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamPositionStorage.cs @@ -6,14 +6,12 @@ // All rights reserved. // ========================================================================== -using EventStore.ClientAPI; - namespace PinkParrot.Infrastructure.CQRS.EventStore { public interface IStreamPositionStorage { - Position? ReadPosition(); + int? ReadPosition(); - void WritePosition(Position position); + void WritePosition(int position); } } \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs index c8e94f264..614053330 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs @@ -5,10 +5,13 @@ // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== + +using System.Threading.Tasks; + namespace PinkParrot.Infrastructure.CQRS.Events { public interface IEventConsumer { - void On(Envelope @event); + Task On(Envelope @event); } } \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Reflection/ReflectionExtensions.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Reflection/ReflectionExtensions.cs new file mode 100644 index 000000000..1d8537f75 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Reflection/ReflectionExtensions.cs @@ -0,0 +1,68 @@ +// ========================================================================== +// ReflectionExtensions.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace PinkParrot.Infrastructure.Reflection +{ + public static class ReflectionExtensions + { + public static PropertyInfo[] GetPublicProperties(this Type type) + { + const BindingFlags bindingFlags = + BindingFlags.FlattenHierarchy | + BindingFlags.Public | + BindingFlags.Instance; + + if (!type.GetTypeInfo().IsInterface) + { + return type.GetProperties(bindingFlags); + } + + var flattenProperties = new HashSet(); + + var considered = new List + { + type + }; + + var queue = new Queue(); + + queue.Enqueue(type); + + while (queue.Count > 0) + { + var subType = queue.Dequeue(); + + foreach (var subInterface in subType.GetInterfaces()) + { + if (considered.Contains(subInterface)) + { + continue; + } + + considered.Add(subInterface); + + queue.Enqueue(subInterface); + } + + var typeProperties = subType.GetProperties(bindingFlags); + + foreach (var property in typeProperties) + { + flattenProperties.Add(property); + } + } + + return flattenProperties.ToArray(); + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Reflection/SimpleMapper.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Reflection/SimpleMapper.cs index 8ead4fe25..5ba6dda2c 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Reflection/SimpleMapper.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Reflection/SimpleMapper.cs @@ -111,11 +111,11 @@ namespace PinkParrot.Infrastructure.Reflection var dstType = typeof(TDestination); var srcType = typeof(TSource); - var destinationProperties = dstType.GetProperties(); + var destinationProperties = dstType.GetPublicProperties(); var newMappers = new List(); - foreach (var srcProperty in srcType.GetProperties().Where(x => x.CanRead)) + foreach (var srcProperty in srcType.GetPublicProperties().Where(x => x.CanRead)) { var dstProperty = destinationProperties.FirstOrDefault(x => x.Name == srcProperty.Name); diff --git a/src/pinkparrot_read/PinkParrot.Read/IEntity.cs b/src/pinkparrot_read/PinkParrot.Read/IEntity.cs deleted file mode 100644 index 855af63b5..000000000 --- a/src/pinkparrot_read/PinkParrot.Read/IEntity.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace PinkParrot.Read.Models -{ - public interface IModelSchemaRM1 - { - DateTime Created { get; set; } - string Hints { get; set; } - string Label { get; set; } - DateTime Modified { get; set; } - string Name { get; set; } - Guid SchemaId { get; set; } - } -} \ No newline at end of file diff --git a/src/pinkparrot_read/PinkParrot.Read/IModelSchemaRM.cs b/src/pinkparrot_read/PinkParrot.Read/IModelSchemaRM.cs deleted file mode 100644 index 981e836c3..000000000 --- a/src/pinkparrot_read/PinkParrot.Read/IModelSchemaRM.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace PinkParrot.Read.Models -{ - public interface IModelSchemaRM - { - DateTime Created { get; set; } - DateTime Modified { get; set; } - Guid SchemaId { get; set; } - } -} \ No newline at end of file diff --git a/src/pinkparrot_read/PinkParrot.Read/Repositories/EntityWithSchema.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/EntityWithSchema.cs new file mode 100644 index 000000000..8abc989fb --- /dev/null +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/EntityWithSchema.cs @@ -0,0 +1,26 @@ +// ========================================================================== +// EntityWithSchema.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using PinkParrot.Core.Schema; + +namespace PinkParrot.Read.Repositories +{ + public sealed class EntityWithSchema + { + public IModelSchemaEntity Entity { get; } + + public ModelSchema Schema { get; } + + internal EntityWithSchema(IModelSchemaEntity entity, ModelSchema schema) + { + Entity = entity; + + Schema = schema; + } + } +} diff --git a/src/pinkparrot_read/PinkParrot.Read/Models/IEntity.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/IEntity.cs similarity index 92% rename from src/pinkparrot_read/PinkParrot.Read/Models/IEntity.cs rename to src/pinkparrot_read/PinkParrot.Read/Repositories/IEntity.cs index ad074dc65..953657adc 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Models/IEntity.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/IEntity.cs @@ -8,7 +8,7 @@ using System; -namespace PinkParrot.Read.Models +namespace PinkParrot.Read.Repositories { public interface IEntity { diff --git a/src/pinkparrot_read/PinkParrot.Read/Repositories/IModelSchemaEntity.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/IModelSchemaEntity.cs new file mode 100644 index 000000000..7744afc10 --- /dev/null +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/IModelSchemaEntity.cs @@ -0,0 +1,14 @@ +// ========================================================================== +// IModelSchemaEntity.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== +namespace PinkParrot.Read.Repositories +{ + public interface IModelSchemaEntity : ITenantEntity + { + string Name { get; } + } +} diff --git a/src/pinkparrot_read/PinkParrot.Read/Repositories/IModelSchemaRepository.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/IModelSchemaRepository.cs index 32165aa4c..a485d4249 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Repositories/IModelSchemaRepository.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/IModelSchemaRepository.cs @@ -9,12 +9,17 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using PinkParrot.Read.Models; namespace PinkParrot.Read.Repositories { public interface IModelSchemaRepository { - Task> QueryAllAsync(Guid tenantId); + Task> QueryAllAsync(Guid tenantId); + + Task FindSchemaIdAsync(Guid tenantId, string name); + + Task FindSchemaAsync(Guid tenantId, string name); + + Task FindSchemaAsync(Guid schemaId); } } diff --git a/src/pinkparrot_read/PinkParrot.Read/Models/ITenantEntity.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/ITenantEntity.cs similarity index 82% rename from src/pinkparrot_read/PinkParrot.Read/Models/ITenantEntity.cs rename to src/pinkparrot_read/PinkParrot.Read/Repositories/ITenantEntity.cs index b55127e6d..db84f6935 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Models/ITenantEntity.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/ITenantEntity.cs @@ -8,9 +8,9 @@ using System; -namespace PinkParrot.Read.Models +namespace PinkParrot.Read.Repositories { - public interface ITenantEntity + public interface ITenantEntity : IEntity { Guid TenantId { get; set; } } diff --git a/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/EntityMapper.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/EntityMapper.cs index d0a162c2b..cc1b0d382 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/EntityMapper.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/EntityMapper.cs @@ -8,10 +8,11 @@ using System; using System.Threading.Tasks; +using MongoDB.Bson; using MongoDB.Driver; +using Newtonsoft.Json; using PinkParrot.Infrastructure.CQRS; using PinkParrot.Infrastructure.MongoDb; -using PinkParrot.Read.Models; namespace PinkParrot.Read.Repositories.Implementations { @@ -33,6 +34,20 @@ namespace PinkParrot.Read.Repositories.Implementations return Update(entity, headers); } + public static BsonDocument ToJsonBsonDocument(this T value, JsonSerializerSettings settings) + { + var json = JsonConvert.SerializeObject(value, settings).Replace("$type", "§type"); + + return BsonDocument.Parse(json); + } + + public static T ToJsonObject(this BsonDocument document, JsonSerializerSettings settings) + { + var json = document.ToJson().Replace("§type", "$type"); + + return JsonConvert.DeserializeObject(json, settings); + } + public static T Update(T entity, EnvelopeHeaders headers) where T : IEntity { var timestamp = headers.Timestamp().ToDateTimeUtc(); @@ -64,7 +79,7 @@ namespace PinkParrot.Read.Repositories.Implementations updater(entity); - await collection.ReplaceOneAsync(t => t.Id == entity.Id, entity); + var result = await collection.ReplaceOneAsync(t => t.Id == entity.Id, entity); } } } diff --git a/src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaListRM.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaEntity.cs similarity index 80% rename from src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaListRM.cs rename to src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaEntity.cs index ccae19ee2..abb5bc609 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaListRM.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaEntity.cs @@ -1,5 +1,5 @@ // ========================================================================== -// ModelSchemaRM.cs +// MongoModelSchemaEntity.cs // PinkParrot Headless CMS // ========================================================================== // Copyright (c) PinkParrot Group @@ -9,11 +9,10 @@ using System; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -using PinkParrot.Infrastructure; -namespace PinkParrot.Read.Models +namespace PinkParrot.Read.Repositories.Implementations { - public sealed class ModelSchemaListRM : IEntity, ITenantEntity + public sealed class MongoModelSchemaEntity : IModelSchemaEntity { [BsonId] [BsonElement] @@ -39,5 +38,9 @@ namespace PinkParrot.Read.Models [BsonRequired] [BsonElement] public bool IsDeleted { get; set; } + + [BsonRequired] + [BsonElement] + public BsonDocument Schema { get; set; } } } diff --git a/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaListRepository.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaListRepository.cs deleted file mode 100644 index dcb3ddf1f..000000000 --- a/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaListRepository.cs +++ /dev/null @@ -1,66 +0,0 @@ -// ========================================================================== -// MongoModelSchemaRepository.cs -// PinkParrot Headless CMS -// ========================================================================== -// Copyright (c) PinkParrot Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MongoDB.Driver; -using PinkParrot.Events.Schema; -using PinkParrot.Infrastructure.CQRS; -using PinkParrot.Infrastructure.CQRS.Events; -using PinkParrot.Infrastructure.Dispatching; -using PinkParrot.Infrastructure.MongoDb; -using PinkParrot.Infrastructure.Tasks; -using PinkParrot.Read.Models; - -namespace PinkParrot.Read.Repositories.Implementations -{ - public sealed class MongoModelSchemaListRepository : MongoRepositoryBase, IModelSchemaRepository, ICatchEventConsumer - { - public MongoModelSchemaListRepository(IMongoDatabase database) - : base(database) - { - } - - protected override Task SetupCollectionAsync(IMongoCollection collection) - { - return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Id)); - } - - public IQueryable QuerySchemas() - { - return Collection.AsQueryable(); - } - - public Task> QueryAllAsync(Guid tenantId) - { - return Collection.Find(s => s.TenantId == tenantId && s.IsDeleted == false).ToListAsync(); - } - - public void On(ModelSchemaUpdated @event, EnvelopeHeaders headers) - { - Collection.UpdateAsync(headers, e => e.Name = @event.Properties.Name).Forget(); - } - - public void On(ModelSchemaDeleted @event, EnvelopeHeaders headers) - { - Collection.UpdateAsync(headers, e => e.IsDeleted = true).Forget(); - } - - public void On(ModelSchemaCreated @event, EnvelopeHeaders headers) - { - Collection.CreateAsync(headers, e => e.Name = @event.Properties.Name); - } - - public void On(Envelope @event) - { - this.DispatchAction(@event.Payload, @event.Headers); - } - } -} diff --git a/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaRepository.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaRepository.cs new file mode 100644 index 000000000..0def84158 --- /dev/null +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaRepository.cs @@ -0,0 +1,172 @@ +// ========================================================================== +// MongoModelSchemaRepository.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MongoDB.Bson; +using MongoDB.Driver; +using Newtonsoft.Json; +using PinkParrot.Core.Schema; +using PinkParrot.Core.Schema.Json; +using PinkParrot.Events.Schema; +using PinkParrot.Infrastructure; +using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; +using PinkParrot.Infrastructure.Dispatching; +using PinkParrot.Infrastructure.MongoDb; +using PinkParrot.Infrastructure.Tasks; + +namespace PinkParrot.Read.Repositories.Implementations +{ + public sealed class MongoModelSchemaRepository : MongoRepositoryBase, IModelSchemaRepository, ICatchEventConsumer + { + private readonly JsonSerializerSettings serializerSettings; + private readonly ModelFieldFactory fieldFactory; + + public MongoModelSchemaRepository(IMongoDatabase database, JsonSerializerSettings serializerSettings, ModelFieldFactory fieldFactory) + : base(database) + { + Guard.NotNull(serializerSettings, nameof(serializerSettings)); + Guard.NotNull(fieldFactory, nameof(fieldFactory)); + + this.serializerSettings = serializerSettings; + + this.fieldFactory = fieldFactory; + } + + protected override Task SetupCollectionAsync(IMongoCollection collection) + { + return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Name)); + } + + public async Task> QueryAllAsync(Guid tenantId) + { + var entities = await Collection.Find(s => s.TenantId == tenantId && !s.IsDeleted).ToListAsync(); + + return entities.OfType().ToList(); + } + + public async Task FindSchemaAsync(Guid tenantId, string name) + { + var entity = + await Collection.Find(s => s.Name == name && s.TenantId == tenantId && !s.IsDeleted) + .FirstOrDefaultAsync(); + + return entity != null ? new EntityWithSchema(entity, Deserialize(entity)) : null; + } + + public async Task FindSchemaAsync(Guid schemaId) + { + var entity = + await Collection.Find(s => s.Id == schemaId && !s.IsDeleted) + .FirstOrDefaultAsync(); + + return entity != null ? new EntityWithSchema(entity, Deserialize(entity)) : null; + } + + public async Task FindSchemaIdAsync(Guid tenantId, string name) + { + var entity = + await Collection.Find(s => s.Name == name & s.TenantId == tenantId && !s.IsDeleted) + .Project(Projection.Include(x => x.Id)).FirstOrDefaultAsync(); + + return entity?.Id; + } + + public Task On(ModelSchemaDeleted @event, EnvelopeHeaders headers) + { + return Collection.UpdateAsync(headers, e => e.IsDeleted = true); + } + + public Task On(ModelFieldAdded @event, EnvelopeHeaders headers) + { + return UpdateSchema(headers, s => s.AddField(@event.FieldId, @event.Properties, fieldFactory)); + } + + public Task On(ModelFieldDeleted @event, EnvelopeHeaders headers) + { + return UpdateSchema(headers, s => s.DeleteField(@event.FieldId)); + } + + public Task On(ModelFieldDisabled @event, EnvelopeHeaders headers) + { + return UpdateSchema(headers, s => s.DisableField(@event.FieldId)); + } + + public Task On(ModelFieldEnabled @event, EnvelopeHeaders headers) + { + return UpdateSchema(headers, s => s.EnableField(@event.FieldId)); + } + + public Task On(ModelFieldHidden @event, EnvelopeHeaders headers) + { + return UpdateSchema(headers, s => s.HideField(@event.FieldId)); + } + + public Task On(ModelFieldShown @event, EnvelopeHeaders headers) + { + return UpdateSchema(headers, s => s.ShowField(@event.FieldId)); + } + + public Task On(ModelFieldUpdated @event, EnvelopeHeaders headers) + { + return UpdateSchema(headers, s => s.SetField(@event.FieldId, @event.Properties)); + } + + public Task On(ModelSchemaUpdated @event, EnvelopeHeaders headers) + { + return Collection.UpdateAsync(headers, e => + { + e.Name = @event.Properties.Name; + + UpdateSchema(e, s => s.Update(@event.Properties)); + }); + } + + public Task On(ModelSchemaCreated @event, EnvelopeHeaders headers) + { + return Collection.CreateAsync(headers, e => + { + e.Name = @event.Properties.Name; + + Serialize(e, ModelSchema.Create(@event.Properties)); + }); + } + + public Task On(Envelope @event) + { + return this.DispatchActionAsync(@event.Payload, @event.Headers); + } + + private void UpdateSchema(MongoModelSchemaEntity entity, Func updater) + { + var currentSchema = Deserialize(entity); + + currentSchema = updater(currentSchema); + + Serialize(entity, currentSchema); + } + + private Task UpdateSchema(EnvelopeHeaders headers, Func updater) + { + return Collection.UpdateAsync(headers, e=> UpdateSchema(e, updater)); + } + + private void Serialize(MongoModelSchemaEntity entity, ModelSchema schema) + { + entity.Schema = SchemaDto.Create(schema).ToJsonBsonDocument(serializerSettings); + } + + private ModelSchema Deserialize(MongoModelSchemaEntity entity) + { + return entity?.Schema.ToJsonObject(serializerSettings).ToModelSchema(fieldFactory); + } + } +} diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/IModelSchemaProvider.cs b/src/pinkparrot_read/PinkParrot.Read/Services/IModelSchemaProvider.cs index e058e1131..6480eadee 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Services/IModelSchemaProvider.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Services/IModelSchemaProvider.cs @@ -13,6 +13,6 @@ namespace PinkParrot.Read.Services { public interface IModelSchemaProvider { - Task FindSchemaIdByNameAsync(string name); + Task FindSchemaIdByNameAsync(Guid tenantId, string name); } } diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/ITenantProvider.cs b/src/pinkparrot_read/PinkParrot.Read/Services/ITenantProvider.cs index dc60f64ea..89817f790 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Services/ITenantProvider.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Services/ITenantProvider.cs @@ -13,6 +13,6 @@ namespace PinkParrot.Read.Services { public interface ITenantProvider { - Task ProvideTenantIdByDomainAsync(string domain); + Task ProvideTenantIdByDomainAsync(string domain); } } diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/ModelSchemaProvider.cs b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/ModelSchemaProvider.cs index 2d936f338..66f8fa1b1 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/ModelSchemaProvider.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/ModelSchemaProvider.cs @@ -8,14 +8,77 @@ using System; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; +using PinkParrot.Events.Schema; +using PinkParrot.Infrastructure; +using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; +using PinkParrot.Read.Repositories; +// ReSharper disable InvertIf namespace PinkParrot.Read.Services.Implementations { - public class ModelSchemaProvider : IModelSchemaProvider + public class ModelSchemaProvider : IModelSchemaProvider, ILiveEventConsumer { - public Task FindSchemaIdByNameAsync(string name) + private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10); + private readonly IMemoryCache cache; + private readonly IModelSchemaRepository repository; + + public ModelSchemaProvider(IMemoryCache cache, IModelSchemaRepository repository) + { + Guard.NotNull(cache, nameof(cache)); + Guard.NotNull(repository, nameof(repository)); + + this.cache = cache; + + this.repository = repository; + } + + public async Task FindSchemaIdByNameAsync(Guid tenantId, string name) + { + Guard.NotNullOrEmpty(name, nameof(name)); + + var cacheKey = BuildModelsCacheKey(tenantId, name); + var cacheItem = cache.Get(cacheKey); + + if (cacheItem == null) + { + cacheItem = await repository.FindSchemaAsync(tenantId, name) ?? new EntityWithSchema(null, null); + + cache.Set(cacheKey, cacheItem, CacheDuration); + + if (cacheItem.Entity != null) + { + cache.Set(BuildNamesCacheKey(cacheItem.Entity.Id), cacheItem.Entity.Name, CacheDuration); + } + } + + return cacheItem.Entity?.Id; + } + + public Task On(Envelope @event) + { + if (@event.Payload is ModelSchemaUpdated || @event.Payload is ModelSchemaDeleted) + { + var oldName = cache.Get(BuildNamesCacheKey(@event.Headers.AggregateId())); + + if (oldName != null) + { + cache.Remove(BuildModelsCacheKey(@event.Headers.TenantId(), oldName)); + } + } + + return Task.FromResult(true); + } + + private static string BuildModelsCacheKey(Guid tenantId, string name) + { + return $"Schemas_Models_{tenantId}_{name}"; + } + + private static string BuildNamesCacheKey(Guid schemaId) { - return Task.FromResult(Guid.Empty); + return $"Schema_Names_{schemaId}"; } } } diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs index 3dcc7c641..3bdb03805 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs @@ -19,9 +19,6 @@ namespace PinkParrot.Read.Services.Implementations public ObjectId Id { get; set; } [BsonElement] - public long CommitPosition { get; set; } - - [BsonElement] - public long PreparePosition { get; set; } + public int? Position { get; set; } } } \ No newline at end of file diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs index b927345d7..a6405a831 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs @@ -6,13 +6,12 @@ // All rights reserved. // ========================================================================== -using EventStore.ClientAPI; using MongoDB.Bson; using MongoDB.Driver; using PinkParrot.Infrastructure.CQRS.EventStore; using PinkParrot.Infrastructure.MongoDb; -//// ReSharper disable once ConvertIfStatementToNullCoalescingExpression +// ReSharper disable InvertIf namespace PinkParrot.Read.Services.Implementations { @@ -25,35 +24,23 @@ namespace PinkParrot.Read.Services.Implementations { } - public Position? ReadPosition() + public int? ReadPosition() { var document = Collection.Find(t => t.Id == Id).FirstOrDefault(); - return document != null ? new Position(document.CommitPosition, document.PreparePosition) : Position.Start; - } - - public void WritePosition(Position position) - { - var document = Collection.Find(t => t.Id == Id).FirstOrDefault(); - - var isFound = document != null; - if (document == null) { document = new MongoPosition { Id = Id }; - } - - document.CommitPosition = position.CommitPosition; - document.PreparePosition = position.PreparePosition; - if (isFound) - { - Collection.ReplaceOne(t => t.Id == Id, document); - } - else - { Collection.InsertOne(document); } + + return document.Position; + } + + public void WritePosition(int position) + { + Collection.UpdateOne(t => t.Id == Id, Update.Set(t => t.Position, position)); } } } \ No newline at end of file diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/TenantProvider.cs b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/TenantProvider.cs index 1580581d1..c5c7a5649 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/TenantProvider.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/TenantProvider.cs @@ -13,9 +13,9 @@ namespace PinkParrot.Read.Services.Implementations { public sealed class TenantProvider : ITenantProvider { - public Task ProvideTenantIdByDomainAsync(string domain) + public Task ProvideTenantIdByDomainAsync(string domain) { - return Task.FromResult(Guid.Empty); + return Task.FromResult(Guid.Empty); } } } diff --git a/src/pinkparrot_read/PinkParrot.Read/project.json b/src/pinkparrot_read/PinkParrot.Read/project.json index 3b0c5a5d0..192caa595 100644 --- a/src/pinkparrot_read/PinkParrot.Read/project.json +++ b/src/pinkparrot_read/PinkParrot.Read/project.json @@ -2,6 +2,7 @@ "version": "1.0.0-*", "dependencies": { + "Microsoft.Extensions.Caching.Memory": "1.0.0", "MongoDB.Driver": "2.3.0-rc1", "NETStandard.Library": "1.6.0", "NodaTime": "2.0.0-alpha20160729", diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs index f91da3a54..03d7cfa7c 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs @@ -11,7 +11,7 @@ using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class AddModelField : AggregateCommand + public class AddModelField : TenantCommand { public ModelFieldProperties Properties { get; set; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs index 1f6c6f3e5..c88a30a76 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs @@ -10,7 +10,7 @@ using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class DeleteModelField : AggregateCommand + public class DeleteModelField : TenantCommand { public long FieldId { get; set; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs index 5a35849bb..739827842 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs @@ -10,7 +10,7 @@ using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class DeleteModelSchema : AggregateCommand + public class DeleteModelSchema : TenantCommand { } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs index da258d546..d931649a9 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs @@ -6,11 +6,9 @@ // All rights reserved. // ========================================================================== -using PinkParrot.Infrastructure.CQRS.Commands; - namespace PinkParrot.Write.Schema.Commands { - public class DisableModelField : AggregateCommand + public class DisableModelField : TenantCommand { public long FieldId { get; set; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs index ee0575730..e4b37954d 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs @@ -10,7 +10,7 @@ using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class EnableModelField : AggregateCommand + public class EnableModelField : TenantCommand { public long FieldId { get; set; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs index 435460374..2d7ab24f1 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs @@ -10,7 +10,7 @@ using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class HideModelField : AggregateCommand + public class HideModelField : TenantCommand { public long FieldId { get; set; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs index e048f5617..1979676ff 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs @@ -10,7 +10,7 @@ using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class ShowModelField : AggregateCommand + public class ShowModelField : TenantCommand { public long FieldId { get; set; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs index beeaa87ab..2c0768b4e 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs @@ -11,7 +11,7 @@ using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class UpdateModelField : AggregateCommand + public class UpdateModelField : TenantCommand { public long FieldId { get; set; } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs index 3805a5124..ae428951f 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs @@ -11,7 +11,7 @@ using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class UpdateModelSchema : AggregateCommand + public class UpdateModelSchema : TenantCommand { public ModelSchemaProperties Properties { get; set; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs index 736d05a8e..7a9b72f02 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs @@ -18,7 +18,7 @@ namespace PinkParrot.Write.Schema { public Task HandleAsync(CommandContext context) { - return this.DispatchActionAsync(context.Command, context); + return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command, context); } public Task On(AddModelField command, CommandContext context) diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs index 956dca543..efc2b0b74 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs @@ -9,6 +9,7 @@ using System; using PinkParrot.Core.Schema; using PinkParrot.Events.Schema; +using PinkParrot.Infrastructure; using PinkParrot.Infrastructure.CQRS; using PinkParrot.Infrastructure.CQRS.Events; using PinkParrot.Infrastructure.Dispatching; @@ -45,56 +46,56 @@ namespace PinkParrot.Write.Schema this.fieldFactory = fieldFactory; } - protected void Apply(ModelFieldAdded @event) + public void On(ModelFieldAdded @event) { schema = schema.AddField(@event.FieldId, @event.Properties, fieldFactory); totalFields++; } - protected void Apply(ModelSchemaCreated @event) + public void On(ModelSchemaCreated @event) { tenantId = @event.TenantId; schema = ModelSchema.Create(@event.Properties); } - protected void Apply(ModelFieldUpdated @event) + public void On(ModelFieldUpdated @event) { schema = schema.SetField(@event.FieldId, @event.Properties); } - public void Apply(ModelFieldHidden @event) + public void On(ModelFieldHidden @event) { schema = schema.HideField(@event.FieldId); } - public void Apply(ModelFieldShown @event) + public void On(ModelFieldShown @event) { schema = schema.ShowField(@event.FieldId); } - public void Apply(ModelFieldDisabled @event) + public void On(ModelFieldDisabled @event) { schema = schema.DisableField(@event.FieldId); } - public void Apply(ModelFieldEnabled @event) + public void On(ModelFieldEnabled @event) { schema = schema.EnableField(@event.FieldId); } - protected void Apply(ModelSchemaUpdated @event) + public void On(ModelSchemaUpdated @event) { schema = schema.Update(@event.Properties); } - protected void Apply(ModelFieldDeleted @event) + public void On(ModelFieldDeleted @event) { schema = schema.DeleteField(@event.FieldId); } - protected void Apply(ModelSchemaDeleted @event) + public void On(ModelSchemaDeleted @event) { isDeleted = false; } @@ -197,7 +198,7 @@ namespace PinkParrot.Write.Schema { if (schema != null) { - throw new InvalidOperationException("Schema has already been created."); + throw new DomainException("Schema has already been created."); } } @@ -205,7 +206,7 @@ namespace PinkParrot.Write.Schema { if (isDeleted || schema == null) { - throw new InvalidOperationException("Schema has already been deleted or not created yet."); + throw new DomainException("Schema has already been deleted or not created yet."); } } diff --git a/src/pinkparrot_write/PinkParrot.Write/TenantCommand.cs b/src/pinkparrot_write/PinkParrot.Write/TenantCommand.cs index 66751a814..83311c4f9 100644 --- a/src/pinkparrot_write/PinkParrot.Write/TenantCommand.cs +++ b/src/pinkparrot_write/PinkParrot.Write/TenantCommand.cs @@ -11,7 +11,7 @@ using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write { - public abstract class TenantCommand : AggregateCommand + public abstract class TenantCommand : AggregateCommand, ITenantCommand { public Guid TenantId { get; set; } }