From 0ec44f6a658b9b2dd406876c9458631cff3ea761 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 4 Sep 2016 17:23:29 +0200 Subject: [PATCH] Same very first progress --- .../InfrastructureDependencies.cs | 58 +++++++ .../Configurations/ReadDependencies.cs | 24 +++ src/PinkParrot/Configurations/Serializers.cs | 57 +++++++ src/PinkParrot/Configurations/Swagger.cs | 44 +++++ .../Configurations/WriteDependencies.cs | 27 ++++ src/PinkParrot/Modules/Api/EntityCreated.cs | 19 +++ .../Api/Schemas/SchemaFieldsController.cs | 64 +++++++- .../Modules/Api/Schemas/SchemasController.cs | 59 ++++++- .../Swagger/CamelCaseParameterFilter.cs | 29 ++++ .../Pipeline/Swagger/HidePropertyFilter.cs | 65 ++++++++ .../Pipeline/Swagger/RemoveReadonlyFilter.cs | 36 +++++ src/PinkParrot/Startup.cs | 42 ++--- src/PinkParrot/project.json | 9 +- .../Schema/ModelFieldFactory.cs | 4 +- .../PinkParrot.Core/Schema/ModelSchema.cs | 10 +- .../Schema/ModelSchemaProperties.cs | 2 +- .../Schema/NamedElementProperties.cs | 2 + .../Autofac/AutofacDomainObjectFactory.cs | 10 +- .../CQRS/Commands/AggregateCommand.cs | 2 +- .../IAggregateCommand.cs} | 12 +- .../CQRS/Envelope.cs | 18 ++- .../CQRS/EnvelopeHeaders.cs | 17 ++ .../CQRS/EventStore/DefaultNameResolver.cs | 33 ++++ .../EventStoreDomainObjectRepository.cs | 153 ++++++++++++++++++ .../CQRS/EventStore/EventStoreFormatter.cs | 66 ++++++++ .../CQRS/EventStore/IStreamNameResolver.cs | 17 ++ .../Dispatching/ActionContextDispatcher.cs | 2 +- .../Dispatching/ActionDispatcher.cs | 2 +- .../Dispatching/FuncContextDispatcher.cs | 9 +- .../Dispatching/FuncDispatcher.cs | 9 +- .../DomainObjectDeletedException.cs | 25 +++ .../DomainObjectException.cs | 50 ++++++ .../DomainObjectNotFoundException.cs | 25 +++ .../DomainObjectVersionException.cs | 47 ++++++ .../HideAttribute.cs | 17 ++ .../Json/PropertiesBagConverter.cs | 3 +- .../PropertyValue.cs | 2 +- .../PinkParrot.Infrastructure/project.json | 2 + .../PinkParrot.Read/Models/ModelSchemaRM.cs | 5 + .../Services/ISchemaProvider.cs | 18 +++ .../Implementations/SchemaProvider.cs | 21 +++ .../Schema/Commands/AddModelField.cs | 2 +- .../Schema/Commands/CreateModelSchema.cs | 2 +- .../Schema/Commands/DeleteModelField.cs | 2 +- .../Schema/Commands/DeleteModelSchema.cs | 4 +- .../Schema/Commands/DisableModelField.cs | 2 +- .../Schema/Commands/EnableModelField.cs | 2 +- .../Schema/Commands/HideModelField.cs | 2 +- .../Schema/Commands/ShowModelField.cs | 2 +- .../Schema/Commands/UpdateModelField.cs | 4 +- .../Schema/Commands/UpdateModelSchema.cs | 2 +- .../Schema/ModelSchemaCommandHandler.cs | 24 +-- 52 files changed, 1050 insertions(+), 114 deletions(-) create mode 100644 src/PinkParrot/Configurations/InfrastructureDependencies.cs create mode 100644 src/PinkParrot/Configurations/ReadDependencies.cs create mode 100644 src/PinkParrot/Configurations/Serializers.cs create mode 100644 src/PinkParrot/Configurations/Swagger.cs create mode 100644 src/PinkParrot/Configurations/WriteDependencies.cs create mode 100644 src/PinkParrot/Modules/Api/EntityCreated.cs create mode 100644 src/PinkParrot/Pipeline/Swagger/CamelCaseParameterFilter.cs create mode 100644 src/PinkParrot/Pipeline/Swagger/HidePropertyFilter.cs create mode 100644 src/PinkParrot/Pipeline/Swagger/RemoveReadonlyFilter.cs rename src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/{GetEventStore/GetEventStoreDomainObjectRepository.cs => Commands/IAggregateCommand.cs} (65%) create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamNameResolver.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectDeletedException.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectException.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectNotFoundException.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectVersionException.cs create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure/HideAttribute.cs create mode 100644 src/pinkparrot_read/PinkParrot.Read/Services/ISchemaProvider.cs create mode 100644 src/pinkparrot_read/PinkParrot.Read/Services/Implementations/SchemaProvider.cs diff --git a/src/PinkParrot/Configurations/InfrastructureDependencies.cs b/src/PinkParrot/Configurations/InfrastructureDependencies.cs new file mode 100644 index 000000000..2ef80d4ef --- /dev/null +++ b/src/PinkParrot/Configurations/InfrastructureDependencies.cs @@ -0,0 +1,58 @@ +// ========================================================================== +// InfrastructureDependencies.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Net; +using Autofac; +using EventStore.ClientAPI; +using EventStore.ClientAPI.SystemData; +using PinkParrot.Infrastructure.CQRS.Autofac; +using PinkParrot.Infrastructure.CQRS.Commands; +using PinkParrot.Infrastructure.CQRS.EventStore; + +namespace PinkParrot.Configurations +{ + public class InfrastructureDependencies : Module + { + protected override void Load(ContainerBuilder builder) + { + var eventStore = + EventStoreConnection.Create( + ConnectionSettings.Create() + .UseConsoleLogger() + .UseDebugLogger() + .KeepReconnecting() + .KeepRetrying(), + new IPEndPoint(IPAddress.Loopback, 1113)); + + eventStore.ConnectAsync().Wait(); + + builder.RegisterInstance(new UserCredentials("admin", "changeit")) + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterInstance(eventStore) + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + } + } +} diff --git a/src/PinkParrot/Configurations/ReadDependencies.cs b/src/PinkParrot/Configurations/ReadDependencies.cs new file mode 100644 index 000000000..31b0931bc --- /dev/null +++ b/src/PinkParrot/Configurations/ReadDependencies.cs @@ -0,0 +1,24 @@ +// ========================================================================== +// ReadDependencies.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using Autofac; +using PinkParrot.Read.Services; +using PinkParrot.Read.Services.Implementations; + +namespace PinkParrot.Configurations +{ + public sealed class ReadDependencies : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType() + .As() + .SingleInstance(); + } + } +} diff --git a/src/PinkParrot/Configurations/Serializers.cs b/src/PinkParrot/Configurations/Serializers.cs new file mode 100644 index 000000000..088d96654 --- /dev/null +++ b/src/PinkParrot/Configurations/Serializers.cs @@ -0,0 +1,57 @@ +// ========================================================================== +// Serializers.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +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; + +namespace PinkParrot.Configurations +{ + public static class Serializers + { + private static JsonSerializerSettings ConfigureJson(JsonSerializerSettings settings) + { + settings.Binder = new TypeNameSerializationBinder().Map(typeof(ModelSchema).GetTypeInfo().Assembly); + settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + settings.Converters.Add(new PropertiesBagConverter()); + settings.DateFormatHandling = DateFormatHandling.IsoDateFormat; + settings.DateParseHandling = DateParseHandling.DateTime; + settings.TypeNameHandling = TypeNameHandling.Auto; + + return settings; + } + + private static JsonSerializerSettings CreateSettings() + { + return ConfigureJson(new JsonSerializerSettings()); + } + + public static void AddEventFormatter(this IServiceCollection services) + { + var fieldFactory = + new ModelFieldFactory() + .AddFactory(id => new NumberField(id)); + + services.AddSingleton(t => CreateSettings()); + services.AddSingleton(fieldFactory); + services.AddSingleton(); + } + + public static void AddAppSerializers(this IMvcBuilder mvc) + { + mvc.AddJsonOptions(options => + { + ConfigureJson(options.SerializerSettings); + }); + } + } +} diff --git a/src/PinkParrot/Configurations/Swagger.cs b/src/PinkParrot/Configurations/Swagger.cs new file mode 100644 index 000000000..3089e1665 --- /dev/null +++ b/src/PinkParrot/Configurations/Swagger.cs @@ -0,0 +1,44 @@ +// ========================================================================== +// Swagger.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.PlatformAbstractions; +using PinkParrot.Pipeline.Swagger; +using Swashbuckle.Swagger.Model; + +namespace PinkParrot.Configurations +{ + public static class Swagger + { + public static void AddAppSwagger(this IServiceCollection services) + { + services.AddSwaggerGen(options => + { + options.SingleApiVersion(new Info { Title = "Pink Parrot", Version = "v1" }); + options.OperationFilter(); + options.OperationFilter(); + options.SchemaFilter(); + options.SchemaFilter(); + options.IncludeXmlComments(GetXmlCommentsPath(PlatformServices.Default.Application)); + }); + } + + public static void UseAppSwagger(this IApplicationBuilder app) + { + app.UseSwagger(); + app.UseSwaggerUi(); + } + + private static string GetXmlCommentsPath(ApplicationEnvironment appEnvironment) + { + return Path.Combine(appEnvironment.ApplicationBasePath, "PinkParrot.xml"); + } + } +} diff --git a/src/PinkParrot/Configurations/WriteDependencies.cs b/src/PinkParrot/Configurations/WriteDependencies.cs new file mode 100644 index 000000000..14eb4c9fc --- /dev/null +++ b/src/PinkParrot/Configurations/WriteDependencies.cs @@ -0,0 +1,27 @@ +// ========================================================================== +// WriteDependencies.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using Autofac; +using PinkParrot.Infrastructure.CQRS.Commands; +using PinkParrot.Write.Schema; + +namespace PinkParrot.Configurations +{ + public class WriteDependencies : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .InstancePerDependency(); + } + } +} diff --git a/src/PinkParrot/Modules/Api/EntityCreated.cs b/src/PinkParrot/Modules/Api/EntityCreated.cs new file mode 100644 index 000000000..d62fa332c --- /dev/null +++ b/src/PinkParrot/Modules/Api/EntityCreated.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// EntityCreated.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.ComponentModel.DataAnnotations; + +namespace PinkParrot.Modules.Api +{ + public class EntityCreated + { + [Required] + public Guid Id { get; set; } + } +} diff --git a/src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs b/src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs index 08929ad0e..faf786452 100644 --- a/src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs +++ b/src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs @@ -8,8 +8,12 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using PinkParrot.Core.Schema; using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Write.Schema.Commands; +using Swashbuckle.SwaggerGen.Annotations; + +#pragma warning disable 1584,1711,1572,1573,1581,1580 namespace PinkParrot.Modules.Api.Schemas { @@ -22,51 +26,97 @@ namespace PinkParrot.Modules.Api.Schemas this.commandBus = commandBus; } + /// + /// Adds a new field to the schema with the specified name. + /// + /// The name of the schema. + /// The field properties [HttpPost] [Route("schemas/{name}/fields/")] - public Task Add(AddModelField command) + [SwaggerOperation(Tags = new[] { "Schemas" })] + public Task Add(string name, [FromBody] ModelFieldProperties field) { + var command = new AddModelField { Properties = field }; + return commandBus.PublishAsync(command); } + /// + /// Uüdates the field with the specified schema name and field id. + /// + /// The name of the schema. + /// The id of the field. + /// The field properties [HttpPut] [Route("schemas/{name}/fields/{fieldId:long}/")] - public Task Update(UpdateModelField command) + [SwaggerOperation(Tags = new[] { "Schemas" })] + public Task Update(string name, long fieldId, [FromBody] UpdateModelField command) { return commandBus.PublishAsync(command); } + /// + /// Hides the field with the specified schema name and field id. + /// + /// The name of the schema. + /// The id of the field. [HttpPut] [Route("schemas/{name}/fields/{fieldId:long}/hide/")] - public Task Hide(HideModelField command) + [SwaggerOperation(Tags = new[] { "Schemas" })] + public Task Hide(string name, long fieldId, HideModelField command) { return commandBus.PublishAsync(command); } + /// + /// Sows the field with the specified schema name and field id. + /// + /// The name of the schema. + /// The id of the field. [HttpPut] [Route("schemas/{name}/fields/{fieldId:long}/show/")] - public Task Show(ShowModelField command) + [SwaggerOperation(Tags = new[] { "Schemas" })] + public Task Show(string name, long fieldId, ShowModelField command) { return commandBus.PublishAsync(command); } + /// + /// Enables the field with the specified schema name and field id. + /// + /// The name of the schema. + /// The id of the field. [HttpPut] [Route("schemas/{name}/fields/{fieldId:long}/enable/")] - public Task Enable(EnableModelField command) + [SwaggerOperation(Tags = new[] { "Schemas" })] + public Task Enable(string name, long fieldId, EnableModelField command) { return commandBus.PublishAsync(command); } + /// + /// Disables the field with the specified schema name and field id. + /// + /// The name of the schema. + /// The id of the field. [HttpPut] [Route("schemas/{name}/fields/{fieldId:long}/disable/")] - public Task Enable(DisableModelField command) + [SwaggerOperation(Tags = new[] { "Schemas" })] + public Task Disable(string name, long fieldId, DisableModelField command) { return commandBus.PublishAsync(command); } + + /// + /// Deletes the field with the specified schema name and field id. + /// + /// The name of the schema. + /// The id of the field. [HttpDelete] [Route("schemas/{name}/fields/{fieldId:long}/")] - public Task Delete(DeleteModelField command) + [SwaggerOperation(Tags = new[] { "Schemas" })] + public Task Delete(string name, long fieldId, DeleteModelField command) { return commandBus.PublishAsync(command); } diff --git a/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs b/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs index 833fe5ee0..03cee6941 100644 --- a/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs +++ b/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs @@ -6,12 +6,17 @@ // All rights reserved. // ========================================================================== +using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using PinkParrot.Core.Schema; using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Read.Models; using PinkParrot.Write.Schema.Commands; +using Swashbuckle.SwaggerGen.Annotations; + +#pragma warning disable 1584,1711,1572,1581,1580 namespace PinkParrot.Modules.Api.Schemas { @@ -24,32 +29,74 @@ namespace PinkParrot.Modules.Api.Schemas this.commandBus = commandBus; } + /// + /// Queries all your schemas. + /// [HttpGet] [Route("schemas/")] + [SwaggerOperation(Tags = new[] { "Schemas" })] public Task> Query() { return null; } + /// + /// Creates a new schema. + /// + /// The properties of the schema. + /// + /// Field can be managed later. + /// + /// Schema created + /// Schema update failed [HttpPost] [Route("schemas/")] - public Task Create(CreateModelSchema command) + [SwaggerOperation(Tags = new[] { "Schemas" })] + [ProducesResponseType(typeof(EntityCreated), 204)] + public async Task Create([FromBody] ModelSchemaProperties schema) { - return commandBus.PublishAsync(command); + var command = new CreateModelSchema { AggregateId = Guid.NewGuid(), Properties = schema }; + + await commandBus.PublishAsync(command); + + return CreatedAtAction("Query", new EntityCreated { Id = command.AggregateId }); } + /// + /// Updates the schema with the specified name. + /// + /// The name of the schema. + /// The properties of the schema. + /// Schema update + /// Schema not found + /// Schema update failed [HttpPut] [Route("schemas/{name}/")] - public Task Update(UpdateModelSchema command) + [SwaggerOperation(Tags = new[] { "Schemas" })] + public async Task Update(string name, [FromBody] ModelSchemaProperties schema) { - return commandBus.PublishAsync(command); + var command = new UpdateModelSchema { Properties = schema }; + + await commandBus.PublishAsync(command); + + return NoContent(); } + /// + /// Deletes the schema with the specified name. + /// + /// The name of the schema. + /// Schema deleted + /// Schema not found + /// Schema deletion failed [HttpDelete] [Route("schemas/{name}/")] - public Task Delete() + [SwaggerOperation(Tags = new[] { "Schemas" })] + public async Task Delete(string name) { - return commandBus.PublishAsync(new DeleteModelSchema()); + await commandBus.PublishAsync(new DeleteModelSchema()); + + return NoContent(); } } } \ No newline at end of file diff --git a/src/PinkParrot/Pipeline/Swagger/CamelCaseParameterFilter.cs b/src/PinkParrot/Pipeline/Swagger/CamelCaseParameterFilter.cs new file mode 100644 index 000000000..7cd961884 --- /dev/null +++ b/src/PinkParrot/Pipeline/Swagger/CamelCaseParameterFilter.cs @@ -0,0 +1,29 @@ +// ========================================================================== +// CamelCaseParameterFilter.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using Swashbuckle.Swagger.Model; +using Swashbuckle.SwaggerGen.Generator; + +namespace PinkParrot.Pipeline.Swagger +{ + public sealed class CamelCaseParameterFilter : IOperationFilter + { + public void Apply(Operation operation, OperationFilterContext context) + { + if (operation.Parameters == null) + { + return; + } + + foreach (var parameter in operation.Parameters) + { + parameter.Name = char.ToLowerInvariant(parameter.Name[0]) + parameter.Name.Substring(1); + } + } + } +} diff --git a/src/PinkParrot/Pipeline/Swagger/HidePropertyFilter.cs b/src/PinkParrot/Pipeline/Swagger/HidePropertyFilter.cs new file mode 100644 index 000000000..2b7d18199 --- /dev/null +++ b/src/PinkParrot/Pipeline/Swagger/HidePropertyFilter.cs @@ -0,0 +1,65 @@ +// ========================================================================== +// HidePropertyFilter.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using PinkParrot.Infrastructure; +using Swashbuckle.Swagger.Model; +using Swashbuckle.SwaggerGen.Generator; + +namespace PinkParrot.Pipeline.Swagger +{ + public class HidePropertyFilter : ISchemaFilter, IOperationFilter + { + public void Apply(Schema model, SchemaFilterContext context) + { + foreach (var property in context.JsonContract.UnderlyingType.GetProperties()) + { + var attribute = property.GetCustomAttribute(); + + if (attribute != null) + { + model.Properties.Remove(property.Name); + } + } + } + + public void Apply(Operation operation, OperationFilterContext context) + { + if (context?.ApiDescription.ParameterDescriptions == null) + { + return; + } + + if (operation.Parameters == null) + { + return; + } + + foreach (var parameterDescription in context.ApiDescription.ParameterDescriptions) + { + var metadata = parameterDescription.ModelMetadata as DefaultModelMetadata; + + var hasAttribute = metadata?.Attributes?.Attributes.OfType().Any(); + + if (hasAttribute != true) + { + continue; + } + + var parameter = operation.Parameters.FirstOrDefault(p => p.Name == parameterDescription.Name); + + if (parameter != null) + { + operation.Parameters.Remove(parameter); + } + } + } + } +} diff --git a/src/PinkParrot/Pipeline/Swagger/RemoveReadonlyFilter.cs b/src/PinkParrot/Pipeline/Swagger/RemoveReadonlyFilter.cs new file mode 100644 index 000000000..e437444b3 --- /dev/null +++ b/src/PinkParrot/Pipeline/Swagger/RemoveReadonlyFilter.cs @@ -0,0 +1,36 @@ +// ========================================================================== +// RemoveReadonlyFilter.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using Swashbuckle.Swagger.Model; +using Swashbuckle.SwaggerGen.Generator; + +namespace PinkParrot.Pipeline.Swagger +{ + public class RemoveReadonlyFilter : ISchemaFilter + { + public void Apply(Schema model, SchemaFilterContext context) + { + Apply(model); + } + + private static void Apply(Schema model) + { + model.ReadOnly = null; + + if (model.Properties == null) + { + return; + } + + foreach (var property in model.Properties) + { + Apply(property.Value); + } + } + } +} diff --git a/src/PinkParrot/Startup.cs b/src/PinkParrot/Startup.cs index d1fc8c79e..fcbb58dc0 100644 --- a/src/PinkParrot/Startup.cs +++ b/src/PinkParrot/Startup.cs @@ -11,11 +11,9 @@ using Autofac; using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using PinkParrot.Infrastructure.CQRS.Autofac; -using PinkParrot.Infrastructure.CQRS.Commands; +using PinkParrot.Configurations; // ReSharper disable AccessToModifiedClosure @@ -25,30 +23,18 @@ namespace PinkParrot { public IServiceProvider ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services.AddMvc().AddAppSerializers(); services.AddRouting(); - services.AddSwaggerGen(); + services.AddAppSwagger(); + services.AddEventFormatter(); - IContainer container = null; + var builder = new ContainerBuilder(); + builder.Populate(services); + builder.RegisterModule(); + builder.RegisterModule(); + builder.RegisterModule(); - var containerBuilder = new ContainerBuilder(); - containerBuilder.Populate(services); - - containerBuilder.Register(c => container) - .As() - .SingleInstance(); - - containerBuilder.RegisterType() - .As() - .SingleInstance(); - - containerBuilder.RegisterType() - .As() - .SingleInstance(); - - container = containerBuilder.Build(); - - return new AutofacServiceProvider(container); + return new AutofacServiceProvider(builder.Build()); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) @@ -57,18 +43,12 @@ namespace PinkParrot app.UseMvc(); app.UseStaticFiles(); - app.UseSwagger(); - app.UseSwaggerUi(); + app.UseAppSwagger(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } - - app.Run(async (context) => - { - await context.Response.WriteAsync("Hello World!"); - }); } } } diff --git a/src/PinkParrot/project.json b/src/PinkParrot/project.json index 98df8f0b6..c65f1d414 100644 --- a/src/PinkParrot/project.json +++ b/src/PinkParrot/project.json @@ -1,4 +1,4 @@ -{ +{ "dependencies": { "Autofac": "4.1.0", "Autofac.Extensions.DependencyInjection": "4.0.0", @@ -27,8 +27,7 @@ "PinkParrot.Infrastructure": "1.0.0-*", "PinkParrot.Read": "1.0.0-*", "PinkParrot.Write": "1.0.0-*", - "Swashbuckle.SwaggerGen": "6.0.0-beta901", - "Swashbuckle.SwaggerUi": "6.0.0-beta901" + "Swashbuckle": "6.0.0-beta902" }, "tools": { @@ -46,7 +45,9 @@ "buildOptions": { "emitEntryPoint": true, - "preserveCompilationContext": true + "preserveCompilationContext": true, + "xmlDoc": true, + "nowarn": [ "1591", "1573", "1572" ] }, "runtimeOptions": { diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs index 7f3b47fdd..9ec7e86be 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs @@ -21,11 +21,13 @@ namespace PinkParrot.Core.Schema AddFactory(id => new NumberField(id)); } - public void AddFactory(Func factory) where T : ModelFieldProperties + public ModelFieldFactory AddFactory(Func factory) where T : ModelFieldProperties { Guard.NotNull(factory, nameof(factory)); factories[typeof(T)] = factory; + + return this; } public virtual ModelField CreateField(long id, ModelFieldProperties properties) diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs index ae9493345..ac34a616c 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs @@ -64,22 +64,22 @@ namespace PinkParrot.Core.Schema return new ModelSchema(newMetadata, fields); } - public ModelSchema AddField(long id, ModelFieldProperties properties, ModelFieldFactory factory) + public ModelSchema AddField(long id, ModelFieldProperties fieldProperties, ModelFieldFactory factory) { - var field = factory.CreateField(id, properties); + var field = factory.CreateField(id, fieldProperties); return SetField(field); } - public ModelSchema SetField(long fieldId, ModelFieldProperties properties) + public ModelSchema SetField(long fieldId, ModelFieldProperties fieldProperties) { - Guard.NotNull(properties, nameof(properties)); + Guard.NotNull(fieldProperties, nameof(fieldProperties)); return UpdateField(fieldId, field => { var errors = new List(); - var newField = field.Configure(properties, errors); + var newField = field.Configure(fieldProperties, errors); if (errors.Any()) { diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaProperties.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaProperties.cs index 6965596f1..887a9f538 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaProperties.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaProperties.cs @@ -1,5 +1,5 @@ // ========================================================================== -// ModelSchemaMetadata.cs +// ModelSchemaProperties.cs // PinkParrot Headless CMS // ========================================================================== // Copyright (c) PinkParrot Group diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/NamedElementProperties.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/NamedElementProperties.cs index 2d0a00d73..e90e18767 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/NamedElementProperties.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/NamedElementProperties.cs @@ -7,6 +7,7 @@ // ========================================================================== using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using PinkParrot.Infrastructure; namespace PinkParrot.Core.Schema @@ -17,6 +18,7 @@ namespace PinkParrot.Core.Schema private readonly string label; private readonly string hints; + [Required] public string Name { get { return name; } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Autofac/AutofacDomainObjectFactory.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Autofac/AutofacDomainObjectFactory.cs index 8fbf33ee0..92afe9692 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Autofac/AutofacDomainObjectFactory.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Autofac/AutofacDomainObjectFactory.cs @@ -14,18 +14,18 @@ namespace PinkParrot.Infrastructure.CQRS.Autofac { public sealed class AutofacDomainObjectFactory : IDomainObjectFactory { - private readonly IContainer container; + private readonly ILifetimeScope lifetimeScope; - public AutofacDomainObjectFactory(IContainer container) + public AutofacDomainObjectFactory(ILifetimeScope lifetimeScope) { - Guard.NotNull(container, nameof(container)); + Guard.NotNull(lifetimeScope, nameof(lifetimeScope)); - this.container = container; + this.lifetimeScope = lifetimeScope; } public IAggregate CreateNew(Type type, Guid id) { - return (IAggregate)container.Resolve(type, + return (IAggregate)lifetimeScope.Resolve(type, new NamedParameter("id", id), new NamedParameter("version", 0)); } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/AggregateCommand.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/AggregateCommand.cs index d9541efe1..9c27ac914 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/AggregateCommand.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/AggregateCommand.cs @@ -10,7 +10,7 @@ using System; namespace PinkParrot.Infrastructure.CQRS.Commands { - public class AggregateCommand : ICommand + public class AggregateCommand : IAggregateCommand { public Guid AggregateId { get; set; } } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/GetEventStore/GetEventStoreDomainObjectRepository.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IAggregateCommand.cs similarity index 65% rename from src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/GetEventStore/GetEventStoreDomainObjectRepository.cs rename to src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IAggregateCommand.cs index 9f25e4942..a325658ae 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/GetEventStore/GetEventStoreDomainObjectRepository.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IAggregateCommand.cs @@ -1,13 +1,17 @@ // ========================================================================== -// GetEventStoreDomainObjectRepository.cs +// IAggregateCommand.cs // PinkParrot Headless CMS // ========================================================================== // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== -namespace PinkParrot.Infrastructure.CQRS.GetEventStore + +using System; + +namespace PinkParrot.Infrastructure.CQRS.Commands { - public class GetEventStoreDomainObjectRepository + public interface IAggregateCommand : ICommand { + Guid AggregateId { get; set; } } -} +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Envelope.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Envelope.cs index d79df3091..093791a0c 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Envelope.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Envelope.cs @@ -5,6 +5,7 @@ // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== + namespace PinkParrot.Infrastructure.CQRS { public class Envelope where TPayload : class @@ -28,22 +29,23 @@ namespace PinkParrot.Infrastructure.CQRS } } - public Envelope(TPayload payload) + public Envelope(TPayload payload, EnvelopeHeaders headers) { Guard.NotNull(payload, nameof(payload)); + Guard.NotNull(headers, nameof(headers)); this.payload = payload; - - headers = new EnvelopeHeaders(); + this.headers = headers; } - public Envelope(TPayload payload, EnvelopeHeaders headers) + public Envelope(TPayload payload) + : this(payload, new EnvelopeHeaders()) { - Guard.NotNull(payload, nameof(payload)); - Guard.NotNull(headers, nameof(headers)); + } - this.payload = payload; - this.headers = headers; + public Envelope(TPayload payload, PropertiesBag bag) + : this(payload, new EnvelopeHeaders(bag)) + { } public Envelope To() where TOther : class diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeHeaders.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeHeaders.cs index fb0092932..9cde1fd76 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeHeaders.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeHeaders.cs @@ -9,6 +9,23 @@ namespace PinkParrot.Infrastructure.CQRS { public sealed class EnvelopeHeaders : PropertiesBag { + public EnvelopeHeaders() + { + } + + public EnvelopeHeaders(PropertiesBag bag) + { + if (bag == null) + { + return; + } + + foreach (var property in bag.Properties) + { + Set(property.Key, property.Value); + } + } + public EnvelopeHeaders Clone() { var clone = new EnvelopeHeaders(); diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs new file mode 100644 index 000000000..f3e0f7999 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs @@ -0,0 +1,33 @@ +// ========================================================================== +// DefaultNameResolver.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Globalization; + +namespace PinkParrot.Infrastructure.CQRS.EventStore +{ + public sealed class DefaultNameResolver : IStreamNameResolver + { + private readonly string prefix; + + public DefaultNameResolver() + : this(string.Empty) + { + } + + public DefaultNameResolver(string 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); + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs new file mode 100644 index 000000000..d532c51bb --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs @@ -0,0 +1,153 @@ +// ========================================================================== +// GetEventStoreDomainObjectRepository.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using EventStore.ClientAPI; +using EventStore.ClientAPI.SystemData; +using PinkParrot.Infrastructure.CQRS.Commands; +// ReSharper disable RedundantAssignment + +// ReSharper disable ConvertIfStatementToSwitchStatement +// ReSharper disable TooWideLocalVariableScope + +namespace PinkParrot.Infrastructure.CQRS.EventStore +{ + public sealed class EventStoreDomainObjectRepository : IDomainObjectRepository + { + private const int WritePageSize = 500; + private const int ReadPageSize = 500; + private readonly IEventStoreConnection connection; + private readonly IStreamNameResolver nameResolver; + private readonly IDomainObjectFactory factory; + private readonly UserCredentials credentials; + private readonly EventStoreParser formatter; + + public EventStoreDomainObjectRepository( + IDomainObjectFactory factory, + IStreamNameResolver nameResolver, + IEventStoreConnection connection, + UserCredentials credentials, + EventStoreParser formatter) + { + Guard.NotNull(factory, nameof(factory)); + Guard.NotNull(formatter, nameof(formatter)); + Guard.NotNull(connection, nameof(connection)); + Guard.NotNull(credentials, nameof(credentials)); + Guard.NotNull(nameResolver, nameof(nameResolver)); + + this.factory = factory; + this.formatter = formatter; + this.connection = connection; + this.credentials = credentials; + this.nameResolver = nameResolver; + } + + public async Task GetByIdAsync(Guid id, int version = 0) where TDomainObject : class, IAggregate + { + Guard.GreaterThan(version, 0, nameof(version)); + + var streamName = nameResolver.GetStreamName(typeof(TDomainObject), id); + + var domainObject = (TDomainObject)factory.CreateNew(typeof(TDomainObject), id); + + var sliceStart = 0; + var sliceCount = 0; + + StreamEventsSlice currentSlice; + do + { + sliceCount = sliceStart + ReadPageSize <= version ? ReadPageSize : version - sliceStart + 1; + + currentSlice = await connection.ReadStreamEventsForwardAsync(streamName, sliceStart, sliceCount, false, credentials); + + if (currentSlice.Status == SliceReadStatus.StreamNotFound) + { + throw new DomainObjectNotFoundException(id.ToString(), typeof(TDomainObject)); + } + + if (currentSlice.Status == SliceReadStatus.StreamDeleted) + { + throw new DomainObjectDeletedException(id.ToString(), typeof(TDomainObject)); + } + + sliceStart = currentSlice.NextEventNumber; + + foreach (var resolved in currentSlice.Events) + { + var envelope = formatter.Parse(resolved); + + domainObject.ApplyEvent(envelope); + } + } + while (version >= currentSlice.NextEventNumber && !currentSlice.IsEndOfStream); + + if (domainObject.Version != version && version < int.MaxValue) + { + throw new DomainObjectVersionException(id.ToString(), typeof(TDomainObject), domainObject.Version, version); + } + + return domainObject; + } + + public async Task SaveAsync(IAggregate domainObject, Guid commitId) + { + Guard.NotNull(domainObject, nameof(domainObject)); + + var streamName = nameResolver.GetStreamName(domainObject.GetType(), domainObject.Id); + + var newEvents = domainObject.GetUncomittedEvents(); + + var currVersion = domainObject.Version; + var prevVersion = currVersion - newEvents.Count; + var exptVersion = prevVersion == 0 ? ExpectedVersion.NoStream : prevVersion - 1; + + var eventsToSave = newEvents.Select(x => formatter.ToEventData(x, commitId)).ToList(); + + await InsertEventsAsync(streamName, exptVersion, eventsToSave); + + domainObject.ClearUncommittedEvents(); + } + + private async Task InsertEventsAsync(string streamName, int exptVersion, IReadOnlyCollection eventsToSave) + { + if (eventsToSave.Count > 0) + { + if (eventsToSave.Count < WritePageSize) + { + await connection.AppendToStreamAsync(streamName, exptVersion, eventsToSave, credentials); + } + else + { + var transaction = await connection.StartTransactionAsync(streamName, exptVersion, credentials); + + try + { + for (var p = 0; p < eventsToSave.Count; p += WritePageSize) + { + await transaction.WriteAsync(eventsToSave.Skip(p).Take(WritePageSize)); + } + + await transaction.CommitAsync(); + } + finally + { + transaction.Dispose(); + } + } + } + else + { + Debug.WriteLine(string.Format("No events to insert for: {0}", streamName), "GetEventStoreRepository"); + } + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs new file mode 100644 index 000000000..49be3cb8c --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs @@ -0,0 +1,66 @@ +// ========================================================================== +// EventStoreFormatter.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Text; +using EventStore.ClientAPI; +using Newtonsoft.Json; +using PinkParrot.Infrastructure.CQRS.Events; + +// ReSharper disable InconsistentNaming + +namespace PinkParrot.Infrastructure.CQRS.EventStore +{ + public class EventStoreParser + { + private readonly JsonSerializerSettings serializerSettings; + + public EventStoreParser(JsonSerializerSettings serializerSettings = null) + { + this.serializerSettings = serializerSettings ?? new JsonSerializerSettings(); + } + + public Envelope Parse(ResolvedEvent @event) + { + var headers = ReadJson(@event.Event.Metadata); + + var eventType = Type.GetType(headers.Properties[CommonHeaders.EventType].ToString()); + var eventData = ReadJson(@event.Event.Data, eventType); + + var envelope = new Envelope(eventData, headers); + + envelope.Headers.Set(CommonHeaders.Timestamp, DateTime.SpecifyKind(@event.Event.Created, DateTimeKind.Utc)); + envelope.Headers.Set(CommonHeaders.EventNumber, @event.OriginalEventNumber); + + return envelope; + } + + public EventData ToEventData(Envelope envelope, Guid commitId) + { + var eventType = envelope.Payload.GetType(); + + envelope.Headers.Set(CommonHeaders.CommitId, commitId); + envelope.Headers.Set(CommonHeaders.EventType, eventType.AssemblyQualifiedName); + + var headers = WriteJson(envelope.Headers); + var content = WriteJson(envelope.Payload); + + return new EventData(envelope.Headers.EventId(), eventType.Name, true, content, headers); + } + + private T ReadJson(byte[] data, Type type = null) + { + return (T)JsonConvert.DeserializeObject(Encoding.UTF8.GetString(data), type ?? typeof(T), serializerSettings); + } + + private byte[] WriteJson(object value) + { + return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value, serializerSettings)); + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamNameResolver.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamNameResolver.cs new file mode 100644 index 000000000..35bb89382 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamNameResolver.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// IStreamNameResolver.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Infrastructure.CQRS.EventStore +{ + public interface IStreamNameResolver + { + string GetStreamName(Type aggregateType, Guid id); + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcher.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcher.cs index 64b8cf08f..c1a602008 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcher.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcher.cs @@ -25,7 +25,7 @@ namespace PinkParrot.Infrastructure.Dispatching .Where(Helper.HasRightName) .Where(Helper.HasRightParameters) .Select(ActionContextDispatcherFactory.CreateActionHandler) - .ToDictionary>, Type, Action>(h => h.Item1, h => h.Item2); + .ToDictionary(h => h.Item1, h => h.Item2); } public static bool Dispatch(TTarget target, TIn input, TContext context) diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcher.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcher.cs index ee20958f3..d0b1ede37 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcher.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcher.cs @@ -25,7 +25,7 @@ namespace PinkParrot.Infrastructure.Dispatching .Where(Helper.HasRightName) .Where(Helper.HasRightParameters) .Select(ActionDispatcherFactory.CreateActionHandler) - .ToDictionary>, Type, Action>(h => h.Item1, h => h.Item2); + .ToDictionary(h => h.Item1, h => h.Item2); } public static bool Dispatch(TTarget target, TIn item) diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcher.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcher.cs index c05e2225f..dded47901 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcher.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcher.cs @@ -26,19 +26,14 @@ namespace PinkParrot.Infrastructure.Dispatching .Where(Helper.HasRightParameters) .Where(Helper.HasRightReturnType) .Select(FuncContextDispatcherFactory.CreateFuncHandler) - .ToDictionary>, Type, Func>(h => h.Item1, h => h.Item2); + .ToDictionary(h => h.Item1, h => h.Item2); } public static TOut Dispatch(TTarget target, TIn item, TContext context) { Func handler; - if (Handlers.TryGetValue(item.GetType(), out handler)) - { - return handler(target, item, context); - } - - return default(TOut); + return Handlers.TryGetValue(item.GetType(), out handler) ? handler(target, item, context) : default(TOut); } } } \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcher.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcher.cs index ba7254c21..4ed946528 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcher.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcher.cs @@ -26,19 +26,14 @@ namespace PinkParrot.Infrastructure.Dispatching .Where(Helper.HasRightParameters) .Where(Helper.HasRightReturnType) .Select(FuncDispatcherFactory.CreateFuncHandler) - .ToDictionary>, Type, Func>(h => h.Item1, h => h.Item2); + .ToDictionary(h => h.Item1, h => h.Item2); } public static TOut Dispatch(TTarget target, TIn item) { Func handler; - if (Handlers.TryGetValue(item.GetType(), out handler)) - { - return handler(target, item); - } - - return default(TOut); + return Handlers.TryGetValue(item.GetType(), out handler) ? handler(target, item) : default(TOut); } } } \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectDeletedException.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectDeletedException.cs new file mode 100644 index 000000000..0f18e743d --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectDeletedException.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// DomainObjectDeletedException.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Infrastructure +{ + public class DomainObjectDeletedException : DomainObjectException + { + public DomainObjectDeletedException(string id, Type type) + : base(FormatMessage(id, type), id, type) + { + } + + private static string FormatMessage(string id, Type type) + { + return $"Domain object \'{id}\' (type {type}) not deleted."; + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectException.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectException.cs new file mode 100644 index 000000000..2310e2465 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectException.cs @@ -0,0 +1,50 @@ +// ========================================================================== +// DomainObjectException.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Infrastructure +{ + public class DomainObjectException : Exception + { + private readonly string id; + private readonly string typeName; + + public string TypeName + { + get + { + return typeName; + } + } + + public string Id + { + get + { + return id; + } + } + + protected DomainObjectException(string message, string id, Type type) + : this(message, id, type, null) + { + } + + protected DomainObjectException(string message, string id, Type type, Exception inner) + : base(message, inner) + { + this.id = id; + + if (type != null) + { + typeName = type.Name; + } + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectNotFoundException.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectNotFoundException.cs new file mode 100644 index 000000000..79b75d2d6 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectNotFoundException.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// DomainObjectNotFoundException.cs +// Green Parrot Framework +// ========================================================================== +// Copyright (c) Sebastian Stehle +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Infrastructure +{ + public class DomainObjectNotFoundException : DomainObjectException + { + public DomainObjectNotFoundException(string id, Type type) + : base(FormatMessage(id, type), id, type) + { + } + + private static string FormatMessage(string id, Type type) + { + return $"Domain object \'{id}\' (type {type}) not found."; + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectVersionException.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectVersionException.cs new file mode 100644 index 000000000..cb33391ab --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectVersionException.cs @@ -0,0 +1,47 @@ +// ========================================================================== +// DomainObjectVersionException.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Infrastructure +{ + public class DomainObjectVersionException : DomainObjectException + { + private readonly int currentVersion; + private readonly int expectedVersion; + + public int CurrentVersion + { + get + { + return currentVersion; + } + } + + public int ExpectedVersion + { + get + { + return expectedVersion; + } + } + + public DomainObjectVersionException(string id, Type type, int currentVersion, int expectedVersion) + : base(FormatMessage(id, type, currentVersion, expectedVersion), id, type) + { + this.currentVersion = currentVersion; + + this.expectedVersion = expectedVersion; + } + + private static string FormatMessage(string id, Type type, int currentVersion, int expectedVersion) + { + return $"Request version {expectedVersion} for object '{id}' (type {type}), but found {currentVersion}."; + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/HideAttribute.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/HideAttribute.cs new file mode 100644 index 000000000..f24e6838b --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/HideAttribute.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// HideAttribute.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Infrastructure +{ + [AttributeUsage(AttributeTargets.Property)] + public class HideAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/PropertiesBagConverter.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/PropertiesBagConverter.cs index 1e1a452e8..0ff9fe6ee 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/PropertiesBagConverter.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/PropertiesBagConverter.cs @@ -7,6 +7,7 @@ // ========================================================================== using System; +using System.Reflection; using Newtonsoft.Json; using NodaTime; using NodaTime.Extensions; @@ -17,7 +18,7 @@ namespace PinkParrot.Infrastructure.Json { public override bool CanConvert(Type objectType) { - return objectType == typeof(PropertiesBag); + return typeof(PropertiesBag).IsAssignableFrom(objectType); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertyValue.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertyValue.cs index 95c13f53b..fe841b728 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertyValue.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertyValue.cs @@ -36,7 +36,7 @@ namespace PinkParrot.Infrastructure { typeof(Instant), (p, c) => p.ToInstant(c) }, { typeof(Instant?), (p, c) => p.ToNullableInstant(c) }, { typeof(Guid), (p, c) => p.ToGuid(c) }, - { typeof(Guid?), (p, c) => p.ToNullableGuid(c) }, + { typeof(Guid?), (p, c) => p.ToNullableGuid(c) } }; public object RawValue diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json index d650396e4..8823eff70 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json @@ -2,9 +2,11 @@ "version": "1.0.0-*", "dependencies": { "Autofac": "4.1.0", + "EventStore.ClientAPI.DotNetCore": "1.0.0", "NETStandard.Library": "1.6.0", "Newtonsoft.Json": "9.0.1", "NodaTime": "2.0.0-alpha20160729", + "protobuf-net": "2.1.0", "System.Linq": "4.1.0", "System.Reflection.TypeExtensions": "4.1.0" }, diff --git a/src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaRM.cs b/src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaRM.cs index 86b35ddb7..90aaf1143 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaRM.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaRM.cs @@ -7,15 +7,20 @@ // ========================================================================== using System; +using System.ComponentModel.DataAnnotations; +using PinkParrot.Infrastructure; namespace PinkParrot.Read.Models { public sealed class ModelSchemaRM { + [Hide] public string Id { get; set; } + [Required] public string Name { get; set; } + [Required] public Guid SchemaId { get; set; } } } diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/ISchemaProvider.cs b/src/pinkparrot_read/PinkParrot.Read/Services/ISchemaProvider.cs new file mode 100644 index 000000000..0995e40d8 --- /dev/null +++ b/src/pinkparrot_read/PinkParrot.Read/Services/ISchemaProvider.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// ISchemaProvider.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; + +namespace PinkParrot.Read.Services +{ + public interface ISchemaProvider + { + Task FindSchemaIdByNameAsync(string name); + } +} diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/SchemaProvider.cs b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/SchemaProvider.cs new file mode 100644 index 000000000..3adaf5d3e --- /dev/null +++ b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/SchemaProvider.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// SchemaProvider.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; + +namespace PinkParrot.Read.Services.Implementations +{ + public class SchemaProvider : ISchemaProvider + { + public Task FindSchemaIdByNameAsync(string name) + { + return Task.FromResult(Guid.Empty); + } + } +} diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs index 8ed607163..f91da3a54 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs @@ -13,6 +13,6 @@ namespace PinkParrot.Write.Schema.Commands { public class AddModelField : AggregateCommand { - public ModelFieldProperties Properties; + public ModelFieldProperties Properties { get; set; } } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs index f3b04cc5a..9283ed7c7 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs @@ -13,6 +13,6 @@ namespace PinkParrot.Write.Schema.Commands { public class CreateModelSchema : AggregateCommand { - public ModelSchemaProperties Properties; + public ModelSchemaProperties Properties { get; set; } } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs index fb650e360..1f6c6f3e5 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs @@ -12,6 +12,6 @@ namespace PinkParrot.Write.Schema.Commands { public class DeleteModelField : AggregateCommand { - public long FieldId; + public long FieldId { get; set; } } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs index 5a35849bb..bd7eed81e 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs @@ -6,11 +6,13 @@ // All rights reserved. // ========================================================================== +using System; using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class DeleteModelSchema : AggregateCommand + public class DeleteModelSchema : IAggregateCommand { + public Guid AggregateId { get; set; } } } \ 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 d07d1a0fe..da258d546 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs @@ -12,6 +12,6 @@ namespace PinkParrot.Write.Schema.Commands { public class DisableModelField : AggregateCommand { - public long FieldId; + 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 fd28e8e32..ee0575730 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs @@ -12,6 +12,6 @@ namespace PinkParrot.Write.Schema.Commands { public class EnableModelField : AggregateCommand { - public long FieldId; + 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 2b6640ce0..435460374 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs @@ -12,6 +12,6 @@ namespace PinkParrot.Write.Schema.Commands { public class HideModelField : AggregateCommand { - public long FieldId; + 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 9cef4a2cd..e048f5617 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs @@ -12,6 +12,6 @@ namespace PinkParrot.Write.Schema.Commands { public class ShowModelField : AggregateCommand { - public long FieldId; + 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 eab6cd660..beeaa87ab 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs @@ -13,8 +13,8 @@ namespace PinkParrot.Write.Schema.Commands { public class UpdateModelField : AggregateCommand { - public long FieldId; + public long FieldId { get; set; } - public ModelFieldProperties Properties; + public ModelFieldProperties Properties { get; set; } } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs index d6e5464f0..3805a5124 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs @@ -13,6 +13,6 @@ namespace PinkParrot.Write.Schema.Commands { public class UpdateModelSchema : AggregateCommand { - public ModelSchemaProperties Properties; + public ModelSchemaProperties Properties { get; set; } } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs index dd29aa781..736d05a8e 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs @@ -21,61 +21,61 @@ namespace PinkParrot.Write.Schema return this.DispatchActionAsync(context.Command, context); } - protected Task On(AddModelField command, CommandContext context) + public Task On(AddModelField command, CommandContext context) { return Update(command, context, schema => schema.AddField(command)); } - protected Task On(DeleteModelField command, CommandContext context) + public Task On(DeleteModelField command, CommandContext context) { return Update(command, context, schema => schema.DeleteField(command)); } - protected Task On(DeleteModelSchema command, CommandContext context) + public Task On(DeleteModelSchema command, CommandContext context) { return Update(command, context, schema => schema.Delete(command)); } - protected Task On(DisableModelField command, CommandContext context) + public Task On(DisableModelField command, CommandContext context) { return Update(command, context, schema => schema.DisableField(command)); } - protected Task On(EnableModelField command, CommandContext context) + public Task On(EnableModelField command, CommandContext context) { return Update(command, context, schema => schema.EnableField(command)); } - protected Task On(HideModelField command, CommandContext context) + public Task On(HideModelField command, CommandContext context) { return Update(command, context, schema => schema.HideField(command)); } - protected Task On(ShowModelField command, CommandContext context) + public Task On(ShowModelField command, CommandContext context) { return Update(command, context, schema => schema.ShowField(command)); } - protected Task On(UpdateModelField command, CommandContext context) + public Task On(UpdateModelField command, CommandContext context) { return Update(command, context, schema => schema.UpdateField(command)); } - protected Task On(UpdateModelSchema command, CommandContext context) + public Task On(UpdateModelSchema command, CommandContext context) { return Update(command, context, schema => schema.Update(command)); } - protected Task On(CreateModelSchema command, CommandContext context) + public Task On(CreateModelSchema command, CommandContext context) { var schema = context.Factory.CreateNew(command.AggregateId); schema.Create(command); - return context.Repository.SaveAsync(schema, Guid.NewGuid()); + return context.Repository.SaveAsync(schema, command.AggregateId); } - private async Task Update(AggregateCommand command, CommandContext context, Action updater) + private static async Task Update(IAggregateCommand command, CommandContext context, Action updater) { var schema = await context.Repository.GetByIdAsync(command.AggregateId);