Browse Source

test

pull/1/head
Sebastian Stehle 9 years ago
parent
commit
1962e4fca8
  1. 5
      src/PinkParrot/Configurations/Serializers.cs
  2. 2
      src/PinkParrot/Modules/Api/Schemas/SchemasController.cs
  3. 16
      src/pinkparrot_core/PinkParrot.Core/Schema/IModelFieldProperties.cs
  4. 19
      src/pinkparrot_core/PinkParrot.Core/Schema/IRegisterModelField.cs
  5. 64
      src/pinkparrot_core/PinkParrot.Core/Schema/Json/SchemaDto.cs
  6. 42
      src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs
  7. 49
      src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs
  8. 16
      src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldProperties.cs
  9. 97
      src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldRegistry.cs
  10. 19
      src/pinkparrot_core/PinkParrot.Core/Schema/ModelField_Generic.cs
  11. 84
      src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs
  12. 3
      src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaProperties.cs
  13. 31
      src/pinkparrot_core/PinkParrot.Core/Schema/NamedElementProperties.cs
  14. 15
      src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs
  15. 18
      src/pinkparrot_core/PinkParrot.Core/Schema/NumberFieldProperties.cs
  16. 6
      src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs
  17. 4
      src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs
  18. 4
      src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs
  19. 4
      src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs
  20. 4
      src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs
  21. 4
      src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs
  22. 4
      src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs
  23. 4
      src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs
  24. 4
      src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs
  25. 2
      src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs
  26. 10
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandContext.cs
  27. 1
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs
  28. 7
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs
  29. 35
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/TypeNameSerializationBinder.cs
  30. 75
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/TypeNameRegistry.cs
  31. 27
      src/pinkparrot_read/PinkParrot.Read/Models/ModelFieldDto.cs
  32. 63
      src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaDto.cs
  33. 4
      src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/EntityMapper.cs
  34. 2
      src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoModelSchemaEntity.cs
  35. 47
      src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoModelSchemaRepository.cs
  36. 2
      src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoPositions.cs
  37. 2
      src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoStreamPositionsStorage.cs
  38. 8
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs
  39. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs
  40. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs
  41. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs
  42. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs
  43. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs
  44. 2
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs
  45. 6
      src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs
  46. 66
      src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs
  47. 79
      src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs

5
src/PinkParrot/Configurations/Serializers.cs

@ -11,6 +11,7 @@ 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.Core.Schema.Json;
using PinkParrot.Infrastructure.CQRS.EventStore; using PinkParrot.Infrastructure.CQRS.EventStore;
using PinkParrot.Infrastructure.Json; using PinkParrot.Infrastructure.Json;
@ -38,10 +39,8 @@ namespace PinkParrot.Configurations
public static void AddEventFormatter(this IServiceCollection services) public static void AddEventFormatter(this IServiceCollection services)
{ {
var fieldFactory = new ModelFieldFactory();
services.AddSingleton(t => CreateSettings()); services.AddSingleton(t => CreateSettings());
services.AddSingleton(fieldFactory); services.AddSingleton<SerializationService>();
services.AddSingleton<EventStoreFormatter>(); services.AddSingleton<EventStoreFormatter>();
} }

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

@ -50,7 +50,7 @@ namespace PinkParrot.Modules.Api.Schemas
return NotFound(); return NotFound();
} }
return Ok(SchemaDto.Create(entity.Schema)); return Ok(ModelSchemaDto.Create(entity.Schema));
} }
[HttpPost] [HttpPost]

16
src/pinkparrot_core/PinkParrot.Core/Schema/IModelFieldProperties.cs

@ -0,0 +1,16 @@
// ==========================================================================
// IModelFieldProperties.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using PinkParrot.Infrastructure;
namespace PinkParrot.Core.Schema
{
public interface IModelFieldProperties : IValidatable
{
}
}

19
src/pinkparrot_core/PinkParrot.Core/Schema/IRegisterModelField.cs

@ -0,0 +1,19 @@
// ==========================================================================
// IRegisterModelField.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System;
namespace PinkParrot.Core.Schema
{
public interface IRegisterModelField
{
Type PropertiesType { get; }
ModelField CreateField(long id, string name, IModelFieldProperties properties);
}
}

64
src/pinkparrot_core/PinkParrot.Core/Schema/Json/SchemaDto.cs

@ -1,64 +0,0 @@
// ==========================================================================
// SerializationModel.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System.Collections.Immutable;
using PinkParrot.Infrastructure;
// ReSharper disable LoopCanBeConvertedToQuery
namespace PinkParrot.Core.Schema.Json
{
public class SchemaDto
{
private readonly ModelSchemaProperties properties;
private readonly ImmutableDictionary<long, ModelFieldProperties> fields;
public ImmutableDictionary<long, ModelFieldProperties> Fields
{
get { return fields; }
}
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.ToImmutableDictionary(x => x.Key, x => x.Value.RawProperties);
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;
}
}
}

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

@ -12,6 +12,7 @@ using System.Threading.Tasks;
using PinkParrot.Infrastructure; using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.Tasks; using PinkParrot.Infrastructure.Tasks;
// ReSharper disable InvertIf
// ReSharper disable ConvertIfStatementToReturnStatement // ReSharper disable ConvertIfStatementToReturnStatement
namespace PinkParrot.Core.Schema namespace PinkParrot.Core.Schema
@ -19,6 +20,7 @@ namespace PinkParrot.Core.Schema
public abstract class ModelField public abstract class ModelField
{ {
private readonly long id; private readonly long id;
private string name;
private bool isDisabled; private bool isDisabled;
private bool isHidden; private bool isHidden;
@ -27,15 +29,10 @@ namespace PinkParrot.Core.Schema
get { return id; } get { return id; }
} }
public abstract ModelFieldProperties RawProperties { get; } public string Name
{
public abstract string Name { get; } get { return name; }
}
public abstract string Label { get; }
public abstract string Hints { get; }
public abstract bool IsRequired { get; }
public bool IsHidden public bool IsHidden
{ {
@ -47,14 +44,25 @@ namespace PinkParrot.Core.Schema
get { return isDisabled; } get { return isDisabled; }
} }
protected ModelField(long id) public abstract IModelFieldProperties RawProperties { get; }
public abstract string Label { get; }
public abstract string Hints { get; }
public abstract bool IsRequired { get; }
protected ModelField(long id, string name)
{ {
Guard.GreaterThan(id, 0, nameof(id)); Guard.GreaterThan(id, 0, nameof(id));
Guard.ValidSlug(name, nameof(name));
this.id = id; this.id = id;
this.name = name;
} }
public abstract ModelField Configure(ModelFieldProperties newProperties); public abstract ModelField Update(IModelFieldProperties newProperties);
public Task ValidateAsync(PropertyValue property, ICollection<string> errors) public Task ValidateAsync(PropertyValue property, ICollection<string> errors)
{ {
@ -98,6 +106,18 @@ namespace PinkParrot.Core.Schema
return Update<ModelField>(clone => clone.isDisabled = false); return Update<ModelField>(clone => clone.isDisabled = false);
} }
public ModelField Rename(string newName)
{
if (!newName.IsSlug())
{
var error = new ValidationError("Name must be a valid slug", "Name");
throw new ValidationException($"Cannot rename the field '{name}' ({id})", error);
}
return Update<ModelField>(clone => clone.name = newName);
}
protected T Update<T>(Action<T> updater) where T : ModelField protected T Update<T>(Action<T> updater) where T : ModelField
{ {
var clone = (T)Clone(); var clone = (T)Clone();

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

@ -1,49 +0,0 @@
// ==========================================================================
// ModelFieldFactory.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using PinkParrot.Infrastructure;
namespace PinkParrot.Core.Schema
{
public class ModelFieldFactory
{
private readonly Dictionary<Type, Func<long, ModelFieldProperties, ModelField>> factories
= new Dictionary<Type, Func<long, ModelFieldProperties, ModelField>>();
public ModelFieldFactory()
{
AddFactory<NumberFieldProperties>((id, p) => new NumberField(id, (NumberFieldProperties)p));
}
public ModelFieldFactory AddFactory<T>(Func<long, ModelFieldProperties, ModelField> factory) where T : ModelFieldProperties
{
Guard.NotNull(factory, nameof(factory));
factories[typeof(T)] = factory;
return this;
}
public virtual ModelField CreateField(long id, ModelFieldProperties properties)
{
Guard.NotNull(properties, nameof(properties));
var factory = factories.GetOrDefault(properties.GetType());
if (factory == null)
{
throw new InvalidOperationException("Field type is not supported.");
}
return factory(id, properties);
}
}
}

16
src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldProperties.cs

@ -11,24 +11,18 @@ using PinkParrot.Infrastructure;
namespace PinkParrot.Core.Schema namespace PinkParrot.Core.Schema
{ {
public abstract class ModelFieldProperties : NamedElementProperties public abstract class ModelFieldProperties : NamedElementProperties, IModelFieldProperties
{ {
public bool IsRequired { get; } public bool IsRequired { get; }
protected ModelFieldProperties( protected ModelFieldProperties(string label, string hints, bool isRequired)
bool isRequired, : base(label, hints)
string name,
string label,
string hints)
: base(name, label, hints)
{ {
IsRequired = isRequired; IsRequired = isRequired;
} }
public override void Validate(IList<ValidationError> errors) public void Validate(IList<ValidationError> errors)
{ {
base.Validate(errors);
ValidateCore(errors); ValidateCore(errors);
} }

97
src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldRegistry.cs

@ -0,0 +1,97 @@
// ==========================================================================
// ModelFieldRegistry.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using PinkParrot.Infrastructure;
namespace PinkParrot.Core.Schema
{
public delegate ModelField FactoryFunction(long id, string name, IModelFieldProperties properties);
public sealed class ModelFieldRegistry
{
private readonly Dictionary<string, IRegisterModelField> fieldsByTypeName = new Dictionary<string, IRegisterModelField>();
private readonly Dictionary<Type, IRegisterModelField> fieldsByPropertyType = new Dictionary<Type, IRegisterModelField>();
private sealed class Registered : IRegisterModelField
{
private readonly FactoryFunction fieldFactory;
private readonly Type propertiesType;
private readonly string typeName;
public Type PropertiesType
{
get { return propertiesType; }
}
public string TypeName
{
get { return typeName; }
}
public Registered(FactoryFunction fieldFactory, Type propertiesType)
{
typeName = TypeNameRegistry.GetName(propertiesType);
this.fieldFactory = fieldFactory;
this.propertiesType = propertiesType;
}
ModelField IRegisterModelField.CreateField(long id, string name, IModelFieldProperties properties)
{
return fieldFactory(id, name, properties);
}
}
public void Add<TFieldProperties>(FactoryFunction fieldFactory)
{
Guard.NotNull(fieldFactory, nameof(fieldFactory));
var registered = new Registered(fieldFactory, typeof(TFieldProperties));
fieldsByTypeName[registered.TypeName] = registered;
fieldsByPropertyType[registered.PropertiesType] = registered;
}
public ModelField CreateField(long id, string name, IModelFieldProperties properties)
{
var registered = fieldsByPropertyType[properties.GetType()];
return registered.CreateField(id, name, properties);
}
public IRegisterModelField FindByPropertiesType(Type type)
{
Guard.NotNull(type, nameof(type));
var registered = fieldsByPropertyType.GetOrDefault(type);
if (registered == null)
{
throw new InvalidOperationException($"The field property '{type}' is not supported.");
}
return registered;
}
public IRegisterModelField FindByTypeName(string typeName)
{
Guard.NotNullOrEmpty(typeName, nameof(typeName));
var registered = fieldsByTypeName.GetOrDefault(typeName);
if (registered == null)
{
throw new DomainException($"A field with type '{typeName} is not known.");
}
return registered;
}
}
}

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

@ -15,19 +15,14 @@ namespace PinkParrot.Core.Schema
{ {
private T properties; private T properties;
public override ModelFieldProperties RawProperties public override IModelFieldProperties RawProperties
{ {
get { return properties; } get { return properties; }
} }
public override string Name
{
get { return properties.Name; }
}
public override string Label public override string Label
{ {
get { return properties.Label ?? properties.Name; } get { return properties.Label ?? Name; }
} }
public override string Hints public override string Hints
@ -45,18 +40,18 @@ namespace PinkParrot.Core.Schema
get { return properties; } get { return properties; }
} }
protected ModelField(long id, T properties) protected ModelField(long id, string name, T properties)
: base(id) : base(id, name)
{ {
Guard.NotNull(properties, nameof(properties)); Guard.NotNull(properties, nameof(properties));
this.properties = properties; this.properties = properties;
} }
public override ModelField Configure(ModelFieldProperties newProperties) public override ModelField Update(IModelFieldProperties newProperties)
{ {
Guard.NotNull(newProperties, nameof(newProperties)); Guard.NotNull(newProperties, nameof(newProperties));
var typedProperties = newProperties as T; var typedProperties = newProperties as T;
if (typedProperties == null) if (typedProperties == null)
@ -64,6 +59,8 @@ namespace PinkParrot.Core.Schema
throw new ArgumentException($"Properties must be of type '{typeof(T)}", nameof(newProperties)); throw new ArgumentException($"Properties must be of type '{typeof(T)}", nameof(newProperties));
} }
newProperties.Validate(() => $"Cannot update field with id '{Id}', becase the settings are invalid.");
return Update<ModelField<T>>(clone => clone.properties = typedProperties); return Update<ModelField<T>>(clone => clone.properties = typedProperties);
} }
} }

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

@ -12,24 +12,20 @@ using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using PinkParrot.Infrastructure; using PinkParrot.Infrastructure;
// ReSharper disable InvertIf
namespace PinkParrot.Core.Schema namespace PinkParrot.Core.Schema
{ {
public sealed class ModelSchema public sealed class ModelSchema
{ {
private readonly string name;
private readonly ModelSchemaProperties properties; private readonly ModelSchemaProperties properties;
private readonly ImmutableDictionary<long, ModelField> fieldsById; 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 string Name
{ {
Guard.NotNull(fields, nameof(fields)); get { return name; }
Guard.NotNull(properties, nameof(properties));
this.properties = properties;
fieldsById = fields;
fieldsByName = fields.Values.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
} }
public ImmutableDictionary<long, ModelField> Fields public ImmutableDictionary<long, ModelField> Fields
@ -42,43 +38,56 @@ namespace PinkParrot.Core.Schema
get { return properties; } get { return properties; }
} }
public static ModelSchema Create(ModelSchemaProperties newProperties) public ModelSchema(string name, ModelSchemaProperties properties, ImmutableDictionary<long, ModelField> fields)
{ {
Guard.NotNull(newProperties, nameof(newProperties)); Guard.NotNull(fields, nameof(fields));
Guard.NotNull(properties, nameof(properties));
Guard.ValidSlug(name, nameof(name));
this.name = name;
newProperties.Validate(() => "Failed to create a new model schema."); this.properties = properties;
return new ModelSchema(newProperties, ImmutableDictionary<long, ModelField>.Empty); fieldsById = fields;
fieldsByName = fields.Values.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
} }
public ModelSchema Update(ModelSchemaProperties newProperties) public static ModelSchema Create(string name, ModelSchemaProperties newProperties)
{ {
Guard.NotNull(newProperties, nameof(newProperties)); Guard.NotNull(newProperties, nameof(newProperties));
newProperties.Validate(() => "Failed to update the model schema."); if (!name.IsSlug())
{
var error = new ValidationError("Name must be a valid slug", "Name");
throw new ValidationException($"Cannot rename the schema '{name}'", error);
}
return new ModelSchema(newProperties, fieldsById); return new ModelSchema(name, newProperties, ImmutableDictionary<long, ModelField>.Empty);
} }
public ModelSchema AddField(long id, ModelFieldProperties fieldProperties, ModelFieldFactory factory) public ModelSchema Update(ModelSchemaProperties newProperties)
{ {
var field = factory.CreateField(id, fieldProperties); Guard.NotNull(newProperties, nameof(newProperties));
return ReplaceOrAddField(field); return new ModelSchema(name, newProperties, fieldsById);
} }
public ModelSchema SetField(long fieldId, ModelFieldProperties fieldProperties) public ModelSchema AddOrUpdateField(ModelField field)
{ {
Guard.NotNull(fieldProperties, nameof(fieldProperties)); Guard.NotNull(field, nameof(field));
return UpdateField(fieldId, field => if (fieldsById.Values.Any(f => f.Name == field.Name && f.Id != field.Id))
{ {
fieldProperties.Validate(() => $"Cannot update field with id '{fieldId}', becase the settings are invalid."); throw new ValidationException($"A field with name '{field.Name}' already exists.");
}
var newField = field.Configure(fieldProperties); return new ModelSchema(name, properties, fieldsById.SetItem(field.Id, field));
}
return newField; public ModelSchema UpdateField(long fieldId, IModelFieldProperties newProperties)
}); {
return UpdateField(fieldId, field => field.Update(newProperties));
} }
public ModelSchema DisableField(long fieldId) public ModelSchema DisableField(long fieldId)
@ -101,13 +110,20 @@ namespace PinkParrot.Core.Schema
return UpdateField(fieldId, field => field.Show()); return UpdateField(fieldId, field => field.Show());
} }
public ModelSchema RenameField(long fieldId, string newName)
{
return UpdateField(fieldId, field => field.Rename(newName));
}
public ModelSchema DeleteField(long fieldId) public ModelSchema DeleteField(long fieldId)
{ {
return new ModelSchema(properties, fieldsById.Remove(fieldId)); return new ModelSchema(name, properties, fieldsById.Remove(fieldId));
} }
private ModelSchema UpdateField(long fieldId, Func<ModelField, ModelField> updater) public ModelSchema UpdateField(long fieldId, Func<ModelField, ModelField> updater)
{ {
Guard.NotNull(updater, nameof(updater));
ModelField field; ModelField field;
if (!fieldsById.TryGetValue(fieldId, out field)) if (!fieldsById.TryGetValue(fieldId, out field))
@ -117,19 +133,7 @@ namespace PinkParrot.Core.Schema
var newField = updater(field); var newField = updater(field);
return ReplaceOrAddField(newField); return AddOrUpdateField(newField);
}
private ModelSchema ReplaceOrAddField(ModelField field)
{
Guard.NotNull(field, nameof(field));
if (fieldsById.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.SetItem(field.Id, field));
} }
public async Task ValidateAsync(PropertiesBag data, IList<ValidationError> errors) public async Task ValidateAsync(PropertiesBag data, IList<ValidationError> errors)

3
src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaProperties.cs

@ -10,10 +10,9 @@ namespace PinkParrot.Core.Schema
public sealed class ModelSchemaProperties : NamedElementProperties public sealed class ModelSchemaProperties : NamedElementProperties
{ {
public ModelSchemaProperties( public ModelSchemaProperties(
string name,
string label, string label,
string hints) string hints)
: base(name, label, hints) : base(label, hints)
{ {
} }
} }

31
src/pinkparrot_core/PinkParrot.Core/Schema/NamedElementProperties.cs

@ -6,21 +6,12 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using PinkParrot.Infrastructure;
namespace PinkParrot.Core.Schema namespace PinkParrot.Core.Schema
{ {
public abstract class NamedElementProperties : IValidatable public abstract class NamedElementProperties
{ {
private readonly string name;
private readonly string label; private readonly string label;
private readonly string hints; private readonly string hints;
public string Name
{
get { return name; }
}
public string Label public string Label
{ {
@ -32,28 +23,10 @@ namespace PinkParrot.Core.Schema
get { return hints; } get { return hints; }
} }
protected NamedElementProperties( protected NamedElementProperties(string label, string hints)
string name,
string label,
string hints)
{ {
this.name = name;
this.label = label; this.label = label;
this.hints = hints; this.hints = hints;
} }
public virtual void Validate(IList<ValidationError> errors)
{
Guard.NotNull(errors, nameof(errors));
if (string.IsNullOrWhiteSpace(Name))
{
errors.Add(new ValidationError("Name cannot be empty.", "Name"));
}
else if (!Name.IsSlug())
{
errors.Add(new ValidationError("Name must be a valid slug.", "Name"));
}
}
} }
} }

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

@ -9,6 +9,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using PinkParrot.Infrastructure; using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.Tasks; using PinkParrot.Infrastructure.Tasks;
@ -27,8 +28,13 @@ namespace PinkParrot.Core.Schema
get { return Properties.MinValue; } get { return Properties.MinValue; }
} }
public NumberField(long id, NumberFieldProperties properties) public double[] AllowedValues
: base(id, properties) {
get { return Properties.AllowedValues; }
}
public NumberField(long id, string name, NumberFieldProperties properties)
: base(id, name, properties)
{ {
} }
@ -47,6 +53,11 @@ namespace PinkParrot.Core.Schema
{ {
errors.Add($"Must be less than {MaxValue}"); errors.Add($"Must be less than {MaxValue}");
} }
if (AllowedValues != null && !AllowedValues.Contains(value))
{
errors.Add($"Can only be one of the following value: {string.Join(", ", AllowedValues)}");
}
} }
catch (InvalidCastException) catch (InvalidCastException)
{ {

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

@ -11,39 +11,43 @@ using PinkParrot.Infrastructure;
namespace PinkParrot.Core.Schema namespace PinkParrot.Core.Schema
{ {
[TypeName("Number")] [TypeName("number")]
public sealed class NumberFieldProperties : ModelFieldProperties public sealed class NumberFieldProperties : ModelFieldProperties
{ {
public string Placeholder { get; set; }
public double? DefaultValue { get; } 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 double[] AllowedValues { get; }
public NumberFieldProperties( public NumberFieldProperties(
bool isRequired,
string name,
string label, string label,
string hints, string hints,
string placeholder, string placeholder,
double? minValue, double? minValue,
double? maxValue, double? maxValue,
double? defaultValue) double? defaultValue,
: base(isRequired, name, label, hints) double[] allowedValues,
bool isRequired)
: base(label, hints, isRequired)
{ {
Placeholder = placeholder; Placeholder = placeholder;
MinValue = minValue; MinValue = minValue;
MaxValue = maxValue; MaxValue = maxValue;
AllowedValues = allowedValues;
DefaultValue = defaultValue; DefaultValue = defaultValue;
} }
protected override void ValidateCore(IList<ValidationError> errors) protected override void ValidateCore(IList<ValidationError> errors)
{ {
if (MaxValue.HasValue && MinValue.HasValue) if (MaxValue.HasValue && MinValue.HasValue && MinValue.Value >= MaxValue.Value)
{ {
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"));
} }

6
src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs

@ -7,13 +7,17 @@
// ========================================================================== // ==========================================================================
using PinkParrot.Core.Schema; using PinkParrot.Core.Schema;
using PinkParrot.Infrastructure;
namespace PinkParrot.Events.Schema namespace PinkParrot.Events.Schema
{ {
[TypeName("ModelFieldAddedEvent")]
public class ModelFieldAdded : TenantEvent public class ModelFieldAdded : TenantEvent
{ {
public long FieldId; public long FieldId;
public ModelFieldProperties Properties; public string Name;
public IModelFieldProperties Properties;
} }
} }

4
src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs

@ -5,8 +5,12 @@
// Copyright (c) PinkParrot Group // Copyright (c) PinkParrot Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using PinkParrot.Infrastructure;
namespace PinkParrot.Events.Schema namespace PinkParrot.Events.Schema
{ {
[TypeName("ModelFieldDeletedEvent")]
public class ModelFieldDeleted : TenantEvent public class ModelFieldDeleted : TenantEvent
{ {
public long FieldId; public long FieldId;

4
src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs

@ -5,8 +5,12 @@
// Copyright (c) PinkParrot Group // Copyright (c) PinkParrot Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using PinkParrot.Infrastructure;
namespace PinkParrot.Events.Schema namespace PinkParrot.Events.Schema
{ {
[TypeName("ModelFieldDisabledEvent")]
public class ModelFieldDisabled : TenantEvent public class ModelFieldDisabled : TenantEvent
{ {
public long FieldId; public long FieldId;

4
src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs

@ -5,8 +5,12 @@
// Copyright (c) PinkParrot Group // Copyright (c) PinkParrot Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using PinkParrot.Infrastructure;
namespace PinkParrot.Events.Schema namespace PinkParrot.Events.Schema
{ {
[TypeName("ModelFieldEnabledEvent")]
public class ModelFieldEnabled : TenantEvent public class ModelFieldEnabled : TenantEvent
{ {
public long FieldId; public long FieldId;

4
src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs

@ -5,8 +5,12 @@
// Copyright (c) PinkParrot Group // Copyright (c) PinkParrot Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using PinkParrot.Infrastructure;
namespace PinkParrot.Events.Schema namespace PinkParrot.Events.Schema
{ {
[TypeName("ModelFieldHiddenEvent")]
public class ModelFieldHidden : TenantEvent public class ModelFieldHidden : TenantEvent
{ {
public long FieldId; public long FieldId;

4
src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs

@ -5,8 +5,12 @@
// Copyright (c) PinkParrot Group // Copyright (c) PinkParrot Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using PinkParrot.Infrastructure;
namespace PinkParrot.Events.Schema namespace PinkParrot.Events.Schema
{ {
[TypeName("ModelFieldShownEvent")]
public class ModelFieldShown : TenantEvent public class ModelFieldShown : TenantEvent
{ {
public long FieldId; public long FieldId;

4
src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs

@ -7,13 +7,15 @@
// ========================================================================== // ==========================================================================
using PinkParrot.Core.Schema; using PinkParrot.Core.Schema;
using PinkParrot.Infrastructure;
namespace PinkParrot.Events.Schema namespace PinkParrot.Events.Schema
{ {
[TypeName("ModelFieldUpdatedEvent")]
public class ModelFieldUpdated : TenantEvent public class ModelFieldUpdated : TenantEvent
{ {
public long FieldId; public long FieldId;
public ModelFieldProperties Properties; public IModelFieldProperties Properties;
} }
} }

4
src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs

@ -7,11 +7,15 @@
// ========================================================================== // ==========================================================================
using PinkParrot.Core.Schema; using PinkParrot.Core.Schema;
using PinkParrot.Infrastructure;
namespace PinkParrot.Events.Schema namespace PinkParrot.Events.Schema
{ {
[TypeName("ModelSchemaCreatedEvent")]
public class ModelSchemaCreated : TenantEvent public class ModelSchemaCreated : TenantEvent
{ {
public string Name;
public ModelSchemaProperties Properties; public ModelSchemaProperties Properties;
} }
} }

4
src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs

@ -5,8 +5,12 @@
// Copyright (c) PinkParrot Group // Copyright (c) PinkParrot Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using PinkParrot.Infrastructure;
namespace PinkParrot.Events.Schema namespace PinkParrot.Events.Schema
{ {
[TypeName("ModelSchemaDeleted")]
public class ModelSchemaDeleted : TenantEvent public class ModelSchemaDeleted : TenantEvent
{ {
} }

2
src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs

@ -7,9 +7,11 @@
// ========================================================================== // ==========================================================================
using PinkParrot.Core.Schema; using PinkParrot.Core.Schema;
using PinkParrot.Infrastructure;
namespace PinkParrot.Events.Schema namespace PinkParrot.Events.Schema
{ {
[TypeName("ModelSchemaUpdated")]
public class ModelSchemaUpdated : TenantEvent public class ModelSchemaUpdated : TenantEvent
{ {
public ModelSchemaProperties Properties; public ModelSchemaProperties Properties;

10
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandContext.cs

@ -17,10 +17,10 @@ namespace PinkParrot.Infrastructure.CQRS.Commands
private readonly ICommand command; private readonly ICommand command;
private Exception exception; private Exception exception;
private bool isSucceeded; private bool isSucceeded;
public ICommand Command public IDomainObjectRepository Repository
{ {
get { return command; } get { return repository; }
} }
public IDomainObjectFactory Factory public IDomainObjectFactory Factory
@ -28,9 +28,9 @@ namespace PinkParrot.Infrastructure.CQRS.Commands
get { return factory; } get { return factory; }
} }
public IDomainObjectRepository Repository public ICommand Command
{ {
get { return repository; } get { return command; }
} }
public bool IsHandled public bool IsHandled

1
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs

@ -14,7 +14,6 @@ namespace PinkParrot.Infrastructure.CQRS
public const string Timestamp = "Timestamp"; public const string Timestamp = "Timestamp";
public const string TenantId = "TenantId"; public const string TenantId = "TenantId";
public const string EventId = "EventId"; public const string EventId = "EventId";
public const string EventType = "EventType";
public const string EventNumber = "EventNumber"; public const string EventNumber = "EventNumber";
} }
} }

7
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs

@ -30,7 +30,7 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
{ {
var headers = ReadJson<PropertiesBag>(@event.Event.Metadata); var headers = ReadJson<PropertiesBag>(@event.Event.Metadata);
var eventType = Type.GetType(headers.Properties[CommonHeaders.EventType].ToString()); var eventType = TypeNameRegistry.GetType(@event.Event.EventType);
var eventData = ReadJson<IEvent>(@event.Event.Data, eventType); var eventData = ReadJson<IEvent>(@event.Event.Data, eventType);
var envelope = new Envelope<IEvent>(eventData, headers); var envelope = new Envelope<IEvent>(eventData, headers);
@ -43,15 +43,14 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore
public EventData ToEventData(Envelope<IEvent> envelope, Guid commitId) public EventData ToEventData(Envelope<IEvent> envelope, Guid commitId)
{ {
var eventType = envelope.Payload.GetType(); var eventType = TypeNameRegistry.GetName(envelope.Payload.GetType());
envelope.Headers.Set(CommonHeaders.CommitId, commitId); envelope.Headers.Set(CommonHeaders.CommitId, commitId);
envelope.Headers.Set(CommonHeaders.EventType, eventType.AssemblyQualifiedName);
var headers = WriteJson(envelope.Headers); var headers = WriteJson(envelope.Headers);
var content = WriteJson(envelope.Payload); var content = WriteJson(envelope.Payload);
return new EventData(envelope.Headers.EventId(), eventType.Name, true, content, headers); return new EventData(envelope.Headers.EventId(), eventType, true, content, headers);
} }
private T ReadJson<T>(byte[] data, Type type = null) private T ReadJson<T>(byte[] data, Type type = null)

35
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/TypeNameSerializationBinder.cs

@ -7,46 +7,15 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Reflection;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
namespace PinkParrot.Infrastructure.Json namespace PinkParrot.Infrastructure.Json
{ {
public class TypeNameSerializationBinder : DefaultSerializationBinder public class TypeNameSerializationBinder : DefaultSerializationBinder
{ {
private readonly Dictionary<Type, string> namesByType = new Dictionary<Type, string>();
private readonly Dictionary<string, Type> typesByName = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
public TypeNameSerializationBinder Map(Type type, string name)
{
Guard.NotNull(type, nameof(type));
Guard.NotNull(name, nameof(name));
namesByType.Add(type, name);
typesByName.Add(name, type);
return this;
}
public TypeNameSerializationBinder Map(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
{
var typeNameAttribute = type.GetTypeInfo().GetCustomAttribute<TypeNameAttribute>();
if (!string.IsNullOrWhiteSpace(typeNameAttribute?.TypeName))
{
Map(type, typeNameAttribute.TypeName);
}
}
return this;
}
public override Type BindToType(string assemblyName, string typeName) public override Type BindToType(string assemblyName, string typeName)
{ {
var type = typesByName.GetOrDefault(typeName); var type = TypeNameRegistry.GetType(typeName);
return type ?? base.BindToType(assemblyName, typeName); return type ?? base.BindToType(assemblyName, typeName);
} }
@ -55,7 +24,7 @@ namespace PinkParrot.Infrastructure.Json
{ {
assemblyName = null; assemblyName = null;
var name = namesByType.GetOrDefault(serializedType); var name = TypeNameRegistry.GetName(serializedType);
if (name != null) if (name != null)
{ {

75
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/TypeNameRegistry.cs

@ -0,0 +1,75 @@
// ==========================================================================
// TypeNameRegistry.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Reflection;
namespace PinkParrot.Infrastructure
{
public class TypeNameRegistry
{
private static readonly Dictionary<Type, string> namesByType = new Dictionary<Type, string>();
private static readonly Dictionary<string, Type> typesByName = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
public static void Map(Type type, string name)
{
Guard.NotNull(type, nameof(type));
Guard.NotNull(name, nameof(name));
lock (namesByType)
{
namesByType.Add(type, name);
typesByName.Add(name, type);
}
}
public static void Map(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
{
var typeNameAttribute = type.GetTypeInfo().GetCustomAttribute<TypeNameAttribute>();
if (!string.IsNullOrWhiteSpace(typeNameAttribute?.TypeName))
{
Map(type, typeNameAttribute.TypeName);
}
}
}
public static string GetName<T>()
{
return GetName(typeof(T));
}
public static string GetName(Type type)
{
var result = namesByType.GetOrDefault(type);
if (result == null)
{
throw new ArgumentException($"There is not name for type '{type}");
}
return result;
}
public static Type GetType(string name)
{
var result = typesByName.GetOrDefault(name);
if (result == null)
{
throw new ArgumentException($"There is not type for name '{name}");
}
return result;
}
}
}

27
src/pinkparrot_read/PinkParrot.Read/Models/ModelFieldDto.cs

@ -0,0 +1,27 @@
// ==========================================================================
// ModelFieldDto.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using Newtonsoft.Json;
using PinkParrot.Core.Schema;
namespace PinkParrot.Read.Models
{
public class ModelFieldDto
{
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public IModelFieldProperties Properties { get; set; }
public static ModelFieldDto Create(ModelField field)
{
return new ModelFieldDto { Name = field.Name, Properties = field.RawProperties };
}
}
}

63
src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaDto.cs

@ -0,0 +1,63 @@
// ==========================================================================
// ModelSchemaDto.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using PinkParrot.Core.Schema;
using PinkParrot.Infrastructure;
using Newtonsoft.Json;
// ReSharper disable UseObjectOrCollectionInitializer
// ReSharper disable InvertIf
namespace PinkParrot.Read.Models
{
public sealed class ModelSchemaDto
{
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public Dictionary<long, ModelFieldDto> Fields { get; set; }
[JsonProperty]
public ModelSchemaProperties Properties { get; set; }
public static ModelSchemaDto Create(ModelSchema schema)
{
Guard.NotNull(schema, nameof(schema));
var dto = new ModelSchemaDto { Properties = schema.Properties, Name = schema.Name };
dto.Fields =
schema.Fields.ToDictionary(
kvp => kvp.Key,
kvp => ModelFieldDto.Create(kvp.Value));
return dto;
}
public ModelSchema ToSchema(ModelFieldRegistry registry)
{
Guard.NotNull(registry, nameof(registry));
var schema = ModelSchema.Create(Name, Properties);
if (Fields != null)
{
foreach (var kvp in Fields)
{
var field = kvp.Value;
schema = schema.AddOrUpdateField(registry.CreateField(kvp.Key, field.Name, field.Properties));
}
}
return schema;
}
}
}

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

@ -36,14 +36,14 @@ namespace PinkParrot.Read.Repositories.Implementations
public static BsonDocument ToJsonBsonDocument<T>(this T value, JsonSerializerSettings settings) public static BsonDocument ToJsonBsonDocument<T>(this T value, JsonSerializerSettings settings)
{ {
var json = JsonConvert.SerializeObject(value, settings).Replace("$type", "§type"); var json = JsonConvert.SerializeObject(value, settings);
return BsonDocument.Parse(json); return BsonDocument.Parse(json);
} }
public static T ToJsonObject<T>(this BsonDocument document, JsonSerializerSettings settings) public static T ToJsonObject<T>(this BsonDocument document, JsonSerializerSettings settings)
{ {
var json = document.ToJson().Replace("§type", "$type"); var json = document.ToJson();
return JsonConvert.DeserializeObject<T>(json, settings); return JsonConvert.DeserializeObject<T>(json, settings);
} }

2
src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaEntity.cs → src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoModelSchemaEntity.cs

@ -10,7 +10,7 @@ using System;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
namespace PinkParrot.Read.Repositories.Implementations namespace PinkParrot.Read.Repositories.Implementations.Mongo
{ {
public sealed class MongoModelSchemaEntity : IModelSchemaEntity public sealed class MongoModelSchemaEntity : IModelSchemaEntity
{ {

47
src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaRepository.cs → src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoModelSchemaRepository.cs

@ -13,30 +13,29 @@ using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Newtonsoft.Json; using Newtonsoft.Json;
using PinkParrot.Core.Schema; using PinkParrot.Core.Schema;
using PinkParrot.Core.Schema.Json;
using PinkParrot.Events.Schema; using PinkParrot.Events.Schema;
using PinkParrot.Infrastructure; 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;
using PinkParrot.Infrastructure.MongoDb; using PinkParrot.Infrastructure.MongoDb;
using PinkParrot.Read.Models;
namespace PinkParrot.Read.Repositories.Implementations namespace PinkParrot.Read.Repositories.Implementations.Mongo
{ {
public sealed class MongoModelSchemaRepository : MongoRepositoryBase<MongoModelSchemaEntity>, IModelSchemaRepository, ICatchEventConsumer public sealed class MongoModelSchemaRepository : MongoRepositoryBase<MongoModelSchemaEntity>, IModelSchemaRepository, ICatchEventConsumer
{ {
private readonly JsonSerializerSettings serializerSettings; private readonly JsonSerializerSettings serializerSettings;
private readonly ModelFieldFactory factory; private readonly ModelFieldRegistry fieldRegistry;
public MongoModelSchemaRepository(IMongoDatabase database, JsonSerializerSettings serializerSettings, ModelFieldFactory factory) public MongoModelSchemaRepository(IMongoDatabase database, JsonSerializerSettings serializerSettings, ModelFieldRegistry fieldRegistry)
: base(database) : base(database)
{ {
Guard.NotNull(serializerSettings, nameof(serializerSettings)); Guard.NotNull(serializerSettings, nameof(serializerSettings));
Guard.NotNull(factory, nameof(factory)); Guard.NotNull(fieldRegistry, nameof(fieldRegistry));
this.serializerSettings = serializerSettings; this.serializerSettings = serializerSettings;
this.fieldRegistry = fieldRegistry;
this.factory = factory;
} }
protected override Task SetupCollectionAsync(IMongoCollection<MongoModelSchemaEntity> collection) protected override Task SetupCollectionAsync(IMongoCollection<MongoModelSchemaEntity> collection)
@ -83,11 +82,6 @@ namespace PinkParrot.Read.Repositories.Implementations
return Collection.UpdateAsync(headers, e => e.IsDeleted = true); 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, factory));
}
public Task On(ModelFieldDeleted @event, EnvelopeHeaders headers) public Task On(ModelFieldDeleted @event, EnvelopeHeaders headers)
{ {
return UpdateSchema(headers, s => s.DeleteField(@event.FieldId)); return UpdateSchema(headers, s => s.DeleteField(@event.FieldId));
@ -115,29 +109,28 @@ namespace PinkParrot.Read.Repositories.Implementations
public Task On(ModelFieldUpdated @event, EnvelopeHeaders headers) public Task On(ModelFieldUpdated @event, EnvelopeHeaders headers)
{ {
return UpdateSchema(headers, s => s.SetField(@event.FieldId, @event.Properties)); return UpdateSchema(headers, s => s.UpdateField(@event.FieldId, @event.Properties));
} }
public Task On(ModelSchemaUpdated @event, EnvelopeHeaders headers) public Task On(ModelSchemaUpdated @event, EnvelopeHeaders headers)
{ {
return Collection.UpdateAsync(headers, e => return UpdateSchema(headers, s => s.Update(@event.Properties));
{ }
if (!string.IsNullOrWhiteSpace(@event.Properties.Name))
{
e.Name = @event.Properties.Name;
}
UpdateSchema(e, s => s.Update(@event.Properties)); public Task On(ModelFieldAdded @event, EnvelopeHeaders headers)
}); {
var field = fieldRegistry.CreateField(@event.FieldId, @event.Name, @event.Properties);
return UpdateSchema(headers, s => s.AddOrUpdateField(field));
} }
public Task On(ModelSchemaCreated @event, EnvelopeHeaders headers) public Task On(ModelSchemaCreated @event, EnvelopeHeaders headers)
{ {
return Collection.CreateAsync(headers, e => return Collection.CreateAsync(headers, e =>
{ {
e.Name = @event.Properties.Name; e.Name = @event.Name;
Serialize(e, ModelSchema.Create(@event.Properties)); Serialize(e, ModelSchema.Create(@event.Name, @event.Properties));
}); });
} }
@ -162,12 +155,16 @@ namespace PinkParrot.Read.Repositories.Implementations
private void Serialize(MongoModelSchemaEntity entity, ModelSchema schema) private void Serialize(MongoModelSchemaEntity entity, ModelSchema schema)
{ {
entity.Schema = SchemaDto.Create(schema).ToJsonBsonDocument(serializerSettings); var dto = ModelSchemaDto.Create(schema);
entity.Schema = dto.ToJsonBsonDocument(serializerSettings);
} }
private ModelSchema Deserialize(MongoModelSchemaEntity entity) private ModelSchema Deserialize(MongoModelSchemaEntity entity)
{ {
return entity?.Schema.ToJsonObject<SchemaDto>(serializerSettings).ToModelSchema(factory); var dto = entity?.Schema.ToJsonObject<ModelSchemaDto>(serializerSettings);
return dto?.ToSchema(fieldRegistry);
} }
} }
} }

2
src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs → src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoPositions.cs

@ -10,7 +10,7 @@ using System.Runtime.Serialization;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
namespace PinkParrot.Read.Services.Implementations namespace PinkParrot.Read.Repositories.Implementations.Mongo
{ {
[DataContract] [DataContract]
public class MongoPosition public class MongoPosition

2
src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs → src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoStreamPositionsStorage.cs

@ -13,7 +13,7 @@ using PinkParrot.Infrastructure.MongoDb;
// ReSharper disable InvertIf // ReSharper disable InvertIf
namespace PinkParrot.Read.Services.Implementations namespace PinkParrot.Read.Repositories.Implementations.Mongo
{ {
public sealed class MongoStreamPositionsStorage : MongoRepositoryBase<MongoPosition>, IStreamPositionStorage public sealed class MongoStreamPositionsStorage : MongoRepositoryBase<MongoPosition>, IStreamPositionStorage
{ {

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

@ -6,12 +6,16 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using PinkParrot.Core.Schema; using Newtonsoft.Json.Linq;
namespace PinkParrot.Write.Schema.Commands namespace PinkParrot.Write.Schema.Commands
{ {
public class AddModelField : TenantCommand public class AddModelField : TenantCommand
{ {
public ModelFieldProperties Properties { get; set; } public string Name;
public string Type;
public JToken Properties;
} }
} }

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

@ -12,6 +12,8 @@ namespace PinkParrot.Write.Schema.Commands
{ {
public class CreateModelSchema : TenantCommand public class CreateModelSchema : TenantCommand
{ {
public string Name { get; set; }
public ModelSchemaProperties Properties { get; set; } public ModelSchemaProperties Properties { get; set; }
} }
} }

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

@ -10,6 +10,6 @@ namespace PinkParrot.Write.Schema.Commands
{ {
public class DeleteModelField : TenantCommand public class DeleteModelField : TenantCommand
{ {
public long FieldId { get; set; } public long FieldId;
} }
} }

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

@ -10,6 +10,6 @@ namespace PinkParrot.Write.Schema.Commands
{ {
public class DisableModelField : TenantCommand public class DisableModelField : TenantCommand
{ {
public long FieldId { get; set; } public long FieldId;
} }
} }

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

@ -10,6 +10,6 @@ namespace PinkParrot.Write.Schema.Commands
{ {
public class EnableModelField : TenantCommand public class EnableModelField : TenantCommand
{ {
public long FieldId { get; set; } public long FieldId;
} }
} }

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

@ -10,6 +10,6 @@ namespace PinkParrot.Write.Schema.Commands
{ {
public class HideModelField : TenantCommand public class HideModelField : TenantCommand
{ {
public long FieldId { get; set; } public long FieldId;
} }
} }

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

@ -10,6 +10,6 @@ namespace PinkParrot.Write.Schema.Commands
{ {
public class ShowModelField : TenantCommand public class ShowModelField : TenantCommand
{ {
public long FieldId { get; set; } public long FieldId;
} }
} }

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

@ -6,14 +6,14 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using PinkParrot.Core.Schema; using Newtonsoft.Json.Linq;
namespace PinkParrot.Write.Schema.Commands namespace PinkParrot.Write.Schema.Commands
{ {
public class UpdateModelField : TenantCommand public class UpdateModelField : TenantCommand
{ {
public long FieldId { get; set; } public long FieldId;
public ModelFieldProperties Properties { get; set; } public JToken Properties { get; set; }
} }
} }

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

@ -8,6 +8,9 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using PinkParrot.Core.Schema;
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Infrastructure.Dispatching; using PinkParrot.Infrastructure.Dispatching;
using PinkParrot.Write.Schema.Commands; using PinkParrot.Write.Schema.Commands;
@ -16,66 +19,91 @@ namespace PinkParrot.Write.Schema
{ {
public class ModelSchemaCommandHandler : ICommandHandler public class ModelSchemaCommandHandler : ICommandHandler
{ {
private readonly ModelFieldRegistry registry;
private readonly JsonSerializer serializer;
public ModelSchemaCommandHandler(ModelFieldRegistry registry, JsonSerializer serializer)
{
this.registry = registry;
this.serializer = serializer;
}
public Task<bool> HandleAsync(CommandContext context) public Task<bool> HandleAsync(CommandContext context)
{ {
return context.IsHandled ? Task.FromResult(false) : 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(CreateModelSchema command, CommandContext context)
{ {
return Update(command, context, schema => schema.AddField(command)); var schema = context.Factory.CreateNew<ModelSchemaDomainObject>(command.AggregateId);
schema.Create(command.TenantId, command.Name, command.Properties);
return context.Repository.SaveAsync(schema, command.AggregateId);
} }
public Task On(DeleteModelField command, CommandContext context) public Task On(DeleteModelSchema command, CommandContext context)
{ {
return Update(command, context, schema => schema.DeleteField(command)); return UpdateAsync(command, context, s => s.Delete());
} }
public Task On(DeleteModelSchema command, CommandContext context) public Task On(DeleteModelField command, CommandContext context)
{ {
return Update(command, context, schema => schema.Delete(command)); return UpdateAsync(command, context, s => s.DeleteField(command.FieldId));
} }
public Task On(DisableModelField command, CommandContext context) public Task On(DisableModelField command, CommandContext context)
{ {
return Update(command, context, schema => schema.DisableField(command)); return UpdateAsync(command, context, s => s.DisableField(command.FieldId));
} }
public Task On(EnableModelField command, CommandContext context) public Task On(EnableModelField command, CommandContext context)
{ {
return Update(command, context, schema => schema.EnableField(command)); return UpdateAsync(command, context, s => s.EnableField(command.FieldId));
} }
public Task On(HideModelField command, CommandContext context) public Task On(HideModelField command, CommandContext context)
{ {
return Update(command, context, schema => schema.HideField(command)); return UpdateAsync(command, context, s => s.HideField(command.FieldId));
} }
public Task On(ShowModelField command, CommandContext context) public Task On(ShowModelField command, CommandContext context)
{ {
return Update(command, context, schema => schema.ShowField(command)); return UpdateAsync(command, context, s => s.ShowField(command.FieldId));
} }
public Task On(UpdateModelField command, CommandContext context) public Task On(UpdateModelSchema command, CommandContext context)
{ {
return Update(command, context, schema => schema.UpdateField(command)); return UpdateAsync(command, context, s => s.Update(command.Properties));
} }
public Task On(UpdateModelSchema command, CommandContext context) public Task On(AddModelField command, CommandContext context)
{ {
return Update(command, context, schema => schema.Update(command)); var propertiesType = registry.FindByTypeName(command.Type).PropertiesType;
var propertiesValue = (IModelFieldProperties)command.Properties.ToObject(propertiesType, serializer);
return UpdateAsync(command, context, s => s.AddField(command.Name, propertiesValue));
} }
public Task On(CreateModelSchema command, CommandContext context) public Task On(UpdateModelField command, CommandContext context)
{ {
var schema = context.Factory.CreateNew<ModelSchemaDomainObject>(command.AggregateId); return UpdateAsync(command, context, s =>
{
var field = s.Schema.Fields.GetOrDefault(command.FieldId);
schema.Create(command); if (field == null)
{
throw new DomainObjectNotFoundException(command.FieldId.ToString(), typeof(ModelField));
}
return context.Repository.SaveAsync(schema, command.AggregateId); var propertiesType = registry.FindByPropertiesType(field.RawProperties.GetType()).PropertiesType;
var propertiesValue = (IModelFieldProperties)command.Properties.ToObject(propertiesType, serializer);
s.UpdateField(command.FieldId, propertiesValue);
});
} }
private static async Task Update(IAggregateCommand command, CommandContext context, Action<ModelSchemaDomainObject> updater) private static async Task UpdateAsync(IAggregateCommand command, CommandContext context, Action<ModelSchemaDomainObject> updater)
{ {
var schema = await context.Repository.GetByIdAsync<ModelSchemaDomainObject>(command.AggregateId); var schema = await context.Repository.GetByIdAsync<ModelSchemaDomainObject>(command.AggregateId);

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

@ -13,13 +13,12 @@ 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;
using PinkParrot.Write.Schema.Commands;
namespace PinkParrot.Write.Schema namespace PinkParrot.Write.Schema
{ {
public class ModelSchemaDomainObject : DomainObject public class ModelSchemaDomainObject : DomainObject, ITenantAggregate
{ {
private readonly ModelFieldFactory factory; private readonly ModelFieldRegistry registry;
private Guid tenantId; private Guid tenantId;
private bool isDeleted; private bool isDeleted;
private long totalFields; private long totalFields;
@ -40,15 +39,17 @@ namespace PinkParrot.Write.Schema
get { return isDeleted; } get { return isDeleted; }
} }
public ModelSchemaDomainObject(Guid id, int version, ModelFieldFactory factory) public ModelSchemaDomainObject(Guid id, int version, ModelFieldRegistry registry)
: base(id, version) : base(id, version)
{ {
this.factory = factory; Guard.NotNull(registry, nameof(registry));
this.registry = registry;
} }
public void On(ModelFieldAdded @event) public void On(ModelFieldAdded @event)
{ {
schema = schema.AddField(@event.FieldId, @event.Properties, factory); schema = schema.AddOrUpdateField(registry.CreateField(@event.FieldId, @event.Name, @event.Properties));
totalFields++; totalFields++;
} }
@ -57,12 +58,12 @@ namespace PinkParrot.Write.Schema
{ {
tenantId = @event.TenantId; tenantId = @event.TenantId;
schema = ModelSchema.Create(@event.Properties); schema = ModelSchema.Create(@event.Name, @event.Properties);
} }
public void On(ModelFieldUpdated @event) public void On(ModelFieldUpdated @event)
{ {
schema = schema.SetField(@event.FieldId, @event.Properties); schema = schema.UpdateField(@event.FieldId, @event.Properties);
} }
public void On(ModelFieldHidden @event) public void On(ModelFieldHidden @event)
@ -100,98 +101,98 @@ namespace PinkParrot.Write.Schema
isDeleted = false; isDeleted = false;
} }
public void AddField(AddModelField command) public void AddField(string name, IModelFieldProperties properties)
{ {
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
var id = ++totalFields; var id = ++totalFields;
schema = schema.AddField(id, command.Properties, factory); schema = schema.AddOrUpdateField(registry.CreateField(id, name, properties));
RaiseEvent(new ModelFieldAdded { FieldId = id, Properties = command.Properties }, true); RaiseEvent(new ModelFieldAdded { FieldId = id, Properties = properties }, true);
} }
public void Create(CreateModelSchema command) public void Create(Guid newTenantId, string name, ModelSchemaProperties properties)
{ {
VerifyNotCreated(); VerifyNotCreated();
tenantId = command.TenantId; tenantId = newTenantId;
schema = ModelSchema.Create(command.Properties); schema = ModelSchema.Create(name, properties);
RaiseEvent(new ModelSchemaCreated { Properties = command.Properties }, true); RaiseEvent(new ModelSchemaCreated { Properties = properties }, true);
} }
public void Update(UpdateModelSchema command) public void Update(ModelSchemaProperties properties)
{ {
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
schema = schema.Update(command.Properties); schema = schema.Update(properties);
RaiseEvent(new ModelSchemaUpdated { Properties = command.Properties }, true); RaiseEvent(new ModelSchemaUpdated { Properties = properties }, true);
} }
public void UpdateField(UpdateModelField command) public void UpdateField(long fieldId, IModelFieldProperties properties)
{ {
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
schema = schema.SetField(command.FieldId, command.Properties); schema = schema.UpdateField(fieldId, properties);
RaiseEvent(new ModelFieldUpdated { FieldId = command.FieldId, Properties = command.Properties }, true); RaiseEvent(new ModelFieldUpdated { FieldId = fieldId, Properties = properties }, true);
} }
public void HideField(HideModelField command) public void HideField(long fieldId)
{ {
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
schema = schema.HideField(command.FieldId); schema = schema.HideField(fieldId);
RaiseEvent(new ModelFieldHidden { FieldId = command.FieldId }, true); RaiseEvent(new ModelFieldHidden { FieldId = fieldId }, true);
} }
public void ShowField(ShowModelField command) public void ShowField(long fieldId)
{ {
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
schema = schema.ShowField(command.FieldId); schema = schema.ShowField(fieldId);
RaiseEvent(new ModelFieldShown { FieldId = command.FieldId }, true); RaiseEvent(new ModelFieldShown { FieldId = fieldId }, true);
} }
public void DisableField(DisableModelField command) public void DisableField(long fieldId)
{ {
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
schema = schema.DisableField(command.FieldId); schema = schema.DisableField(fieldId);
RaiseEvent(new ModelFieldDisabled { FieldId = command.FieldId }, true); RaiseEvent(new ModelFieldDisabled { FieldId = fieldId }, true);
} }
public void EnableField(EnableModelField command) public void EnableField(long fieldId)
{ {
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
schema = schema.EnableField(command.FieldId); schema = schema.EnableField(fieldId);
RaiseEvent(new ModelFieldEnabled { FieldId = command.FieldId }, true); RaiseEvent(new ModelFieldEnabled { FieldId = fieldId }, true);
} }
public void Delete(DeleteModelSchema command) public void DeleteField(long fieldId)
{ {
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
isDeleted = true; schema = schema.DeleteField(fieldId);
RaiseEvent(new ModelSchemaDeleted(), true); RaiseEvent(new ModelFieldDeleted { FieldId = fieldId }, true);
} }
public void DeleteField(DeleteModelField command) public void Delete()
{ {
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
schema = schema.DeleteField(command.FieldId); isDeleted = true;
RaiseEvent(new ModelFieldDeleted { FieldId = command.FieldId }, true); RaiseEvent(new ModelSchemaDeleted(), true);
} }
private void VerifyNotCreated() private void VerifyNotCreated()

Loading…
Cancel
Save