diff --git a/src/PinkParrot/Configurations/Serializers.cs b/src/PinkParrot/Configurations/Serializers.cs index 4ee69921b..f68b12759 100644 --- a/src/PinkParrot/Configurations/Serializers.cs +++ b/src/PinkParrot/Configurations/Serializers.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using PinkParrot.Core.Schema; +using PinkParrot.Core.Schema.Json; using PinkParrot.Infrastructure.CQRS.EventStore; using PinkParrot.Infrastructure.Json; @@ -38,10 +39,8 @@ namespace PinkParrot.Configurations public static void AddEventFormatter(this IServiceCollection services) { - var fieldFactory = new ModelFieldFactory(); - services.AddSingleton(t => CreateSettings()); - services.AddSingleton(fieldFactory); + services.AddSingleton(); services.AddSingleton(); } diff --git a/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs b/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs index 990588fea..953a6692c 100644 --- a/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs +++ b/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs @@ -50,7 +50,7 @@ namespace PinkParrot.Modules.Api.Schemas return NotFound(); } - return Ok(SchemaDto.Create(entity.Schema)); + return Ok(ModelSchemaDto.Create(entity.Schema)); } [HttpPost] diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/IModelFieldProperties.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/IModelFieldProperties.cs new file mode 100644 index 000000000..a011a0bae --- /dev/null +++ b/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 + { + } +} diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/IRegisterModelField.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/IRegisterModelField.cs new file mode 100644 index 000000000..bfff4f862 --- /dev/null +++ b/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); + } +} \ No newline at end of file diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/Json/SchemaDto.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/Json/SchemaDto.cs deleted file mode 100644 index 909373b13..000000000 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/Json/SchemaDto.cs +++ /dev/null @@ -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 fields; - - public ImmutableDictionary Fields - { - get { return fields; } - } - - public ModelSchemaProperties Properties - { - get { return properties; } - } - - public SchemaDto(ModelSchemaProperties properties, ImmutableDictionary fields) - { - Guard.NotNull(fields, nameof(fields)); - Guard.NotNull(properties, nameof(properties)); - - this.properties = properties; - - this.fields = fields; - } - - public static SchemaDto Create(ModelSchema schema) - { - Guard.NotNull(schema, nameof(schema)); - - var fields = schema.Fields.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; - } - } -} diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs index b41ad697a..ad23f4a50 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using PinkParrot.Infrastructure; using PinkParrot.Infrastructure.Tasks; +// ReSharper disable InvertIf // ReSharper disable ConvertIfStatementToReturnStatement namespace PinkParrot.Core.Schema @@ -19,6 +20,7 @@ namespace PinkParrot.Core.Schema public abstract class ModelField { private readonly long id; + private string name; private bool isDisabled; private bool isHidden; @@ -27,15 +29,10 @@ namespace PinkParrot.Core.Schema get { return id; } } - public abstract ModelFieldProperties RawProperties { get; } - - public abstract string Name { get; } - - public abstract string Label { get; } - - public abstract string Hints { get; } - - public abstract bool IsRequired { get; } + public string Name + { + get { return name; } + } public bool IsHidden { @@ -47,14 +44,25 @@ namespace PinkParrot.Core.Schema 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.ValidSlug(name, nameof(name)); this.id = id; + + this.name = name; } - public abstract ModelField Configure(ModelFieldProperties newProperties); + public abstract ModelField Update(IModelFieldProperties newProperties); public Task ValidateAsync(PropertyValue property, ICollection errors) { @@ -98,6 +106,18 @@ namespace PinkParrot.Core.Schema return Update(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(clone => clone.name = newName); + } + protected T Update(Action updater) where T : ModelField { var clone = (T)Clone(); diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs deleted file mode 100644 index 17259139a..000000000 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldFactory.cs +++ /dev/null @@ -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> factories - = new Dictionary>(); - - public ModelFieldFactory() - { - AddFactory((id, p) => new NumberField(id, (NumberFieldProperties)p)); - } - - public ModelFieldFactory AddFactory(Func factory) where T : ModelFieldProperties - { - Guard.NotNull(factory, nameof(factory)); - - factories[typeof(T)] = factory; - - return this; - } - - public virtual ModelField CreateField(long id, ModelFieldProperties properties) - { - 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); - } - } -} - diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldProperties.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldProperties.cs index 52399f804..6db47beb1 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldProperties.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldProperties.cs @@ -11,24 +11,18 @@ using PinkParrot.Infrastructure; namespace PinkParrot.Core.Schema { - public abstract class ModelFieldProperties : NamedElementProperties + public abstract class ModelFieldProperties : NamedElementProperties, IModelFieldProperties { public bool IsRequired { get; } - protected ModelFieldProperties( - bool isRequired, - string name, - string label, - string hints) - : base(name, label, hints) + protected ModelFieldProperties(string label, string hints, bool isRequired) + : base(label, hints) { IsRequired = isRequired; } - public override void Validate(IList errors) - { - base.Validate(errors); - + public void Validate(IList errors) + { ValidateCore(errors); } diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldRegistry.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelFieldRegistry.cs new file mode 100644 index 000000000..c0d967e8d --- /dev/null +++ b/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 fieldsByTypeName = new Dictionary(); + private readonly Dictionary fieldsByPropertyType = new Dictionary(); + + 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(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; + } + } +} diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField_Generic.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField_Generic.cs index d9eedf239..f05a25b08 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField_Generic.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelField_Generic.cs @@ -15,19 +15,14 @@ namespace PinkParrot.Core.Schema { private T properties; - public override ModelFieldProperties RawProperties + public override IModelFieldProperties RawProperties { get { return properties; } } - public override string Name - { - get { return properties.Name; } - } - public override string Label { - get { return properties.Label ?? properties.Name; } + get { return properties.Label ?? Name; } } public override string Hints @@ -45,18 +40,18 @@ namespace PinkParrot.Core.Schema get { return properties; } } - protected ModelField(long id, T properties) - : base(id) + protected ModelField(long id, string name, T properties) + : base(id, name) { Guard.NotNull(properties, nameof(properties)); this.properties = properties; } - public override ModelField Configure(ModelFieldProperties newProperties) + public override ModelField Update(IModelFieldProperties newProperties) { Guard.NotNull(newProperties, nameof(newProperties)); - + var typedProperties = newProperties as T; if (typedProperties == null) @@ -64,6 +59,8 @@ namespace PinkParrot.Core.Schema 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>(clone => clone.properties = typedProperties); } } diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs index 137aebdfe..88949d608 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchema.cs @@ -12,24 +12,20 @@ using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using PinkParrot.Infrastructure; +// ReSharper disable InvertIf namespace PinkParrot.Core.Schema { public sealed class ModelSchema { + private readonly string name; private readonly ModelSchemaProperties properties; private readonly ImmutableDictionary fieldsById; private readonly Dictionary fieldsByName; - public ModelSchema(ModelSchemaProperties properties, ImmutableDictionary fields) + public string Name { - Guard.NotNull(fields, nameof(fields)); - Guard.NotNull(properties, nameof(properties)); - - this.properties = properties; - - fieldsById = fields; - fieldsByName = fields.Values.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); + get { return name; } } public ImmutableDictionary Fields @@ -42,43 +38,56 @@ namespace PinkParrot.Core.Schema get { return properties; } } - public static ModelSchema Create(ModelSchemaProperties newProperties) + public ModelSchema(string name, ModelSchemaProperties properties, ImmutableDictionary 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.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)); - 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.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) @@ -101,13 +110,20 @@ namespace PinkParrot.Core.Schema 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) { - return new ModelSchema(properties, fieldsById.Remove(fieldId)); + return new ModelSchema(name, properties, fieldsById.Remove(fieldId)); } - private ModelSchema UpdateField(long fieldId, Func updater) + public ModelSchema UpdateField(long fieldId, Func updater) { + Guard.NotNull(updater, nameof(updater)); + ModelField field; if (!fieldsById.TryGetValue(fieldId, out field)) @@ -117,19 +133,7 @@ namespace PinkParrot.Core.Schema var newField = updater(field); - return ReplaceOrAddField(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)); + return AddOrUpdateField(newField); } public async Task ValidateAsync(PropertiesBag data, IList errors) diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaProperties.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaProperties.cs index 887a9f538..a651dc86e 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaProperties.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/ModelSchemaProperties.cs @@ -10,10 +10,9 @@ namespace PinkParrot.Core.Schema public sealed class ModelSchemaProperties : NamedElementProperties { public ModelSchemaProperties( - string name, string label, string hints) - : base(name, label, hints) + : base(label, hints) { } } diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/NamedElementProperties.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/NamedElementProperties.cs index bbe0853c4..cdfce8f45 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/NamedElementProperties.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/NamedElementProperties.cs @@ -6,21 +6,12 @@ // All rights reserved. // ========================================================================== -using System.Collections.Generic; -using PinkParrot.Infrastructure; - namespace PinkParrot.Core.Schema { - public abstract class NamedElementProperties : IValidatable + public abstract class NamedElementProperties { - private readonly string name; private readonly string label; private readonly string hints; - - public string Name - { - get { return name; } - } public string Label { @@ -32,28 +23,10 @@ namespace PinkParrot.Core.Schema get { return hints; } } - protected NamedElementProperties( - string name, - string label, - string hints) + protected NamedElementProperties(string label, string hints) { - this.name = name; this.label = label; this.hints = hints; } - - public virtual void Validate(IList 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")); - } - } } } \ No newline at end of file diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs index ee38d4f61..f7e826ebb 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/NumberField.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Threading.Tasks; using PinkParrot.Infrastructure; using PinkParrot.Infrastructure.Tasks; @@ -27,8 +28,13 @@ namespace PinkParrot.Core.Schema get { return Properties.MinValue; } } - public NumberField(long id, NumberFieldProperties properties) - : base(id, properties) + public double[] AllowedValues + { + 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}"); } + + if (AllowedValues != null && !AllowedValues.Contains(value)) + { + errors.Add($"Can only be one of the following value: {string.Join(", ", AllowedValues)}"); + } } catch (InvalidCastException) { diff --git a/src/pinkparrot_core/PinkParrot.Core/Schema/NumberFieldProperties.cs b/src/pinkparrot_core/PinkParrot.Core/Schema/NumberFieldProperties.cs index bffd645a8..d1d3efc7b 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schema/NumberFieldProperties.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schema/NumberFieldProperties.cs @@ -11,39 +11,43 @@ using PinkParrot.Infrastructure; namespace PinkParrot.Core.Schema { - [TypeName("Number")] + [TypeName("number")] public sealed class NumberFieldProperties : ModelFieldProperties { + public string Placeholder { get; set; } + public double? DefaultValue { get; } public double? MaxValue { get; } public double? MinValue { get; } - public string Placeholder { get; set; } + public double[] AllowedValues { get; } public NumberFieldProperties( - bool isRequired, - string name, string label, string hints, string placeholder, double? minValue, double? maxValue, - double? defaultValue) - : base(isRequired, name, label, hints) + double? defaultValue, + double[] allowedValues, + bool isRequired) + : base(label, hints, isRequired) { Placeholder = placeholder; MinValue = minValue; MaxValue = maxValue; + AllowedValues = allowedValues; + DefaultValue = defaultValue; } protected override void ValidateCore(IList 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")); } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs index 52435fe2f..eb17dd1b9 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldAdded.cs @@ -7,13 +7,17 @@ // ========================================================================== using PinkParrot.Core.Schema; +using PinkParrot.Infrastructure; namespace PinkParrot.Events.Schema { + [TypeName("ModelFieldAddedEvent")] public class ModelFieldAdded : TenantEvent { public long FieldId; - public ModelFieldProperties Properties; + public string Name; + + public IModelFieldProperties Properties; } } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs index 5f9dbec71..1d0ece1b4 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDeleted.cs @@ -5,8 +5,12 @@ // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== + +using PinkParrot.Infrastructure; + namespace PinkParrot.Events.Schema { + [TypeName("ModelFieldDeletedEvent")] public class ModelFieldDeleted : TenantEvent { public long FieldId; diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs index e47b8084a..039c407da 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldDisabled.cs @@ -5,8 +5,12 @@ // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== + +using PinkParrot.Infrastructure; + namespace PinkParrot.Events.Schema { + [TypeName("ModelFieldDisabledEvent")] public class ModelFieldDisabled : TenantEvent { public long FieldId; diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs index 327d70637..41429afd1 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldEnabled.cs @@ -5,8 +5,12 @@ // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== + +using PinkParrot.Infrastructure; + namespace PinkParrot.Events.Schema { + [TypeName("ModelFieldEnabledEvent")] public class ModelFieldEnabled : TenantEvent { public long FieldId; diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs index 284ccf369..eebe99787 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldHidden.cs @@ -5,8 +5,12 @@ // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== + +using PinkParrot.Infrastructure; + namespace PinkParrot.Events.Schema { + [TypeName("ModelFieldHiddenEvent")] public class ModelFieldHidden : TenantEvent { public long FieldId; diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs index f2a6b7310..b3c6f5008 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldShown.cs @@ -5,8 +5,12 @@ // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== + +using PinkParrot.Infrastructure; + namespace PinkParrot.Events.Schema { + [TypeName("ModelFieldShownEvent")] public class ModelFieldShown : TenantEvent { public long FieldId; diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs index 135f5230e..8615fd9e9 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelFieldUpdated.cs @@ -7,13 +7,15 @@ // ========================================================================== using PinkParrot.Core.Schema; +using PinkParrot.Infrastructure; namespace PinkParrot.Events.Schema { + [TypeName("ModelFieldUpdatedEvent")] public class ModelFieldUpdated : TenantEvent { public long FieldId; - public ModelFieldProperties Properties; + public IModelFieldProperties Properties; } } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs index b2f5b268c..784bf372a 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaCreated.cs @@ -7,11 +7,15 @@ // ========================================================================== using PinkParrot.Core.Schema; +using PinkParrot.Infrastructure; namespace PinkParrot.Events.Schema { + [TypeName("ModelSchemaCreatedEvent")] public class ModelSchemaCreated : TenantEvent { + public string Name; + public ModelSchemaProperties Properties; } } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs index 16f577d09..6c11384a6 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaDeleted.cs @@ -5,8 +5,12 @@ // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== + +using PinkParrot.Infrastructure; + namespace PinkParrot.Events.Schema { + [TypeName("ModelSchemaDeleted")] public class ModelSchemaDeleted : TenantEvent { } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs index d9f2b98f1..6d081f6d0 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schema/ModelSchemaUpdated.cs @@ -7,9 +7,11 @@ // ========================================================================== using PinkParrot.Core.Schema; +using PinkParrot.Infrastructure; namespace PinkParrot.Events.Schema { + [TypeName("ModelSchemaUpdated")] public class ModelSchemaUpdated : TenantEvent { public ModelSchemaProperties Properties; diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandContext.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandContext.cs index ef68d81b8..7c8a19b75 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandContext.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Commands/CommandContext.cs @@ -17,10 +17,10 @@ namespace PinkParrot.Infrastructure.CQRS.Commands private readonly ICommand command; private Exception exception; private bool isSucceeded; - - public ICommand Command + + public IDomainObjectRepository Repository { - get { return command; } + get { return repository; } } public IDomainObjectFactory Factory @@ -28,9 +28,9 @@ namespace PinkParrot.Infrastructure.CQRS.Commands get { return factory; } } - public IDomainObjectRepository Repository + public ICommand Command { - get { return repository; } + get { return command; } } public bool IsHandled diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs index eb2a0a56f..4c1c529d1 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs @@ -14,7 +14,6 @@ namespace PinkParrot.Infrastructure.CQRS public const string Timestamp = "Timestamp"; public const string TenantId = "TenantId"; public const string EventId = "EventId"; - public const string EventType = "EventType"; public const string EventNumber = "EventNumber"; } } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs index 75677f981..5b5262981 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs @@ -30,7 +30,7 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore { var headers = ReadJson(@event.Event.Metadata); - var eventType = Type.GetType(headers.Properties[CommonHeaders.EventType].ToString()); + var eventType = TypeNameRegistry.GetType(@event.Event.EventType); var eventData = ReadJson(@event.Event.Data, eventType); var envelope = new Envelope(eventData, headers); @@ -43,15 +43,14 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore public EventData ToEventData(Envelope 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.EventType, eventType.AssemblyQualifiedName); var headers = WriteJson(envelope.Headers); 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(byte[] data, Type type = null) diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/TypeNameSerializationBinder.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/TypeNameSerializationBinder.cs index f475fa6f1..d0e171503 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/TypeNameSerializationBinder.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Json/TypeNameSerializationBinder.cs @@ -7,46 +7,15 @@ // ========================================================================== using System; -using System.Collections.Generic; -using System.Reflection; using Newtonsoft.Json.Serialization; namespace PinkParrot.Infrastructure.Json { public class TypeNameSerializationBinder : DefaultSerializationBinder { - private readonly Dictionary namesByType = new Dictionary(); - private readonly Dictionary typesByName = new Dictionary(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(); - - if (!string.IsNullOrWhiteSpace(typeNameAttribute?.TypeName)) - { - Map(type, typeNameAttribute.TypeName); - } - } - - return this; - } - public override Type BindToType(string assemblyName, string typeName) { - var type = typesByName.GetOrDefault(typeName); + var type = TypeNameRegistry.GetType(typeName); return type ?? base.BindToType(assemblyName, typeName); } @@ -55,7 +24,7 @@ namespace PinkParrot.Infrastructure.Json { assemblyName = null; - var name = namesByType.GetOrDefault(serializedType); + var name = TypeNameRegistry.GetName(serializedType); if (name != null) { diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/TypeNameRegistry.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/TypeNameRegistry.cs new file mode 100644 index 000000000..754cadf9a --- /dev/null +++ b/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 namesByType = new Dictionary(); + private static readonly Dictionary typesByName = new Dictionary(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(); + + if (!string.IsNullOrWhiteSpace(typeNameAttribute?.TypeName)) + { + Map(type, typeNameAttribute.TypeName); + } + } + } + + public static string GetName() + { + 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; + } + } +} diff --git a/src/pinkparrot_read/PinkParrot.Read/Models/ModelFieldDto.cs b/src/pinkparrot_read/PinkParrot.Read/Models/ModelFieldDto.cs new file mode 100644 index 000000000..7bb4088ad --- /dev/null +++ b/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 }; + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaDto.cs b/src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaDto.cs new file mode 100644 index 000000000..7ed58811c --- /dev/null +++ b/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 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; + } + } +} diff --git a/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/EntityMapper.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/EntityMapper.cs index 9b53a6169..003d7ae70 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/EntityMapper.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/EntityMapper.cs @@ -36,14 +36,14 @@ namespace PinkParrot.Read.Repositories.Implementations public static BsonDocument ToJsonBsonDocument(this T value, JsonSerializerSettings settings) { - var json = JsonConvert.SerializeObject(value, settings).Replace("$type", "§type"); + var json = JsonConvert.SerializeObject(value, settings); return BsonDocument.Parse(json); } public static T ToJsonObject(this BsonDocument document, JsonSerializerSettings settings) { - var json = document.ToJson().Replace("§type", "$type"); + var json = document.ToJson(); return JsonConvert.DeserializeObject(json, settings); } diff --git a/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaEntity.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoModelSchemaEntity.cs similarity index 95% rename from src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaEntity.cs rename to src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoModelSchemaEntity.cs index abb5bc609..13df3bf46 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaEntity.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoModelSchemaEntity.cs @@ -10,7 +10,7 @@ using System; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -namespace PinkParrot.Read.Repositories.Implementations +namespace PinkParrot.Read.Repositories.Implementations.Mongo { public sealed class MongoModelSchemaEntity : IModelSchemaEntity { diff --git a/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaRepository.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoModelSchemaRepository.cs similarity index 82% rename from src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaRepository.cs rename to src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoModelSchemaRepository.cs index 016ca78a3..f4c1e2be4 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaRepository.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoModelSchemaRepository.cs @@ -13,30 +13,29 @@ using System.Threading.Tasks; 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.Read.Models; -namespace PinkParrot.Read.Repositories.Implementations +namespace PinkParrot.Read.Repositories.Implementations.Mongo { public sealed class MongoModelSchemaRepository : MongoRepositoryBase, IModelSchemaRepository, ICatchEventConsumer { 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) { Guard.NotNull(serializerSettings, nameof(serializerSettings)); - Guard.NotNull(factory, nameof(factory)); + Guard.NotNull(fieldRegistry, nameof(fieldRegistry)); this.serializerSettings = serializerSettings; - - this.factory = factory; + this.fieldRegistry = fieldRegistry; } protected override Task SetupCollectionAsync(IMongoCollection collection) @@ -83,11 +82,6 @@ namespace PinkParrot.Read.Repositories.Implementations 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) { return UpdateSchema(headers, s => s.DeleteField(@event.FieldId)); @@ -115,29 +109,28 @@ namespace PinkParrot.Read.Repositories.Implementations 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) { - return Collection.UpdateAsync(headers, e => - { - if (!string.IsNullOrWhiteSpace(@event.Properties.Name)) - { - e.Name = @event.Properties.Name; - } + return UpdateSchema(headers, s => s.Update(@event.Properties)); + } - 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) { 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) { - entity.Schema = SchemaDto.Create(schema).ToJsonBsonDocument(serializerSettings); + var dto = ModelSchemaDto.Create(schema); + + entity.Schema = dto.ToJsonBsonDocument(serializerSettings); } private ModelSchema Deserialize(MongoModelSchemaEntity entity) { - return entity?.Schema.ToJsonObject(serializerSettings).ToModelSchema(factory); + var dto = entity?.Schema.ToJsonObject(serializerSettings); + + return dto?.ToSchema(fieldRegistry); } } } diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoPositions.cs similarity index 91% rename from src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs rename to src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoPositions.cs index 3bdb03805..3c6119843 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoPositions.cs @@ -10,7 +10,7 @@ using System.Runtime.Serialization; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -namespace PinkParrot.Read.Services.Implementations +namespace PinkParrot.Read.Repositories.Implementations.Mongo { [DataContract] public class MongoPosition diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoStreamPositionsStorage.cs similarity index 95% rename from src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs rename to src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoStreamPositionsStorage.cs index ae918a8ac..a04b43b4e 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/Mongo/MongoStreamPositionsStorage.cs @@ -13,7 +13,7 @@ using PinkParrot.Infrastructure.MongoDb; // ReSharper disable InvertIf -namespace PinkParrot.Read.Services.Implementations +namespace PinkParrot.Read.Repositories.Implementations.Mongo { public sealed class MongoStreamPositionsStorage : MongoRepositoryBase, IStreamPositionStorage { diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs index 1a27dc6fa..41d2be3e7 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/AddModelField.cs @@ -6,12 +6,16 @@ // All rights reserved. // ========================================================================== -using PinkParrot.Core.Schema; +using Newtonsoft.Json.Linq; namespace PinkParrot.Write.Schema.Commands { public class AddModelField : TenantCommand { - public ModelFieldProperties Properties { get; set; } + public string Name; + + public string Type; + + public JToken Properties; } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs index 3795a1a23..9bce9a9b2 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs @@ -12,6 +12,8 @@ namespace PinkParrot.Write.Schema.Commands { public class CreateModelSchema : TenantCommand { + public string Name { get; set; } + public ModelSchemaProperties Properties { get; set; } } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs index f8ec9ad64..5a9d0b0d2 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelField.cs @@ -10,6 +10,6 @@ namespace PinkParrot.Write.Schema.Commands { public class DeleteModelField : TenantCommand { - public long FieldId { get; set; } + public long FieldId; } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs index d931649a9..08a4aef63 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DisableModelField.cs @@ -10,6 +10,6 @@ namespace PinkParrot.Write.Schema.Commands { public class DisableModelField : TenantCommand { - public long FieldId { get; set; } + public long FieldId; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs index feeba187e..5554f8f9d 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/EnableModelField.cs @@ -10,6 +10,6 @@ namespace PinkParrot.Write.Schema.Commands { public class EnableModelField : TenantCommand { - public long FieldId { get; set; } + public long FieldId; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs index a8b24e445..929d1d7bb 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/HideModelField.cs @@ -10,6 +10,6 @@ namespace PinkParrot.Write.Schema.Commands { public class HideModelField : TenantCommand { - public long FieldId { get; set; } + public long FieldId; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs index 8668851a2..d9754a999 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/ShowModelField.cs @@ -10,6 +10,6 @@ namespace PinkParrot.Write.Schema.Commands { public class ShowModelField : TenantCommand { - public long FieldId { get; set; } + public long FieldId; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs index 2bd2a02fc..4c08fa47b 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/UpdateModelField.cs @@ -6,14 +6,14 @@ // All rights reserved. // ========================================================================== -using PinkParrot.Core.Schema; +using Newtonsoft.Json.Linq; namespace PinkParrot.Write.Schema.Commands { public class UpdateModelField : TenantCommand { - public long FieldId { get; set; } + public long FieldId; - public ModelFieldProperties Properties { get; set; } + public JToken Properties { get; set; } } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs index 7a9b72f02..d47225b41 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaCommandHandler.cs @@ -8,6 +8,9 @@ using System; using System.Threading.Tasks; +using Newtonsoft.Json; +using PinkParrot.Core.Schema; +using PinkParrot.Infrastructure; using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.Dispatching; using PinkParrot.Write.Schema.Commands; @@ -16,66 +19,91 @@ namespace PinkParrot.Write.Schema { 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 HandleAsync(CommandContext 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(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) { - return Update(command, context, schema => schema.DisableField(command)); + return UpdateAsync(command, context, s => s.DisableField(command.FieldId)); } 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) { - return Update(command, context, schema => schema.HideField(command)); + return UpdateAsync(command, context, s => s.HideField(command.FieldId)); } 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(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 updater) + private static async Task UpdateAsync(IAggregateCommand command, CommandContext context, Action updater) { var schema = await context.Repository.GetByIdAsync(command.AggregateId); diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs index 036b81bfa..7297c3365 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs @@ -13,13 +13,12 @@ using PinkParrot.Infrastructure; using PinkParrot.Infrastructure.CQRS; using PinkParrot.Infrastructure.CQRS.Events; using PinkParrot.Infrastructure.Dispatching; -using PinkParrot.Write.Schema.Commands; 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 bool isDeleted; private long totalFields; @@ -40,15 +39,17 @@ namespace PinkParrot.Write.Schema get { return isDeleted; } } - public ModelSchemaDomainObject(Guid id, int version, ModelFieldFactory factory) + public ModelSchemaDomainObject(Guid id, int version, ModelFieldRegistry registry) : base(id, version) { - this.factory = factory; + Guard.NotNull(registry, nameof(registry)); + + this.registry = registry; } 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++; } @@ -57,12 +58,12 @@ namespace PinkParrot.Write.Schema { tenantId = @event.TenantId; - schema = ModelSchema.Create(@event.Properties); + schema = ModelSchema.Create(@event.Name, @event.Properties); } public void On(ModelFieldUpdated @event) { - schema = schema.SetField(@event.FieldId, @event.Properties); + schema = schema.UpdateField(@event.FieldId, @event.Properties); } public void On(ModelFieldHidden @event) @@ -100,98 +101,98 @@ namespace PinkParrot.Write.Schema isDeleted = false; } - public void AddField(AddModelField command) + public void AddField(string name, IModelFieldProperties properties) { VerifyCreatedAndNotDeleted(); 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(); - 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(); - 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(); - 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(); - 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(); - 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(); - 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(); - 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(); - 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(); - schema = schema.DeleteField(command.FieldId); + isDeleted = true; - RaiseEvent(new ModelFieldDeleted { FieldId = command.FieldId }, true); + RaiseEvent(new ModelSchemaDeleted(), true); } private void VerifyNotCreated()