start sharing of entities

This commit is contained in:
2020-10-05 15:10:40 +02:00
parent 91f5c6579f
commit b1aaa14c2a
18 changed files with 114 additions and 192 deletions
-132
View File
@@ -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;
}
/// <summary>
/// Get an entity by Id
/// </summary>
protected async Task<T> GetById<T>(string id)
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
// TODO store-aware?
return await session.LoadAsync<T>(id);
}
}
/// <summary>
/// Get an entity by Id and transform the result
/// </summary>
protected async Task<EntityResult<T>> Save<T>(T model, IValidator<T> 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<T>.Fail(validation);
}
}
// find all Raven Ids
List<ObjectTraverser.Result<GenerateIdAttribute>> ravenIds = ObjectTraverser.FindAttribute<GenerateIdAttribute>(model);
// set unset Raven Ids
foreach (ObjectTraverser.Result<GenerateIdAttribute> 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<T>.Success(model);
}
/// <summary>
/// Deletes an entity by Id
/// </summary>
protected async Task<EntityResult<T>> DeleteById<T>(string id)
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
T entity = await session.LoadAsync<T>(id);
// TODO || !Store.Has(entity))
if (entity == null)
{
return EntityResult<T>.Fail("@errors.ondelete.idnotfound");
}
session.Delete(entity);
await session.SaveChangesAsync();
return EntityResult<T>.Success();
}
}
}
}
+20 -16
View File
@@ -111,8 +111,9 @@ namespace zero.Core.Api
/// <inheritdoc />
public async Task<EntityResult<T>> SaveModel<T>(T model, IValidator<T> validator = null, Action<IMetadataDictionary> meta = null) where T : IZeroIdEntity
public async Task<EntityResult<T>> SaveModel<T>(T model, IValidator<T> validator = null, Action<IMetadataDictionary> 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<T>.Success(model);
@@ -309,7 +313,7 @@ namespace zero.Core.Api
/// <summary>
/// Updates or creates an entity with an optional validator
/// </summary>
Task<EntityResult<T>> SaveModel<T>(T model, IValidator<T> validator = null, Action<IMetadataDictionary> meta = null) where T : IZeroIdEntity;
Task<EntityResult<T>> SaveModel<T>(T model, IValidator<T> validator = null, Action<IMetadataDictionary> meta = null) where T : IZeroEntity;
/// <summary>
/// Deletes an entity by Id
-3
View File
@@ -4,9 +4,6 @@ namespace zero.Core.Entities
{
public class Country : ZeroEntity, ICountry
{
/// <inheritdoc />
public string AppId { get; set; }
/// <inheritdoc />
public bool IsPreferred { get; set; }
-3
View File
@@ -4,9 +4,6 @@ namespace zero.Core.Entities
{
public class Language : ZeroEntity, ILanguage
{
/// <inheritdoc />
public string AppId { get; set; }
/// <inheritdoc />
public string Code { get; set; }
-3
View File
@@ -6,9 +6,6 @@ namespace zero.Core.Entities
/// <inheritdoc />
public class Media : ZeroEntity, IMedia
{
/// <inheritdoc />
public string AppId { get; set; }
/// <inheritdoc />
public string FileId { get; set; }
-3
View File
@@ -5,9 +5,6 @@ namespace zero.Core.Entities
/// <inheritdoc />
public class MediaFolder : ZeroEntity, IMediaFolder
{
/// <inheritdoc />
public string AppId { get; set; }
/// <inheritdoc />
public string ParentId { get; set; }
}
-3
View File
@@ -16,9 +16,6 @@ namespace zero.Core.Entities
/// <inheritdoc />
public string PageTypeAlias { get; set; }
/// <inheritdoc />
public string AppId { get; set; }
/// <inheritdoc />
public DateTimeOffset? PublishDate { get; set; }
-3
View File
@@ -4,9 +4,6 @@ namespace zero.Core.Entities
{
public class Preview : ZeroEntity, IPreview
{
/// <inheritdoc />
public string AppId { get; set; }
/// <inheritdoc />
public string OriginalId { get; set; }
@@ -4,9 +4,6 @@ namespace zero.Core.Entities
{
public class RecycledEntity : ZeroEntity, IRecycledEntity
{
/// <inheritdoc />
public string AppId { get; set; }
/// <inheritdoc />
public string OriginalId { get; set; }
@@ -10,9 +10,6 @@ namespace zero.Core.Entities
{
/// <inheritdoc />
public string SpaceAlias { get; set; }
/// <inheritdoc />
public string AppId { get; set; }
}
-3
View File
@@ -4,9 +4,6 @@ namespace zero.Core.Entities
{
public class Translation : ZeroEntity, ITranslation
{
/// <inheritdoc />
public string AppId { get; set; }
/// <inheritdoc />
public string LanguageId { get; set; }
-3
View File
@@ -6,9 +6,6 @@ namespace zero.Core.Entities
{
public class User : ZeroEntity, IUser, IZeroDbConventions
{
/// <inheritdoc />
public string AppId { get; set; }
/// <inheritdoc />
public string CurrentAppId { get; set; }
-3
View File
@@ -5,9 +5,6 @@ namespace zero.Core.Entities
{
public class UserRole : ZeroEntity, IUserRole, IZeroDbConventions
{
/// <inheritdoc />
public string AppId { get; set; }
/// <inheritdoc/>
public string Description { get; set; }
+4 -1
View File
@@ -9,6 +9,9 @@ namespace zero.Core.Entities
/// <inheritdoc/>
public string Id { get; set; }
/// <inheritdoc/>
public string AppId { get; set; }
/// <inheritdoc/>
public string Name { get; set; }
@@ -38,7 +41,7 @@ namespace zero.Core.Entities
}
public interface IZeroEntity : IZeroIdEntity
public interface IZeroEntity : IZeroIdEntity, IAppAwareEntity
{
/// <summary>
/// Full name of the entity
+7 -7
View File
@@ -101,7 +101,7 @@ namespace zero.Core.Extensions
/// <summary>
/// Check if this value is unique within a collection
/// </summary>
public static IRuleBuilderOptions<T, TProperty> Unique<T, TProperty>(this IRuleBuilder<T, TProperty> ruleBuilder, IBackofficeStore store) where T : IZeroIdEntity
public static IRuleBuilderOptions<T, TProperty> Unique<T, TProperty>(this IRuleBuilder<T, TProperty> 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<T>()
.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
/// <summary>
/// Check if this value is at least set once to the expected value within a collection
/// </summary>
public static IRuleBuilderOptions<T, TProperty> ExpectAnyUnique<T, TProperty>(this IRuleBuilder<T, TProperty> ruleBuilder, IBackofficeStore store, TProperty expectedValue) where T : IZeroIdEntity
public static IRuleBuilderOptions<T, TProperty> ExpectAnyUnique<T, TProperty>(this IRuleBuilder<T, TProperty> 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<T>()
.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
/// <summary>
/// Check if this reference exists and is an entity which can be referenced (appId = shared for shareable entities or appId = current)
/// </summary>
public static IRuleBuilderOptions<T, string> Exists<T>(this IRuleBuilder<T, string> ruleBuilder, IBackofficeStore store) where T : IZeroIdEntity
public static IRuleBuilderOptions<T, string> Exists<T>(this IRuleBuilder<T, string> ruleBuilder, IBackofficeStore store) where T : IZeroEntity
{
return ruleBuilder.Exists<T, T>(store);
}
@@ -152,7 +152,7 @@ namespace zero.Core.Extensions
/// <summary>
/// Check if this reference exists and is an entity which can be referenced (appId = shared for shareable entities or appId = current)
/// </summary>
public static IRuleBuilderOptions<T, string> Exists<T, TReference>(this IRuleBuilder<T, string> ruleBuilder, IBackofficeStore store) where T : IZeroIdEntity where TReference : IZeroIdEntity
public static IRuleBuilderOptions<T, string> Exists<T, TReference>(this IRuleBuilder<T, string> 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;
+80
View File
@@ -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<T>(IAsyncDocumentSession session, T model) where T : IZeroEntity
{
await OnSave<T>(session, model, true);
}
public async Task OnUpdate<T>(IAsyncDocumentSession session, T model) where T : IZeroEntity
{
await OnSave<T>(session, model, false);
}
async Task OnSave<T>(IAsyncDocumentSession session, T model, bool isCreate = false) where T : IZeroEntity
{
string sharedId = Constants.Database.SharedAppId;
if (model.AppId != sharedId)
{
return;
}
IList<T> inheritedModels = new List<T>();
IList<IApplication> apps = await session.Query<IApplication>().Where(x => x.Id != sharedId).ToListAsync();
if (!isCreate)
{
string id = model.Id;
inheritedModels = await session.Query<T>().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);
}
}
}
}
@@ -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 ? '<i class="ui-table-field-shared fth-radio"></i>' : '', true);
render(item.appId === zero.sharedAppId ? '<i class="ui-table-field-shared fth-radio"></i>' : '', true);
}
else
{
+1 -1
View File
@@ -75,7 +75,7 @@ namespace zero.Web.Defaults
zero.Sections.Add<SettingsSection>();
zero.Settings.AddGroup<SystemSettings>();
zero.Settings.AddGroup<PluginSettings>();
//zero.Settings.AddGroup<PluginSettings>();
zero.Mapper.Add<UserMapperConfig>();
zero.Mapper.Add<CountryMapperConfig>();