mirror of https://github.com/Squidex/squidex.git
122 changed files with 5044 additions and 118 deletions
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppAggregateCommand.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public class AppAggregateCommand : AppCommand, IAggregateCommand |
||||
|
{ |
||||
|
Guid IAggregateCommand.AggregateId |
||||
|
{ |
||||
|
get { return AppId.Id; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppCommand.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public abstract class AppCommand : SquidexCommand |
||||
|
{ |
||||
|
public NamedId<Guid> AppId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,173 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppCommandMiddleware.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Commands; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Guards; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Services; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Dispatching; |
||||
|
using Squidex.Shared.Users; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps |
||||
|
{ |
||||
|
public class AppCommandMiddleware : ICommandMiddleware |
||||
|
{ |
||||
|
private readonly IAggregateHandler handler; |
||||
|
private readonly IAppProvider appProvider; |
||||
|
private readonly IAppPlansProvider appPlansProvider; |
||||
|
private readonly IAppPlanBillingManager appPlansBillingManager; |
||||
|
private readonly IUserResolver userResolver; |
||||
|
|
||||
|
public AppCommandMiddleware( |
||||
|
IAggregateHandler handler, |
||||
|
IAppProvider appProvider, |
||||
|
IAppPlansProvider appPlansProvider, |
||||
|
IAppPlanBillingManager appPlansBillingManager, |
||||
|
IUserResolver userResolver) |
||||
|
{ |
||||
|
Guard.NotNull(handler, nameof(handler)); |
||||
|
Guard.NotNull(appProvider, nameof(appProvider)); |
||||
|
Guard.NotNull(userResolver, nameof(userResolver)); |
||||
|
Guard.NotNull(appPlansProvider, nameof(appPlansProvider)); |
||||
|
Guard.NotNull(appPlansBillingManager, nameof(appPlansBillingManager)); |
||||
|
|
||||
|
this.handler = handler; |
||||
|
this.userResolver = userResolver; |
||||
|
this.appProvider = appProvider; |
||||
|
this.appPlansProvider = appPlansProvider; |
||||
|
this.appPlansBillingManager = appPlansBillingManager; |
||||
|
} |
||||
|
|
||||
|
protected Task On(CreateApp command, CommandContext context) |
||||
|
{ |
||||
|
return handler.CreateAsync<AppDomainObject>(context, async a => |
||||
|
{ |
||||
|
await GuardApp.CanCreate(command, appProvider); |
||||
|
|
||||
|
a.Create(command); |
||||
|
|
||||
|
context.Complete(EntityCreatedResult.Create(a.State.Id, a.Version)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(AssignContributor command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<AppDomainObject>(context, async a => |
||||
|
{ |
||||
|
await GuardAppContributors.CanAssign(a.State.Contributors, command, userResolver, appPlansProvider.GetPlan(a.State.Plan.PlanId)); |
||||
|
|
||||
|
a.AssignContributor(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(RemoveContributor command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<AppDomainObject>(context, a => |
||||
|
{ |
||||
|
GuardAppContributors.CanRemove(a.State.Contributors, command); |
||||
|
|
||||
|
a.RemoveContributor(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(AttachClient command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<AppDomainObject>(context, a => |
||||
|
{ |
||||
|
GuardAppClients.CanAttach(a.State.Clients, command); |
||||
|
|
||||
|
a.AttachClient(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(UpdateClient command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<AppDomainObject>(context, a => |
||||
|
{ |
||||
|
GuardAppClients.CanUpdate(a.State.Clients, command); |
||||
|
|
||||
|
a.UpdateClient(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(RevokeClient command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<AppDomainObject>(context, a => |
||||
|
{ |
||||
|
GuardAppClients.CanRevoke(a.State.Clients, command); |
||||
|
|
||||
|
a.RevokeClient(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(AddLanguage command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<AppDomainObject>(context, a => |
||||
|
{ |
||||
|
GuardAppLanguages.CanAdd(a.State.LanguagesConfig, command); |
||||
|
|
||||
|
a.AddLanguage(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(RemoveLanguage command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<AppDomainObject>(context, a => |
||||
|
{ |
||||
|
GuardAppLanguages.CanRemove(a.State.LanguagesConfig, command); |
||||
|
|
||||
|
a.RemoveLanguage(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(UpdateLanguage command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<AppDomainObject>(context, a => |
||||
|
{ |
||||
|
GuardAppLanguages.CanUpdate(a.State.LanguagesConfig, command); |
||||
|
|
||||
|
a.UpdateLanguage(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(ChangePlan command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<AppDomainObject>(context, async a => |
||||
|
{ |
||||
|
GuardApp.CanChangePlan(command, a.State.Plan, appPlansProvider); |
||||
|
|
||||
|
if (command.FromCallback) |
||||
|
{ |
||||
|
a.ChangePlan(command); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var result = await appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, a.State.Id, a.State.Name, command.PlanId); |
||||
|
|
||||
|
if (result is PlanChangedResult) |
||||
|
{ |
||||
|
a.ChangePlan(command); |
||||
|
} |
||||
|
|
||||
|
context.Complete(result); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public async Task HandleAsync(CommandContext context, Func<Task> next) |
||||
|
{ |
||||
|
if (!await this.DispatchActionAsync(context.Command, context)) |
||||
|
{ |
||||
|
await next(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,229 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppDomainObject.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Commands; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.State; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Domain.Apps.Events.Apps; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps |
||||
|
{ |
||||
|
public class AppDomainObject : DomainObjectBase<AppDomainObject, AppState> |
||||
|
{ |
||||
|
public AppDomainObject Create(CreateApp command) |
||||
|
{ |
||||
|
ThrowIfCreated(); |
||||
|
|
||||
|
var appId = new NamedId<Guid>(command.AppId, command.Name); |
||||
|
|
||||
|
UpdateState(command, s => s.Name = command.Name); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, CreateInitalEvent(appId))); |
||||
|
RaiseEvent(SimpleMapper.Map(command, CreateInitialOwner(appId, command))); |
||||
|
RaiseEvent(SimpleMapper.Map(command, CreateInitialLanguage(appId))); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public AppDomainObject UpdateLanguage(UpdateLanguage command) |
||||
|
{ |
||||
|
ThrowIfNotCreated(); |
||||
|
|
||||
|
UpdateLanguages(command, l => |
||||
|
{ |
||||
|
var fallback = command.Fallback; |
||||
|
|
||||
|
if (fallback != null && fallback.Count > 0) |
||||
|
{ |
||||
|
var existingLangauges = l.OfType<LanguageConfig>().Select(x => x.Language); |
||||
|
|
||||
|
fallback = fallback.Intersect(existingLangauges).ToList(); |
||||
|
} |
||||
|
|
||||
|
l = l.Set(new LanguageConfig(command.Language, command.IsOptional, fallback)); |
||||
|
|
||||
|
if (command.IsMaster) |
||||
|
{ |
||||
|
l = l.MakeMaster(command.Language); |
||||
|
} |
||||
|
|
||||
|
return l; |
||||
|
}); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new AppLanguageUpdated())); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public AppDomainObject UpdateClient(UpdateClient command) |
||||
|
{ |
||||
|
ThrowIfNotCreated(); |
||||
|
|
||||
|
if (!string.IsNullOrWhiteSpace(command.Name)) |
||||
|
{ |
||||
|
UpdateClients(command, c => c.Rename(command.Id, command.Name)); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new AppClientRenamed())); |
||||
|
} |
||||
|
|
||||
|
if (command.Permission.HasValue) |
||||
|
{ |
||||
|
UpdateClients(command, c => c.Update(command.Id, command.Permission.Value)); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new AppClientUpdated { Permission = command.Permission.Value })); |
||||
|
} |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public AppDomainObject AssignContributor(AssignContributor command) |
||||
|
{ |
||||
|
ThrowIfNotCreated(); |
||||
|
|
||||
|
UpdateContributors(command, c => c.Assign(command.ContributorId, command.Permission)); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned())); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public AppDomainObject RemoveContributor(RemoveContributor command) |
||||
|
{ |
||||
|
ThrowIfNotCreated(); |
||||
|
|
||||
|
UpdateContributors(command, c => c.Remove(command.ContributorId)); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new AppContributorRemoved())); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public AppDomainObject AttachClient(AttachClient command) |
||||
|
{ |
||||
|
ThrowIfNotCreated(); |
||||
|
|
||||
|
UpdateClients(command, c => c.Add(command.Id, command.Secret)); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new AppClientAttached())); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public AppDomainObject RevokeClient(RevokeClient command) |
||||
|
{ |
||||
|
ThrowIfNotCreated(); |
||||
|
|
||||
|
UpdateClients(command, c => c.Revoke(command.Id)); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new AppClientRevoked())); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public AppDomainObject AddLanguage(AddLanguage command) |
||||
|
{ |
||||
|
ThrowIfNotCreated(); |
||||
|
|
||||
|
UpdateLanguages(command, l => l.Set(new LanguageConfig(command.Language))); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new AppLanguageAdded())); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public AppDomainObject RemoveLanguage(RemoveLanguage command) |
||||
|
{ |
||||
|
ThrowIfNotCreated(); |
||||
|
|
||||
|
UpdateLanguages(command, l => l.Remove(command.Language)); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new AppLanguageRemoved())); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public AppDomainObject ChangePlan(ChangePlan command) |
||||
|
{ |
||||
|
ThrowIfNotCreated(); |
||||
|
|
||||
|
UpdateState(command, s => s.Plan = new AppPlan(command.Actor, command.PlanId)); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new AppPlanChanged())); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
private void RaiseEvent(AppEvent @event) |
||||
|
{ |
||||
|
if (@event.AppId == null) |
||||
|
{ |
||||
|
@event.AppId = new NamedId<Guid>(State.Id, State.Name); |
||||
|
} |
||||
|
|
||||
|
RaiseEvent(Envelope.Create(@event)); |
||||
|
} |
||||
|
|
||||
|
private static AppCreated CreateInitalEvent(NamedId<Guid> appId) |
||||
|
{ |
||||
|
return new AppCreated { AppId = appId }; |
||||
|
} |
||||
|
|
||||
|
private static AppLanguageAdded CreateInitialLanguage(NamedId<Guid> id) |
||||
|
{ |
||||
|
return new AppLanguageAdded { AppId = id, Language = Language.EN }; |
||||
|
} |
||||
|
|
||||
|
private static AppContributorAssigned CreateInitialOwner(NamedId<Guid> id, SquidexCommand command) |
||||
|
{ |
||||
|
return new AppContributorAssigned { AppId = id, ContributorId = command.Actor.Identifier, Permission = AppContributorPermission.Owner }; |
||||
|
} |
||||
|
|
||||
|
private void ThrowIfNotCreated() |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(State.Name)) |
||||
|
{ |
||||
|
throw new DomainException("App has not been created."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ThrowIfCreated() |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(State.Name)) |
||||
|
{ |
||||
|
throw new DomainException("App has already been created."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void UpdateClients(ICommand command, Func<AppClients, AppClients> updater) |
||||
|
{ |
||||
|
UpdateState(command, s => s.Clients = updater(s.Clients)); |
||||
|
} |
||||
|
|
||||
|
private void UpdateContributors(ICommand command, Func<AppContributors, AppContributors> updater) |
||||
|
{ |
||||
|
UpdateState(command, s => s.Contributors = updater(s.Contributors)); |
||||
|
} |
||||
|
|
||||
|
private void UpdateLanguages(ICommand command, Func<LanguagesConfig, LanguagesConfig> updater) |
||||
|
{ |
||||
|
UpdateState(command, s => s.LanguagesConfig = updater(s.LanguagesConfig)); |
||||
|
} |
||||
|
|
||||
|
protected override AppState CloneState(ICommand command, Action<AppState> updater) |
||||
|
{ |
||||
|
return State.Clone().Update((SquidexCommand)command, updater); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppEntityExtensions.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps |
||||
|
{ |
||||
|
public static class AppEntityExtensions |
||||
|
{ |
||||
|
public static PartitionResolver PartitionResolver(this IAppEntity entity) |
||||
|
{ |
||||
|
return entity.LanguagesConfig.ToResolver(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,145 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppHistoryEventsCreator.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.History; |
||||
|
using Squidex.Domain.Apps.Events.Apps; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Dispatching; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps |
||||
|
{ |
||||
|
public class AppHistoryEventsCreator : HistoryEventsCreatorBase |
||||
|
{ |
||||
|
public AppHistoryEventsCreator(TypeNameRegistry typeNameRegistry) |
||||
|
: base(typeNameRegistry) |
||||
|
{ |
||||
|
AddEventMessage<AppContributorAssigned>( |
||||
|
"assigned {user:[Contributor]} as [Permission]"); |
||||
|
|
||||
|
AddEventMessage<AppContributorRemoved>( |
||||
|
"removed {user:[Contributor]} from app"); |
||||
|
|
||||
|
AddEventMessage<AppClientAttached>( |
||||
|
"added client {[Id]} to app"); |
||||
|
|
||||
|
AddEventMessage<AppClientRevoked>( |
||||
|
"revoked client {[Id]}"); |
||||
|
|
||||
|
AddEventMessage<AppClientUpdated>( |
||||
|
"updated client {[Id]}"); |
||||
|
|
||||
|
AddEventMessage<AppClientRenamed>( |
||||
|
"renamed client {[Id]} to {[Name]}"); |
||||
|
|
||||
|
AddEventMessage<AppLanguageAdded>( |
||||
|
"added language {[Language]}"); |
||||
|
|
||||
|
AddEventMessage<AppLanguageRemoved>( |
||||
|
"removed language {[Language]}"); |
||||
|
|
||||
|
AddEventMessage<AppLanguageUpdated>( |
||||
|
"updated language {[Language]}"); |
||||
|
|
||||
|
AddEventMessage<AppMasterLanguageSet>( |
||||
|
"changed master language to {[Language]}"); |
||||
|
} |
||||
|
|
||||
|
protected Task<HistoryEventToStore> On(AppContributorRemoved @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
const string channel = "settings.contributors"; |
||||
|
|
||||
|
return Task.FromResult( |
||||
|
ForEvent(@event, channel) |
||||
|
.AddParameter("Contributor", @event.ContributorId)); |
||||
|
} |
||||
|
|
||||
|
protected Task<HistoryEventToStore> On(AppContributorAssigned @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
const string channel = "settings.contributors"; |
||||
|
|
||||
|
return Task.FromResult( |
||||
|
ForEvent(@event, channel) |
||||
|
.AddParameter("Contributor", @event.ContributorId).AddParameter("Permission", @event.Permission)); |
||||
|
} |
||||
|
|
||||
|
protected Task<HistoryEventToStore> On(AppClientAttached @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
const string channel = "settings.clients"; |
||||
|
|
||||
|
return Task.FromResult( |
||||
|
ForEvent(@event, channel) |
||||
|
.AddParameter("Id", @event.Id)); |
||||
|
} |
||||
|
|
||||
|
protected Task<HistoryEventToStore> On(AppClientRevoked @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
const string channel = "settings.clients"; |
||||
|
|
||||
|
return Task.FromResult( |
||||
|
ForEvent(@event, channel) |
||||
|
.AddParameter("Id", @event.Id)); |
||||
|
} |
||||
|
|
||||
|
protected Task<HistoryEventToStore> On(AppClientRenamed @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
const string channel = "settings.clients"; |
||||
|
|
||||
|
return Task.FromResult( |
||||
|
ForEvent(@event, channel) |
||||
|
.AddParameter("Id", @event.Id).AddParameter("Name", ClientName(@event))); |
||||
|
} |
||||
|
|
||||
|
protected Task<HistoryEventToStore> On(AppLanguageAdded @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
const string channel = "settings.languages"; |
||||
|
|
||||
|
return Task.FromResult( |
||||
|
ForEvent(@event, channel) |
||||
|
.AddParameter("Language", @event.Language)); |
||||
|
} |
||||
|
|
||||
|
protected Task<HistoryEventToStore> On(AppLanguageRemoved @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
const string channel = "settings.languages"; |
||||
|
|
||||
|
return Task.FromResult( |
||||
|
ForEvent(@event, channel) |
||||
|
.AddParameter("Language", @event.Language)); |
||||
|
} |
||||
|
|
||||
|
protected Task<HistoryEventToStore> On(AppLanguageUpdated @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
const string channel = "settings.languages"; |
||||
|
|
||||
|
return Task.FromResult( |
||||
|
ForEvent(@event, channel) |
||||
|
.AddParameter("Language", @event.Language)); |
||||
|
} |
||||
|
|
||||
|
protected Task<HistoryEventToStore> On(AppMasterLanguageSet @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
const string channel = "settings.languages"; |
||||
|
|
||||
|
return Task.FromResult( |
||||
|
ForEvent(@event, channel) |
||||
|
.AddParameter("Language", @event.Language)); |
||||
|
} |
||||
|
|
||||
|
protected override Task<HistoryEventToStore> CreateEventCoreAsync(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
return this.DispatchFuncAsync(@event.Payload, @event.Headers, (HistoryEventToStore)null); |
||||
|
} |
||||
|
|
||||
|
private static string ClientName(AppClientRenamed @event) |
||||
|
{ |
||||
|
return !string.IsNullOrWhiteSpace(@event.Name) ? @event.Name : @event.Id; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AddLanguage.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class AddLanguage : AppAggregateCommand |
||||
|
{ |
||||
|
public Language Language { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AssignContributor.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class AssignContributor : AppAggregateCommand |
||||
|
{ |
||||
|
public string ContributorId { get; set; } |
||||
|
|
||||
|
public AppContributorPermission Permission { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AttachClient.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class AttachClient : AppAggregateCommand |
||||
|
{ |
||||
|
public string Id { get; set; } |
||||
|
|
||||
|
public string Secret { get; } = RandomHash.New(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ChangePlan.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class ChangePlan : AppAggregateCommand |
||||
|
{ |
||||
|
public bool FromCallback { get; set; } |
||||
|
|
||||
|
public string PlanId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
// ==========================================================================
|
||||
|
// CreateApp.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class CreateApp : SquidexCommand, IAggregateCommand |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public Guid AppId { get; set; } |
||||
|
|
||||
|
Guid IAggregateCommand.AggregateId |
||||
|
{ |
||||
|
get { return AppId; } |
||||
|
} |
||||
|
|
||||
|
public CreateApp() |
||||
|
{ |
||||
|
AppId = Guid.NewGuid(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RemoveContributor.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class RemoveContributor : AppAggregateCommand |
||||
|
{ |
||||
|
public string ContributorId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RemoveLanguage.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class RemoveLanguage : AppAggregateCommand |
||||
|
{ |
||||
|
public Language Language { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RevokeClient.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class RevokeClient : AppAggregateCommand |
||||
|
{ |
||||
|
public string Id { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// UpdateClient.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class UpdateClient : AppAggregateCommand |
||||
|
{ |
||||
|
public string Id { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public AppClientPermission? Permission { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// ==========================================================================
|
||||
|
// UpdateLanguage.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class UpdateLanguage : AppAggregateCommand |
||||
|
{ |
||||
|
public Language Language { get; set; } |
||||
|
|
||||
|
public bool IsOptional { get; set; } |
||||
|
|
||||
|
public bool IsMaster { get; set; } |
||||
|
|
||||
|
public List<Language> Fallback { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
// ==========================================================================
|
||||
|
// GuardApp.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Commands; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Services; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Guards |
||||
|
{ |
||||
|
public static class GuardApp |
||||
|
{ |
||||
|
public static Task CanCreate(CreateApp command, IAppProvider appProvider) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
return Validate.It(() => "Cannot create app.", async error => |
||||
|
{ |
||||
|
if (await appProvider.GetAppAsync(command.Name) != null) |
||||
|
{ |
||||
|
error(new ValidationError($"An app with name '{command.Name}' already exists", nameof(command.Name))); |
||||
|
} |
||||
|
|
||||
|
if (!command.Name.IsSlug()) |
||||
|
{ |
||||
|
error(new ValidationError("Name must be a valid slug.", nameof(command.Name))); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanChangePlan(ChangePlan command, AppPlan plan, IAppPlansProvider appPlans) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
Validate.It(() => "Cannot change plan.", error => |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(command.PlanId)) |
||||
|
{ |
||||
|
error(new ValidationError("PlanId is not defined.", nameof(command.PlanId))); |
||||
|
} |
||||
|
else if (appPlans.GetPlan(command.PlanId) == null) |
||||
|
{ |
||||
|
error(new ValidationError("Plan id not available.", nameof(command.PlanId))); |
||||
|
} |
||||
|
|
||||
|
if (!string.IsNullOrWhiteSpace(command.PlanId) && plan != null && !plan.Owner.Equals(command.Actor)) |
||||
|
{ |
||||
|
error(new ValidationError("Plan can only be changed from current user.")); |
||||
|
} |
||||
|
|
||||
|
if (string.Equals(command.PlanId, plan?.PlanId, StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
error(new ValidationError("App has already this plan.")); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
// ==========================================================================
|
||||
|
// GuardAppClients.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Guards |
||||
|
{ |
||||
|
public static class GuardAppClients |
||||
|
{ |
||||
|
public static void CanAttach(AppClients clients, AttachClient command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
Validate.It(() => "Cannot attach client.", error => |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(command.Id)) |
||||
|
{ |
||||
|
error(new ValidationError("Client id must be defined.", nameof(command.Id))); |
||||
|
} |
||||
|
else if (clients.ContainsKey(command.Id)) |
||||
|
{ |
||||
|
error(new ValidationError("Client id already added.", nameof(command.Id))); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanRevoke(AppClients clients, RevokeClient command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
GetClientOrThrow(clients, command.Id); |
||||
|
|
||||
|
Validate.It(() => "Cannot revoke client.", error => |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(command.Id)) |
||||
|
{ |
||||
|
error(new ValidationError("Client id must be defined.", nameof(command.Id))); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanUpdate(AppClients clients, UpdateClient command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
var client = GetClientOrThrow(clients, command.Id); |
||||
|
|
||||
|
Validate.It(() => "Cannot revoke client.", error => |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(command.Id)) |
||||
|
{ |
||||
|
error(new ValidationError("Client id must be defined.", nameof(command.Id))); |
||||
|
} |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(command.Name) && command.Permission == null) |
||||
|
{ |
||||
|
error(new ValidationError("Either name or permission must be defined.", nameof(command.Name), nameof(command.Permission))); |
||||
|
} |
||||
|
|
||||
|
if (command.Permission.HasValue && !command.Permission.Value.IsEnumValue()) |
||||
|
{ |
||||
|
error(new ValidationError("Permission is not valid.", nameof(command.Permission))); |
||||
|
} |
||||
|
|
||||
|
if (client != null) |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(command.Name) && string.Equals(client.Name, command.Name)) |
||||
|
{ |
||||
|
error(new ValidationError("Client already has this name.", nameof(command.Permission))); |
||||
|
} |
||||
|
|
||||
|
if (command.Permission == client.Permission) |
||||
|
{ |
||||
|
error(new ValidationError("Client already has this permission.", nameof(command.Permission))); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private static AppClient GetClientOrThrow(AppClients clients, string id) |
||||
|
{ |
||||
|
if (id == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
if (!clients.TryGetValue(id, out var client)) |
||||
|
{ |
||||
|
throw new DomainObjectNotFoundException(id, "Clients", typeof(AppDomainObject)); |
||||
|
} |
||||
|
|
||||
|
return client; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
// ==========================================================================
|
||||
|
// GuardAppContributors.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Commands; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Services; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Shared.Users; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Guards |
||||
|
{ |
||||
|
public static class GuardAppContributors |
||||
|
{ |
||||
|
public static Task CanAssign(AppContributors contributors, AssignContributor command, IUserResolver users, IAppLimitsPlan plan) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
return Validate.It(() => "Cannot assign contributor.", async error => |
||||
|
{ |
||||
|
if (!command.Permission.IsEnumValue()) |
||||
|
{ |
||||
|
error(new ValidationError("Permission is not valid.", nameof(command.Permission))); |
||||
|
} |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(command.ContributorId)) |
||||
|
{ |
||||
|
error(new ValidationError("Contributor id not assigned.", nameof(command.ContributorId))); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (await users.FindByIdAsync(command.ContributorId) == null) |
||||
|
{ |
||||
|
error(new ValidationError("Cannot find contributor id.", nameof(command.ContributorId))); |
||||
|
} |
||||
|
else if (contributors.TryGetValue(command.ContributorId, out var existing)) |
||||
|
{ |
||||
|
if (existing == command.Permission) |
||||
|
{ |
||||
|
error(new ValidationError("Contributor has already this permission.", nameof(command.Permission))); |
||||
|
} |
||||
|
} |
||||
|
else if (plan.MaxContributors == contributors.Count) |
||||
|
{ |
||||
|
error(new ValidationError("You have reached the maximum number of contributors for your plan.")); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanRemove(AppContributors contributors, RemoveContributor command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
Validate.It(() => "Cannot remove contributor.", error => |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(command.ContributorId)) |
||||
|
{ |
||||
|
error(new ValidationError("Contributor id not assigned.", nameof(command.ContributorId))); |
||||
|
} |
||||
|
|
||||
|
var ownerIds = contributors.Where(x => x.Value == AppContributorPermission.Owner).Select(x => x.Key).ToList(); |
||||
|
|
||||
|
if (ownerIds.Count == 1 && ownerIds.Contains(command.ContributorId)) |
||||
|
{ |
||||
|
error(new ValidationError("Cannot remove the only owner.", nameof(command.ContributorId))); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (!contributors.ContainsKey(command.ContributorId)) |
||||
|
{ |
||||
|
throw new DomainObjectNotFoundException(command.ContributorId, "Contributors", typeof(AppDomainObject)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,100 @@ |
|||||
|
// ==========================================================================
|
||||
|
// GuardAppLanguages.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Guards |
||||
|
{ |
||||
|
public static class GuardAppLanguages |
||||
|
{ |
||||
|
public static void CanAdd(LanguagesConfig languages, AddLanguage command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
Validate.It(() => "Cannot add language.", error => |
||||
|
{ |
||||
|
if (command.Language == null) |
||||
|
{ |
||||
|
error(new ValidationError("Language cannot be null.", nameof(command.Language))); |
||||
|
} |
||||
|
else if (languages.Contains(command.Language)) |
||||
|
{ |
||||
|
error(new ValidationError("Language already added.", nameof(command.Language))); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanRemove(LanguagesConfig languages, RemoveLanguage command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
var languageConfig = GetLanguageConfigOrThrow(languages, command.Language); |
||||
|
|
||||
|
Validate.It(() => "Cannot remove language.", error => |
||||
|
{ |
||||
|
if (command.Language == null) |
||||
|
{ |
||||
|
error(new ValidationError("Language cannot be null.", nameof(command.Language))); |
||||
|
} |
||||
|
|
||||
|
if (languages.Master == languageConfig) |
||||
|
{ |
||||
|
error(new ValidationError("Language config is master.", nameof(command.Language))); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanUpdate(LanguagesConfig languages, UpdateLanguage command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
var languageConfig = GetLanguageConfigOrThrow(languages, command.Language); |
||||
|
|
||||
|
Validate.It(() => "Cannot update language.", error => |
||||
|
{ |
||||
|
if (command.Language == null) |
||||
|
{ |
||||
|
error(new ValidationError("Language cannot be null.", nameof(command.Language))); |
||||
|
} |
||||
|
|
||||
|
if ((languages.Master == languageConfig || command.IsMaster) && command.IsOptional) |
||||
|
{ |
||||
|
error(new ValidationError("Cannot make master language optional.", nameof(command.IsMaster))); |
||||
|
} |
||||
|
|
||||
|
if (command.Fallback != null) |
||||
|
{ |
||||
|
foreach (var fallback in command.Fallback) |
||||
|
{ |
||||
|
if (!languages.Contains(fallback)) |
||||
|
{ |
||||
|
error(new ValidationError($"Config does not contain fallback language {fallback}.", nameof(command.Fallback))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private static LanguageConfig GetLanguageConfigOrThrow(LanguagesConfig languages, Language language) |
||||
|
{ |
||||
|
if (language == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
if (!languages.TryGetConfig(language, out var languageConfig)) |
||||
|
{ |
||||
|
throw new DomainObjectNotFoundException(language, "Languages", typeof(AppDomainObject)); |
||||
|
} |
||||
|
|
||||
|
return languageConfig; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IAppEntity.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps |
||||
|
{ |
||||
|
public interface IAppEntity : IEntity, IEntityWithVersion |
||||
|
{ |
||||
|
string Name { get; } |
||||
|
|
||||
|
AppPlan Plan { get; } |
||||
|
|
||||
|
AppClients Clients { get; } |
||||
|
|
||||
|
AppContributors Contributors { get; } |
||||
|
|
||||
|
LanguagesConfig LanguagesConfig { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IAppLimitsPlan.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Services |
||||
|
{ |
||||
|
public interface IAppLimitsPlan |
||||
|
{ |
||||
|
string Id { get; } |
||||
|
|
||||
|
string Name { get; } |
||||
|
|
||||
|
string Costs { get; } |
||||
|
|
||||
|
long MaxApiCalls { get; } |
||||
|
|
||||
|
long MaxAssetSize { get; } |
||||
|
|
||||
|
int MaxContributors { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IAppPlanBillingManager.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Services |
||||
|
{ |
||||
|
public interface IAppPlanBillingManager |
||||
|
{ |
||||
|
bool HasPortal { get; } |
||||
|
|
||||
|
Task<IChangePlanResult> ChangePlanAsync(string userId, Guid appId, string appName, string planId); |
||||
|
|
||||
|
Task<string> GetPortalLinkAsync(string userId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IAppPlansProvider.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Services |
||||
|
{ |
||||
|
public interface IAppPlansProvider |
||||
|
{ |
||||
|
IEnumerable<IAppLimitsPlan> GetAvailablePlans(); |
||||
|
|
||||
|
bool IsConfiguredPlan(string planId); |
||||
|
|
||||
|
IAppLimitsPlan GetPlanUpgradeForApp(IAppEntity app); |
||||
|
|
||||
|
IAppLimitsPlan GetPlanUpgrade(string planId); |
||||
|
|
||||
|
IAppLimitsPlan GetPlanForApp(IAppEntity app); |
||||
|
|
||||
|
IAppLimitsPlan GetPlan(string planId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IChangePlanResult.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Services |
||||
|
{ |
||||
|
public interface IChangePlanResult |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ConfigAppLimitsPlan.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations |
||||
|
{ |
||||
|
public sealed class ConfigAppLimitsPlan : IAppLimitsPlan |
||||
|
{ |
||||
|
public string Id { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string Costs { get; set; } |
||||
|
|
||||
|
public long MaxApiCalls { get; set; } |
||||
|
|
||||
|
public long MaxAssetSize { get; set; } |
||||
|
|
||||
|
public int MaxContributors { get; set; } |
||||
|
|
||||
|
public ConfigAppLimitsPlan Clone() |
||||
|
{ |
||||
|
return (ConfigAppLimitsPlan)MemberwiseClone(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ConfigAppPlansProvider.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations |
||||
|
{ |
||||
|
public sealed class ConfigAppPlansProvider : IAppPlansProvider |
||||
|
{ |
||||
|
private static readonly ConfigAppLimitsPlan Infinite = new ConfigAppLimitsPlan |
||||
|
{ |
||||
|
Id = "infinite", |
||||
|
Name = "Infinite", |
||||
|
MaxApiCalls = -1, |
||||
|
MaxAssetSize = -1, |
||||
|
MaxContributors = -1 |
||||
|
}; |
||||
|
|
||||
|
private readonly Dictionary<string, ConfigAppLimitsPlan> plansById; |
||||
|
private readonly List<ConfigAppLimitsPlan> plansList; |
||||
|
|
||||
|
public ConfigAppPlansProvider(IEnumerable<ConfigAppLimitsPlan> config) |
||||
|
{ |
||||
|
Guard.NotNull(config, nameof(config)); |
||||
|
|
||||
|
plansList = config.Select(c => c.Clone()).OrderBy(x => x.MaxApiCalls).ToList(); |
||||
|
plansById = plansList.ToDictionary(c => c.Id, StringComparer.OrdinalIgnoreCase); |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<IAppLimitsPlan> GetAvailablePlans() |
||||
|
{ |
||||
|
return plansList; |
||||
|
} |
||||
|
|
||||
|
public bool IsConfiguredPlan(string planId) |
||||
|
{ |
||||
|
return planId != null && plansById.ContainsKey(planId); |
||||
|
} |
||||
|
|
||||
|
public IAppLimitsPlan GetPlanForApp(IAppEntity app) |
||||
|
{ |
||||
|
Guard.NotNull(app, nameof(app)); |
||||
|
|
||||
|
return GetPlan(app.Plan?.PlanId); |
||||
|
} |
||||
|
|
||||
|
public IAppLimitsPlan GetPlan(string planId) |
||||
|
{ |
||||
|
return GetPlanCore(planId); |
||||
|
} |
||||
|
|
||||
|
public IAppLimitsPlan GetPlanUpgradeForApp(IAppEntity app) |
||||
|
{ |
||||
|
Guard.NotNull(app, nameof(app)); |
||||
|
|
||||
|
return GetPlanUpgrade(app.Plan?.PlanId); |
||||
|
} |
||||
|
|
||||
|
public IAppLimitsPlan GetPlanUpgrade(string planId) |
||||
|
{ |
||||
|
var plan = GetPlanCore(planId); |
||||
|
|
||||
|
var nextPlanIndex = plansList.IndexOf(plan); |
||||
|
|
||||
|
if (nextPlanIndex >= 0 && nextPlanIndex < plansList.Count - 1) |
||||
|
{ |
||||
|
return plansList[nextPlanIndex + 1]; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private ConfigAppLimitsPlan GetPlanCore(string planId) |
||||
|
{ |
||||
|
return plansById.GetOrDefault(planId ?? string.Empty) ?? plansById.Values.FirstOrDefault() ?? Infinite; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// ==========================================================================
|
||||
|
// NoopAppPlanBillingManager.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations |
||||
|
{ |
||||
|
public sealed class NoopAppPlanBillingManager : IAppPlanBillingManager |
||||
|
{ |
||||
|
public bool HasPortal |
||||
|
{ |
||||
|
get { return false; } |
||||
|
} |
||||
|
|
||||
|
public Task<IChangePlanResult> ChangePlanAsync(string userId, Guid appId, string appName, string planId) |
||||
|
{ |
||||
|
return Task.FromResult<IChangePlanResult>(PlanChangedResult.Instance); |
||||
|
} |
||||
|
|
||||
|
public Task<string> GetPortalLinkAsync(string userId) |
||||
|
{ |
||||
|
return Task.FromResult(string.Empty); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// ==========================================================================
|
||||
|
// PlanChangeAsyncResult.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Services |
||||
|
{ |
||||
|
public sealed class PlanChangeAsyncResult : IChangePlanResult |
||||
|
{ |
||||
|
public static readonly PlanChangeAsyncResult Instance = new PlanChangeAsyncResult(); |
||||
|
|
||||
|
private PlanChangeAsyncResult() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// ==========================================================================
|
||||
|
// PlanChangedResult.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Services |
||||
|
{ |
||||
|
public sealed class PlanChangedResult : IChangePlanResult |
||||
|
{ |
||||
|
public static readonly PlanChangedResult Instance = new PlanChangedResult(); |
||||
|
|
||||
|
private PlanChangedResult() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RedirectToCheckoutResult.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Services |
||||
|
{ |
||||
|
public sealed class RedirectToCheckoutResult : IChangePlanResult |
||||
|
{ |
||||
|
public Uri Url { get; } |
||||
|
|
||||
|
public RedirectToCheckoutResult(Uri url) |
||||
|
{ |
||||
|
Guard.NotNull(url, nameof(url)); |
||||
|
|
||||
|
Url = url; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppState.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Newtonsoft.Json; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.State |
||||
|
{ |
||||
|
public sealed class AppState : DomainObjectState<AppState>, IAppEntity |
||||
|
{ |
||||
|
[JsonProperty] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public AppPlan Plan { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public AppClients Clients { get; set; } = AppClients.Empty; |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public AppContributors Contributors { get; set; } = AppContributors.Empty; |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public LanguagesConfig LanguagesConfig { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,117 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AssetCommandMiddleware.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Assets.Commands; |
||||
|
using Squidex.Domain.Apps.Entities.Assets.Guards; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Assets; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Dispatching; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public class AssetCommandMiddleware : ICommandMiddleware |
||||
|
{ |
||||
|
private readonly IAggregateHandler handler; |
||||
|
private readonly IAssetStore assetStore; |
||||
|
private readonly IAssetThumbnailGenerator assetThumbnailGenerator; |
||||
|
|
||||
|
public AssetCommandMiddleware( |
||||
|
IAggregateHandler handler, |
||||
|
IAssetStore assetStore, |
||||
|
IAssetThumbnailGenerator assetThumbnailGenerator) |
||||
|
{ |
||||
|
Guard.NotNull(handler, nameof(handler)); |
||||
|
Guard.NotNull(assetStore, nameof(assetStore)); |
||||
|
Guard.NotNull(assetThumbnailGenerator, nameof(assetThumbnailGenerator)); |
||||
|
|
||||
|
this.handler = handler; |
||||
|
this.assetStore = assetStore; |
||||
|
this.assetThumbnailGenerator = assetThumbnailGenerator; |
||||
|
} |
||||
|
|
||||
|
protected async Task On(CreateAsset command, CommandContext context) |
||||
|
{ |
||||
|
command.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead()); |
||||
|
try |
||||
|
{ |
||||
|
var asset = await handler.CreateAsync<AssetDomainObject>(context, async a => |
||||
|
{ |
||||
|
GuardAsset.CanCreate(command); |
||||
|
|
||||
|
a.Create(command); |
||||
|
|
||||
|
await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead()); |
||||
|
|
||||
|
context.Complete(EntityCreatedResult.Create(a.State.Id, a.Version)); |
||||
|
}); |
||||
|
|
||||
|
await assetStore.CopyTemporaryAsync(context.ContextId.ToString(), asset.State.Id.ToString(), asset.State.FileVersion, null); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
await assetStore.DeleteTemporaryAsync(context.ContextId.ToString()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected async Task On(UpdateAsset command, CommandContext context) |
||||
|
{ |
||||
|
command.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead()); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var asset = await handler.UpdateAsync<AssetDomainObject>(context, async a => |
||||
|
{ |
||||
|
GuardAsset.CanUpdate(command); |
||||
|
|
||||
|
a.Update(command); |
||||
|
|
||||
|
await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead()); |
||||
|
|
||||
|
context.Complete(new AssetSavedResult(a.Version, a.State.FileVersion)); |
||||
|
}); |
||||
|
|
||||
|
await assetStore.CopyTemporaryAsync(context.ContextId.ToString(), asset.State.Id.ToString(), asset.State.FileVersion, null); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
await assetStore.DeleteTemporaryAsync(context.ContextId.ToString()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected Task On(RenameAsset command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<AssetDomainObject>(context, a => |
||||
|
{ |
||||
|
GuardAsset.CanRename(command, a.State.FileName); |
||||
|
|
||||
|
a.Rename(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(DeleteAsset command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<AssetDomainObject>(context, a => |
||||
|
{ |
||||
|
GuardAsset.CanDelete(command); |
||||
|
|
||||
|
a.Delete(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public async Task HandleAsync(CommandContext context, Func<Task> next) |
||||
|
{ |
||||
|
if (!await this.DispatchActionAsync(context.Command, context)) |
||||
|
{ |
||||
|
await next(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AssetDomainObject.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Domain.Apps.Entities.Assets.Commands; |
||||
|
using Squidex.Domain.Apps.Entities.Assets.State; |
||||
|
using Squidex.Domain.Apps.Events.Assets; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public class AssetDomainObject : DomainObjectBase<AssetDomainObject, AssetState> |
||||
|
{ |
||||
|
public AssetDomainObject Create(CreateAsset command) |
||||
|
{ |
||||
|
VerifyNotCreated(); |
||||
|
|
||||
|
var @event = SimpleMapper.Map(command, new AssetCreated |
||||
|
{ |
||||
|
FileName = command.File.FileName, |
||||
|
FileSize = command.File.FileSize, |
||||
|
FileVersion = State.FileVersion + 1, |
||||
|
MimeType = command.File.MimeType, |
||||
|
PixelWidth = command.ImageInfo?.PixelWidth, |
||||
|
PixelHeight = command.ImageInfo?.PixelHeight, |
||||
|
IsImage = command.ImageInfo != null |
||||
|
}); |
||||
|
|
||||
|
UpdateState(command, s => SimpleMapper.Map(@event, s)); |
||||
|
|
||||
|
RaiseEvent(@event); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public AssetDomainObject Update(UpdateAsset command) |
||||
|
{ |
||||
|
VerifyCreatedAndNotDeleted(); |
||||
|
|
||||
|
var @event = SimpleMapper.Map(command, new AssetUpdated |
||||
|
{ |
||||
|
FileVersion = State.FileVersion + 1, |
||||
|
FileSize = command.File.FileSize, |
||||
|
MimeType = command.File.MimeType, |
||||
|
PixelWidth = command.ImageInfo?.PixelWidth, |
||||
|
PixelHeight = command.ImageInfo?.PixelHeight, |
||||
|
IsImage = command.ImageInfo != null |
||||
|
}); |
||||
|
|
||||
|
UpdateState(command, s => SimpleMapper.Map(@event, s)); |
||||
|
|
||||
|
RaiseEvent(@event); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public AssetDomainObject Delete(DeleteAsset command) |
||||
|
{ |
||||
|
VerifyCreatedAndNotDeleted(); |
||||
|
|
||||
|
UpdateState(command, s => s.IsDeleted = true); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new AssetDeleted { DeletedSize = State.TotalSize })); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public AssetDomainObject Rename(RenameAsset command) |
||||
|
{ |
||||
|
VerifyCreatedAndNotDeleted(); |
||||
|
|
||||
|
UpdateState(command, s => s.FileName = command.FileName); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new AssetRenamed())); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
private void VerifyNotCreated() |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(State.FileName)) |
||||
|
{ |
||||
|
throw new DomainException("Asset has already been created."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void VerifyCreatedAndNotDeleted() |
||||
|
{ |
||||
|
if (State.IsDeleted || string.IsNullOrWhiteSpace(State.FileName)) |
||||
|
{ |
||||
|
throw new DomainException("Asset has already been deleted or not created yet."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override AssetState CloneState(ICommand command, Action<AssetState> updater) |
||||
|
{ |
||||
|
return State.Clone().Update((SquidexCommand)command, updater); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AssetSavedResult.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public class AssetSavedResult : EntitySavedResult |
||||
|
{ |
||||
|
public long FileVersion { get; } |
||||
|
|
||||
|
public AssetSavedResult(long version, long fileVersion) |
||||
|
: base(version) |
||||
|
{ |
||||
|
FileVersion = fileVersion; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AssetAggregateCommand.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
||||
|
{ |
||||
|
public abstract class AssetAggregateCommand : AppCommand, IAggregateCommand |
||||
|
{ |
||||
|
public Guid AssetId { get; set; } |
||||
|
|
||||
|
Guid IAggregateCommand.AggregateId |
||||
|
{ |
||||
|
get { return AssetId; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// CreateAsset.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure.Assets; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
||||
|
{ |
||||
|
public sealed class CreateAsset : AssetAggregateCommand |
||||
|
{ |
||||
|
public AssetFile File { get; set; } |
||||
|
|
||||
|
public ImageInfo ImageInfo { get; set; } |
||||
|
|
||||
|
public CreateAsset() |
||||
|
{ |
||||
|
AssetId = Guid.NewGuid(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DeleteAsset.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
||||
|
{ |
||||
|
public sealed class DeleteAsset : AssetAggregateCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RenameAsset.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
||||
|
{ |
||||
|
public sealed class RenameAsset : AssetAggregateCommand |
||||
|
{ |
||||
|
public string FileName { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// ==========================================================================
|
||||
|
// UpdateAsset.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure.Assets; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets.Commands |
||||
|
{ |
||||
|
public sealed class UpdateAsset : AssetAggregateCommand |
||||
|
{ |
||||
|
public AssetFile File { get; set; } |
||||
|
|
||||
|
public ImageInfo ImageInfo { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
// ==========================================================================
|
||||
|
// GuardAsset.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Entities.Assets.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets.Guards |
||||
|
{ |
||||
|
public static class GuardAsset |
||||
|
{ |
||||
|
public static void CanRename(RenameAsset command, string oldName) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
Validate.It(() => "Cannot rename asset.", error => |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(command.FileName)) |
||||
|
{ |
||||
|
error(new ValidationError("Name must be defined.", nameof(command.FileName))); |
||||
|
} |
||||
|
|
||||
|
if (string.Equals(command.FileName, oldName)) |
||||
|
{ |
||||
|
error(new ValidationError("Name is equal to old name.", nameof(command.FileName))); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanCreate(CreateAsset command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
} |
||||
|
|
||||
|
public static void CanUpdate(UpdateAsset command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
} |
||||
|
|
||||
|
public static void CanDelete(DeleteAsset command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IAssetEntity.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.ValidateContent; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public interface IAssetEntity : |
||||
|
IEntity, |
||||
|
IEntityWithAppRef, |
||||
|
IEntityWithCreatedBy, |
||||
|
IEntityWithLastModifiedBy, |
||||
|
IEntityWithVersion, |
||||
|
IAssetInfo |
||||
|
{ |
||||
|
string MimeType { get; } |
||||
|
|
||||
|
long FileVersion { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IAssetEventConsumer.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public interface IAssetEventConsumer : IEventConsumer |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IAssetStatsEntity.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public interface IAssetStatsEntity |
||||
|
{ |
||||
|
DateTime Date { get; } |
||||
|
|
||||
|
long TotalSize { get; } |
||||
|
|
||||
|
long TotalCount { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IAssetRepository.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets.Repositories |
||||
|
{ |
||||
|
public interface IAssetRepository |
||||
|
{ |
||||
|
Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0); |
||||
|
|
||||
|
Task<IAssetEntity> FindAssetAsync(Guid id); |
||||
|
|
||||
|
Task<long> CountAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IAssetStatsRepository.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets.Repositories |
||||
|
{ |
||||
|
public interface IAssetStatsRepository |
||||
|
{ |
||||
|
Task<IReadOnlyList<IAssetStatsEntity>> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate); |
||||
|
|
||||
|
Task<long> GetTotalSizeAsync(Guid appId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AssetState.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Newtonsoft.Json; |
||||
|
using Squidex.Domain.Apps.Core.ValidateContent; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets.State |
||||
|
{ |
||||
|
public sealed class AssetState : DomainObjectState<AssetState>, |
||||
|
IAssetEntity, |
||||
|
IAssetInfo, |
||||
|
IUpdateableEntityWithAppRef |
||||
|
{ |
||||
|
[JsonProperty] |
||||
|
public Guid AppId { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public string FileName { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public string MimeType { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public long FileVersion { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public long FileSize { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public long TotalSize { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public bool IsImage { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public int? PixelWidth { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public int? PixelHeight { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public bool IsDeleted { get; set; } |
||||
|
|
||||
|
Guid IAssetInfo.AssetId |
||||
|
{ |
||||
|
get { return Id; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DomainObjectState.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Newtonsoft.Json; |
||||
|
using NodaTime; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public abstract class DomainObjectState<T> : Cloneable<T>, |
||||
|
IUpdateableEntityWithCreatedBy, |
||||
|
IUpdateableEntityWithLastModifiedBy, |
||||
|
IUpdateableEntityWithVersion |
||||
|
where T : Cloneable |
||||
|
{ |
||||
|
[JsonProperty] |
||||
|
public Guid Id { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public RefToken CreatedBy { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public RefToken LastModifiedBy { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public Instant Created { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public Instant LastModified { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public long Version { get; set; } |
||||
|
|
||||
|
public T Clone() |
||||
|
{ |
||||
|
return Clone(x => { }); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EntityMapper.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using NodaTime; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public static class EntityMapper |
||||
|
{ |
||||
|
public static T Update<T>(this T entity, SquidexCommand command, Action<T> updater = null) where T : IEntity |
||||
|
{ |
||||
|
var timestamp = SystemClock.Instance.GetCurrentInstant(); |
||||
|
|
||||
|
SetAppId(entity, command); |
||||
|
SetVersion(entity); |
||||
|
SetCreated(entity, timestamp); |
||||
|
SetCreatedBy(entity, command); |
||||
|
SetLastModified(entity, timestamp); |
||||
|
SetLastModifiedBy(entity, command); |
||||
|
|
||||
|
updater?.Invoke(entity); |
||||
|
|
||||
|
return entity; |
||||
|
} |
||||
|
|
||||
|
private static void SetLastModified(IEntity entity, Instant timestamp) |
||||
|
{ |
||||
|
entity.LastModified = timestamp; |
||||
|
} |
||||
|
|
||||
|
private static void SetCreated(IEntity entity, Instant timestamp) |
||||
|
{ |
||||
|
if (entity.Created == default(Instant)) |
||||
|
{ |
||||
|
entity.Created = timestamp; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void SetVersion(IEntity entity) |
||||
|
{ |
||||
|
if (entity is IUpdateableEntityWithVersion withVersion) |
||||
|
{ |
||||
|
withVersion.Version++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void SetCreatedBy(IEntity entity, SquidexCommand command) |
||||
|
{ |
||||
|
if (entity is IUpdateableEntityWithCreatedBy withCreatedBy && withCreatedBy.CreatedBy == null) |
||||
|
{ |
||||
|
withCreatedBy.CreatedBy = command.Actor; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void SetLastModifiedBy(IEntity entity, SquidexCommand command) |
||||
|
{ |
||||
|
if (entity is IUpdateableEntityWithLastModifiedBy withModifiedBy) |
||||
|
{ |
||||
|
withModifiedBy.LastModifiedBy = command.Actor; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void SetAppId(IEntity entity, SquidexCommand command) |
||||
|
{ |
||||
|
if (entity is IUpdateableEntityWithAppRef appEntity && command is AppCommand appCommand) |
||||
|
{ |
||||
|
appEntity.AppId = appCommand.AppId.Id; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
// ==========================================================================
|
||||
|
// HistoryEventToStore.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.History |
||||
|
{ |
||||
|
public sealed class HistoryEventToStore |
||||
|
{ |
||||
|
private readonly Dictionary<string, string> parameters = new Dictionary<string, string>(); |
||||
|
|
||||
|
public string Channel { get; } |
||||
|
|
||||
|
public string Message { get; } |
||||
|
|
||||
|
public IReadOnlyDictionary<string, string> Parameters |
||||
|
{ |
||||
|
get { return parameters; } |
||||
|
} |
||||
|
|
||||
|
public HistoryEventToStore(string channel, string message) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(channel, nameof(channel)); |
||||
|
Guard.NotNullOrEmpty(message, nameof(message)); |
||||
|
|
||||
|
Channel = channel; |
||||
|
|
||||
|
Message = message; |
||||
|
} |
||||
|
|
||||
|
public HistoryEventToStore AddParameter(string key, object value) |
||||
|
{ |
||||
|
parameters[key] = value.ToString(); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
// ==========================================================================
|
||||
|
// HistoryEventsCreatorBase.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.History |
||||
|
{ |
||||
|
public abstract class HistoryEventsCreatorBase : IHistoryEventsCreator |
||||
|
{ |
||||
|
private readonly Dictionary<string, string> texts = new Dictionary<string, string>(); |
||||
|
private readonly TypeNameRegistry typeNameRegistry; |
||||
|
|
||||
|
public IReadOnlyDictionary<string, string> Texts |
||||
|
{ |
||||
|
get { return texts; } |
||||
|
} |
||||
|
|
||||
|
protected HistoryEventsCreatorBase(TypeNameRegistry typeNameRegistry) |
||||
|
{ |
||||
|
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); |
||||
|
|
||||
|
this.typeNameRegistry = typeNameRegistry; |
||||
|
} |
||||
|
|
||||
|
protected void AddEventMessage<TEvent>(string message) where TEvent : IEvent |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(message, nameof(message)); |
||||
|
|
||||
|
texts[typeNameRegistry.GetName<TEvent>()] = message; |
||||
|
} |
||||
|
|
||||
|
protected bool HasEventText(IEvent @event) |
||||
|
{ |
||||
|
var message = typeNameRegistry.GetName(@event.GetType()); |
||||
|
|
||||
|
return texts.ContainsKey(message); |
||||
|
} |
||||
|
|
||||
|
protected HistoryEventToStore ForEvent(IEvent @event, string channel) |
||||
|
{ |
||||
|
var message = typeNameRegistry.GetName(@event.GetType()); |
||||
|
|
||||
|
return new HistoryEventToStore(channel, message); |
||||
|
} |
||||
|
|
||||
|
public Task<HistoryEventToStore> CreateEventAsync(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
if (HasEventText(@event.Payload)) |
||||
|
{ |
||||
|
return CreateEventCoreAsync(@event); |
||||
|
} |
||||
|
|
||||
|
return Task.FromResult<HistoryEventToStore>(null); |
||||
|
} |
||||
|
|
||||
|
protected abstract Task<HistoryEventToStore> CreateEventCoreAsync(Envelope<IEvent> @event); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IHistoryEventEntity.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.History |
||||
|
{ |
||||
|
public interface IHistoryEventEntity : IEntity |
||||
|
{ |
||||
|
Guid EventId { get; } |
||||
|
|
||||
|
RefToken Actor { get; } |
||||
|
|
||||
|
string Message { get; } |
||||
|
|
||||
|
long Version { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IHistoryEventsCreator.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.History |
||||
|
{ |
||||
|
public interface IHistoryEventsCreator |
||||
|
{ |
||||
|
IReadOnlyDictionary<string, string> Texts { get; } |
||||
|
|
||||
|
Task<HistoryEventToStore> CreateEventAsync(Envelope<IEvent> @event); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IHistoryEventRepository.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.History.Repositories |
||||
|
{ |
||||
|
public interface IHistoryEventRepository |
||||
|
{ |
||||
|
Task<IReadOnlyList<IHistoryEventEntity>> QueryByChannelAsync(Guid appId, string channelPrefix, int count); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IAppProvider.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Apps; |
||||
|
using Squidex.Domain.Apps.Entities.Rules; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public interface IAppProvider |
||||
|
{ |
||||
|
Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(string appName, Guid id); |
||||
|
|
||||
|
Task<IAppEntity> GetAppAsync(string appName); |
||||
|
|
||||
|
Task<ISchemaEntity> GetSchemaAsync(string appName, Guid id, bool provideDeleted = false); |
||||
|
|
||||
|
Task<ISchemaEntity> GetSchemaAsync(string appName, string name, bool provideDeleted = false); |
||||
|
|
||||
|
Task<List<ISchemaEntity>> GetSchemasAsync(string appName); |
||||
|
|
||||
|
Task<List<IRuleEntity>> GetRulesAsync(string appName); |
||||
|
|
||||
|
Task<List<IAppEntity>> GetUserApps(string userId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IEntity.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using NodaTime; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public interface IEntity |
||||
|
{ |
||||
|
Guid Id { get; set; } |
||||
|
|
||||
|
Instant Created { get; set; } |
||||
|
|
||||
|
Instant LastModified { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IEntityWithAppRef.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public interface IEntityWithAppRef |
||||
|
{ |
||||
|
Guid AppId { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IEntityWithCreatedBy.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public interface IEntityWithCreatedBy |
||||
|
{ |
||||
|
RefToken CreatedBy { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IEntityWithLastModifiedBy.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public interface IEntityWithLastModifiedBy |
||||
|
{ |
||||
|
RefToken LastModifiedBy { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IEntityWithVersion.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public interface IEntityWithVersion |
||||
|
{ |
||||
|
long Version { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IUpdateableEntityWithAppRef.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public interface IUpdateableEntityWithAppRef |
||||
|
{ |
||||
|
Guid AppId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IUpdateableEntityWithCreatedBy.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public interface IUpdateableEntityWithCreatedBy |
||||
|
{ |
||||
|
RefToken CreatedBy { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IUpdateableEntityWithLastModifiedBy.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public interface IUpdateableEntityWithLastModifiedBy |
||||
|
{ |
||||
|
RefToken LastModifiedBy { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IUpdateableEntityWithVersion.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public interface IUpdateableEntityWithVersion |
||||
|
{ |
||||
|
long Version { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
// ==========================================================================
|
||||
|
// CreateRule.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Commands |
||||
|
{ |
||||
|
public sealed class CreateRule : RuleEditCommand |
||||
|
{ |
||||
|
public CreateRule() |
||||
|
{ |
||||
|
RuleId = Guid.NewGuid(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DeleteRule.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Commands |
||||
|
{ |
||||
|
public sealed class DeleteRule : RuleAggregateCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DisableRule.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Commands |
||||
|
{ |
||||
|
public sealed class DisableRule : RuleAggregateCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EnableRule.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Commands |
||||
|
{ |
||||
|
public sealed class EnableRule : RuleAggregateCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleAggregateCommand.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Commands |
||||
|
{ |
||||
|
public abstract class RuleAggregateCommand : AppCommand, IAggregateCommand |
||||
|
{ |
||||
|
public Guid RuleId { get; set; } |
||||
|
|
||||
|
Guid IAggregateCommand.AggregateId |
||||
|
{ |
||||
|
get { return RuleId; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleEditCommand.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Commands |
||||
|
{ |
||||
|
public abstract class RuleEditCommand : RuleAggregateCommand |
||||
|
{ |
||||
|
public RuleTrigger Trigger { get; set; } |
||||
|
|
||||
|
public RuleAction Action { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// UpdateRule.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Commands |
||||
|
{ |
||||
|
public sealed class UpdateRule : RuleEditCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
// ==========================================================================
|
||||
|
// GuardRule.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Entities; |
||||
|
using Squidex.Domain.Apps.Entities.Rules.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Guards |
||||
|
{ |
||||
|
public static class GuardRule |
||||
|
{ |
||||
|
public static Task CanCreate(CreateRule command, IAppProvider appProvider) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
return Validate.It(() => "Cannot create rule.", async error => |
||||
|
{ |
||||
|
if (command.Trigger == null) |
||||
|
{ |
||||
|
error(new ValidationError("Trigger must be defined.", nameof(command.Trigger))); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Name, command.Trigger, appProvider); |
||||
|
|
||||
|
errors.Foreach(error); |
||||
|
} |
||||
|
|
||||
|
if (command.Action == null) |
||||
|
{ |
||||
|
error(new ValidationError("Trigger must be defined.", nameof(command.Action))); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var errors = await RuleActionValidator.ValidateAsync(command.Action); |
||||
|
|
||||
|
errors.Foreach(error); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static Task CanUpdate(UpdateRule command, IAppProvider appProvider) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
return Validate.It(() => "Cannot update rule.", async error => |
||||
|
{ |
||||
|
if (command.Trigger == null && command.Action == null) |
||||
|
{ |
||||
|
error(new ValidationError("Either trigger or action must be defined.", nameof(command.Trigger), nameof(command.Action))); |
||||
|
} |
||||
|
|
||||
|
if (command.Trigger != null) |
||||
|
{ |
||||
|
var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Name, command.Trigger, appProvider); |
||||
|
|
||||
|
errors.Foreach(error); |
||||
|
} |
||||
|
|
||||
|
if (command.Action != null) |
||||
|
{ |
||||
|
var errors = await RuleActionValidator.ValidateAsync(command.Action); |
||||
|
|
||||
|
errors.Foreach(error); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanEnable(EnableRule command, Rule rule) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
Validate.It(() => "Cannot enable rule.", error => |
||||
|
{ |
||||
|
if (rule.IsEnabled) |
||||
|
{ |
||||
|
error(new ValidationError("Rule is already enabled.")); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanDisable(DisableRule command, Rule rule) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
Validate.It(() => "Cannot disable rule.", error => |
||||
|
{ |
||||
|
if (!rule.IsEnabled) |
||||
|
{ |
||||
|
error(new ValidationError("Rule is already disabled.")); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanDelete(DeleteRule command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleActionValidator.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Guards |
||||
|
{ |
||||
|
public sealed class RuleActionValidator : IRuleActionVisitor<Task<IEnumerable<ValidationError>>> |
||||
|
{ |
||||
|
public static Task<IEnumerable<ValidationError>> ValidateAsync(RuleAction action) |
||||
|
{ |
||||
|
Guard.NotNull(action, nameof(action)); |
||||
|
|
||||
|
var visitor = new RuleActionValidator(); |
||||
|
|
||||
|
return action.Accept(visitor); |
||||
|
} |
||||
|
|
||||
|
public Task<IEnumerable<ValidationError>> Visit(WebhookAction action) |
||||
|
{ |
||||
|
var errors = new List<ValidationError>(); |
||||
|
|
||||
|
if (action.Url == null || !action.Url.IsAbsoluteUri) |
||||
|
{ |
||||
|
errors.Add(new ValidationError("Url must be specified and absolute.", nameof(action.Url))); |
||||
|
} |
||||
|
|
||||
|
return Task.FromResult<IEnumerable<ValidationError>>(errors); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleTriggerValidator.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
using Squidex.Domain.Apps.Entities; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Guards |
||||
|
{ |
||||
|
public sealed class RuleTriggerValidator : IRuleTriggerVisitor<Task<IEnumerable<ValidationError>>> |
||||
|
{ |
||||
|
public Func<Guid, Task<ISchemaEntity>> SchemaProvider { get; } |
||||
|
|
||||
|
public RuleTriggerValidator(Func<Guid, Task<ISchemaEntity>> schemaProvider) |
||||
|
{ |
||||
|
SchemaProvider = schemaProvider; |
||||
|
} |
||||
|
|
||||
|
public static Task<IEnumerable<ValidationError>> ValidateAsync(string appName, RuleTrigger action, IAppProvider appProvider) |
||||
|
{ |
||||
|
Guard.NotNull(action, nameof(action)); |
||||
|
Guard.NotNull(appProvider, nameof(appProvider)); |
||||
|
|
||||
|
var visitor = new RuleTriggerValidator(x => appProvider.GetSchemaAsync(appName, x)); |
||||
|
|
||||
|
return action.Accept(visitor); |
||||
|
} |
||||
|
|
||||
|
public async Task<IEnumerable<ValidationError>> Visit(ContentChangedTrigger trigger) |
||||
|
{ |
||||
|
if (trigger.Schemas != null) |
||||
|
{ |
||||
|
var schemaErrors = await Task.WhenAll( |
||||
|
trigger.Schemas.Select(async s => |
||||
|
await SchemaProvider(s.SchemaId) == null |
||||
|
? new ValidationError($"Schema {s.SchemaId} does not exist.", nameof(trigger.Schemas)) |
||||
|
: null)); |
||||
|
|
||||
|
return schemaErrors.Where(x => x != null).ToList(); |
||||
|
} |
||||
|
|
||||
|
return new List<ValidationError>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IRuleEntity.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules |
||||
|
{ |
||||
|
public interface IRuleEntity : |
||||
|
IEntity, |
||||
|
IEntityWithAppRef, |
||||
|
IEntityWithCreatedBy, |
||||
|
IEntityWithLastModifiedBy, |
||||
|
IEntityWithVersion |
||||
|
{ |
||||
|
Rule RuleDef { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IRuleEventEntity.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules |
||||
|
{ |
||||
|
public interface IRuleEventEntity : IEntity |
||||
|
{ |
||||
|
RuleJob Job { get; } |
||||
|
|
||||
|
Instant? NextAttempt { get; } |
||||
|
|
||||
|
RuleJobResult JobResult { get; } |
||||
|
|
||||
|
RuleResult Result { get; } |
||||
|
|
||||
|
int NumCalls { get; } |
||||
|
|
||||
|
string LastDump { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IRuleEventRepository.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Repositories |
||||
|
{ |
||||
|
public interface IRuleEventRepository |
||||
|
{ |
||||
|
Task EnqueueAsync(RuleJob job, Instant nextAttempt); |
||||
|
|
||||
|
Task EnqueueAsync(Guid id, Instant nextAttempt); |
||||
|
|
||||
|
Task MarkSentAsync(Guid jobId, string dump, RuleResult result, RuleJobResult jobResult, TimeSpan elapsed, Instant? nextCall); |
||||
|
|
||||
|
Task QueryPendingAsync(Instant now, Func<IRuleEventEntity, Task> callback, CancellationToken cancellationToken = default(CancellationToken)); |
||||
|
|
||||
|
Task<int> CountByAppAsync(Guid appId); |
||||
|
|
||||
|
Task<IReadOnlyList<IRuleEventEntity>> QueryByAppAsync(Guid appId, int skip = 0, int take = 20); |
||||
|
|
||||
|
Task<IRuleEventEntity> FindAsync(Guid id); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleCommandMiddleware.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Rules.Commands; |
||||
|
using Squidex.Domain.Apps.Entities.Rules.Guards; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Dispatching; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules |
||||
|
{ |
||||
|
public class RuleCommandMiddleware : ICommandMiddleware |
||||
|
{ |
||||
|
private readonly IAggregateHandler handler; |
||||
|
private readonly IAppProvider appProvider; |
||||
|
|
||||
|
public RuleCommandMiddleware(IAggregateHandler handler, IAppProvider appProvider) |
||||
|
{ |
||||
|
Guard.NotNull(handler, nameof(handler)); |
||||
|
Guard.NotNull(appProvider, nameof(appProvider)); |
||||
|
|
||||
|
this.handler = handler; |
||||
|
|
||||
|
this.appProvider = appProvider; |
||||
|
} |
||||
|
|
||||
|
protected Task On(CreateRule command, CommandContext context) |
||||
|
{ |
||||
|
return handler.CreateAsync<RuleDomainObject>(context, async w => |
||||
|
{ |
||||
|
await GuardRule.CanCreate(command, appProvider); |
||||
|
|
||||
|
w.Create(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(UpdateRule command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<RuleDomainObject>(context, async c => |
||||
|
{ |
||||
|
await GuardRule.CanUpdate(command, appProvider); |
||||
|
|
||||
|
c.Update(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(EnableRule command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<RuleDomainObject>(context, r => |
||||
|
{ |
||||
|
GuardRule.CanEnable(command, r.State.RuleDef); |
||||
|
|
||||
|
r.Enable(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(DisableRule command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<RuleDomainObject>(context, r => |
||||
|
{ |
||||
|
GuardRule.CanDisable(command, r.State.RuleDef); |
||||
|
|
||||
|
r.Disable(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(DeleteRule command, CommandContext context) |
||||
|
{ |
||||
|
return handler.UpdateAsync<RuleDomainObject>(context, c => |
||||
|
{ |
||||
|
GuardRule.CanDelete(command); |
||||
|
|
||||
|
c.Delete(command); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public async Task HandleAsync(CommandContext context, Func<Task> next) |
||||
|
{ |
||||
|
if (!await this.DispatchActionAsync(context.Command, context)) |
||||
|
{ |
||||
|
await next(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,157 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleDequeuer.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Threading.Tasks.Dataflow; |
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Entities.Rules.Repositories; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
using Squidex.Infrastructure.Timers; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules |
||||
|
{ |
||||
|
public sealed class RuleDequeuer : DisposableObjectBase, IExternalSystem |
||||
|
{ |
||||
|
private readonly ActionBlock<IRuleEventEntity> requestBlock; |
||||
|
private readonly IRuleEventRepository ruleEventRepository; |
||||
|
private readonly RuleService ruleService; |
||||
|
private readonly CompletionTimer timer; |
||||
|
private readonly ConcurrentDictionary<Guid, bool> executing = new ConcurrentDictionary<Guid, bool>(); |
||||
|
private readonly IClock clock; |
||||
|
private readonly ISemanticLog log; |
||||
|
|
||||
|
public RuleDequeuer(RuleService ruleService, IRuleEventRepository ruleEventRepository, ISemanticLog log, IClock clock) |
||||
|
{ |
||||
|
Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository)); |
||||
|
Guard.NotNull(ruleService, nameof(ruleService)); |
||||
|
Guard.NotNull(clock, nameof(clock)); |
||||
|
Guard.NotNull(log, nameof(log)); |
||||
|
|
||||
|
this.ruleEventRepository = ruleEventRepository; |
||||
|
this.ruleService = ruleService; |
||||
|
|
||||
|
this.clock = clock; |
||||
|
|
||||
|
this.log = log; |
||||
|
|
||||
|
requestBlock = |
||||
|
new ActionBlock<IRuleEventEntity>(HandleAsync, |
||||
|
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 32, BoundedCapacity = 32 }); |
||||
|
|
||||
|
timer = new CompletionTimer(5000, QueryAsync); |
||||
|
} |
||||
|
|
||||
|
protected override void DisposeObject(bool disposing) |
||||
|
{ |
||||
|
if (disposing) |
||||
|
{ |
||||
|
timer.StopAsync().Wait(); |
||||
|
|
||||
|
requestBlock.Complete(); |
||||
|
requestBlock.Completion.Wait(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Connect() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void Next() |
||||
|
{ |
||||
|
timer.SkipCurrentDelay(); |
||||
|
} |
||||
|
|
||||
|
private async Task QueryAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var now = clock.GetCurrentInstant(); |
||||
|
|
||||
|
await ruleEventRepository.QueryPendingAsync(now, requestBlock.SendAsync, cancellationToken); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
log.LogError(ex, w => w |
||||
|
.WriteProperty("action", "QueueWebhookEvents") |
||||
|
.WriteProperty("status", "Failed")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task HandleAsync(IRuleEventEntity @event) |
||||
|
{ |
||||
|
if (!executing.TryAdd(@event.Id, false)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var job = @event.Job; |
||||
|
|
||||
|
var response = await ruleService.InvokeAsync(job.ActionName, job.ActionData); |
||||
|
|
||||
|
var jobInvoke = ComputeJobInvoke(response.Result, @event, job); |
||||
|
var jobResult = ComputeJobResult(response.Result, jobInvoke); |
||||
|
|
||||
|
await ruleEventRepository.MarkSentAsync(@event.Id, response.Dump, response.Result, jobResult, response.Elapsed, jobInvoke); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
log.LogError(ex, w => w |
||||
|
.WriteProperty("action", "SendWebhookEvent") |
||||
|
.WriteProperty("status", "Failed")); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
executing.TryRemove(@event.Id, out var value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static RuleJobResult ComputeJobResult(RuleResult result, Instant? nextCall) |
||||
|
{ |
||||
|
if (result != RuleResult.Success && !nextCall.HasValue) |
||||
|
{ |
||||
|
return RuleJobResult.Failed; |
||||
|
} |
||||
|
else if (result != RuleResult.Success && nextCall.HasValue) |
||||
|
{ |
||||
|
return RuleJobResult.Retry; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return RuleJobResult.Success; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static Instant? ComputeJobInvoke(RuleResult result, IRuleEventEntity @event, RuleJob job) |
||||
|
{ |
||||
|
if (result != RuleResult.Success) |
||||
|
{ |
||||
|
switch (@event.NumCalls) |
||||
|
{ |
||||
|
case 0: |
||||
|
return job.Created.Plus(Duration.FromMinutes(5)); |
||||
|
case 1: |
||||
|
return job.Created.Plus(Duration.FromHours(1)); |
||||
|
case 2: |
||||
|
return job.Created.Plus(Duration.FromHours(6)); |
||||
|
case 3: |
||||
|
return job.Created.Plus(Duration.FromHours(12)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleDomainObject.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Entities.Rules.Commands; |
||||
|
using Squidex.Domain.Apps.Entities.Rules.State; |
||||
|
using Squidex.Domain.Apps.Events.Rules; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules |
||||
|
{ |
||||
|
public class RuleDomainObject : DomainObjectBase<RuleDomainObject, RuleState> |
||||
|
{ |
||||
|
public void Create(CreateRule command) |
||||
|
{ |
||||
|
VerifyNotCreated(); |
||||
|
|
||||
|
UpdateRule(command, r => new Rule(command.Trigger, command.Action)); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new RuleCreated())); |
||||
|
} |
||||
|
|
||||
|
public void Update(UpdateRule command) |
||||
|
{ |
||||
|
VerifyCreatedAndNotDeleted(); |
||||
|
|
||||
|
UpdateRule(command, r => r.Update(command.Trigger).Update(command.Action)); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new RuleUpdated())); |
||||
|
} |
||||
|
|
||||
|
public void Enable(EnableRule command) |
||||
|
{ |
||||
|
VerifyCreatedAndNotDeleted(); |
||||
|
|
||||
|
UpdateRule(command, r => r.Enable()); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new RuleEnabled())); |
||||
|
} |
||||
|
|
||||
|
public void Disable(DisableRule command) |
||||
|
{ |
||||
|
VerifyCreatedAndNotDeleted(); |
||||
|
|
||||
|
UpdateRule(command, r => r.Disable()); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new RuleDisabled())); |
||||
|
} |
||||
|
|
||||
|
public void Delete(DeleteRule command) |
||||
|
{ |
||||
|
VerifyCreatedAndNotDeleted(); |
||||
|
|
||||
|
UpdateState(command, s => s.IsDeleted = true); |
||||
|
|
||||
|
RaiseEvent(SimpleMapper.Map(command, new RuleDeleted())); |
||||
|
} |
||||
|
|
||||
|
private void VerifyNotCreated() |
||||
|
{ |
||||
|
if (State.RuleDef != null) |
||||
|
{ |
||||
|
throw new DomainException("Webhook has already been created."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void VerifyCreatedAndNotDeleted() |
||||
|
{ |
||||
|
if (State.IsDeleted || State.RuleDef == null) |
||||
|
{ |
||||
|
throw new DomainException("Webhook has already been deleted or not created yet."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void UpdateRule(ICommand command, Func<Rule, Rule> updater) |
||||
|
{ |
||||
|
UpdateState(command, s => s.RuleDef = updater(s.RuleDef)); |
||||
|
} |
||||
|
|
||||
|
protected override RuleState CloneState(ICommand command, Action<RuleState> updater) |
||||
|
{ |
||||
|
return State.Clone().Update((SquidexCommand)command, updater); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleEnqueuer.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Entities.Rules.Repositories; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules |
||||
|
{ |
||||
|
public sealed class RuleEnqueuer : IEventConsumer |
||||
|
{ |
||||
|
private readonly IRuleEventRepository ruleEventRepository; |
||||
|
private readonly IAppProvider appProvider; |
||||
|
private readonly RuleService ruleService; |
||||
|
|
||||
|
public string Name |
||||
|
{ |
||||
|
get { return GetType().Name; } |
||||
|
} |
||||
|
|
||||
|
public string EventsFilter |
||||
|
{ |
||||
|
get { return ".*"; } |
||||
|
} |
||||
|
|
||||
|
public RuleEnqueuer( |
||||
|
IRuleEventRepository ruleEventRepository, IAppProvider appProvider, |
||||
|
RuleService ruleService) |
||||
|
{ |
||||
|
Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository)); |
||||
|
Guard.NotNull(ruleService, nameof(ruleService)); |
||||
|
|
||||
|
Guard.NotNull(appProvider, nameof(appProvider)); |
||||
|
|
||||
|
this.ruleEventRepository = ruleEventRepository; |
||||
|
this.ruleService = ruleService; |
||||
|
|
||||
|
this.appProvider = appProvider; |
||||
|
} |
||||
|
|
||||
|
public Task ClearAsync() |
||||
|
{ |
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
|
||||
|
public async Task On(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
if (@event.Payload is AppEvent appEvent) |
||||
|
{ |
||||
|
var rules = await appProvider.GetRulesAsync(appEvent.AppId.Name); |
||||
|
|
||||
|
foreach (var ruleEntity in rules) |
||||
|
{ |
||||
|
var job = ruleService.CreateJob(ruleEntity.RuleDef, @event); |
||||
|
|
||||
|
if (job != null) |
||||
|
{ |
||||
|
await ruleEventRepository.EnqueueAsync(job, job.Created); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleJobResult.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules |
||||
|
{ |
||||
|
public enum RuleJobResult |
||||
|
{ |
||||
|
Pending, |
||||
|
Success, |
||||
|
Retry, |
||||
|
Failed |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleState.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Newtonsoft.Json; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.State |
||||
|
{ |
||||
|
public sealed class RuleState : DomainObjectState<RuleState>, IRuleEntity |
||||
|
{ |
||||
|
[JsonProperty] |
||||
|
public Guid AppId { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public Rule RuleDef { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public bool IsDeleted { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SchemaAggregateCommand.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public abstract class SchemaAggregateCommand : SchemaCommand, IAggregateCommand |
||||
|
{ |
||||
|
Guid IAggregateCommand.AggregateId |
||||
|
{ |
||||
|
get { return SchemaId.Id; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SchemaCommand.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities |
||||
|
{ |
||||
|
public abstract class SchemaCommand : AppCommand |
||||
|
{ |
||||
|
public NamedId<Guid> SchemaId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AddField.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class AddField : SchemaAggregateCommand |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string Partitioning { get; set; } |
||||
|
|
||||
|
public FieldProperties Properties { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ConfigureScripts.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class ConfigureScripts : SchemaAggregateCommand |
||||
|
{ |
||||
|
public string ScriptQuery { get; set; } |
||||
|
|
||||
|
public string ScriptCreate { get; set; } |
||||
|
|
||||
|
public string ScriptUpdate { get; set; } |
||||
|
|
||||
|
public string ScriptDelete { get; set; } |
||||
|
|
||||
|
public string ScriptChange { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// ==========================================================================
|
||||
|
// CreateSchema.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using SchemaFields = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.CreateSchemaField>; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class CreateSchema : AppCommand, IAggregateCommand |
||||
|
{ |
||||
|
public Guid SchemaId { get; set; } |
||||
|
|
||||
|
public SchemaFields Fields { get; set; } |
||||
|
|
||||
|
public SchemaProperties Properties { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
Guid IAggregateCommand.AggregateId |
||||
|
{ |
||||
|
get { return SchemaId; } |
||||
|
} |
||||
|
|
||||
|
public CreateSchema() |
||||
|
{ |
||||
|
SchemaId = Guid.NewGuid(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// ==========================================================================
|
||||
|
// CreateSchemaField.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class CreateSchemaField |
||||
|
{ |
||||
|
public string Partitioning { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public bool IsHidden { get; set; } |
||||
|
|
||||
|
public bool IsLocked { get; set; } |
||||
|
|
||||
|
public bool IsDisabled { get; set; } |
||||
|
|
||||
|
public FieldProperties Properties { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DeleteField.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class DeleteField : FieldCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DeleteSchema.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class DeleteSchema : SchemaAggregateCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DisableField.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class DisableField : FieldCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EnableField.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class EnableField : FieldCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// ==========================================================================
|
||||
|
// FieldCommand.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public class FieldCommand : SchemaAggregateCommand |
||||
|
{ |
||||
|
public long FieldId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// HideField.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class HideField : FieldCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// LockField.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class LockField : FieldCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// PublishSchema.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class PublishSchema : SchemaAggregateCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ReorderFields.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class ReorderFields : SchemaAggregateCommand |
||||
|
{ |
||||
|
public List<long> FieldIds { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ShowField.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class ShowField : FieldCommand |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue