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