Browse Source

A lot progress

pull/1/head
Sebastian Stehle 9 years ago
parent
commit
b8d6590e8b
  1. 13
      src/PinkParrot/Configurations/InfrastructureModule.cs
  2. 3
      src/PinkParrot/Configurations/ReadModule.cs
  3. 17
      src/PinkParrot/Configurations/Serializers.cs
  4. 23
      src/PinkParrot/Modules/Api/BaseController.cs
  5. 2
      src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs
  6. 39
      src/PinkParrot/Modules/Api/Schemas/SchemasController.cs
  7. 2
      src/PinkParrot/Modules/Api/Schemas/SchemasDto.cs
  8. 40
      src/PinkParrot/Modules/ControllerBase.cs
  9. 25
      src/PinkParrot/Pipeline/CommandHandlers/EnrichWithAggregateIdHandler.cs
  10. 20
      src/PinkParrot/Pipeline/CommandHandlers/EnrichWithTenantIdHandler.cs
  11. 17
      src/PinkParrot/Pipeline/ITenantFeature.cs
  12. 45
      src/PinkParrot/Pipeline/TenantMiddleware.cs
  13. 4
      src/PinkParrot/Startup.cs
  14. 68
      src/pinkparrot_core/PinkParrot.Core/Schema/Json/SchemaDto.cs
  15. 20
      src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs
  16. 9
      src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs
  17. 5
      src/pinkparrot_core/PinkParrot.Core/Schema/ModelField_Generic.cs
  18. 57
      src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs
  19. 4
      src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs
  20. 27
      src/pinkparrot_core/PinkParrot.Core/Schema/NumberFieldProperties.cs
  21. 2
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs
  22. 5
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs
  23. 15
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs
  24. 9
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs
  25. 63
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreBus.cs
  26. 2
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs
  27. 6
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamPositionStorage.cs
  28. 5
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs
  29. 68
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Reflection/ReflectionExtensions.cs
  30. 4
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Reflection/SimpleMapper.cs
  31. 14
      src/pinkparrot_read/PinkParrot.Read/IEntity.cs
  32. 11
      src/pinkparrot_read/PinkParrot.Read/IModelSchemaRM.cs
  33. 26
      src/pinkparrot_read/PinkParrot.Read/Repositories/EntityWithSchema.cs
  34. 2
      src/pinkparrot_read/PinkParrot.Read/Repositories/IEntity.cs
  35. 14
      src/pinkparrot_read/PinkParrot.Read/Repositories/IModelSchemaEntity.cs
  36. 9
      src/pinkparrot_read/PinkParrot.Read/Repositories/IModelSchemaRepository.cs
  37. 4
      src/pinkparrot_read/PinkParrot.Read/Repositories/ITenantEntity.cs
  38. 19
      src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/EntityMapper.cs
  39. 11
      src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaEntity.cs
  40. 66
      src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaListRepository.cs
  41. 172
      src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaRepository.cs
  42. 2
      src/pinkparrot_read/PinkParrot.Read/Services/IModelSchemaProvider.cs
  43. 2
      src/pinkparrot_read/PinkParrot.Read/Services/ITenantProvider.cs
  44. 69
      src/pinkparrot_read/PinkParrot.Read/Services/Implementations/ModelSchemaProvider.cs
  45. 5
      src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs
  46. 29
      src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs
  47. 4
      src/pinkparrot_read/PinkParrot.Read/Services/Implementations/TenantProvider.cs
  48. 1
      src/pinkparrot_read/PinkParrot.Read/project.json
  49. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs
  50. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs
  51. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs
  52. 4
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs
  53. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs
  54. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs
  55. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs
  56. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs
  57. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs
  58. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs
  59. 25
      src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs
  60. 2
      src/pinkparrot_write/PinkParrot.Write/TenantCommand.cs

13
src/PinkParrot/Configurations/InfrastructureModule.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// InfrastructureDependencies.cs // InfrastructureModule.cs
// PinkParrot Headless CMS // PinkParrot Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) PinkParrot Group // Copyright (c) PinkParrot Group
@ -13,10 +13,12 @@ using EventStore.ClientAPI.SystemData;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver; using MongoDB.Driver;
using PinkParrot.Infrastructure.CQRS.Autofac; using PinkParrot.Infrastructure.CQRS.Autofac;
using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Infrastructure.CQRS.EventStore; using PinkParrot.Infrastructure.CQRS.EventStore;
using PinkParrot.Pipeline;
using PinkParrot.Read.Services.Implementations; using PinkParrot.Read.Services.Implementations;
namespace PinkParrot.Configurations namespace PinkParrot.Configurations
@ -63,7 +65,7 @@ namespace PinkParrot.Configurations
.As<IDomainObjectFactory>() .As<IDomainObjectFactory>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<DefaultNameResolver>() builder.RegisterInstance(new DefaultNameResolver("pinkparrot"))
.As<IStreamNameResolver>() .As<IStreamNameResolver>()
.SingleInstance(); .SingleInstance();
@ -89,7 +91,12 @@ namespace PinkParrot.Configurations
{ {
public static void UseAppEventBus(this IApplicationBuilder app) public static void UseAppEventBus(this IApplicationBuilder app)
{ {
app.ApplicationServices.GetService(typeof(EventStoreBus)); app.ApplicationServices.GetService<EventStoreBus>().Subscribe("pinkparrot");
}
public static void UseAppTenants(this IApplicationBuilder app)
{
app.UseMiddleware<TenantMiddleware>();
} }
} }
} }

3
src/PinkParrot/Configurations/ReadModule.cs

@ -25,9 +25,10 @@ namespace PinkParrot.Configurations
builder.RegisterType<ModelSchemaProvider>() builder.RegisterType<ModelSchemaProvider>()
.As<IModelSchemaProvider>() .As<IModelSchemaProvider>()
.As<ILiveEventConsumer>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<MongoModelSchemaListRepository>() builder.RegisterType<MongoModelSchemaRepository>()
.As<IModelSchemaRepository>() .As<IModelSchemaRepository>()
.As<ICatchEventConsumer>() .As<ICatchEventConsumer>()
.SingleInstance(); .SingleInstance();

17
src/PinkParrot/Configurations/Serializers.cs

@ -7,15 +7,12 @@
// ========================================================================== // ==========================================================================
using System.Reflection; using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
using PinkParrot.Core.Schema; using PinkParrot.Core.Schema;
using PinkParrot.Infrastructure.CQRS.EventStore; using PinkParrot.Infrastructure.CQRS.EventStore;
using PinkParrot.Infrastructure.Json; 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 namespace PinkParrot.Configurations
{ {
@ -40,18 +37,16 @@ namespace PinkParrot.Configurations
public static void AddEventFormatter(this IServiceCollection services) public static void AddEventFormatter(this IServiceCollection services)
{ {
var fieldFactory = var fieldFactory = new ModelFieldFactory();
new ModelFieldFactory()
.AddFactory<NumberFieldProperties>(id => new NumberField(id));
ServiceCollectionServiceExtensions.AddSingleton(services, t => CreateSettings()); services.AddSingleton(t => CreateSettings());
ServiceCollectionServiceExtensions.AddSingleton(services, fieldFactory); services.AddSingleton(fieldFactory);
ServiceCollectionServiceExtensions.AddSingleton<EventStoreFormatter>(services); services.AddSingleton<EventStoreFormatter>();
} }
public static void AddAppSerializers(this IMvcBuilder mvc) public static void AddAppSerializers(this IMvcBuilder mvc)
{ {
MvcJsonMvcBuilderExtensions.AddJsonOptions(mvc, options => mvc.AddJsonOptions(options =>
{ {
ConfigureJson(options.SerializerSettings); ConfigureJson(options.SerializerSettings);
}); });

23
src/PinkParrot/Modules/Api/BaseController.cs

@ -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;
}
}
}

2
src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs

@ -17,7 +17,7 @@ using Swashbuckle.SwaggerGen.Annotations;
namespace PinkParrot.Modules.Api.Schemas namespace PinkParrot.Modules.Api.Schemas
{ {
public class SchemasFieldsController : BaseController public class SchemasFieldsController : ControllerBase
{ {
public SchemasFieldsController(ICommandBus commandBus) public SchemasFieldsController(ICommandBus commandBus)
: base(commandBus) : base(commandBus)

39
src/PinkParrot/Modules/Api/Schemas/SchemasController.cs

@ -12,10 +12,10 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using PinkParrot.Core.Schema; using PinkParrot.Core.Schema;
using PinkParrot.Core.Schema.Json;
using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Infrastructure.Reflection; using PinkParrot.Infrastructure.Reflection;
using PinkParrot.Read.Repositories; using PinkParrot.Read.Repositories;
using PinkParrot.Read.Services;
using PinkParrot.Write.Schema.Commands; using PinkParrot.Write.Schema.Commands;
using Swashbuckle.SwaggerGen.Annotations; using Swashbuckle.SwaggerGen.Annotations;
@ -23,17 +23,14 @@ using Swashbuckle.SwaggerGen.Annotations;
namespace PinkParrot.Modules.Api.Schemas namespace PinkParrot.Modules.Api.Schemas
{ {
public class SchemasController : BaseController public class SchemasController : ControllerBase
{ {
private readonly IModelSchemaRepository modelSchemaRepository; private readonly IModelSchemaRepository modelSchemaRepository;
private readonly ITenantProvider tenantProvider;
public SchemasController(ICommandBus commandBus, ITenantProvider tenantProvider, IModelSchemaRepository modelSchemaRepository) public SchemasController(ICommandBus commandBus, IModelSchemaRepository modelSchemaRepository)
: base(commandBus) : base(commandBus)
{ {
this.modelSchemaRepository = modelSchemaRepository; this.modelSchemaRepository = modelSchemaRepository;
this.tenantProvider = tenantProvider;
} }
/// <summary> /// <summary>
@ -42,12 +39,34 @@ namespace PinkParrot.Modules.Api.Schemas
[HttpGet] [HttpGet]
[Route("schemas/")] [Route("schemas/")]
[SwaggerOperation(Tags = new[] { "Schemas" })] [SwaggerOperation(Tags = new[] { "Schemas" })]
public async Task<List<SchemaListModel>> Query() [ProducesResponseType(typeof(List<SchemasDto>), 200)]
public async Task<List<SchemasDto>> Query()
{ {
var tenantId = await tenantProvider.ProvideTenantIdByDomainAsync(Request.Host.ToString()); var schemas = await modelSchemaRepository.QueryAllAsync(TenantId);
var schemes = await modelSchemaRepository.QueryAllAsync(tenantId);
return schemas.Select(s => SimpleMapper.Map(s, new SchemasDto())).ToList();
}
/// <summary>
/// Gets the schema with the specified name.
/// </summary>
/// <param name="name">The name of the schema.</param>
/// <response code="200">Schema returned</response>
/// <response code="404">Schema not found</response>
[HttpGet]
[Route("schemas/{name}/")]
[SwaggerOperation(Tags = new[] { "Schemas" })]
[ProducesResponseType(typeof(SchemaDto), 200)]
public async Task<ActionResult> Get(string name)
{
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));
} }
/// <summary> /// <summary>

2
src/PinkParrot/Modules/Api/Schemas/SchemaListModel.cs → src/PinkParrot/Modules/Api/Schemas/SchemasDto.cs

@ -11,7 +11,7 @@ using System.ComponentModel.DataAnnotations;
namespace PinkParrot.Modules.Api.Schemas namespace PinkParrot.Modules.Api.Schemas
{ {
public class SchemaListModel public class SchemasDto
{ {
[Required] [Required]
public Guid Id { get; set; } public Guid Id { get; set; }

40
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<ITenantFeature>();
if (tenantFeature == null)
{
throw new InvalidOperationException("Not in a tenant context");
}
return tenantFeature.TenantId;
}
}
}
}

25
src/PinkParrot/Pipeline/CommandHandlers/EnrichWithAggregateIdHandler.cs

@ -9,8 +9,11 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Read.Services; using PinkParrot.Read.Services;
using PinkParrot.Write;
using PinkParrot.Write.Schema;
// ReSharper disable InvertIf // ReSharper disable InvertIf
@ -32,16 +35,32 @@ namespace PinkParrot.Pipeline.CommandHandlers
{ {
var aggregateCommand = context.Command as IAggregateCommand; var aggregateCommand = context.Command as IAggregateCommand;
if (aggregateCommand != null && aggregateCommand.AggregateId == Guid.Empty) if (aggregateCommand == null || aggregateCommand.AggregateId != Guid.Empty)
{ {
return false;
}
var tenantCommand = context.Command as ITenantCommand;
if (tenantCommand == null)
{
return false;
}
var routeValues = actionContextAccessor.ActionContext.RouteData.Values; var routeValues = actionContextAccessor.ActionContext.RouteData.Values;
if (routeValues.ContainsKey("name")) if (routeValues.ContainsKey("name"))
{ {
var schemeName = routeValues["name"]; var schemaName = routeValues["name"].ToString();
var id = await modelSchemaProvider.FindSchemaIdByNameAsync(tenantCommand.TenantId, schemaName);
aggregateCommand.AggregateId = await modelSchemaProvider.FindSchemaIdByNameAsync(schemeName.ToString()); if (!id.HasValue)
{
throw new DomainObjectNotFoundException(schemaName, typeof(ModelSchemaDomainObject));
} }
aggregateCommand.AggregateId = id.Value;
} }
return false; return false;

20
src/PinkParrot/Pipeline/CommandHandlers/EnrichWithTenantIdHandler.cs

@ -6,10 +6,10 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Read.Services;
using PinkParrot.Write; using PinkParrot.Write;
// ReSharper disable InvertIf // ReSharper disable InvertIf
@ -18,28 +18,30 @@ namespace PinkParrot.Pipeline.CommandHandlers
{ {
public sealed class EnrichWithTenantIdHandler : ICommandHandler public sealed class EnrichWithTenantIdHandler : ICommandHandler
{ {
private readonly ITenantProvider tenantProvider;
private readonly IHttpContextAccessor httpContextAccessor; private readonly IHttpContextAccessor httpContextAccessor;
public EnrichWithTenantIdHandler(ITenantProvider tenantProvider, IHttpContextAccessor httpContextAccessor) public EnrichWithTenantIdHandler(IHttpContextAccessor httpContextAccessor)
{ {
this.tenantProvider = tenantProvider;
this.httpContextAccessor = httpContextAccessor; this.httpContextAccessor = httpContextAccessor;
} }
public async Task<bool> HandleAsync(CommandContext context) public Task<bool> HandleAsync(CommandContext context)
{ {
var tenantCommand = context.Command as ITenantCommand; var tenantCommand = context.Command as ITenantCommand;
if (tenantCommand != null) if (tenantCommand != null)
{ {
var domain = httpContextAccessor.HttpContext.Request.Host.ToString(); var tenantFeature = httpContextAccessor.HttpContext.Features.Get<ITenantFeature>();
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);
} }
} }
} }

17
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; }
}
}

45
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<ITenantFeature>(new TenantFeature { TenantId = tenantId.Value });
}
await next(context);
}
}
}

4
src/PinkParrot/Startup.cs

@ -25,14 +25,15 @@ namespace PinkParrot
{ {
services.AddMvc().AddAppSerializers(); services.AddMvc().AddAppSerializers();
services.AddRouting(); services.AddRouting();
services.AddMemoryCache();
services.AddAppSwagger(); services.AddAppSwagger();
services.AddEventFormatter(); services.AddEventFormatter();
var builder = new ContainerBuilder(); var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterModule<InfrastructureModule>(); builder.RegisterModule<InfrastructureModule>();
builder.RegisterModule<ReadModule>(); builder.RegisterModule<ReadModule>();
builder.RegisterModule<WriteModule>(); builder.RegisterModule<WriteModule>();
builder.Populate(services);
return new AutofacServiceProvider(builder.Build()); return new AutofacServiceProvider(builder.Build());
} }
@ -41,6 +42,7 @@ namespace PinkParrot
{ {
loggerFactory.AddConsole(); loggerFactory.AddConsole();
app.UseAppTenants();
app.UseMvc(); app.UseMvc();
app.UseStaticFiles(); app.UseStaticFiles();
app.UseAppSwagger(); app.UseAppSwagger();

68
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<long, ModelFieldProperties> fields;
[Required]
public ImmutableDictionary<long, ModelFieldProperties> Fields
{
get { return fields; }
}
[Required]
public ModelSchemaProperties Properties
{
get { return properties; }
}
public SchemaDto(ModelSchemaProperties properties, ImmutableDictionary<long, ModelFieldProperties> 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;
}
}
}

20
src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs

@ -80,41 +80,21 @@ namespace PinkParrot.Core.Schema
public ModelField Hide() public ModelField Hide()
{ {
if (isHidden)
{
throw new DomainException($"The field '{Name} is already hidden.");
}
return Update<ModelField>(clone => clone.isHidden = true); return Update<ModelField>(clone => clone.isHidden = true);
} }
public ModelField Show() public ModelField Show()
{ {
if (!isHidden)
{
throw new DomainException($"The field '{Name} is already visible.");
}
return Update<ModelField>(clone => clone.isHidden = false); return Update<ModelField>(clone => clone.isHidden = false);
} }
public ModelField Disable() public ModelField Disable()
{ {
if (isDisabled)
{
throw new DomainException($"The field '{Name} is already disabled.");
}
return Update<ModelField>(clone => clone.isDisabled = true); return Update<ModelField>(clone => clone.isDisabled = true);
} }
public ModelField Enable() public ModelField Enable()
{ {
if (!isDisabled)
{
throw new DomainException($"The field '{Name} is already enabled.");
}
return Update<ModelField>(clone => clone.isDisabled = false); return Update<ModelField>(clone => clone.isDisabled = false);
} }

9
src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs

@ -14,14 +14,15 @@ namespace PinkParrot.Core.Schema
{ {
public class ModelFieldFactory public class ModelFieldFactory
{ {
private readonly Dictionary<Type, Func<long, ModelField>> factories = new Dictionary<Type, Func<long, ModelField>>(); private readonly Dictionary<Type, Func<long, ModelFieldProperties, ModelField>> factories
= new Dictionary<Type, Func<long, ModelFieldProperties, ModelField>>();
public ModelFieldFactory() public ModelFieldFactory()
{ {
AddFactory<NumberFieldProperties>(id => new NumberField(id)); AddFactory<NumberFieldProperties>((id, p) => new NumberField(id, (NumberFieldProperties)p));
} }
public ModelFieldFactory AddFactory<T>(Func<long, ModelField> factory) where T : ModelFieldProperties public ModelFieldFactory AddFactory<T>(Func<long, ModelFieldProperties, ModelField> factory) where T : ModelFieldProperties
{ {
Guard.NotNull(factory, nameof(factory)); Guard.NotNull(factory, nameof(factory));
@ -41,7 +42,7 @@ namespace PinkParrot.Core.Schema
throw new InvalidOperationException("Field type is not supported."); throw new InvalidOperationException("Field type is not supported.");
} }
return factory(id); return factory(id, properties);
} }
} }
} }

5
src/pinkparrot_core/PinkParrot.Core/Schema/ModelField_Generic.cs

@ -46,9 +46,12 @@ namespace PinkParrot.Core.Schema
get { return properties; } get { return properties; }
} }
protected ModelField(long id) protected ModelField(long id, T properties)
: base(id) : base(id)
{ {
Guard.NotNull(properties, nameof(properties));
this.properties = properties;
} }
public override ModelField Configure(ModelFieldProperties newProperties, IList<ValidationError> errors) public override ModelField Configure(ModelFieldProperties newProperties, IList<ValidationError> errors)

57
src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs

@ -18,7 +18,7 @@ namespace PinkParrot.Core.Schema
public sealed class ModelSchema public sealed class ModelSchema
{ {
private readonly ModelSchemaProperties properties; private readonly ModelSchemaProperties properties;
private readonly ImmutableDictionary<long, ModelField> fields; private readonly ImmutableDictionary<long, ModelField> fieldsById;
private readonly Dictionary<string, ModelField> fieldsByName; private readonly Dictionary<string, ModelField> fieldsByName;
public ModelSchema(ModelSchemaProperties properties, ImmutableDictionary<long, ModelField> fields) public ModelSchema(ModelSchemaProperties properties, ImmutableDictionary<long, ModelField> fields)
@ -26,30 +26,31 @@ namespace PinkParrot.Core.Schema
Guard.NotNull(fields, nameof(fields)); Guard.NotNull(fields, nameof(fields));
Guard.NotNull(properties, nameof(properties)); Guard.NotNull(properties, nameof(properties));
this.fields = fields;
this.properties = properties; this.properties = properties;
fieldsById = fields;
fieldsByName = fields.Values.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); 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<ValidationError>(); var errors = new List<ValidationError>();
metadata.Validate(errors); properties.Validate(errors);
if (errors.Any()) if (errors.Any())
{ {
throw new ValidationException("Failed to create a new model schema.", errors); throw new ValidationException("Failed to create a new model schema.", errors);
} }
return new ModelSchema(metadata, ImmutableDictionary<long, ModelField>.Empty); return new ModelSchema(properties, ImmutableDictionary<long, ModelField>.Empty);
} }
public IReadOnlyDictionary<long, ModelField> Fields public ImmutableDictionary<long, ModelField> Fields
{ {
get { return fields; } get { return fieldsById; }
} }
public ModelSchemaProperties Properties public ModelSchemaProperties Properties
@ -61,14 +62,14 @@ namespace PinkParrot.Core.Schema
{ {
Guard.NotNull(newMetadata, nameof(newMetadata)); Guard.NotNull(newMetadata, nameof(newMetadata));
return new ModelSchema(newMetadata, fields); return new ModelSchema(newMetadata, fieldsById);
} }
public ModelSchema AddField(long id, ModelFieldProperties fieldProperties, ModelFieldFactory factory) public ModelSchema AddField(long id, ModelFieldProperties fieldProperties, ModelFieldFactory factory)
{ {
var field = factory.CreateField(id, fieldProperties); var field = factory.CreateField(id, fieldProperties);
return SetField(field); return ReplaceOrAddField(field);
} }
public ModelSchema SetField(long fieldId, ModelFieldProperties fieldProperties) public ModelSchema SetField(long fieldId, ModelFieldProperties fieldProperties)
@ -110,41 +111,35 @@ namespace PinkParrot.Core.Schema
return UpdateField(fieldId, field => field.Show()); 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, fieldsById.Remove(fieldId));
} }
return new ModelSchema(properties, fields.SetItem(field.Id, field)); private ModelSchema UpdateField(long fieldId, Func<ModelField, ModelField> updater)
}
public ModelSchema DeleteField(long fieldId)
{ {
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<ModelField, ModelField> 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}'.", throw new ValidationException($"A field with name '{field.Name}' already exists.");
new ValidationError("Field does not exist.", "fieldId"));
} }
var newField = updater(field); return new ModelSchema(properties, fieldsById.SetItem(field.Id, field));
return SetField(newField);
} }
public async Task ValidateAsync(PropertiesBag data) public async Task ValidateAsync(PropertiesBag data)

4
src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs

@ -27,8 +27,8 @@ namespace PinkParrot.Core.Schema
get { return Properties.MinValue; } get { return Properties.MinValue; }
} }
public NumberField(long id) public NumberField(long id, NumberFieldProperties properties)
: base(id) : base(id, properties)
{ {
} }

27
src/pinkparrot_core/PinkParrot.Core/Schema/NumberFieldProperties.cs

@ -14,29 +14,52 @@ namespace PinkParrot.Core.Schema
[TypeName("Number")] [TypeName("Number")]
public sealed class NumberFieldProperties : ModelFieldProperties public sealed class NumberFieldProperties : ModelFieldProperties
{ {
public double? DefaultValue { get; }
public double? MaxValue { get; } public double? MaxValue { get; }
public double? MinValue { get; } public double? MinValue { get; }
public string Placeholder { get; set; }
public NumberFieldProperties( public NumberFieldProperties(
bool isRequired, bool isRequired,
string name, string name,
string label, string label,
string hints, string hints,
string placeholder,
double? minValue, double? minValue,
double? maxValue) double? maxValue,
double? defaultValue)
: base(isRequired, name, label, hints) : base(isRequired, name, label, hints)
{ {
Placeholder = placeholder;
MinValue = minValue; MinValue = minValue;
MaxValue = maxValue; MaxValue = maxValue;
DefaultValue = defaultValue;
} }
protected override void ValidateCore(IList<ValidationError> errors) protected override void ValidateCore(IList<ValidationError> 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")); 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"));
}
}
} }
} }
} }

2
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs

@ -13,7 +13,7 @@ namespace PinkParrot.Infrastructure.CQRS.Commands
{ {
public interface IDomainObjectRepository public interface IDomainObjectRepository
{ {
Task<TDomainObject> GetByIdAsync<TDomainObject>(Guid id, int version = 0) where TDomainObject : class, IAggregate; Task<TDomainObject> GetByIdAsync<TDomainObject>(Guid id, int version = int.MaxValue) where TDomainObject : class, IAggregate;
Task SaveAsync(IAggregate domainObject, Guid commitId); Task SaveAsync(IAggregate domainObject, Guid commitId);
} }

5
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs

@ -54,6 +54,11 @@ namespace PinkParrot.Infrastructure.CQRS.Commands
context.MarkFailed(e); context.MarkFailed(e);
} }
} }
if (context.Exception != null)
{
throw context.Exception;
}
} }
} }
} }

15
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs

@ -52,20 +52,15 @@ namespace PinkParrot.Infrastructure.CQRS
{ {
ApplyEvent(envelopeToAdd); ApplyEvent(envelopeToAdd);
} }
else
{
version++;
}
} }
protected void RaiseEvent(IEvent @event, bool disableApply = false) protected void RaiseEvent(IEvent @event, bool disableApply = false)
{ {
Guard.NotNull(@event, nameof(@event)); RaiseEvent(EnvelopeFactory.ForEvent(@event, this), disableApply);
var envelopeToAdd = EnvelopeFactory.ForEvent(@event, this);
uncomittedEvents.Add(envelopeToAdd);
if (!disableApply)
{
ApplyEvent(envelopeToAdd);
}
} }
void IAggregate.ApplyEvent(Envelope<IEvent> @event) void IAggregate.ApplyEvent(Envelope<IEvent> @event)

9
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/DefaultNameResolver.cs

@ -15,19 +15,16 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
{ {
private readonly string prefix; private readonly string prefix;
public DefaultNameResolver()
: this(string.Empty)
{
}
public DefaultNameResolver(string prefix) public DefaultNameResolver(string prefix)
{ {
Guard.NotNullOrEmpty(prefix, nameof(prefix));
this.prefix = prefix; this.prefix = prefix;
} }
public string GetStreamName(Type aggregateType, Guid id) 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);
} }
} }
} }

63
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreBus.cs

@ -8,8 +8,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using EventStore.ClientAPI; using EventStore.ClientAPI;
using EventStore.ClientAPI.SystemData; using EventStore.ClientAPI.SystemData;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -26,7 +26,8 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
private readonly IEnumerable<ICatchEventConsumer> catchConsumers; private readonly IEnumerable<ICatchEventConsumer> catchConsumers;
private readonly ILogger<EventStoreBus> logger; private readonly ILogger<EventStoreBus> logger;
private readonly IStreamPositionStorage positions; private readonly IStreamPositionStorage positions;
private EventStoreAllCatchUpSubscription catchSubscription; private EventStoreCatchUpSubscription catchSubscription;
private bool isLive;
public EventStoreBus( public EventStoreBus(
ILogger<EventStoreBus> logger, ILogger<EventStoreBus> logger,
@ -52,15 +53,18 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
this.credentials = credentials; this.credentials = credentials;
this.liveConsumers = liveConsumers; this.liveConsumers = liveConsumers;
this.catchConsumers = catchConsumers; this.catchConsumers = catchConsumers;
Subscribe();
} }
private void Subscribe() public void Subscribe(string streamName = "$all")
{ {
var position = positions.ReadPosition(); Guard.NotNullOrEmpty(streamName, nameof(streamName));
if (catchSubscription != null)
{
return;
}
var now = DateTime.UtcNow; var position = positions.ReadPosition();
logger.LogInformation($"Subscribing from: {0}", position); logger.LogInformation($"Subscribing from: {0}", position);
@ -70,11 +74,15 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
true, true,
true); true);
catchSubscription = connection.SubscribeToAllFrom(position, settings, (s, resolvedEvent) => catchSubscription =
{ connection.SubscribeToStreamFrom(streamName, position, settings,
var requireUpdate = false; OnEvent,
OnLiveProcessingStarted,
userCredentials: credentials);
}
Debug.WriteLine($"Last Position: {catchSubscription.LastProcessedPosition}"); private void OnEvent(EventStoreCatchUpSubscription subscription, ResolvedEvent resolvedEvent)
{
try try
{ {
if (resolvedEvent.OriginalEvent.EventStreamId.StartsWith("$", StringComparison.OrdinalIgnoreCase)) if (resolvedEvent.OriginalEvent.EventStreamId.StartsWith("$", StringComparison.OrdinalIgnoreCase))
@ -82,39 +90,41 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
return; return;
} }
if (liveConsumers.Any() || catchConsumers.Any()) if (!liveConsumers.Any() && !catchConsumers.Any())
{ {
requireUpdate = true; return;
}
var @event = formatter.Parse(resolvedEvent); var @event = formatter.Parse(resolvedEvent);
if (resolvedEvent.Event.Created > now) if (isLive)
{ {
Dispatch(liveConsumers, @event); DispatchConsumers(liveConsumers, @event).Wait();
} }
Dispatch(catchConsumers, @event); DispatchConsumers(catchConsumers, @event).Wait();
}
requireUpdate = requireUpdate || catchSubscription.LastProcessedPosition.CommitPosition % 2 == 0;
} }
finally finally
{ {
if (requireUpdate) positions.WritePosition(resolvedEvent.OriginalEventNumber);
{
positions.WritePosition(catchSubscription.LastProcessedPosition);
} }
} }
}, userCredentials: credentials);
private void OnLiveProcessingStarted(EventStoreCatchUpSubscription subscription)
{
isLive = true;
} }
private void Dispatch(IEnumerable<IEventConsumer> consumers, Envelope<IEvent> @event) private Task DispatchConsumers(IEnumerable<IEventConsumer> consumers, Envelope<IEvent> @event)
{ {
foreach (var consumer in consumers) return Task.WhenAll(consumers.Select(c => DispatchConsumer(@event, c)).ToList());
}
private async Task DispatchConsumer(Envelope<IEvent> @event, IEventConsumer consumer)
{ {
try try
{ {
consumer.On(@event); await consumer.On(@event);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -124,5 +134,4 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
} }
} }
} }
}
} }

2
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs

@ -51,7 +51,7 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
this.nameResolver = nameResolver; this.nameResolver = nameResolver;
} }
public async Task<TDomainObject> GetByIdAsync<TDomainObject>(Guid id, int version = 0) where TDomainObject : class, IAggregate public async Task<TDomainObject> GetByIdAsync<TDomainObject>(Guid id, int version = int.MaxValue) where TDomainObject : class, IAggregate
{ {
Guard.GreaterThan(version, 0, nameof(version)); Guard.GreaterThan(version, 0, nameof(version));

6
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamPositionStorage.cs

@ -6,14 +6,12 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using EventStore.ClientAPI;
namespace PinkParrot.Infrastructure.CQRS.EventStore namespace PinkParrot.Infrastructure.CQRS.EventStore
{ {
public interface IStreamPositionStorage public interface IStreamPositionStorage
{ {
Position? ReadPosition(); int? ReadPosition();
void WritePosition(Position position); void WritePosition(int position);
} }
} }

5
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs

@ -5,10 +5,13 @@
// Copyright (c) PinkParrot Group // Copyright (c) PinkParrot Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Threading.Tasks;
namespace PinkParrot.Infrastructure.CQRS.Events namespace PinkParrot.Infrastructure.CQRS.Events
{ {
public interface IEventConsumer public interface IEventConsumer
{ {
void On(Envelope<IEvent> @event); Task On(Envelope<IEvent> @event);
} }
} }

68
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<PropertyInfo>();
var considered = new List<Type>
{
type
};
var queue = new Queue<Type>();
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();
}
}
}

4
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Reflection/SimpleMapper.cs

@ -111,11 +111,11 @@ namespace PinkParrot.Infrastructure.Reflection
var dstType = typeof(TDestination); var dstType = typeof(TDestination);
var srcType = typeof(TSource); var srcType = typeof(TSource);
var destinationProperties = dstType.GetProperties(); var destinationProperties = dstType.GetPublicProperties();
var newMappers = new List<PropertyMapper>(); var newMappers = new List<PropertyMapper>();
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); var dstProperty = destinationProperties.FirstOrDefault(x => x.Name == srcProperty.Name);

14
src/pinkparrot_read/PinkParrot.Read/IEntity.cs

@ -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; }
}
}

11
src/pinkparrot_read/PinkParrot.Read/IModelSchemaRM.cs

@ -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; }
}
}

26
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;
}
}
}

2
src/pinkparrot_read/PinkParrot.Read/Models/IEntity.cs → src/pinkparrot_read/PinkParrot.Read/Repositories/IEntity.cs

@ -8,7 +8,7 @@
using System; using System;
namespace PinkParrot.Read.Models namespace PinkParrot.Read.Repositories
{ {
public interface IEntity public interface IEntity
{ {

14
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; }
}
}

9
src/pinkparrot_read/PinkParrot.Read/Repositories/IModelSchemaRepository.cs

@ -9,12 +9,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using PinkParrot.Read.Models;
namespace PinkParrot.Read.Repositories namespace PinkParrot.Read.Repositories
{ {
public interface IModelSchemaRepository public interface IModelSchemaRepository
{ {
Task<List<ModelSchemaListRM>> QueryAllAsync(Guid tenantId); Task<List<IModelSchemaEntity>> QueryAllAsync(Guid tenantId);
Task<Guid?> FindSchemaIdAsync(Guid tenantId, string name);
Task<EntityWithSchema> FindSchemaAsync(Guid tenantId, string name);
Task<EntityWithSchema> FindSchemaAsync(Guid schemaId);
} }
} }

4
src/pinkparrot_read/PinkParrot.Read/Models/ITenantEntity.cs → src/pinkparrot_read/PinkParrot.Read/Repositories/ITenantEntity.cs

@ -8,9 +8,9 @@
using System; using System;
namespace PinkParrot.Read.Models namespace PinkParrot.Read.Repositories
{ {
public interface ITenantEntity public interface ITenantEntity : IEntity
{ {
Guid TenantId { get; set; } Guid TenantId { get; set; }
} }

19
src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/EntityMapper.cs

@ -8,10 +8,11 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Newtonsoft.Json;
using PinkParrot.Infrastructure.CQRS; using PinkParrot.Infrastructure.CQRS;
using PinkParrot.Infrastructure.MongoDb; using PinkParrot.Infrastructure.MongoDb;
using PinkParrot.Read.Models;
namespace PinkParrot.Read.Repositories.Implementations namespace PinkParrot.Read.Repositories.Implementations
{ {
@ -33,6 +34,20 @@ namespace PinkParrot.Read.Repositories.Implementations
return Update(entity, headers); return Update(entity, headers);
} }
public static BsonDocument ToJsonBsonDocument<T>(this T value, JsonSerializerSettings settings)
{
var json = JsonConvert.SerializeObject(value, settings).Replace("$type", "§type");
return BsonDocument.Parse(json);
}
public static T ToJsonObject<T>(this BsonDocument document, JsonSerializerSettings settings)
{
var json = document.ToJson().Replace("§type", "$type");
return JsonConvert.DeserializeObject<T>(json, settings);
}
public static T Update<T>(T entity, EnvelopeHeaders headers) where T : IEntity public static T Update<T>(T entity, EnvelopeHeaders headers) where T : IEntity
{ {
var timestamp = headers.Timestamp().ToDateTimeUtc(); var timestamp = headers.Timestamp().ToDateTimeUtc();
@ -64,7 +79,7 @@ namespace PinkParrot.Read.Repositories.Implementations
updater(entity); updater(entity);
await collection.ReplaceOneAsync(t => t.Id == entity.Id, entity); var result = await collection.ReplaceOneAsync(t => t.Id == entity.Id, entity);
} }
} }
} }

11
src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaListRM.cs → src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaEntity.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// ModelSchemaRM.cs // MongoModelSchemaEntity.cs
// PinkParrot Headless CMS // PinkParrot Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) PinkParrot Group // Copyright (c) PinkParrot Group
@ -9,11 +9,10 @@
using System; using System;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes; 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] [BsonId]
[BsonElement] [BsonElement]
@ -39,5 +38,9 @@ namespace PinkParrot.Read.Models
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public bool IsDeleted { get; set; } public bool IsDeleted { get; set; }
[BsonRequired]
[BsonElement]
public BsonDocument Schema { get; set; }
} }
} }

66
src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaListRepository.cs

@ -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<ModelSchemaListRM>, IModelSchemaRepository, ICatchEventConsumer
{
public MongoModelSchemaListRepository(IMongoDatabase database)
: base(database)
{
}
protected override Task SetupCollectionAsync(IMongoCollection<ModelSchemaListRM> collection)
{
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Id));
}
public IQueryable<ModelSchemaListRM> QuerySchemas()
{
return Collection.AsQueryable();
}
public Task<List<ModelSchemaListRM>> 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<IEvent> @event)
{
this.DispatchAction(@event.Payload, @event.Headers);
}
}
}

172
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<MongoModelSchemaEntity>, 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<MongoModelSchemaEntity> collection)
{
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Name));
}
public async Task<List<IModelSchemaEntity>> QueryAllAsync(Guid tenantId)
{
var entities = await Collection.Find(s => s.TenantId == tenantId && !s.IsDeleted).ToListAsync();
return entities.OfType<IModelSchemaEntity>().ToList();
}
public async Task<EntityWithSchema> 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<EntityWithSchema> 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<Guid?> FindSchemaIdAsync(Guid tenantId, string name)
{
var entity =
await Collection.Find(s => s.Name == name & s.TenantId == tenantId && !s.IsDeleted)
.Project<MongoModelSchemaEntity>(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<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
private void UpdateSchema(MongoModelSchemaEntity entity, Func<ModelSchema, ModelSchema> updater)
{
var currentSchema = Deserialize(entity);
currentSchema = updater(currentSchema);
Serialize(entity, currentSchema);
}
private Task UpdateSchema(EnvelopeHeaders headers, Func<ModelSchema, ModelSchema> 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<SchemaDto>(serializerSettings).ToModelSchema(fieldFactory);
}
}
}

2
src/pinkparrot_read/PinkParrot.Read/Services/IModelSchemaProvider.cs

@ -13,6 +13,6 @@ namespace PinkParrot.Read.Services
{ {
public interface IModelSchemaProvider public interface IModelSchemaProvider
{ {
Task<Guid> FindSchemaIdByNameAsync(string name); Task<Guid?> FindSchemaIdByNameAsync(Guid tenantId, string name);
} }
} }

2
src/pinkparrot_read/PinkParrot.Read/Services/ITenantProvider.cs

@ -13,6 +13,6 @@ namespace PinkParrot.Read.Services
{ {
public interface ITenantProvider public interface ITenantProvider
{ {
Task<Guid> ProvideTenantIdByDomainAsync(string domain); Task<Guid?> ProvideTenantIdByDomainAsync(string domain);
} }
} }

69
src/pinkparrot_read/PinkParrot.Read/Services/Implementations/ModelSchemaProvider.cs

@ -8,14 +8,77 @@
using System; using System;
using System.Threading.Tasks; 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 namespace PinkParrot.Read.Services.Implementations
{ {
public class ModelSchemaProvider : IModelSchemaProvider public class ModelSchemaProvider : IModelSchemaProvider, ILiveEventConsumer
{ {
public Task<Guid> 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<Guid?> FindSchemaIdByNameAsync(Guid tenantId, string name)
{
Guard.NotNullOrEmpty(name, nameof(name));
var cacheKey = BuildModelsCacheKey(tenantId, name);
var cacheItem = cache.Get<EntityWithSchema>(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<IEvent> @event)
{
if (@event.Payload is ModelSchemaUpdated || @event.Payload is ModelSchemaDeleted)
{
var oldName = cache.Get<string>(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}";
} }
} }
} }

5
src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs

@ -19,9 +19,6 @@ namespace PinkParrot.Read.Services.Implementations
public ObjectId Id { get; set; } public ObjectId Id { get; set; }
[BsonElement] [BsonElement]
public long CommitPosition { get; set; } public int? Position { get; set; }
[BsonElement]
public long PreparePosition { get; set; }
} }
} }

29
src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs

@ -6,13 +6,12 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using EventStore.ClientAPI;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using PinkParrot.Infrastructure.CQRS.EventStore; using PinkParrot.Infrastructure.CQRS.EventStore;
using PinkParrot.Infrastructure.MongoDb; using PinkParrot.Infrastructure.MongoDb;
//// ReSharper disable once ConvertIfStatementToNullCoalescingExpression // ReSharper disable InvertIf
namespace PinkParrot.Read.Services.Implementations 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<MongoPosition, MongoPosition>(); var document = Collection.Find(t => t.Id == Id).FirstOrDefault<MongoPosition, MongoPosition>();
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<MongoPosition, MongoPosition>();
var isFound = document != null;
if (document == null) if (document == null)
{ {
document = new MongoPosition { Id = Id }; document = new MongoPosition { Id = Id };
}
document.CommitPosition = position.CommitPosition; Collection.InsertOne(document);
document.PreparePosition = position.PreparePosition; }
if (isFound) return document.Position;
{
Collection.ReplaceOne(t => t.Id == Id, document);
} }
else
public void WritePosition(int position)
{ {
Collection.InsertOne(document); Collection.UpdateOne(t => t.Id == Id, Update.Set(t => t.Position, position));
}
} }
} }
} }

4
src/pinkparrot_read/PinkParrot.Read/Services/Implementations/TenantProvider.cs

@ -13,9 +13,9 @@ namespace PinkParrot.Read.Services.Implementations
{ {
public sealed class TenantProvider : ITenantProvider public sealed class TenantProvider : ITenantProvider
{ {
public Task<Guid> ProvideTenantIdByDomainAsync(string domain) public Task<Guid?> ProvideTenantIdByDomainAsync(string domain)
{ {
return Task.FromResult(Guid.Empty); return Task.FromResult<Guid?>(Guid.Empty);
} }
} }
} }

1
src/pinkparrot_read/PinkParrot.Read/project.json

@ -2,6 +2,7 @@
"version": "1.0.0-*", "version": "1.0.0-*",
"dependencies": { "dependencies": {
"Microsoft.Extensions.Caching.Memory": "1.0.0",
"MongoDB.Driver": "2.3.0-rc1", "MongoDB.Driver": "2.3.0-rc1",
"NETStandard.Library": "1.6.0", "NETStandard.Library": "1.6.0",
"NodaTime": "2.0.0-alpha20160729", "NodaTime": "2.0.0-alpha20160729",

2
src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs

@ -11,7 +11,7 @@ using PinkParrot.Infrastructure.CQRS.Commands;
namespace PinkParrot.Write.Schema.Commands namespace PinkParrot.Write.Schema.Commands
{ {
public class AddModelField : AggregateCommand public class AddModelField : TenantCommand
{ {
public ModelFieldProperties Properties { get; set; } public ModelFieldProperties Properties { get; set; }
} }

2
src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs

@ -10,7 +10,7 @@ using PinkParrot.Infrastructure.CQRS.Commands;
namespace PinkParrot.Write.Schema.Commands namespace PinkParrot.Write.Schema.Commands
{ {
public class DeleteModelField : AggregateCommand public class DeleteModelField : TenantCommand
{ {
public long FieldId { get; set; } public long FieldId { get; set; }
} }

2
src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs

@ -10,7 +10,7 @@ using PinkParrot.Infrastructure.CQRS.Commands;
namespace PinkParrot.Write.Schema.Commands namespace PinkParrot.Write.Schema.Commands
{ {
public class DeleteModelSchema : AggregateCommand public class DeleteModelSchema : TenantCommand
{ {
} }
} }

4
src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs

@ -6,11 +6,9 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using PinkParrot.Infrastructure.CQRS.Commands;
namespace PinkParrot.Write.Schema.Commands namespace PinkParrot.Write.Schema.Commands
{ {
public class DisableModelField : AggregateCommand public class DisableModelField : TenantCommand
{ {
public long FieldId { get; set; } public long FieldId { get; set; }
} }

2
src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs

@ -10,7 +10,7 @@ using PinkParrot.Infrastructure.CQRS.Commands;
namespace PinkParrot.Write.Schema.Commands namespace PinkParrot.Write.Schema.Commands
{ {
public class EnableModelField : AggregateCommand public class EnableModelField : TenantCommand
{ {
public long FieldId { get; set; } public long FieldId { get; set; }
} }

2
src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs

@ -10,7 +10,7 @@ using PinkParrot.Infrastructure.CQRS.Commands;
namespace PinkParrot.Write.Schema.Commands namespace PinkParrot.Write.Schema.Commands
{ {
public class HideModelField : AggregateCommand public class HideModelField : TenantCommand
{ {
public long FieldId { get; set; } public long FieldId { get; set; }
} }

2
src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs

@ -10,7 +10,7 @@ using PinkParrot.Infrastructure.CQRS.Commands;
namespace PinkParrot.Write.Schema.Commands namespace PinkParrot.Write.Schema.Commands
{ {
public class ShowModelField : AggregateCommand public class ShowModelField : TenantCommand
{ {
public long FieldId { get; set; } public long FieldId { get; set; }
} }

2
src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs

@ -11,7 +11,7 @@ using PinkParrot.Infrastructure.CQRS.Commands;
namespace PinkParrot.Write.Schema.Commands namespace PinkParrot.Write.Schema.Commands
{ {
public class UpdateModelField : AggregateCommand public class UpdateModelField : TenantCommand
{ {
public long FieldId { get; set; } public long FieldId { get; set; }

2
src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelSchema.cs

@ -11,7 +11,7 @@ using PinkParrot.Infrastructure.CQRS.Commands;
namespace PinkParrot.Write.Schema.Commands namespace PinkParrot.Write.Schema.Commands
{ {
public class UpdateModelSchema : AggregateCommand public class UpdateModelSchema : TenantCommand
{ {
public ModelSchemaProperties Properties { get; set; } public ModelSchemaProperties Properties { get; set; }
} }

2
src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs

@ -18,7 +18,7 @@ namespace PinkParrot.Write.Schema
{ {
public Task<bool> HandleAsync(CommandContext context) public Task<bool> 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) public Task On(AddModelField command, CommandContext context)

25
src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs

@ -9,6 +9,7 @@
using System; using System;
using PinkParrot.Core.Schema; using PinkParrot.Core.Schema;
using PinkParrot.Events.Schema; using PinkParrot.Events.Schema;
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS; using PinkParrot.Infrastructure.CQRS;
using PinkParrot.Infrastructure.CQRS.Events; using PinkParrot.Infrastructure.CQRS.Events;
using PinkParrot.Infrastructure.Dispatching; using PinkParrot.Infrastructure.Dispatching;
@ -45,56 +46,56 @@ namespace PinkParrot.Write.Schema
this.fieldFactory = fieldFactory; this.fieldFactory = fieldFactory;
} }
protected void Apply(ModelFieldAdded @event) public void On(ModelFieldAdded @event)
{ {
schema = schema.AddField(@event.FieldId, @event.Properties, fieldFactory); schema = schema.AddField(@event.FieldId, @event.Properties, fieldFactory);
totalFields++; totalFields++;
} }
protected void Apply(ModelSchemaCreated @event) public void On(ModelSchemaCreated @event)
{ {
tenantId = @event.TenantId; tenantId = @event.TenantId;
schema = ModelSchema.Create(@event.Properties); schema = ModelSchema.Create(@event.Properties);
} }
protected void Apply(ModelFieldUpdated @event) public void On(ModelFieldUpdated @event)
{ {
schema = schema.SetField(@event.FieldId, @event.Properties); schema = schema.SetField(@event.FieldId, @event.Properties);
} }
public void Apply(ModelFieldHidden @event) public void On(ModelFieldHidden @event)
{ {
schema = schema.HideField(@event.FieldId); schema = schema.HideField(@event.FieldId);
} }
public void Apply(ModelFieldShown @event) public void On(ModelFieldShown @event)
{ {
schema = schema.ShowField(@event.FieldId); schema = schema.ShowField(@event.FieldId);
} }
public void Apply(ModelFieldDisabled @event) public void On(ModelFieldDisabled @event)
{ {
schema = schema.DisableField(@event.FieldId); schema = schema.DisableField(@event.FieldId);
} }
public void Apply(ModelFieldEnabled @event) public void On(ModelFieldEnabled @event)
{ {
schema = schema.EnableField(@event.FieldId); schema = schema.EnableField(@event.FieldId);
} }
protected void Apply(ModelSchemaUpdated @event) public void On(ModelSchemaUpdated @event)
{ {
schema = schema.Update(@event.Properties); schema = schema.Update(@event.Properties);
} }
protected void Apply(ModelFieldDeleted @event) public void On(ModelFieldDeleted @event)
{ {
schema = schema.DeleteField(@event.FieldId); schema = schema.DeleteField(@event.FieldId);
} }
protected void Apply(ModelSchemaDeleted @event) public void On(ModelSchemaDeleted @event)
{ {
isDeleted = false; isDeleted = false;
} }
@ -197,7 +198,7 @@ namespace PinkParrot.Write.Schema
{ {
if (schema != null) 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) 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.");
} }
} }

2
src/pinkparrot_write/PinkParrot.Write/TenantCommand.cs

@ -11,7 +11,7 @@ using PinkParrot.Infrastructure.CQRS.Commands;
namespace PinkParrot.Write namespace PinkParrot.Write
{ {
public abstract class TenantCommand : AggregateCommand public abstract class TenantCommand : AggregateCommand, ITenantCommand
{ {
public Guid TenantId { get; set; } public Guid TenantId { get; set; }
} }

Loading…
Cancel
Save