diff --git a/zero.Core/Entities/Blueprints/Blueprint.cs b/zero.Core/Entities/Blueprints/Blueprint.cs new file mode 100644 index 00000000..cfe1ea1d --- /dev/null +++ b/zero.Core/Entities/Blueprints/Blueprint.cs @@ -0,0 +1,23 @@ +using zero.Core.Attributes; + +namespace zero.Core.Entities +{ + public class Blueprint : IBlueprint + { + /// + public string Id { get; set; } + + /// + public IZeroEntity Content { get; set; } + } + + + [Collection("Blueprints")] + public interface IBlueprint : IZeroIdEntity, IZeroDbConventions + { + /// + /// Contains the entity content + /// + IZeroEntity Content { get; set; } + } +} diff --git a/zero.Core/Entities/InheritAttribute.cs b/zero.Core/Entities/InheritAttribute.cs index b5a06d26..8b1e17c2 100644 --- a/zero.Core/Entities/InheritAttribute.cs +++ b/zero.Core/Entities/InheritAttribute.cs @@ -5,4 +5,15 @@ namespace zero.Core.Entities public class OverwriteAttribute : Attribute { } + + + public class ReferenceAttribute : Attribute + { + public Type Type { get; set; } + + public ReferenceAttribute(Type type) + { + Type = type; + } + } } \ No newline at end of file diff --git a/zero.Core/Entities/Ref.cs b/zero.Core/Entities/Ref.cs new file mode 100644 index 00000000..9b99a944 --- /dev/null +++ b/zero.Core/Entities/Ref.cs @@ -0,0 +1,35 @@ +using System; + +namespace zero.Core.Entities +{ + public class Ref where T : IZeroIdEntity + { + public Ref() { } + + public Ref(string id) + { + Id = id; + } + + public string Id { get; private set; } + + + public override string ToString() + { + return Id; + } + } + + + public class Refs where T : IZeroIdEntity + { + public Refs() { } + + public Refs(params string[] ids) + { + Ids = ids; + } + + public string[] Ids { get; private set; } = new string[0] { }; + } +} diff --git a/zero.Core/Entities/ZeroEntity.cs b/zero.Core/Entities/ZeroEntity.cs index 28e83ee3..29a4a6ce 100644 --- a/zero.Core/Entities/ZeroEntity.cs +++ b/zero.Core/Entities/ZeroEntity.cs @@ -34,7 +34,7 @@ namespace zero.Core.Entities public DateTimeOffset CreatedDate { get; set; } /// - public string OriginId { get; set; } + public string BlueprintId { get; set; } } @@ -85,6 +85,6 @@ namespace zero.Core.Entities /// /// Id of the original base entity (which this one inherits from) /// - string OriginId { get; set; } + string BlueprintId { get; set; } } } diff --git a/zero.Core/Extensions/RavenDocumentStoreExtensions.cs b/zero.Core/Extensions/RavenDocumentStoreExtensions.cs index 4e0c2772..e5816c1b 100644 --- a/zero.Core/Extensions/RavenDocumentStoreExtensions.cs +++ b/zero.Core/Extensions/RavenDocumentStoreExtensions.cs @@ -1,6 +1,7 @@ using Raven.Client.Documents; using Raven.Client.Documents.Conventions; using Raven.Client.Documents.Operations.CompareExchange; +using Raven.Client.Json.Serialization.NewtonsoftJson; using System; using System.Linq; using System.Reflection; @@ -25,6 +26,15 @@ namespace zero.Core.Extensions store.Conventions.IdentityPartsSeparator = '.'; + (store.Conventions.Serialization as NewtonsoftJsonSerializationConventions).CustomizeJsonDeserializer = x => + { + x.Converters.Add(new RefJsonConverter()); + }; + (store.Conventions.Serialization as NewtonsoftJsonSerializationConventions).CustomizeJsonSerializer = x => + { + x.Converters.Add(new RefJsonConverter()); + }; + store.Conventions.FindCollectionName = type => { Type finalType = type; diff --git a/zero.Core/Utils/ObjectTraverser.cs b/zero.Core/Utils/ObjectTraverser.cs index dcf7f66e..d669ba8b 100644 --- a/zero.Core/Utils/ObjectTraverser.cs +++ b/zero.Core/Utils/ObjectTraverser.cs @@ -110,24 +110,10 @@ namespace zero.Core.Utils exploredObjects.Add(key); - System.Collections.ICollection enumerable = value as System.Collections.ICollection; - - // this property is a list - if (enumerable != null && !(value is string)) - { - int idx = 0; - foreach (object item in enumerable) - { - FindAttribute(item, value, CombineKey(String.Empty, key, SQBKT_OPEN + idx + SQBKT_CLOSE), null, exploredObjects, found); - idx += 1; - } - return; - } - // check if the current property contains the attribute if (property != null) { - T attribute = property.GetCustomAttribute(); + T attribute = property.GetCustomAttribute(true); if (attribute != null) { @@ -142,6 +128,20 @@ namespace zero.Core.Utils } } + System.Collections.ICollection enumerable = value as System.Collections.ICollection; + + // this property is a list + if (enumerable != null && !(value is string)) + { + int idx = 0; + foreach (object item in enumerable) + { + FindAttribute(item, value, CombineKey(String.Empty, key, SQBKT_OPEN + idx + SQBKT_CLOSE), null, exploredObjects, found); + idx += 1; + } + return; + } + // check if our property is a primitive type if (value == null || value.GetType().IsPrimitive || value is string || value is DateTime || value is DateTimeOffset || value is Uri) { diff --git a/zero.Core/Utils/RefJsonConverter.cs b/zero.Core/Utils/RefJsonConverter.cs new file mode 100644 index 00000000..74e44a81 --- /dev/null +++ b/zero.Core/Utils/RefJsonConverter.cs @@ -0,0 +1,60 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using zero.Core.Entities; +using zero.Core.Extensions; + +namespace zero.Core.Utils +{ + public class RefJsonConverter : JsonConverter + { + private readonly Type type; + + private readonly string idProperty; + + public RefJsonConverter() + { + type = typeof(Ref<>); + idProperty = nameof(Ref.Id); + } + + + public override bool CanRead => true; + + public override bool CanWrite => true; + + public override bool CanConvert(Type objectType) => objectType.IsGenericType && objectType.GetGenericTypeDefinition() == type; + + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + JToken t = JToken.FromObject(value); + string id = t?.Value(idProperty); + + if (t.Type != JTokenType.Object || id.IsNullOrEmpty()) + { + writer.WriteNull(); + } + else + { + writer.WriteValue(id); + } + } + + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (!(reader.Value is string)) + { + return null; + } + + if (((string)reader.Value).IsNullOrEmpty()) + { + return null; + } + + return Activator.CreateInstance(objectType, new object[1] { reader.Value }); + } + } +} diff --git a/zero.Debug/Controllers/MigrationController.Blueprints.cs b/zero.Debug/Controllers/MigrationController.Blueprints.cs new file mode 100644 index 00000000..8c49719a --- /dev/null +++ b/zero.Debug/Controllers/MigrationController.Blueprints.cs @@ -0,0 +1,138 @@ +using Microsoft.AspNetCore.Mvc; +using Raven.Client.Documents; +using Raven.Client.Documents.Session; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using zero.Commerce.Entities; +using zero.Core; +using zero.Core.Api; +using zero.Core.Attributes; +using zero.Core.Entities; +using zero.Core.Extensions; +using zero.Core.Utils; + +namespace zero.Debug.Controllers +{ + public partial class MigrationController : Controller + { + + + [HttpGet] + public async Task CreateBlueprints() + { + IList apps; + + using (IAsyncDocumentSession session = Raven.OpenAsyncSession()) + { + apps = await session.Query().ToListAsync(); + } + + async Task Handle() where T : IZeroIdEntity + { + return await CreateBlueprintsForAll(apps); + } + + return Json(new + { + //Applications = await Handle(), + //Categories = await Handle(), + //Channels = await Handle(), + //Countries = await Handle(), + //Currencies = await Handle(), + //Customers = await Handle(), + //Languages = await Handle(), + //MailTemplates = await Handle(), + //Manufacturers = await Handle(), + //Media = await Handle(), + //MediaFolders = await Handle(), + //NumberTemplates = await Handle(), + //OrderDetailStates = await Handle(), + //Orders = await Handle(), + //Pages = await Handle(), + //ProductProperties = await Handle(), + //Products = await Handle(), + //RecycleBin = await Handle(), + ShippingOptions = await Handle(), + //SpaceContents = await Handle(), + //TaxRates = await Handle(), + //Translations = await Handle(), + //UserRoles = await Handle(), + //Users = await Handle(), + }); + } + + + async Task> CreateBlueprintsForAll(IList apps) where T : IZeroIdEntity + { + HashSet ids = new HashSet(); + IList items; + + using (IAsyncDocumentSession session = Raven.OpenAsyncSession()) + { + session.Advanced.MaxNumberOfRequestsPerSession = 10000; + + items = await session.Query().ToListAsync(); + + foreach (T item in items) + { + HashSet variants = ExtractBlueprints(item, apps); + + if (variants.Count > 0) + { + ids.Add("blueprint." + item.Id); + + foreach (IZeroIdEntity variant in variants) + { + ((IZeroEntity)variant).BlueprintId = item.Id; + + // find all references + List> references = ObjectTraverser.FindAttribute(variant); + + foreach (ObjectTraverser.Result reference in references) + { + object value = reference.Property.GetValue(reference.Parent, null); + + //if (String.IsNullOrWhiteSpace(id) || id.StartsWith(NEW_ID)) + //{ + // item.Property.SetValue(item.Parent, item.Item.Length.HasValue ? Raven.Id(item.Item.Length.Value) : Raven.Id()); + //} + } + + //await session.StoreAsync(variant); + ids.Add(variant.Id); + } + } + } + + //await session.SaveChangesAsync(); + } + + return ids; + } + + + HashSet ExtractBlueprints(T model, IList apps) where T : IZeroIdEntity + { + HashSet newModels = new HashSet(); + + IAppAwareEntity appAwareEntity = model as IAppAwareEntity; + IZeroEntity zeroEntity = model as IZeroEntity; + + if (appAwareEntity != null && appAwareEntity.AppId == "shared") + { + foreach (IApplication app in apps) + { + IZeroEntity newSpecificEntity = zeroEntity.Clone(); + newSpecificEntity.Id = null; + ((IAppAwareEntity)newSpecificEntity).AppId = app.Id; + + newModels.Add(newSpecificEntity); + } + } + + return newModels; + } + } +} \ No newline at end of file diff --git a/zero.Debug/Controllers/MigrationController.cs b/zero.Debug/Controllers/MigrationController.cs index 883cc1d4..abb29202 100644 --- a/zero.Debug/Controllers/MigrationController.cs +++ b/zero.Debug/Controllers/MigrationController.cs @@ -15,7 +15,7 @@ using zero.Core.Utils; namespace zero.Debug.Controllers { - public class MigrationController : Controller + public partial class MigrationController : Controller { private IDocumentStore Raven { get; set; } diff --git a/zero.Web.UI/App/components/tables/table-value.js b/zero.Web.UI/App/components/tables/table-value.js index ee82c086..bd6258a9 100644 --- a/zero.Web.UI/App/components/tables/table-value.js +++ b/zero.Web.UI/App/components/tables/table-value.js @@ -36,7 +36,7 @@ export default function (el, binding) if (!column.as || column.as === 'text' || column.as === 'html') { const hasFunc = typeof column.render === 'function'; - const hasSharedIndicator = column.shared === true && item.appId === zero.sharedAppId; + const hasSharedIndicator = column.shared === true && !!item.blueprintId; let html = hasFunc ? column.render(item, column) : value; let isHtml = column.as === 'html' || hasSharedIndicator; @@ -87,7 +87,7 @@ export default function (el, binding) // render global flag else if (column.as === 'shared') { - render(value === zero.sharedAppId ? '' : '', true); + render(!!item.blueprintId ? '' : '', true); } else { diff --git a/zero.Web/ZeroBuilder.cs b/zero.Web/ZeroBuilder.cs index 8a0fddc0..4dcf5a41 100644 --- a/zero.Web/ZeroBuilder.cs +++ b/zero.Web/ZeroBuilder.cs @@ -85,6 +85,7 @@ namespace zero.Web { // TODO this shall only be configurated for backoffice controllers x.SerializerSettings.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy())); + x.SerializerSettings.Converters.Add(new RefJsonConverter()); x.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; x.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;