diff --git a/zero.Core/Api/ApiBase.cs b/zero.Core/Api/ApiBase.cs deleted file mode 100644 index 6aca7896..00000000 --- a/zero.Core/Api/ApiBase.cs +++ /dev/null @@ -1,132 +0,0 @@ -using FluentValidation; -using FluentValidation.Results; -using Raven.Client.Documents; -using Raven.Client.Documents.Session; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using zero.Core.Attributes; -using zero.Core.Entities; -using zero.Core.Extensions; -using zero.Core.Utils; - -namespace zero.Core.Api -{ - public abstract class ApiBase - { - protected IDocumentStore Raven { get; set; } - - protected const string ASTERISK = "*"; - - protected const string NEW_ID = "new:"; - - - public ApiBase(IDocumentStore raven) - { - Raven = raven; - } - - - /// - /// Get an entity by Id - /// - protected async Task GetById(string id) - { - using (IAsyncDocumentSession session = Raven.OpenAsyncSession()) - { - // TODO store-aware? - return await session.LoadAsync(id); - } - } - - - - /// - /// Get an entity by Id and transform the result - /// - protected async Task> Save(T model, IValidator validator = null) where T : IZeroEntity - { - // check for alias - //if (model is IUrlAliasEntity) - //{ - // IUrlAliasEntity entity = operation.Model as IUrlAliasEntity; - // entity.Alias = entity.Alias?.ToLower().ToUrlSegment(); - //} - - // run validator - if (validator != null) - { - ValidationResult validation = await validator.ValidateAsync(model); - - if (!validation.IsValid) - { - return EntityResult.Fail(validation); - } - } - - // find all Raven Ids - List> ravenIds = ObjectTraverser.FindAttribute(model); - - // set unset Raven Ids - foreach (ObjectTraverser.Result item in ravenIds) - { - string id = item.Property.GetValue(item.Parent, null) as string; - 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()); - } - } - - // set default properties - if (model.Id.IsNullOrEmpty()) - { - model.CreatedDate = DateTimeOffset.Now; - - if (model is IAppAwareEntity) - { - (model as IAppAwareEntity).AppId = Constants.Database.SharedAppId; // TODO correct app id - } - - if (model is ILanguageAwareEntity) - { - (model as ILanguageAwareEntity).LanguageId = "zero.languages.1-A"; // TODO correct language id - } - } - - // update name alias - model.Alias = Safenames.Alias(model.Name); - - using (IAsyncDocumentSession session = Raven.OpenAsyncSession()) - { - await session.StoreAsync(model); - await session.SaveChangesAsync(); - } - - return EntityResult.Success(model); - } - - - /// - /// Deletes an entity by Id - /// - protected async Task> DeleteById(string id) - { - using (IAsyncDocumentSession session = Raven.OpenAsyncSession()) - { - T entity = await session.LoadAsync(id); - - // TODO || !Store.Has(entity)) - if (entity == null) - { - return EntityResult.Fail("@errors.ondelete.idnotfound"); - } - - session.Delete(entity); - - await session.SaveChangesAsync(); - - return EntityResult.Success(); - } - } - } -} \ No newline at end of file diff --git a/zero.Core/Api/BackofficeApi.cs b/zero.Core/Api/BackofficeApi.cs index d61e0868..7146eb90 100644 --- a/zero.Core/Api/BackofficeApi.cs +++ b/zero.Core/Api/BackofficeApi.cs @@ -111,8 +111,9 @@ namespace zero.Core.Api /// - public async Task> SaveModel(T model, IValidator validator = null, Action meta = null) where T : IZeroIdEntity + public async Task> SaveModel(T model, IValidator validator = null, Action meta = null) where T : IZeroEntity { + bool isCreate = false; // check for alias //if (model is IUrlAliasEntity) //{ @@ -122,7 +123,6 @@ namespace zero.Core.Api // get specifics IAppAwareEntity appAwareEntity = model as IAppAwareEntity; - IZeroEntity zeroEntity = model as IZeroEntity; // run validator if (validator != null) @@ -194,11 +194,10 @@ namespace zero.Core.Api // set default properties if (model.Id.IsNullOrEmpty()) { - if (zeroEntity != null) - { - zeroEntity.CreatedDate = DateTimeOffset.Now; - zeroEntity.CreatedById = userId; - } + isCreate = true; + + model.CreatedDate = DateTimeOffset.Now; + model.CreatedById = userId; if (model is ILanguageAwareEntity) { @@ -207,22 +206,27 @@ namespace zero.Core.Api } // update name alias and last modified - if (zeroEntity != null) - { - zeroEntity.Alias = Safenames.Alias(zeroEntity.Name); - zeroEntity.LastModifiedById = userId; - zeroEntity.LastModifiedDate = DateTimeOffset.Now; - zeroEntity.CreatedById ??= userId; - } + model.Alias = Safenames.Alias(model.Name); + model.LastModifiedById = userId; + model.LastModifiedDate = DateTimeOffset.Now; + model.CreatedById ??= userId; using IAsyncDocumentSession session = Raven.OpenAsyncSession(); - session.Advanced.WaitForIndexesAfterSaveChanges(throwOnTimeout: false); await session.StoreAsync(model); meta?.Invoke(session.Advanced.GetMetadataFor(model)); + if (isCreate) + { + await new SyncPseudo().OnCreate(session, model); + } + else + { + await new SyncPseudo().OnUpdate(session, model); + } + await session.SaveChangesAsync(); return EntityResult.Success(model); @@ -309,7 +313,7 @@ namespace zero.Core.Api /// /// Updates or creates an entity with an optional validator /// - Task> SaveModel(T model, IValidator validator = null, Action meta = null) where T : IZeroIdEntity; + Task> SaveModel(T model, IValidator validator = null, Action meta = null) where T : IZeroEntity; /// /// Deletes an entity by Id diff --git a/zero.Core/Entities/Country.cs b/zero.Core/Entities/Country.cs index e5d335be..0bf270dd 100644 --- a/zero.Core/Entities/Country.cs +++ b/zero.Core/Entities/Country.cs @@ -4,9 +4,6 @@ namespace zero.Core.Entities { public class Country : ZeroEntity, ICountry { - /// - public string AppId { get; set; } - /// public bool IsPreferred { get; set; } diff --git a/zero.Core/Entities/Language.cs b/zero.Core/Entities/Language.cs index 1f3ee8e7..336a7532 100644 --- a/zero.Core/Entities/Language.cs +++ b/zero.Core/Entities/Language.cs @@ -4,9 +4,6 @@ namespace zero.Core.Entities { public class Language : ZeroEntity, ILanguage { - /// - public string AppId { get; set; } - /// public string Code { get; set; } diff --git a/zero.Core/Entities/Media/Media.cs b/zero.Core/Entities/Media/Media.cs index 08cc077c..0df91dab 100644 --- a/zero.Core/Entities/Media/Media.cs +++ b/zero.Core/Entities/Media/Media.cs @@ -6,9 +6,6 @@ namespace zero.Core.Entities /// public class Media : ZeroEntity, IMedia { - /// - public string AppId { get; set; } - /// public string FileId { get; set; } diff --git a/zero.Core/Entities/Media/MediaFolder.cs b/zero.Core/Entities/Media/MediaFolder.cs index fb8c2678..70bb5b87 100644 --- a/zero.Core/Entities/Media/MediaFolder.cs +++ b/zero.Core/Entities/Media/MediaFolder.cs @@ -5,9 +5,6 @@ namespace zero.Core.Entities /// public class MediaFolder : ZeroEntity, IMediaFolder { - /// - public string AppId { get; set; } - /// public string ParentId { get; set; } } diff --git a/zero.Core/Entities/Pages/Page.cs b/zero.Core/Entities/Pages/Page.cs index 52ce6b0a..68a20038 100644 --- a/zero.Core/Entities/Pages/Page.cs +++ b/zero.Core/Entities/Pages/Page.cs @@ -16,9 +16,6 @@ namespace zero.Core.Entities /// public string PageTypeAlias { get; set; } - /// - public string AppId { get; set; } - /// public DateTimeOffset? PublishDate { get; set; } diff --git a/zero.Core/Entities/Preview.cs b/zero.Core/Entities/Preview.cs index c6342f71..d0255f72 100644 --- a/zero.Core/Entities/Preview.cs +++ b/zero.Core/Entities/Preview.cs @@ -4,9 +4,6 @@ namespace zero.Core.Entities { public class Preview : ZeroEntity, IPreview { - /// - public string AppId { get; set; } - /// public string OriginalId { get; set; } diff --git a/zero.Core/Entities/RecycleBin/RecycledEntity.cs b/zero.Core/Entities/RecycleBin/RecycledEntity.cs index 385f25c1..c026d6d4 100644 --- a/zero.Core/Entities/RecycleBin/RecycledEntity.cs +++ b/zero.Core/Entities/RecycleBin/RecycledEntity.cs @@ -4,9 +4,6 @@ namespace zero.Core.Entities { public class RecycledEntity : ZeroEntity, IRecycledEntity { - /// - public string AppId { get; set; } - /// public string OriginalId { get; set; } diff --git a/zero.Core/Entities/Spaces/SpaceContent.cs b/zero.Core/Entities/Spaces/SpaceContent.cs index 1fcad4df..a6838da8 100644 --- a/zero.Core/Entities/Spaces/SpaceContent.cs +++ b/zero.Core/Entities/Spaces/SpaceContent.cs @@ -10,9 +10,6 @@ namespace zero.Core.Entities { /// public string SpaceAlias { get; set; } - - /// - public string AppId { get; set; } } diff --git a/zero.Core/Entities/Translation.cs b/zero.Core/Entities/Translation.cs index 717888d6..51428c7d 100644 --- a/zero.Core/Entities/Translation.cs +++ b/zero.Core/Entities/Translation.cs @@ -4,9 +4,6 @@ namespace zero.Core.Entities { public class Translation : ZeroEntity, ITranslation { - /// - public string AppId { get; set; } - /// public string LanguageId { get; set; } diff --git a/zero.Core/Entities/User/User.cs b/zero.Core/Entities/User/User.cs index 118ffb0e..10d2e14e 100644 --- a/zero.Core/Entities/User/User.cs +++ b/zero.Core/Entities/User/User.cs @@ -6,9 +6,6 @@ namespace zero.Core.Entities { public class User : ZeroEntity, IUser, IZeroDbConventions { - /// - public string AppId { get; set; } - /// public string CurrentAppId { get; set; } diff --git a/zero.Core/Entities/User/UserRole.cs b/zero.Core/Entities/User/UserRole.cs index ad768aeb..383415a8 100644 --- a/zero.Core/Entities/User/UserRole.cs +++ b/zero.Core/Entities/User/UserRole.cs @@ -5,9 +5,6 @@ namespace zero.Core.Entities { public class UserRole : ZeroEntity, IUserRole, IZeroDbConventions { - /// - public string AppId { get; set; } - /// public string Description { get; set; } diff --git a/zero.Core/Entities/ZeroEntity.cs b/zero.Core/Entities/ZeroEntity.cs index 29a4a6ce..63ba2add 100644 --- a/zero.Core/Entities/ZeroEntity.cs +++ b/zero.Core/Entities/ZeroEntity.cs @@ -9,6 +9,9 @@ namespace zero.Core.Entities /// public string Id { get; set; } + /// + public string AppId { get; set; } + /// public string Name { get; set; } @@ -38,7 +41,7 @@ namespace zero.Core.Entities } - public interface IZeroEntity : IZeroIdEntity + public interface IZeroEntity : IZeroIdEntity, IAppAwareEntity { /// /// Full name of the entity diff --git a/zero.Core/Extensions/ValidatorExtensions.cs b/zero.Core/Extensions/ValidatorExtensions.cs index 615cc000..7b1509bd 100644 --- a/zero.Core/Extensions/ValidatorExtensions.cs +++ b/zero.Core/Extensions/ValidatorExtensions.cs @@ -101,7 +101,7 @@ namespace zero.Core.Extensions /// /// Check if this value is unique within a collection /// - public static IRuleBuilderOptions Unique(this IRuleBuilder ruleBuilder, IBackofficeStore store) where T : IZeroIdEntity + public static IRuleBuilderOptions Unique(this IRuleBuilder ruleBuilder, IBackofficeStore store) where T : IZeroEntity { return ruleBuilder.MustAsync(async (entity, value, context, cancellation) => { @@ -110,7 +110,7 @@ namespace zero.Core.Extensions using IAsyncDocumentSession session = store.Raven.OpenAsyncSession(); bool any = await session.Advanced.AsyncDocumentQuery() - .Scope(store.AppContext.AppId, includeShared) + .Scope(entity.AppId, false) .WhereNotEquals(nameof(IZeroIdEntity.Id), entity.Id) .WhereEquals(context.Rule.PropertyName.ToPascalCaseId(), value) .AnyAsync(cancellation); @@ -123,7 +123,7 @@ namespace zero.Core.Extensions /// /// Check if this value is at least set once to the expected value within a collection /// - public static IRuleBuilderOptions ExpectAnyUnique(this IRuleBuilder ruleBuilder, IBackofficeStore store, TProperty expectedValue) where T : IZeroIdEntity + public static IRuleBuilderOptions ExpectAnyUnique(this IRuleBuilder ruleBuilder, IBackofficeStore store, TProperty expectedValue) where T : IZeroEntity { return ruleBuilder.MustAsync(async (entity, value, context, cancellation) => { @@ -132,7 +132,7 @@ namespace zero.Core.Extensions using IAsyncDocumentSession session = store.Raven.OpenAsyncSession(); return await session.Advanced.AsyncDocumentQuery() - .Scope(store.AppContext.AppId, includeShared) + .Scope(entity.AppId, includeShared) .WhereNotEquals(nameof(IZeroIdEntity.Id), entity.Id) .WhereEquals(context.Rule.PropertyName.ToPascalCaseId(), expectedValue) .AnyAsync(cancellation); @@ -143,7 +143,7 @@ namespace zero.Core.Extensions /// /// Check if this reference exists and is an entity which can be referenced (appId = shared for shareable entities or appId = current) /// - public static IRuleBuilderOptions Exists(this IRuleBuilder ruleBuilder, IBackofficeStore store) where T : IZeroIdEntity + public static IRuleBuilderOptions Exists(this IRuleBuilder ruleBuilder, IBackofficeStore store) where T : IZeroEntity { return ruleBuilder.Exists(store); } @@ -152,7 +152,7 @@ namespace zero.Core.Extensions /// /// Check if this reference exists and is an entity which can be referenced (appId = shared for shareable entities or appId = current) /// - public static IRuleBuilderOptions Exists(this IRuleBuilder ruleBuilder, IBackofficeStore store) where T : IZeroIdEntity where TReference : IZeroIdEntity + public static IRuleBuilderOptions Exists(this IRuleBuilder ruleBuilder, IBackofficeStore store) where T : IZeroEntity where TReference : IZeroEntity { return ruleBuilder.MustAsync(async (entity, id, context, cancellation) => { @@ -168,7 +168,7 @@ namespace zero.Core.Extensions if (typeof(IAppAwareEntity).IsAssignableFrom(typeof(TReference))) { - return model != null && ((IAppAwareEntity)model).InScope(store.AppContext.AppId); + return model != null && ((IAppAwareEntity)model).InScope(entity.AppId); } return model != null; diff --git a/zero.Core/SyncPseudo.cs b/zero.Core/SyncPseudo.cs new file mode 100644 index 00000000..f56a72a1 --- /dev/null +++ b/zero.Core/SyncPseudo.cs @@ -0,0 +1,80 @@ +using Raven.Client.Documents; +using Raven.Client.Documents.Session; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using zero.Core; +using zero.Core.Entities; +using zero.Core.Extensions; + +namespace zero.Core +{ + public class SyncPseudo + { + public SyncPseudo() + { + + } + + + public async Task OnCreate(IAsyncDocumentSession session, T model) where T : IZeroEntity + { + await OnSave(session, model, true); + } + + + public async Task OnUpdate(IAsyncDocumentSession session, T model) where T : IZeroEntity + { + await OnSave(session, model, false); + } + + + async Task OnSave(IAsyncDocumentSession session, T model, bool isCreate = false) where T : IZeroEntity + { + string sharedId = Constants.Database.SharedAppId; + + if (model.AppId != sharedId) + { + return; + } + + IList inheritedModels = new List(); + IList apps = await session.Query().Where(x => x.Id != sharedId).ToListAsync(); + + if (!isCreate) + { + string id = model.Id; + inheritedModels = await session.Query().Where(x => x.BlueprintId == id).ToListAsync(); + } + + foreach (IApplication app in apps) + { + T inheritedModel = inheritedModels.FirstOrDefault(x => x.AppId == app.Id); + + // the model does not yet exist in the app, so we need to create it + if (inheritedModel == null) + { + inheritedModel = model.Clone(); + inheritedModel.Id = null; + } + // we need to override allowed properties in the inherited model + else + { + // TODO correctly override properties + if (inheritedModel is ITranslation) + { + ((ITranslation)inheritedModel).Key = ((ITranslation)model).Key; + ((ITranslation)inheritedModel).Value = ((ITranslation)model).Value; + ((ITranslation)inheritedModel).LastModifiedById = ((ITranslation)model).LastModifiedById; + ((ITranslation)inheritedModel).LastModifiedDate = ((ITranslation)model).LastModifiedDate; + } + } + + inheritedModel.AppId = app.Id; + inheritedModel.BlueprintId = model.Id; + + await session.StoreAsync(inheritedModel); + } + } + } +} diff --git a/zero.Web.UI/App/components/tables/table-value.js b/zero.Web.UI/App/components/tables/table-value.js index bd6258a9..4c3daab6 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.blueprintId; + const hasSharedIndicator = column.shared === true && item.appId === zero.sharedAppId; 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(!!item.blueprintId ? '' : '', true); + render(item.appId === zero.sharedAppId ? '' : '', true); } else { diff --git a/zero.Web/Defaults/ZeroBackofficePlugin.cs b/zero.Web/Defaults/ZeroBackofficePlugin.cs index 23db6821..0cad09a5 100644 --- a/zero.Web/Defaults/ZeroBackofficePlugin.cs +++ b/zero.Web/Defaults/ZeroBackofficePlugin.cs @@ -75,7 +75,7 @@ namespace zero.Web.Defaults zero.Sections.Add(); zero.Settings.AddGroup(); - zero.Settings.AddGroup(); + //zero.Settings.AddGroup(); zero.Mapper.Add(); zero.Mapper.Add();