first draft of blueprint synchronization works
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using zero.Core.Entities;
|
||||
|
||||
namespace zero.Core.Blueprints
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class Blueprint<T> : Blueprint where T : ZeroEntity, new()
|
||||
{
|
||||
public List<BlueprintField<T>> Fields { get; private set; } = new();
|
||||
|
||||
|
||||
public Blueprint() : base(typeof(T))
|
||||
{
|
||||
Sync(x => x.Name);
|
||||
Sync(x => x.Alias);
|
||||
Sync(x => x.Sort);
|
||||
Sync(x => x.IsActive);
|
||||
Sync(x => x.LanguageId);
|
||||
Sync(x => x.Key);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Merges blueprint data into a model based on the configuration
|
||||
/// </summary>
|
||||
public virtual T Apply(T blueprint, T model)
|
||||
{
|
||||
// reset blueprint options for model in case no blueprint was found
|
||||
if (blueprint == null)
|
||||
{
|
||||
model.Blueprint = null;
|
||||
return model;
|
||||
}
|
||||
|
||||
foreach (BlueprintField<T> field in Fields)
|
||||
{
|
||||
// do not sync disabled fields
|
||||
if (!field.IsSynced && !field.IsLocked)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// desynced fields are not synced with the blueprint
|
||||
// (but only if they are not locked)
|
||||
if (model.Blueprint.Desync.Contains(field.FieldName, StringComparer) && !field.IsLocked)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// apply new value to model property
|
||||
field.Apply(blueprint, model);
|
||||
}
|
||||
|
||||
ApplyDefaults(blueprint, model);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ZeroEntity Apply(ZeroEntity blueprint, ZeroEntity model) => Apply(blueprint as T, model as T);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates meta data for an entity which should always be synchronized
|
||||
/// </summary>
|
||||
protected virtual void ApplyDefaults(T blueprint, T model)
|
||||
{
|
||||
model.Hash = blueprint.Hash;
|
||||
model.CreatedById = blueprint.CreatedById;
|
||||
model.CreatedDate = blueprint.CreatedDate;
|
||||
model.LastModifiedById = blueprint.LastModifiedById;
|
||||
model.LastModifiedDate = blueprint.LastModifiedDate;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize a field
|
||||
/// </summary>
|
||||
public BlueprintField<T> Sync(Expression<Func<T, object>> selector)
|
||||
{
|
||||
BlueprintField<T> field = Field(selector);
|
||||
field.IsSynced = true;
|
||||
return field;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Lock a field so it always get synchronized no and cannot be changed
|
||||
/// </summary>
|
||||
public BlueprintField<T> Lock(Expression<Func<T, object>> selector)
|
||||
{
|
||||
BlueprintField<T> field = Field(selector);
|
||||
field.IsLocked = true;
|
||||
return field;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Remove a field from synchronisation
|
||||
/// </summary>
|
||||
public void Remove(Expression<Func<T, object>> selector)
|
||||
{
|
||||
BlueprintField<T> field = new(selector);
|
||||
Fields.RemoveAll(x => x.FieldName.Equals(field.FieldName, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get existing field or create a new one
|
||||
/// </summary>
|
||||
protected BlueprintField<T> Field(Expression<Func<T, object>> selector)
|
||||
{
|
||||
BlueprintField<T> tempField = new(selector);
|
||||
BlueprintField<T> storedField = Fields.FirstOrDefault(x => x.FieldName.Equals(tempField.FieldName, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (storedField != null)
|
||||
{
|
||||
return storedField;
|
||||
}
|
||||
|
||||
Fields.Add(tempField);
|
||||
return tempField;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public abstract class Blueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of the associated entity
|
||||
/// </summary>
|
||||
public Type ContentType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// String comparer for property name comparison
|
||||
/// </summary>
|
||||
protected readonly StringComparer StringComparer = StringComparer.InvariantCultureIgnoreCase;
|
||||
|
||||
|
||||
public Blueprint(Type type)
|
||||
{
|
||||
ContentType = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges blueprint data into a model based on the configuration
|
||||
/// </summary>
|
||||
public abstract ZeroEntity Apply(ZeroEntity blueprint, ZeroEntity model);
|
||||
}
|
||||
|
||||
|
||||
public class DefaultBlueprint<T> : Blueprint<T> where T : ZeroEntity, new()
|
||||
{
|
||||
public DefaultBlueprint(Action<Blueprint<T>> expression)
|
||||
{
|
||||
expression(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Extensions;
|
||||
|
||||
namespace zero.Core.Blueprints
|
||||
{
|
||||
public class BlueprintField<T> where T : ZeroEntity
|
||||
{
|
||||
public Expression<Func<T, object>> Expression { get; private set; }
|
||||
|
||||
public PropertyInfo PropertyInfo { get; private set; }
|
||||
|
||||
public string FieldName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// This field is synced as long as it's not desynced for a certain entity
|
||||
/// </summary>
|
||||
public bool IsSynced { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This field is synced and cannot be desynced
|
||||
/// </summary>
|
||||
public bool IsLocked { get; set; }
|
||||
|
||||
Action<T, T> _applyHook { get; set; }
|
||||
|
||||
Func<T, object> _selectorFunc = null;
|
||||
|
||||
|
||||
internal BlueprintField(Expression<Func<T, object>> selector)
|
||||
{
|
||||
Expression = selector;
|
||||
FieldName = GetPropertyPath(selector, out MemberExpression member);
|
||||
PropertyInfo = member?.Member as PropertyInfo;
|
||||
|
||||
if (PropertyInfo == null)
|
||||
{
|
||||
throw new ArgumentException("selector parameter has to be a property selector");
|
||||
}
|
||||
|
||||
if (FieldName.IsNullOrEmpty())
|
||||
{
|
||||
throw new Exception("Could not find a matching field name");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual void Apply(T blueprint, T model)
|
||||
{
|
||||
if (_applyHook != null)
|
||||
{
|
||||
_applyHook(blueprint, model);
|
||||
return;
|
||||
}
|
||||
|
||||
_selectorFunc ??= Expression.Compile();
|
||||
PropertyInfo.SetValue(model, _selectorFunc(blueprint));
|
||||
}
|
||||
|
||||
|
||||
public virtual void OnUpdate(Action<T, T> onUpdate)
|
||||
{
|
||||
_applyHook = onUpdate;
|
||||
}
|
||||
|
||||
|
||||
static string GetPropertyPath(Expression expr, out MemberExpression member)
|
||||
{
|
||||
StringBuilder path = new();
|
||||
MemberExpression memberExpression = GetMemberExpression(expr);
|
||||
do
|
||||
{
|
||||
member = memberExpression;
|
||||
|
||||
if (path.Length > 0)
|
||||
{
|
||||
path.Insert(0, ".");
|
||||
}
|
||||
path.Insert(0, memberExpression.Member.Name);
|
||||
memberExpression = GetMemberExpression(memberExpression.Expression);
|
||||
}
|
||||
while (memberExpression != null);
|
||||
|
||||
return path.ToString();
|
||||
}
|
||||
|
||||
|
||||
static MemberExpression GetMemberExpression(Expression expression)
|
||||
{
|
||||
if (expression is MemberExpression)
|
||||
{
|
||||
return (MemberExpression)expression;
|
||||
}
|
||||
else if (expression is LambdaExpression)
|
||||
{
|
||||
var lambdaExpression = expression as LambdaExpression;
|
||||
if (lambdaExpression.Body is MemberExpression)
|
||||
{
|
||||
return (MemberExpression)lambdaExpression.Body;
|
||||
}
|
||||
else if (lambdaExpression.Body is UnaryExpression)
|
||||
{
|
||||
return ((MemberExpression)((UnaryExpression)lambdaExpression.Body).Operand);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Raven.Client.Documents;
|
||||
using Raven.Client.Documents.Session;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Blueprints;
|
||||
using zero.Core.Collections;
|
||||
using zero.Core.Database;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Extensions;
|
||||
|
||||
namespace zero.Core.Routing
|
||||
{
|
||||
public class BlueprintInterceptor : CollectionInterceptor
|
||||
{
|
||||
protected IZeroContext Context { get; set; }
|
||||
|
||||
protected IZeroStore Store { get; set; }
|
||||
|
||||
protected ILogger<BlueprintInterceptor> Logger { get; set; }
|
||||
|
||||
protected IBlueprintService BlueprintService { get; set; }
|
||||
|
||||
IList<Application> Apps { get; set; }
|
||||
|
||||
|
||||
public BlueprintInterceptor(IZeroContext context, IZeroStore store, ILogger<BlueprintInterceptor> logger, IBlueprintService blueprintService)
|
||||
{
|
||||
Context = context;
|
||||
Store = store;
|
||||
Logger = logger;
|
||||
BlueprintService = blueprintService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRun(Parameters args)
|
||||
{
|
||||
// we do only update children if we operate on the shared database
|
||||
return (args.Session as AsyncDocumentSession)?.DatabaseName == Context.Options.Raven.Database;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Saved(SaveParameters args)
|
||||
{
|
||||
if (!BlueprintService.TryGetBlueprint(args.Model, out Blueprint blueprint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (Application app in await GetApplications())
|
||||
{
|
||||
using ZeroContextScope scope = Context.CreateScope(app);
|
||||
IZeroDocumentSession session = scope.Store.Session(scope.Database);
|
||||
|
||||
ZeroEntity child = default;
|
||||
|
||||
if (args.IsUpdate)
|
||||
{
|
||||
child = await session.LoadAsync<ZeroEntity>(args.Model.Id);
|
||||
}
|
||||
|
||||
if (child == null)
|
||||
{
|
||||
child = args.Model.Clone();
|
||||
child.Blueprint = new() { Id = args.Model.Id };
|
||||
}
|
||||
else
|
||||
{
|
||||
blueprint.Apply(args.Model, child);
|
||||
}
|
||||
|
||||
count += 1;
|
||||
|
||||
// now we have to store the child
|
||||
// but this will not work with the session as we need to run the scoped interceptors,
|
||||
// therefore we need access to the collection
|
||||
await session.StoreAsync(child);
|
||||
await session.SaveChangesAsync();
|
||||
}
|
||||
|
||||
Logger.LogDebug("Blueprint: Synced {count} children for {name} ({id})", count, args.Model.Name, args.Id);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Deleted(DeleteParameters args)
|
||||
{
|
||||
if (!BlueprintService.TryGetBlueprint(args.Model, out Blueprint blueprint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (Application app in await GetApplications())
|
||||
{
|
||||
using ZeroContextScope scope = Context.CreateScope(app);
|
||||
IZeroDocumentSession session = scope.Store.Session(scope.Database);
|
||||
|
||||
count += 1;
|
||||
|
||||
// now we have to delete the child
|
||||
// but this will not work with the session as we need to run the scoped interceptors,
|
||||
// therefore we need access to the collection
|
||||
session.Delete(args.Model.Id);
|
||||
await session.SaveChangesAsync();
|
||||
}
|
||||
|
||||
Logger.LogDebug("Blueprint: Deleted {count} children for {name} ({id})", count, args.Model.Name, args.Id);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get all applications to choose from
|
||||
/// </summary>
|
||||
async Task<IList<Application>> GetApplications()
|
||||
{
|
||||
if (Apps != null)
|
||||
{
|
||||
return Apps;
|
||||
}
|
||||
|
||||
IAsyncDocumentSession session = Store.Session(global: true);
|
||||
Apps = await session.Query<Application>().ToListAsync();
|
||||
return Apps;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
//public override async Task Saved(SaveParameters args)
|
||||
//{
|
||||
|
||||
// //Logger.LogInformation("Route updates completed (+{added}/~{updated}/-{removed}) for {model} (id: {id})", countRoutes - countUpdatedRoutes, countUpdatedRoutes, obsoleteRoutes.Count, args.Model.Name, args.Model.Id);
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Options;
|
||||
|
||||
namespace zero.Core.Blueprints
|
||||
{
|
||||
public class BlueprintService : IBlueprintService
|
||||
{
|
||||
protected IZeroOptions Options { get; set; }
|
||||
|
||||
protected IReadOnlyCollection<Blueprint> Blueprints { get; set; }
|
||||
|
||||
|
||||
public BlueprintService(IZeroOptions options)
|
||||
{
|
||||
Options = options;
|
||||
Blueprints = Options.Blueprints.GetAllItems();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled<T>(T model) where T : ZeroEntity => IsEnabled(model.GetType());
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled<T>() where T : ZeroEntity => IsEnabled(typeof(T));
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled(Type type) => Blueprints.Any(x => x.ContentType.IsAssignableFrom(type));
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetBlueprint<T>(T model, out Blueprint blueprint) where T : ZeroEntity => TryGetBlueprint(model.GetType(), out blueprint);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetBlueprint<T>(out Blueprint blueprint) where T : ZeroEntity => TryGetBlueprint(typeof(T), out blueprint);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetBlueprint(Type type, out Blueprint blueprint)
|
||||
{
|
||||
blueprint = Blueprints.FirstOrDefault(x => x.ContentType.IsAssignableFrom(type));
|
||||
return blueprint != null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface IBlueprintService
|
||||
{
|
||||
/// <summary>
|
||||
/// Check whether blueprinting functionality is enabled for a certain entity
|
||||
/// </summary>
|
||||
bool IsEnabled<T>() where T : ZeroEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Check whether blueprinting functionality is enabled for a certain entity
|
||||
/// </summary>
|
||||
bool IsEnabled<T>(T model) where T : ZeroEntity;
|
||||
|
||||
bool IsEnabled(Type type);
|
||||
|
||||
bool TryGetBlueprint<T>(T model, out Blueprint blueprint) where T : ZeroEntity;
|
||||
|
||||
bool TryGetBlueprint<T>(out Blueprint blueprint) where T : ZeroEntity;
|
||||
|
||||
bool TryGetBlueprint(Type type, out Blueprint blueprint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using zero.Core.Entities;
|
||||
|
||||
namespace zero.Core.Blueprints
|
||||
{
|
||||
public class CountryBlueprint : Blueprint<Country>
|
||||
{
|
||||
public CountryBlueprint()
|
||||
{
|
||||
Sync(x => x.Code);
|
||||
Sync(x => x.IsPreferred);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Entities;
|
||||
|
||||
namespace zero.Core.Collections
|
||||
{
|
||||
public abstract partial class CollectionInterceptor<T> : ICollectionInterceptor<T> where T : ZeroEntity
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public virtual bool CanRun(Parameters args) => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Task<InterceptorResult<T>> Creating(CreateParameters args) => Task.FromResult<InterceptorResult<T>>(default);
|
||||
|
||||
@@ -38,12 +40,21 @@ namespace zero.Core.Collections
|
||||
}
|
||||
|
||||
|
||||
public abstract partial class CollectionInterceptor : CollectionInterceptor<ZeroEntity>, ICollectionInterceptor { }
|
||||
public abstract partial class CollectionInterceptor : CollectionInterceptor<ZeroEntity>, ICollectionInterceptor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanRun(Parameters args) => base.CanRun(args);
|
||||
}
|
||||
|
||||
public interface ICollectionInterceptor : ICollectionInterceptor<ZeroEntity> { }
|
||||
|
||||
public interface ICollectionInterceptor<T> where T : ZeroEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether any of the interceptor methods is allowed to run based on the parameters
|
||||
/// </summary>
|
||||
bool CanRun(CollectionInterceptor<T>.Parameters args);
|
||||
|
||||
/// <summary>
|
||||
/// Called after an entity has been stored but before the session has saved its changes
|
||||
/// </summary>
|
||||
|
||||
@@ -61,6 +61,11 @@ namespace zero.Core.Collections
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!interceptor.CanRun(instruction.Parameters))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (func == default)
|
||||
{
|
||||
func = expression.Compile();
|
||||
@@ -113,6 +118,11 @@ namespace zero.Core.Collections
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!interceptor.CanRun(instruction.Parameters))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
InterceptorResult<T> beforeResult = instruction?.Results.FirstOrDefault(res => res.InterceptorHash == registration.Hash);
|
||||
|
||||
if (func == default)
|
||||
|
||||
@@ -31,6 +31,11 @@ namespace zero.Core.Collections
|
||||
/// </summary>
|
||||
public IValidator<T> Validator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Access to the collection
|
||||
/// </summary>
|
||||
public ICollectionBase<T> Collection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parameters from the interceptor which ran on before the operation (only available for completed operations)
|
||||
/// </summary>
|
||||
|
||||
@@ -20,7 +20,8 @@ namespace zero.Core.Collections
|
||||
Context = args.Context,
|
||||
Properties = args.Properties,
|
||||
Session = args.Session,
|
||||
Store = args.Store
|
||||
Store = args.Store,
|
||||
Collection = null, //args.Collection
|
||||
//Validator = args.Validator
|
||||
};
|
||||
|
||||
@@ -46,6 +47,16 @@ namespace zero.Core.Collections
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRun(Parameters args)
|
||||
{
|
||||
if (args is ParametersWithModel)
|
||||
{
|
||||
return _base.CanRun(Args<CollectionInterceptor<ZeroEntity>.ParametersWithModel>(args as ParametersWithModel));
|
||||
}
|
||||
return _base.CanRun(Args<CollectionInterceptor<ZeroEntity>.Parameters>(args));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<InterceptorResult<T>> Creating(CreateParameters args) => Result(await _base.Creating(Args<CollectionInterceptor<ZeroEntity>.CreateParameters>(args)));
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using zero.Core.Entities;
|
||||
|
||||
namespace zero.Core.Collections
|
||||
{
|
||||
public class InterceptorHelper
|
||||
{
|
||||
protected ICollectionInterceptorHandler Handler { get; set; }
|
||||
|
||||
|
||||
public InterceptorHelper(ICollectionInterceptorHandler handler)
|
||||
{
|
||||
Handler = handler;
|
||||
}
|
||||
|
||||
|
||||
public async Task<InterceptorInstruction<T, CollectionInterceptor<T>.UpdateParameters>> Updating<T>(T model) where T : ZeroEntity
|
||||
{
|
||||
CollectionInterceptor<T>.UpdateParameters parameters = new();
|
||||
InterceptorInstruction<T, CollectionInterceptor<T>.UpdateParameters> instruction = Handler.Create<T, CollectionInterceptor<T>.UpdateParameters>("update", parameters) ?? new();
|
||||
|
||||
await instruction.HandleBefore(x => x.Updating(instruction.Parameters));
|
||||
|
||||
return instruction;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using zero.Core.Blueprints;
|
||||
using zero.Core.Entities;
|
||||
|
||||
namespace zero.Core.Options
|
||||
{
|
||||
public class BlueprintOptions : ZeroBackofficeCollection<Blueprint>, IZeroCollectionOptions
|
||||
{
|
||||
public BlueprintOptions()
|
||||
{
|
||||
Add<CountryBlueprint>();
|
||||
}
|
||||
|
||||
|
||||
public void Add<T>() where T : Blueprint, new()
|
||||
{
|
||||
Items.Add(new T());
|
||||
}
|
||||
|
||||
|
||||
public void Add<T>(Blueprint<T> implementation) where T : ZeroEntity, new()
|
||||
{
|
||||
Items.Add(implementation);
|
||||
}
|
||||
|
||||
|
||||
public void Add<T>(Action<Blueprint<T>> createExpression) where T : ZeroEntity, new()
|
||||
{
|
||||
Items.Add(new DefaultBlueprint<T>(createExpression));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ namespace zero.Core.Options
|
||||
Integrations = new();
|
||||
Icons = new();
|
||||
Services = new();
|
||||
Blueprints = new();
|
||||
Routing = new();
|
||||
Interceptors = new();
|
||||
Applications = new();
|
||||
@@ -116,6 +117,9 @@ namespace zero.Core.Options
|
||||
/// <inheritdoc />
|
||||
public ServiceOptions Services { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public BlueprintOptions Blueprints { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public RoutingOptions Routing { get; private set; }
|
||||
|
||||
@@ -225,6 +229,11 @@ namespace zero.Core.Options
|
||||
/// </summary>
|
||||
ServiceOptions Services { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
BlueprintOptions Blueprints { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
||||
@@ -79,7 +79,7 @@ const myPlugin = () => ({
|
||||
*/
|
||||
let config = {
|
||||
server: {
|
||||
port: process.env.PORT,
|
||||
port: process.env.PORT || 3399,
|
||||
cors: true
|
||||
},
|
||||
plugins: [createVuePlugin(), ...zeroPlugins, myPlugin()],
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using zero.Core;
|
||||
using zero.Core.Api;
|
||||
using zero.Core.Blueprints;
|
||||
using zero.Core.Collections;
|
||||
using zero.Core.Entities;
|
||||
using zero.Core.Integrations;
|
||||
@@ -50,7 +51,8 @@ namespace zero.Web.Defaults
|
||||
zero.Icons.AddSet("feather", "Feather", "/assets/icons/feather.svg", "fth");
|
||||
zero.Pages.Add<PageFolder>(Constants.Pages.FolderAlias, "@page.folder.name", "@page.folder.description", "fth-folder");
|
||||
|
||||
zero.Interceptors.Add<ZeroEntityRouteInterceptor>(gravity: 1000);
|
||||
zero.Interceptors.Add<ZeroEntityRouteInterceptor>(gravity: 100);
|
||||
zero.Interceptors.Add<BlueprintInterceptor>(gravity: -1);
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +136,9 @@ namespace zero.Web.Defaults
|
||||
|
||||
services.AddScoped<IBackofficeSearchService, BackofficeSearchService>();
|
||||
|
||||
services.AddScoped<IBlueprintService, BlueprintService>();
|
||||
services.AddScoped<BlueprintInterceptor>();
|
||||
|
||||
services.AddScoped<ZeroEntityRouteInterceptor>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Zero.Web.DevServer
|
||||
namespace Zero.Web.DevServer
|
||||
{
|
||||
public class ZeroDevOptions
|
||||
{
|
||||
@@ -12,5 +7,7 @@ namespace Zero.Web.DevServer
|
||||
public bool ForwardLog { get; set; } = false;
|
||||
|
||||
public string WorkingDirectory { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Zero.Web.DevServer
|
||||
{
|
||||
// this is a development-time service,
|
||||
// therefore no way to enable it in production
|
||||
if (!env.IsDevelopment())
|
||||
if (!env.IsDevelopment() || !options.Value.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user