mirror of https://github.com/Squidex/squidex.git
189 changed files with 270 additions and 8734 deletions
@ -1,77 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppEventDispatcher.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Linq; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Apps.Utils |
|||
{ |
|||
public static class AppEventDispatcher |
|||
{ |
|||
public static AppContributors Apply(this AppContributors contributors, AppContributorRemoved @event) |
|||
{ |
|||
return contributors.Remove(@event.ContributorId); |
|||
} |
|||
|
|||
public static AppContributors Apply(this AppContributors contributors, AppContributorAssigned @event) |
|||
{ |
|||
return contributors.Assign(@event.ContributorId, @event.Permission); |
|||
} |
|||
|
|||
public static LanguagesConfig Apply(this LanguagesConfig languagesConfig, AppLanguageAdded @event) |
|||
{ |
|||
return languagesConfig.Set(new LanguageConfig(@event.Language)); |
|||
} |
|||
|
|||
public static LanguagesConfig Apply(this LanguagesConfig languagesConfig, AppLanguageRemoved @event) |
|||
{ |
|||
return languagesConfig.Remove(@event.Language); |
|||
} |
|||
|
|||
public static AppClients Apply(this AppClients clients, AppClientAttached @event) |
|||
{ |
|||
return clients.Add(@event.Id, @event.Secret); |
|||
} |
|||
|
|||
public static AppClients Apply(this AppClients clients, AppClientRevoked @event) |
|||
{ |
|||
return clients.Revoke(@event.Id); |
|||
} |
|||
|
|||
public static AppClients Apply(this AppClients clients, AppClientRenamed @event) |
|||
{ |
|||
return clients.Rename(@event.Id, @event.Name); |
|||
} |
|||
|
|||
public static AppClients Apply(this AppClients clients, AppClientUpdated @event) |
|||
{ |
|||
return clients.Update(@event.Id, @event.Permission); |
|||
} |
|||
|
|||
public static LanguagesConfig Apply(this LanguagesConfig languagesConfig, AppLanguageUpdated @event) |
|||
{ |
|||
var fallback = @event.Fallback; |
|||
|
|||
if (fallback != null && fallback.Count > 0) |
|||
{ |
|||
var existingLangauges = languagesConfig.OfType<LanguageConfig>().Select(x => x.Language); |
|||
|
|||
fallback = fallback.Intersect(existingLangauges).ToList(); |
|||
} |
|||
|
|||
languagesConfig = languagesConfig.Set(new LanguageConfig(@event.Language, @event.IsOptional, fallback)); |
|||
|
|||
if (@event.IsMaster) |
|||
{ |
|||
languagesConfig = languagesConfig.MakeMaster(@event.Language); |
|||
} |
|||
|
|||
return languagesConfig; |
|||
} |
|||
} |
|||
} |
|||
@ -1,45 +0,0 @@ |
|||
// ==========================================================================
|
|||
// RuleEventDispatcher.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Rules.Utils |
|||
{ |
|||
public static class RuleEventDispatcher |
|||
{ |
|||
public static Rule Create(RuleCreated @event) |
|||
{ |
|||
return new Rule(@event.Trigger, @event.Action); |
|||
} |
|||
|
|||
public static Rule Apply(this Rule rule, RuleUpdated @event) |
|||
{ |
|||
if (@event.Trigger != null) |
|||
{ |
|||
return rule.Update(@event.Trigger); |
|||
} |
|||
|
|||
if (@event.Action != null) |
|||
{ |
|||
return rule.Update(@event.Action); |
|||
} |
|||
|
|||
return rule; |
|||
} |
|||
|
|||
public static Rule Apply(this Rule rule, RuleEnabled @event) |
|||
{ |
|||
return rule.Enable(); |
|||
} |
|||
|
|||
public static Rule Apply(this Rule rule, RuleDisabled @event) |
|||
{ |
|||
return rule.Disable(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,133 +0,0 @@ |
|||
// ==========================================================================
|
|||
// SchemaEventDispatcher.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Domain.Apps.Core; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Schemas.Utils |
|||
{ |
|||
public static class SchemaEventDispatcher |
|||
{ |
|||
public static Schema Create(SchemaCreated @event, FieldRegistry registry) |
|||
{ |
|||
var schema = new Schema(@event.Name); |
|||
|
|||
if (@event.Properties != null) |
|||
{ |
|||
schema = schema.Update(@event.Properties); |
|||
} |
|||
|
|||
if (@event.Fields != null) |
|||
{ |
|||
var fieldId = 1; |
|||
|
|||
foreach (var eventField in @event.Fields) |
|||
{ |
|||
var partitioning = |
|||
string.Equals(eventField.Partitioning, Partitioning.Language.Key, StringComparison.OrdinalIgnoreCase) ? |
|||
Partitioning.Language : |
|||
Partitioning.Invariant; |
|||
|
|||
var field = registry.CreateField(fieldId, eventField.Name, partitioning, eventField.Properties); |
|||
|
|||
if (eventField.IsHidden) |
|||
{ |
|||
field = field.Hide(); |
|||
} |
|||
|
|||
if (eventField.IsDisabled) |
|||
{ |
|||
field = field.Disable(); |
|||
} |
|||
|
|||
if (eventField.IsLocked) |
|||
{ |
|||
field = field.Lock(); |
|||
} |
|||
|
|||
schema = schema.AddField(field); |
|||
|
|||
fieldId++; |
|||
} |
|||
} |
|||
|
|||
return schema; |
|||
} |
|||
|
|||
public static Schema Apply(this Schema schema, FieldAdded @event, FieldRegistry registry) |
|||
{ |
|||
var partitioning = |
|||
string.Equals(@event.Partitioning, Partitioning.Language.Key, StringComparison.OrdinalIgnoreCase) ? |
|||
Partitioning.Language : |
|||
Partitioning.Invariant; |
|||
|
|||
var field = registry.CreateField(@event.FieldId.Id, @event.Name, partitioning, @event.Properties); |
|||
|
|||
schema = schema.DeleteField(@event.FieldId.Id); |
|||
schema = schema.AddField(field); |
|||
|
|||
return schema; |
|||
} |
|||
|
|||
public static Schema Apply(this Schema schema, FieldUpdated @event) |
|||
{ |
|||
return schema.UpdateField(@event.FieldId.Id, @event.Properties); |
|||
} |
|||
|
|||
public static Schema Apply(this Schema schema, FieldLocked @event) |
|||
{ |
|||
return schema.LockField(@event.FieldId.Id); |
|||
} |
|||
|
|||
public static Schema Apply(this Schema schema, FieldHidden @event) |
|||
{ |
|||
return schema.HideField(@event.FieldId.Id); |
|||
} |
|||
|
|||
public static Schema Apply(this Schema schema, FieldShown @event) |
|||
{ |
|||
return schema.ShowField(@event.FieldId.Id); |
|||
} |
|||
|
|||
public static Schema Apply(this Schema schema, FieldDisabled @event) |
|||
{ |
|||
return schema.DisableField(@event.FieldId.Id); |
|||
} |
|||
|
|||
public static Schema Apply(this Schema schema, FieldEnabled @event) |
|||
{ |
|||
return schema.EnableField(@event.FieldId.Id); |
|||
} |
|||
|
|||
public static Schema Apply(this Schema schema, SchemaUpdated @event) |
|||
{ |
|||
return schema.Update(@event.Properties); |
|||
} |
|||
|
|||
public static Schema Apply(this Schema schema, SchemaFieldsReordered @event) |
|||
{ |
|||
return schema.ReorderFields(@event.FieldIds); |
|||
} |
|||
|
|||
public static Schema Apply(this Schema schema, FieldDeleted @event) |
|||
{ |
|||
return schema.DeleteField(@event.FieldId.Id); |
|||
} |
|||
|
|||
public static Schema Apply(this Schema schema, SchemaPublished @event) |
|||
{ |
|||
return schema.Publish(); |
|||
} |
|||
|
|||
public static Schema Apply(this Schema schema, SchemaUnpublished @event) |
|||
{ |
|||
return schema.Unpublish(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,20 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppEntityExtensions.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Apps |
|||
{ |
|||
public static class AppEntityExtensions |
|||
{ |
|||
public static PartitionResolver PartitionResolver(this IAppEntity entity) |
|||
{ |
|||
return entity.LanguagesConfig.ToResolver(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,145 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppHistoryEventsCreator.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Events.Apps; |
|||
using Squidex.Domain.Apps.Read.History; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.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; |
|||
} |
|||
} |
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IAppEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Apps |
|||
{ |
|||
public interface IAppEntity : IEntity, IEntityWithVersion |
|||
{ |
|||
string Etag { get; } |
|||
|
|||
string Name { get; } |
|||
|
|||
string PlanId { get; } |
|||
|
|||
string PlanOwner { get; } |
|||
|
|||
AppClients Clients { get; } |
|||
|
|||
AppContributors Contributors { get; } |
|||
|
|||
LanguagesConfig LanguagesConfig { get; } |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IAppLimitsPlan.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Read.Apps.Services |
|||
{ |
|||
public interface IAppLimitsPlan |
|||
{ |
|||
string Id { get; } |
|||
|
|||
string Name { get; } |
|||
|
|||
string Costs { get; } |
|||
|
|||
long MaxApiCalls { get; } |
|||
|
|||
long MaxAssetSize { get; } |
|||
|
|||
int MaxContributors { get; } |
|||
} |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IAppPlanBillingManager.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Apps.Services |
|||
{ |
|||
public interface IAppPlanBillingManager |
|||
{ |
|||
bool HasPortal { get; } |
|||
|
|||
Task<IChangePlanResult> ChangePlanAsync(string userId, Guid appId, string appName, string planId); |
|||
|
|||
Task<string> GetPortalLinkAsync(string userId); |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IAppPlansProvider.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.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); |
|||
} |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IChangePlanResult.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Read.Apps.Services |
|||
{ |
|||
public interface IChangePlanResult |
|||
{ |
|||
} |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
// ==========================================================================
|
|||
// ConfigAppLimitsPlan.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Read.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(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,86 +0,0 @@ |
|||
// ==========================================================================
|
|||
// ConfigAppLimitsProvider.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.Read.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.PlanId); |
|||
} |
|||
|
|||
public IAppLimitsPlan GetPlan(string planId) |
|||
{ |
|||
return GetPlanCore(planId); |
|||
} |
|||
|
|||
public IAppLimitsPlan GetPlanUpgradeForApp(IAppEntity app) |
|||
{ |
|||
Guard.NotNull(app, nameof(app)); |
|||
|
|||
return GetPlanUpgrade(app.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; |
|||
} |
|||
} |
|||
} |
|||
@ -1,31 +0,0 @@ |
|||
// ==========================================================================
|
|||
// NoopAppPlanBillingManager.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.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); |
|||
} |
|||
} |
|||
} |
|||
@ -1,19 +0,0 @@ |
|||
// ==========================================================================
|
|||
// PlanChangeAsyncResult.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Read.Apps.Services |
|||
{ |
|||
public sealed class PlanChangeAsyncResult : IChangePlanResult |
|||
{ |
|||
public static readonly PlanChangeAsyncResult Instance = new PlanChangeAsyncResult(); |
|||
|
|||
private PlanChangeAsyncResult() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -1,19 +0,0 @@ |
|||
// ==========================================================================
|
|||
// PlanChangedResult.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Read.Apps.Services |
|||
{ |
|||
public sealed class PlanChangedResult : IChangePlanResult |
|||
{ |
|||
public static readonly PlanChangedResult Instance = new PlanChangedResult(); |
|||
|
|||
private PlanChangedResult() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
// ==========================================================================
|
|||
// RedirectToCheckoutResult.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Apps.Services |
|||
{ |
|||
public sealed class RedirectToCheckoutResult : IChangePlanResult |
|||
{ |
|||
public Uri Url { get; } |
|||
|
|||
public RedirectToCheckoutResult(Uri url) |
|||
{ |
|||
Guard.NotNull(url, nameof(url)); |
|||
|
|||
Url = url; |
|||
} |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IAssetEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.ValidateContent; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Assets |
|||
{ |
|||
public interface IAssetEntity : |
|||
IEntity, |
|||
IEntityWithAppRef, |
|||
IEntityWithCreatedBy, |
|||
IEntityWithLastModifiedBy, |
|||
IEntityWithVersion, |
|||
IAssetInfo |
|||
{ |
|||
string MimeType { get; } |
|||
|
|||
long FileVersion { get; } |
|||
} |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IAssetEventConsumer.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Assets |
|||
{ |
|||
public interface IAssetEventConsumer : IEventConsumer |
|||
{ |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IAssetStatsEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Assets |
|||
{ |
|||
public interface IAssetStatsEntity |
|||
{ |
|||
DateTime Date { get; } |
|||
|
|||
long TotalSize { get; } |
|||
|
|||
long TotalCount { get; } |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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.Read.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); |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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.Read.Assets.Repositories |
|||
{ |
|||
public interface IAssetStatsRepository |
|||
{ |
|||
Task<IReadOnlyList<IAssetStatsEntity>> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate); |
|||
|
|||
Task<long> GetTotalSizeAsync(Guid appId); |
|||
} |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
// ==========================================================================
|
|||
// CachingProviderBase.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read |
|||
{ |
|||
public abstract class CachingProviderBase |
|||
{ |
|||
private readonly IMemoryCache cache; |
|||
|
|||
protected IMemoryCache Cache |
|||
{ |
|||
get { return cache; } |
|||
} |
|||
|
|||
protected CachingProviderBase(IMemoryCache cache) |
|||
{ |
|||
Guard.NotNull(cache, nameof(cache)); |
|||
|
|||
this.cache = cache; |
|||
} |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
// ==========================================================================
|
|||
// ContentHistoryEventsCreator.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Events.Contents; |
|||
using Squidex.Domain.Apps.Read.History; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents |
|||
{ |
|||
public sealed class ContentHistoryEventsCreator : HistoryEventsCreatorBase |
|||
{ |
|||
public ContentHistoryEventsCreator(TypeNameRegistry typeNameRegistry) |
|||
: base(typeNameRegistry) |
|||
{ |
|||
AddEventMessage<ContentCreated>( |
|||
"created content item."); |
|||
|
|||
AddEventMessage<ContentUpdated>( |
|||
"updated content item."); |
|||
|
|||
AddEventMessage<ContentDeleted>( |
|||
"deleted content item."); |
|||
|
|||
AddEventMessage<ContentStatusChanged>( |
|||
"changed status of content item to {[Status]}."); |
|||
} |
|||
|
|||
protected override Task<HistoryEventToStore> CreateEventCoreAsync(Envelope<IEvent> @event) |
|||
{ |
|||
var channel = $"contents.{@event.Headers.AggregateId()}"; |
|||
|
|||
var result = ForEvent(@event.Payload, channel); |
|||
|
|||
if (@event.Payload is ContentStatusChanged contentStatusChanged) |
|||
{ |
|||
result = result.AddParameter("Status", contentStatusChanged.Status); |
|||
} |
|||
|
|||
return Task.FromResult(result); |
|||
} |
|||
} |
|||
} |
|||
@ -1,216 +0,0 @@ |
|||
// ==========================================================================
|
|||
// ContentQueryService.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.OData; |
|||
using Microsoft.OData.UriParser; |
|||
using NodaTime; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Core.Scripting; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Contents.Edm; |
|||
using Squidex.Domain.Apps.Read.Contents.Repositories; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Reflection; |
|||
using Squidex.Infrastructure.Security; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents |
|||
{ |
|||
public sealed class ContentQueryService : IContentQueryService |
|||
{ |
|||
private readonly IContentRepository contentRepository; |
|||
private readonly IAppProvider appProvider; |
|||
private readonly IScriptEngine scriptEngine; |
|||
private readonly EdmModelBuilder modelBuilder; |
|||
|
|||
public ContentQueryService( |
|||
IContentRepository contentRepository, |
|||
IAppProvider appProvider, |
|||
IScriptEngine scriptEngine, |
|||
EdmModelBuilder modelBuilder) |
|||
{ |
|||
Guard.NotNull(contentRepository, nameof(contentRepository)); |
|||
Guard.NotNull(scriptEngine, nameof(scriptEngine)); |
|||
Guard.NotNull(modelBuilder, nameof(modelBuilder)); |
|||
Guard.NotNull(appProvider, nameof(appProvider)); |
|||
|
|||
this.contentRepository = contentRepository; |
|||
this.appProvider = appProvider; |
|||
this.scriptEngine = scriptEngine; |
|||
this.modelBuilder = modelBuilder; |
|||
} |
|||
|
|||
public async Task<(ISchemaEntity Schema, IContentEntity Content)> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id) |
|||
{ |
|||
Guard.NotNull(app, nameof(app)); |
|||
Guard.NotNull(user, nameof(user)); |
|||
Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName)); |
|||
|
|||
var isFrontendClient = user.IsInClient("squidex-frontend"); |
|||
|
|||
var schema = await FindSchemaAsync(app, schemaIdOrName); |
|||
|
|||
var content = await contentRepository.FindContentAsync(app, schema, id); |
|||
|
|||
if (content == null || (content.Status != Status.Published && !isFrontendClient)) |
|||
{ |
|||
throw new DomainObjectNotFoundException(id.ToString(), typeof(ISchemaEntity)); |
|||
} |
|||
|
|||
content = TransformContent(user, schema, new List<IContentEntity> { content })[0]; |
|||
|
|||
return (schema, content); |
|||
} |
|||
|
|||
public async Task<(ISchemaEntity Schema, long Total, IReadOnlyList<IContentEntity> Items)> QueryWithCountAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query) |
|||
{ |
|||
Guard.NotNull(app, nameof(app)); |
|||
Guard.NotNull(user, nameof(user)); |
|||
Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName)); |
|||
|
|||
var schema = await FindSchemaAsync(app, schemaIdOrName); |
|||
|
|||
var parsedQuery = ParseQuery(app, query, schema); |
|||
|
|||
var status = ParseStatus(user, archived); |
|||
|
|||
var taskForItems = contentRepository.QueryAsync(app, schema, status.ToArray(), parsedQuery); |
|||
var taskForCount = contentRepository.CountAsync(app, schema, status.ToArray(), parsedQuery); |
|||
|
|||
await Task.WhenAll(taskForItems, taskForCount); |
|||
|
|||
var list = TransformContent(user, schema, taskForItems.Result.ToList()); |
|||
|
|||
return (schema, taskForCount.Result, list); |
|||
} |
|||
|
|||
public async Task<(ISchemaEntity Schema, long Total, IReadOnlyList<IContentEntity> Items)> QueryWithCountAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet<Guid> ids) |
|||
{ |
|||
Guard.NotNull(ids, nameof(ids)); |
|||
Guard.NotNull(app, nameof(app)); |
|||
Guard.NotNull(user, nameof(user)); |
|||
Guard.NotNullOrEmpty(schemaIdOrName, nameof(schemaIdOrName)); |
|||
|
|||
var schema = await FindSchemaAsync(app, schemaIdOrName); |
|||
|
|||
var status = ParseStatus(user, archived); |
|||
|
|||
var taskForItems = contentRepository.QueryAsync(app, schema, status.ToArray(), ids); |
|||
var taskForCount = contentRepository.CountAsync(app, schema, status.ToArray(), ids); |
|||
|
|||
await Task.WhenAll(taskForItems, taskForCount); |
|||
|
|||
var list = TransformContent(user, schema, taskForItems.Result.ToList()); |
|||
|
|||
return (schema, taskForCount.Result, list); |
|||
} |
|||
|
|||
private List<IContentEntity> TransformContent(ClaimsPrincipal user, ISchemaEntity schema, List<IContentEntity> contents) |
|||
{ |
|||
var scriptText = schema.ScriptQuery; |
|||
|
|||
if (!string.IsNullOrWhiteSpace(scriptText)) |
|||
{ |
|||
for (var i = 0; i < contents.Count; i++) |
|||
{ |
|||
var content = contents[i]; |
|||
var contentData = scriptEngine.Transform(new ScriptContext { User = user, Data = content.Data, ContentId = content.Id }, scriptText); |
|||
|
|||
contents[i] = SimpleMapper.Map(content, new Content { Data = contentData }); |
|||
} |
|||
} |
|||
|
|||
return contents; |
|||
} |
|||
|
|||
private ODataUriParser ParseQuery(IAppEntity app, string query, ISchemaEntity schema) |
|||
{ |
|||
try |
|||
{ |
|||
var model = modelBuilder.BuildEdmModel(schema, app); |
|||
|
|||
return model.ParseQuery(query); |
|||
} |
|||
catch (ODataException ex) |
|||
{ |
|||
throw new ValidationException($"Failed to parse query: {ex.Message}", ex); |
|||
} |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> FindSchemaAsync(IAppEntity app, string schemaIdOrName) |
|||
{ |
|||
Guard.NotNull(app, nameof(app)); |
|||
|
|||
ISchemaEntity schema = null; |
|||
|
|||
if (Guid.TryParse(schemaIdOrName, out var id)) |
|||
{ |
|||
schema = await appProvider.GetSchemaAsync(app.Name, id); |
|||
} |
|||
|
|||
if (schema == null) |
|||
{ |
|||
schema = await appProvider.GetSchemaAsync(app.Name, schemaIdOrName); |
|||
} |
|||
|
|||
if (schema == null) |
|||
{ |
|||
throw new DomainObjectNotFoundException(schemaIdOrName, typeof(ISchemaEntity)); |
|||
} |
|||
|
|||
return schema; |
|||
} |
|||
|
|||
private static List<Status> ParseStatus(ClaimsPrincipal user, bool archived) |
|||
{ |
|||
var status = new List<Status>(); |
|||
|
|||
if (user.IsInClient("squidex-frontend")) |
|||
{ |
|||
if (archived) |
|||
{ |
|||
status.Add(Status.Archived); |
|||
} |
|||
else |
|||
{ |
|||
status.Add(Status.Draft); |
|||
status.Add(Status.Published); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
status.Add(Status.Published); |
|||
} |
|||
|
|||
return status; |
|||
} |
|||
|
|||
private sealed class Content : IContentEntity |
|||
{ |
|||
public Guid Id { get; set; } |
|||
public Guid AppId { get; set; } |
|||
|
|||
public long Version { get; set; } |
|||
|
|||
public Instant Created { get; set; } |
|||
public Instant LastModified { get; set; } |
|||
|
|||
public RefToken CreatedBy { get; set; } |
|||
public RefToken LastModifiedBy { get; set; } |
|||
|
|||
public NamedContentData Data { get; set; } |
|||
|
|||
public Status Status { get; set; } |
|||
} |
|||
} |
|||
} |
|||
@ -1,74 +0,0 @@ |
|||
// ==========================================================================
|
|||
// EdmModelBuilder.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Microsoft.OData.Edm; |
|||
using Squidex.Domain.Apps.Core; |
|||
using Squidex.Domain.Apps.Core.GenerateEdmSchema; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.Edm |
|||
{ |
|||
public class EdmModelBuilder : CachingProviderBase |
|||
{ |
|||
public EdmModelBuilder(IMemoryCache cache) |
|||
: base(cache) |
|||
{ |
|||
} |
|||
|
|||
public virtual IEdmModel BuildEdmModel(ISchemaEntity schema, IAppEntity app) |
|||
{ |
|||
Guard.NotNull(schema, nameof(schema)); |
|||
|
|||
var cacheKey = $"{schema.Id}_{schema.Version}_{app.Id}_{app.Version}"; |
|||
|
|||
var result = Cache.GetOrCreate<IEdmModel>(cacheKey, entry => |
|||
{ |
|||
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(60); |
|||
|
|||
return BuildEdmModel(schema.SchemaDef, app.PartitionResolver()); |
|||
}); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private static EdmModel BuildEdmModel(Schema schema, PartitionResolver partitionResolver) |
|||
{ |
|||
var model = new EdmModel(); |
|||
|
|||
var container = new EdmEntityContainer("Squidex", "Container"); |
|||
|
|||
var schemaType = schema.BuildEdmType(partitionResolver, x => |
|||
{ |
|||
model.AddElement(x); |
|||
|
|||
return x; |
|||
}); |
|||
|
|||
var entityType = new EdmEntityType("Squidex", schema.Name); |
|||
entityType.AddStructuralProperty("data", new EdmComplexTypeReference(schemaType, false)); |
|||
entityType.AddStructuralProperty("version", EdmPrimitiveTypeKind.Int32); |
|||
entityType.AddStructuralProperty("created", EdmPrimitiveTypeKind.DateTimeOffset); |
|||
entityType.AddStructuralProperty("createdBy", EdmPrimitiveTypeKind.String); |
|||
entityType.AddStructuralProperty("lastModified", EdmPrimitiveTypeKind.DateTimeOffset); |
|||
entityType.AddStructuralProperty("lastModifiedBy", EdmPrimitiveTypeKind.String); |
|||
|
|||
model.AddElement(container); |
|||
model.AddElement(schemaType); |
|||
model.AddElement(entityType); |
|||
|
|||
container.AddEntitySet("ContentSet", entityType); |
|||
|
|||
return model; |
|||
} |
|||
} |
|||
} |
|||
@ -1,39 +0,0 @@ |
|||
// ==========================================================================
|
|||
// EdmModelExtensions.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using Microsoft.OData.Edm; |
|||
using Microsoft.OData.UriParser; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.Edm |
|||
{ |
|||
public static class EdmModelExtensions |
|||
{ |
|||
public static ODataUriParser ParseQuery(this IEdmModel model, string query) |
|||
{ |
|||
if (!model.EntityContainer.EntitySets().Any()) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
query = query ?? string.Empty; |
|||
|
|||
var path = model.EntityContainer.EntitySets().First().Path.Path.Split('.').Last(); |
|||
|
|||
if (query.StartsWith("?", StringComparison.Ordinal)) |
|||
{ |
|||
query = query.Substring(1); |
|||
} |
|||
|
|||
var parser = new ODataUriParser(model, new Uri($"{path}?{query}", UriKind.Relative)); |
|||
|
|||
return parser; |
|||
} |
|||
} |
|||
} |
|||
@ -1,85 +0,0 @@ |
|||
// ==========================================================================
|
|||
// CachingGraphQLService.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Assets.Repositories; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL |
|||
{ |
|||
public sealed class CachingGraphQLService : CachingProviderBase, IGraphQLService |
|||
{ |
|||
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(60); |
|||
private readonly IContentQueryService contentQuery; |
|||
private readonly IGraphQLUrlGenerator urlGenerator; |
|||
private readonly IAssetRepository assetRepository; |
|||
private readonly IAppProvider appProvider; |
|||
|
|||
public CachingGraphQLService(IMemoryCache cache, |
|||
IAppProvider appProvider, |
|||
IAssetRepository assetRepository, |
|||
IContentQueryService contentQuery, |
|||
IGraphQLUrlGenerator urlGenerator) |
|||
: base(cache) |
|||
{ |
|||
Guard.NotNull(appProvider, nameof(appProvider)); |
|||
Guard.NotNull(assetRepository, nameof(assetRepository)); |
|||
Guard.NotNull(contentQuery, nameof(urlGenerator)); |
|||
Guard.NotNull(contentQuery, nameof(contentQuery)); |
|||
|
|||
this.appProvider = appProvider; |
|||
this.assetRepository = assetRepository; |
|||
this.contentQuery = contentQuery; |
|||
this.urlGenerator = urlGenerator; |
|||
} |
|||
|
|||
public async Task<(object Data, object[] Errors)> QueryAsync(IAppEntity app, ClaimsPrincipal user, GraphQLQuery query) |
|||
{ |
|||
Guard.NotNull(app, nameof(app)); |
|||
Guard.NotNull(query, nameof(query)); |
|||
|
|||
if (string.IsNullOrWhiteSpace(query.Query)) |
|||
{ |
|||
return (new object(), new object[0]); |
|||
} |
|||
|
|||
var modelContext = await GetModelAsync(app); |
|||
var queryContext = new GraphQLQueryContext(app, assetRepository, contentQuery, user, urlGenerator); |
|||
|
|||
return await modelContext.ExecuteAsync(queryContext, query); |
|||
} |
|||
|
|||
private async Task<GraphQLModel> GetModelAsync(IAppEntity app) |
|||
{ |
|||
var cacheKey = CreateCacheKey(app.Id, app.Etag); |
|||
|
|||
var modelContext = Cache.Get<GraphQLModel>(cacheKey); |
|||
|
|||
if (modelContext == null) |
|||
{ |
|||
var allSchemas = await appProvider.GetSchemasAsync(app.Name); |
|||
|
|||
modelContext = new GraphQLModel(app, allSchemas.Where(x => x.IsPublished), urlGenerator); |
|||
|
|||
Cache.Set(cacheKey, modelContext, CacheDuration); |
|||
} |
|||
|
|||
return modelContext; |
|||
} |
|||
|
|||
private static object CreateCacheKey(Guid appId, string etag) |
|||
{ |
|||
return $"GraphQLModel_{appId}_{etag}"; |
|||
} |
|||
} |
|||
} |
|||
@ -1,229 +0,0 @@ |
|||
// ==========================================================================
|
|||
// GraphQLContext.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 GraphQL; |
|||
using GraphQL.Resolvers; |
|||
using GraphQL.Types; |
|||
using Squidex.Domain.Apps.Core; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Assets; |
|||
using Squidex.Domain.Apps.Read.Contents.GraphQL.Types; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using GraphQLSchema = GraphQL.Types.Schema; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL |
|||
{ |
|||
public sealed class GraphQLModel : IGraphQLContext |
|||
{ |
|||
private readonly Dictionary<Type, Func<Field, (IGraphType ResolveType, IFieldResolver Resolver)>> fieldInfos; |
|||
private readonly Dictionary<Guid, ContentGraphType> schemaTypes = new Dictionary<Guid, ContentGraphType>(); |
|||
private readonly Dictionary<Guid, ISchemaEntity> schemas; |
|||
private readonly PartitionResolver partitionResolver; |
|||
private readonly IAppEntity app; |
|||
private readonly IGraphType assetType; |
|||
private readonly IGraphType assetListType; |
|||
private readonly GraphQLSchema graphQLSchema; |
|||
|
|||
public bool CanGenerateAssetSourceUrl { get; } |
|||
|
|||
public GraphQLModel(IAppEntity app, IEnumerable<ISchemaEntity> schemas, IGraphQLUrlGenerator urlGenerator) |
|||
{ |
|||
this.app = app; |
|||
|
|||
CanGenerateAssetSourceUrl = urlGenerator.CanGenerateAssetSourceUrl; |
|||
|
|||
partitionResolver = app.PartitionResolver(); |
|||
|
|||
assetType = new AssetGraphType(this); |
|||
assetListType = new ListGraphType(new NonNullGraphType(assetType)); |
|||
|
|||
fieldInfos = new Dictionary<Type, Func<Field, (IGraphType ResolveType, IFieldResolver Resolver)>> |
|||
{ |
|||
{ |
|||
typeof(StringField), |
|||
field => ResolveDefault("String") |
|||
}, |
|||
{ |
|||
typeof(BooleanField), |
|||
field => ResolveDefault("Boolean") |
|||
}, |
|||
{ |
|||
typeof(NumberField), |
|||
field => ResolveDefault("Float") |
|||
}, |
|||
{ |
|||
typeof(DateTimeField), |
|||
field => ResolveDefault("Date") |
|||
}, |
|||
{ |
|||
typeof(JsonField), |
|||
field => ResolveDefault("Json") |
|||
}, |
|||
{ |
|||
typeof(TagsField), |
|||
field => ResolveDefault("String") |
|||
}, |
|||
{ |
|||
typeof(GeolocationField), |
|||
field => ResolveDefault("Geolocation") |
|||
}, |
|||
{ |
|||
typeof(AssetsField), |
|||
field => ResolveAssets(assetListType) |
|||
}, |
|||
{ |
|||
typeof(ReferencesField), |
|||
field => ResolveReferences(field) |
|||
} |
|||
}; |
|||
|
|||
this.schemas = schemas.ToDictionary(x => x.Id); |
|||
|
|||
graphQLSchema = new GraphQLSchema { Query = new ContentQueryGraphType(this, this.schemas.Values) }; |
|||
|
|||
foreach (var schemaType in schemaTypes.Values) |
|||
{ |
|||
schemaType.Initialize(); |
|||
} |
|||
} |
|||
|
|||
private static (IGraphType ResolveType, IFieldResolver Resolver) ResolveDefault(string name) |
|||
{ |
|||
return (new NoopGraphType(name), new FuncFieldResolver<ContentFieldData, object>(c => c.Source.GetOrDefault(c.FieldName))); |
|||
} |
|||
|
|||
public IFieldResolver ResolveAssetUrl() |
|||
{ |
|||
var resolver = new FuncFieldResolver<IAssetEntity, object>(c => |
|||
{ |
|||
var context = (GraphQLQueryContext)c.UserContext; |
|||
|
|||
return context.UrlGenerator.GenerateAssetUrl(app, c.Source); |
|||
}); |
|||
|
|||
return resolver; |
|||
} |
|||
|
|||
public IFieldResolver ResolveAssetSourceUrl() |
|||
{ |
|||
var resolver = new FuncFieldResolver<IAssetEntity, object>(c => |
|||
{ |
|||
var context = (GraphQLQueryContext)c.UserContext; |
|||
|
|||
return context.UrlGenerator.GenerateAssetSourceUrl(app, c.Source); |
|||
}); |
|||
|
|||
return resolver; |
|||
} |
|||
|
|||
public IFieldResolver ResolveAssetThumbnailUrl() |
|||
{ |
|||
var resolver = new FuncFieldResolver<IAssetEntity, object>(c => |
|||
{ |
|||
var context = (GraphQLQueryContext)c.UserContext; |
|||
|
|||
return context.UrlGenerator.GenerateAssetThumbnailUrl(app, c.Source); |
|||
}); |
|||
|
|||
return resolver; |
|||
} |
|||
|
|||
public IFieldResolver ResolveContentUrl(ISchemaEntity schema) |
|||
{ |
|||
var resolver = new FuncFieldResolver<IContentEntity, object>(c => |
|||
{ |
|||
var context = (GraphQLQueryContext)c.UserContext; |
|||
|
|||
return context.UrlGenerator.GenerateContentUrl(app, schema, c.Source); |
|||
}); |
|||
|
|||
return resolver; |
|||
} |
|||
|
|||
private static ValueTuple<IGraphType, IFieldResolver> ResolveAssets(IGraphType assetListType) |
|||
{ |
|||
var resolver = new FuncFieldResolver<ContentFieldData, object>(c => |
|||
{ |
|||
var context = (GraphQLQueryContext)c.UserContext; |
|||
var contentIds = c.Source.GetOrDefault(c.FieldName); |
|||
|
|||
return context.GetReferencedAssetsAsync(contentIds); |
|||
}); |
|||
|
|||
return (assetListType, resolver); |
|||
} |
|||
|
|||
private ValueTuple<IGraphType, IFieldResolver> ResolveReferences(Field field) |
|||
{ |
|||
var schemaId = ((ReferencesField)field).Properties.SchemaId; |
|||
var schemaType = GetSchemaType(schemaId); |
|||
|
|||
if (schemaType == null) |
|||
{ |
|||
return (null, null); |
|||
} |
|||
|
|||
var resolver = new FuncFieldResolver<ContentFieldData, object>(c => |
|||
{ |
|||
var context = (GraphQLQueryContext)c.UserContext; |
|||
var contentIds = c.Source.GetOrDefault(c.FieldName); |
|||
|
|||
return context.GetReferencedContentsAsync(schemaId, contentIds); |
|||
}); |
|||
|
|||
var schemaFieldType = new ListGraphType(new NonNullGraphType(GetSchemaType(schemaId))); |
|||
|
|||
return (schemaFieldType, resolver); |
|||
} |
|||
|
|||
public async Task<(object Data, object[] Errors)> ExecuteAsync(GraphQLQueryContext context, GraphQLQuery query) |
|||
{ |
|||
Guard.NotNull(context, nameof(context)); |
|||
|
|||
var result = await new DocumentExecuter().ExecuteAsync(options => |
|||
{ |
|||
options.Query = query.Query; |
|||
options.Schema = graphQLSchema; |
|||
options.Inputs = query.Variables?.ToInputs() ?? new Inputs(); |
|||
options.UserContext = context; |
|||
options.OperationName = query.OperationName; |
|||
}).ConfigureAwait(false); |
|||
|
|||
return (result.Data, result.Errors?.Select(x => (object)new { x.Message, x.Locations }).ToArray()); |
|||
} |
|||
|
|||
public IFieldPartitioning ResolvePartition(Partitioning key) |
|||
{ |
|||
return partitionResolver(key); |
|||
} |
|||
|
|||
public IGraphType GetAssetType() |
|||
{ |
|||
return assetType; |
|||
} |
|||
|
|||
public (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(Field field) |
|||
{ |
|||
return fieldInfos[field.GetType()](field); |
|||
} |
|||
|
|||
public IGraphType GetSchemaType(Guid schemaId) |
|||
{ |
|||
var schema = schemas.GetOrDefault(schemaId); |
|||
|
|||
return schema != null ? schemaTypes.GetOrAdd(schemaId, k => new ContentGraphType(schema, this)) : null; |
|||
} |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
// ==========================================================================
|
|||
// GraphQLQuery.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Newtonsoft.Json.Linq; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL |
|||
{ |
|||
public class GraphQLQuery |
|||
{ |
|||
public string OperationName { get; set; } |
|||
|
|||
public string NamedQuery { get; set; } |
|||
|
|||
public string Query { get; set; } |
|||
|
|||
public JObject Variables { get; set; } |
|||
} |
|||
} |
|||
@ -1,67 +0,0 @@ |
|||
// ==========================================================================
|
|||
// GraphQLQueryContext.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Newtonsoft.Json.Linq; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Assets; |
|||
using Squidex.Domain.Apps.Read.Assets.Repositories; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL |
|||
{ |
|||
public sealed class GraphQLQueryContext : QueryContext |
|||
{ |
|||
public IGraphQLUrlGenerator UrlGenerator { get; } |
|||
|
|||
public GraphQLQueryContext(IAppEntity app, IAssetRepository assetRepository, IContentQueryService contentQuery, ClaimsPrincipal user, |
|||
IGraphQLUrlGenerator urlGenerator) |
|||
: base(app, assetRepository, contentQuery, user) |
|||
{ |
|||
UrlGenerator = urlGenerator; |
|||
} |
|||
|
|||
public Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(JToken value) |
|||
{ |
|||
var ids = ParseIds(value); |
|||
|
|||
return GetReferencedAssetsAsync(ids); |
|||
} |
|||
|
|||
public Task<IReadOnlyList<IContentEntity>> GetReferencedContentsAsync(Guid schemaId, JToken value) |
|||
{ |
|||
var ids = ParseIds(value); |
|||
|
|||
return GetReferencedContentsAsync(schemaId, ids); |
|||
} |
|||
|
|||
private static ICollection<Guid> ParseIds(JToken value) |
|||
{ |
|||
try |
|||
{ |
|||
var result = new List<Guid>(); |
|||
|
|||
if (value is JArray) |
|||
{ |
|||
foreach (var id in value) |
|||
{ |
|||
result.Add(Guid.Parse(id.ToString())); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
catch |
|||
{ |
|||
return new List<Guid>(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,38 +0,0 @@ |
|||
// ==========================================================================
|
|||
// SchemaGraphType.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using GraphQL.Resolvers; |
|||
using GraphQL.Types; |
|||
using Squidex.Domain.Apps.Core; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL |
|||
{ |
|||
public interface IGraphQLContext |
|||
{ |
|||
bool CanGenerateAssetSourceUrl { get; } |
|||
|
|||
IFieldPartitioning ResolvePartition(Partitioning key); |
|||
|
|||
IGraphType GetAssetType(); |
|||
|
|||
IGraphType GetSchemaType(Guid schemaId); |
|||
|
|||
IFieldResolver ResolveAssetUrl(); |
|||
|
|||
IFieldResolver ResolveAssetSourceUrl(); |
|||
|
|||
IFieldResolver ResolveAssetThumbnailUrl(); |
|||
|
|||
IFieldResolver ResolveContentUrl(ISchemaEntity schema); |
|||
|
|||
(IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(Field field); |
|||
} |
|||
} |
|||
@ -1,19 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IGraphQLService.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL |
|||
{ |
|||
public interface IGraphQLService |
|||
{ |
|||
Task<(object Data, object[] Errors)> QueryAsync(IAppEntity app, ClaimsPrincipal user, GraphQLQuery query); |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IGraphQLUrlGenerator.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Assets; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL |
|||
{ |
|||
public interface IGraphQLUrlGenerator |
|||
{ |
|||
bool CanGenerateAssetSourceUrl { get; } |
|||
|
|||
string GenerateAssetUrl(IAppEntity app, IAssetEntity asset); |
|||
|
|||
string GenerateAssetThumbnailUrl(IAppEntity app, IAssetEntity asset); |
|||
|
|||
string GenerateAssetSourceUrl(IAppEntity app, IAssetEntity asset); |
|||
|
|||
string GenerateContentUrl(IAppEntity app, ISchemaEntity schema, IContentEntity content); |
|||
} |
|||
} |
|||
@ -1,170 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AssetGraphType.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using GraphQL.Resolvers; |
|||
using GraphQL.Types; |
|||
using Squidex.Domain.Apps.Read.Assets; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types |
|||
{ |
|||
public sealed class AssetGraphType : ObjectGraphType<IAssetEntity> |
|||
{ |
|||
public AssetGraphType(IGraphQLContext context) |
|||
{ |
|||
Name = "AssetDto"; |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "id", |
|||
Resolver = Resolver(x => x.Id.ToString()), |
|||
ResolvedType = new NonNullGraphType(new StringGraphType()), |
|||
Description = "The id of the asset." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "version", |
|||
Resolver = Resolver(x => x.Version), |
|||
ResolvedType = new NonNullGraphType(new IntGraphType()), |
|||
Description = "The version of the asset." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "created", |
|||
Resolver = Resolver(x => x.Created.ToDateTimeUtc()), |
|||
ResolvedType = new NonNullGraphType(new DateGraphType()), |
|||
Description = "The date and time when the asset has been created." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "createdBy", |
|||
Resolver = Resolver(x => x.CreatedBy.ToString()), |
|||
ResolvedType = new NonNullGraphType(new StringGraphType()), |
|||
Description = "The user that has created the asset." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "lastModified", |
|||
Resolver = Resolver(x => x.LastModified.ToDateTimeUtc()), |
|||
ResolvedType = new NonNullGraphType(new DateGraphType()), |
|||
Description = "The date and time when the asset has been modified last." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "lastModifiedBy", |
|||
Resolver = Resolver(x => x.LastModifiedBy.ToString()), |
|||
ResolvedType = new NonNullGraphType(new StringGraphType()), |
|||
Description = "The user that has updated the asset last." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "mimeType", |
|||
Resolver = Resolver(x => x.MimeType), |
|||
ResolvedType = new NonNullGraphType(new StringGraphType()), |
|||
Description = "The mime type." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "url", |
|||
Resolver = context.ResolveAssetUrl(), |
|||
ResolvedType = new NonNullGraphType(new StringGraphType()), |
|||
Description = "The url to the asset." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "thumbnailUrl", |
|||
Resolver = context.ResolveAssetThumbnailUrl(), |
|||
ResolvedType = new StringGraphType(), |
|||
Description = "The thumbnail url to the asset." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "fileName", |
|||
Resolver = Resolver(x => x.FileName), |
|||
ResolvedType = new NonNullGraphType(new StringGraphType()), |
|||
Description = "The file name." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "fileType", |
|||
Resolver = Resolver(x => x.FileName.FileType()), |
|||
ResolvedType = new NonNullGraphType(new StringGraphType()), |
|||
Description = "The file type." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "fileSize", |
|||
Resolver = Resolver(x => x.FileSize), |
|||
ResolvedType = new NonNullGraphType(new IntGraphType()), |
|||
Description = "The size of the file in bytes." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "fileVersion", |
|||
Resolver = Resolver(x => x.FileVersion), |
|||
ResolvedType = new NonNullGraphType(new IntGraphType()), |
|||
Description = "The version of the file." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "isImage", |
|||
Resolver = Resolver(x => x.IsImage), |
|||
ResolvedType = new NonNullGraphType(new BooleanGraphType()), |
|||
Description = "Determines of the created file is an image." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "pixelWidth", |
|||
Resolver = Resolver(x => x.PixelWidth), |
|||
ResolvedType = new IntGraphType(), |
|||
Description = "The width of the image in pixels if the asset is an image." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "pixelHeight", |
|||
Resolver = Resolver(x => x.PixelHeight), |
|||
ResolvedType = new IntGraphType(), |
|||
Description = "The height of the image in pixels if the asset is an image." |
|||
}); |
|||
|
|||
if (context.CanGenerateAssetSourceUrl) |
|||
{ |
|||
AddField(new FieldType |
|||
{ |
|||
Name = "sourceUrl", |
|||
Resolver = context.ResolveAssetSourceUrl(), |
|||
ResolvedType = new StringGraphType(), |
|||
Description = "The source url of the asset." |
|||
}); |
|||
} |
|||
|
|||
Description = "An asset"; |
|||
} |
|||
|
|||
private static IFieldResolver Resolver(Func<IAssetEntity, object> action) |
|||
{ |
|||
return new FuncFieldResolver<IAssetEntity, object>(c => action(c.Source)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,68 +0,0 @@ |
|||
// ==========================================================================
|
|||
// ContentDataGraphType.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Linq; |
|||
using GraphQL.Resolvers; |
|||
using GraphQL.Types; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Infrastructure; |
|||
using Schema = Squidex.Domain.Apps.Core.Schemas.Schema; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types |
|||
{ |
|||
public sealed class ContentDataGraphType : ObjectGraphType<NamedContentData> |
|||
{ |
|||
public ContentDataGraphType(Schema schema, IGraphQLContext context) |
|||
{ |
|||
var schemaName = schema.Properties.Label.WithFallback(schema.Name); |
|||
|
|||
Name = $"{schema.Name.ToPascalCase()}DataDto"; |
|||
|
|||
foreach (var field in schema.Fields.Where(x => !x.IsHidden)) |
|||
{ |
|||
var fieldInfo = context.GetGraphType(field); |
|||
|
|||
if (fieldInfo.ResolveType != null) |
|||
{ |
|||
var fieldName = field.RawProperties.Label.WithFallback(field.Name); |
|||
|
|||
var fieldGraphType = new ObjectGraphType |
|||
{ |
|||
Name = $"{schema.Name.ToPascalCase()}Data{field.Name.ToPascalCase()}Dto" |
|||
}; |
|||
|
|||
var partition = context.ResolvePartition(field.Partitioning); |
|||
|
|||
foreach (var partitionItem in partition) |
|||
{ |
|||
fieldGraphType.AddField(new FieldType |
|||
{ |
|||
Name = partitionItem.Key, |
|||
Resolver = fieldInfo.Resolver, |
|||
ResolvedType = fieldInfo.ResolveType, |
|||
Description = field.RawProperties.Hints |
|||
}); |
|||
} |
|||
|
|||
fieldGraphType.Description = $"The structure of the {fieldName} of a {schemaName} content type."; |
|||
|
|||
var fieldResolver = new FuncFieldResolver<NamedContentData, ContentFieldData>(c => c.Source.GetOrDefault(field.Name)); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = field.Name.ToCamelCase(), |
|||
Resolver = fieldResolver, |
|||
ResolvedType = fieldGraphType |
|||
}); |
|||
} |
|||
} |
|||
|
|||
Description = $"The structure of a {schemaName} content type."; |
|||
} |
|||
} |
|||
} |
|||
@ -1,112 +0,0 @@ |
|||
// ==========================================================================
|
|||
// SchemaGraphType.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using GraphQL.Resolvers; |
|||
using GraphQL.Types; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types |
|||
{ |
|||
public sealed class ContentGraphType : ObjectGraphType<IContentEntity> |
|||
{ |
|||
private readonly ISchemaEntity schema; |
|||
private readonly IGraphQLContext context; |
|||
|
|||
public ContentGraphType(ISchemaEntity schema, IGraphQLContext context) |
|||
{ |
|||
this.context = context; |
|||
this.schema = schema; |
|||
|
|||
Name = $"{schema.Name.ToPascalCase()}Dto"; |
|||
} |
|||
|
|||
public void Initialize() |
|||
{ |
|||
var schemaName = schema.SchemaDef.Properties.Label.WithFallback(schema.Name); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "id", |
|||
Resolver = Resolver(x => x.Id.ToString()), |
|||
ResolvedType = new NonNullGraphType(new StringGraphType()), |
|||
Description = $"The id of the {schemaName} content." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "version", |
|||
Resolver = Resolver(x => x.Version), |
|||
ResolvedType = new NonNullGraphType(new IntGraphType()), |
|||
Description = $"The version of the {schemaName} content." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "created", |
|||
Resolver = Resolver(x => x.Created.ToDateTimeUtc()), |
|||
ResolvedType = new NonNullGraphType(new DateGraphType()), |
|||
Description = $"The date and time when the {schemaName} content has been created." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "createdBy", |
|||
Resolver = Resolver(x => x.CreatedBy.ToString()), |
|||
ResolvedType = new NonNullGraphType(new StringGraphType()), |
|||
Description = $"The user that has created the {schemaName} content." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "lastModified", |
|||
Resolver = Resolver(x => x.LastModified.ToDateTimeUtc()), |
|||
ResolvedType = new NonNullGraphType(new DateGraphType()), |
|||
Description = $"The date and time when the {schemaName} content has been modified last." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "lastModifiedBy", |
|||
Resolver = Resolver(x => x.LastModifiedBy.ToString()), |
|||
ResolvedType = new NonNullGraphType(new StringGraphType()), |
|||
Description = $"The user that has updated the {schemaName} content last." |
|||
}); |
|||
|
|||
AddField(new FieldType |
|||
{ |
|||
Name = "url", |
|||
Resolver = context.ResolveContentUrl(schema), |
|||
ResolvedType = new NonNullGraphType(new StringGraphType()), |
|||
Description = $"The url to the the {schemaName} content." |
|||
}); |
|||
|
|||
var dataType = new ContentDataGraphType(schema.SchemaDef, context); |
|||
|
|||
if (dataType.Fields.Any()) |
|||
{ |
|||
AddField(new FieldType |
|||
{ |
|||
Name = "data", |
|||
Resolver = Resolver(x => x.Data), |
|||
ResolvedType = new NonNullGraphType(dataType), |
|||
Description = $"The data of the {schemaName} content." |
|||
}); |
|||
} |
|||
|
|||
Description = $"The structure of a {schemaName} content type."; |
|||
} |
|||
|
|||
private static IFieldResolver Resolver(Func<IContentEntity, object> action) |
|||
{ |
|||
return new FuncFieldResolver<IContentEntity, object>(c => action(c.Source)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,192 +0,0 @@ |
|||
// ==========================================================================
|
|||
// GraphModelType.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using GraphQL.Resolvers; |
|||
using GraphQL.Types; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types |
|||
{ |
|||
public sealed class ContentQueryGraphType : ObjectGraphType |
|||
{ |
|||
public ContentQueryGraphType(IGraphQLContext graphQLContext, IEnumerable<ISchemaEntity> schemas) |
|||
{ |
|||
AddAssetFind(graphQLContext); |
|||
AddAssetsQuery(graphQLContext); |
|||
|
|||
foreach (var schema in schemas) |
|||
{ |
|||
var schemaName = schema.SchemaDef.Properties.Label.WithFallback(schema.SchemaDef.Name); |
|||
var schemaType = graphQLContext.GetSchemaType(schema.Id); |
|||
|
|||
AddContentFind(schema, schemaType, schemaName); |
|||
AddContentQuery(schema, schemaType, schemaName); |
|||
} |
|||
|
|||
Description = "The app queries."; |
|||
} |
|||
|
|||
private void AddAssetFind(IGraphQLContext graphQLContext) |
|||
{ |
|||
AddField(new FieldType |
|||
{ |
|||
Name = "findAsset", |
|||
Arguments = new QueryArguments |
|||
{ |
|||
new QueryArgument(typeof(StringGraphType)) |
|||
{ |
|||
Name = "id", |
|||
Description = "The id of the asset.", |
|||
DefaultValue = string.Empty |
|||
} |
|||
}, |
|||
ResolvedType = graphQLContext.GetAssetType(), |
|||
Resolver = new FuncFieldResolver<object>(c => |
|||
{ |
|||
var context = (GraphQLQueryContext)c.UserContext; |
|||
var contentId = Guid.Parse(c.GetArgument("id", Guid.Empty.ToString())); |
|||
|
|||
return context.FindAssetAsync(contentId); |
|||
}), |
|||
Description = "Find an asset by id." |
|||
}); |
|||
} |
|||
|
|||
private void AddContentFind(ISchemaEntity schema, IGraphType schemaType, string schemaName) |
|||
{ |
|||
AddField(new FieldType |
|||
{ |
|||
Name = $"find{schema.Name.ToPascalCase()}Content", |
|||
Arguments = new QueryArguments |
|||
{ |
|||
new QueryArgument(typeof(StringGraphType)) |
|||
{ |
|||
Name = "id", |
|||
Description = $"The id of the {schemaName} content.", |
|||
DefaultValue = string.Empty |
|||
} |
|||
}, |
|||
ResolvedType = schemaType, |
|||
Resolver = new FuncFieldResolver<object>(c => |
|||
{ |
|||
var context = (GraphQLQueryContext)c.UserContext; |
|||
var contentId = Guid.Parse(c.GetArgument("id", Guid.Empty.ToString())); |
|||
|
|||
return context.FindContentAsync(schema.Id, contentId); |
|||
}), |
|||
Description = $"Find an {schemaName} content by id." |
|||
}); |
|||
} |
|||
|
|||
private void AddAssetsQuery(IGraphQLContext graphQLContext) |
|||
{ |
|||
AddField(new FieldType |
|||
{ |
|||
Name = "queryAssets", |
|||
Arguments = new QueryArguments |
|||
{ |
|||
new QueryArgument(typeof(IntGraphType)) |
|||
{ |
|||
Name = "top", |
|||
Description = "Optional number of assets to take.", |
|||
DefaultValue = 20 |
|||
}, |
|||
new QueryArgument(typeof(IntGraphType)) |
|||
{ |
|||
Name = "skip", |
|||
Description = "Optional number of assets to skip.", |
|||
DefaultValue = 0 |
|||
}, |
|||
new QueryArgument(typeof(StringGraphType)) |
|||
{ |
|||
Name = "search", |
|||
Description = "Optional query.", |
|||
DefaultValue = string.Empty |
|||
} |
|||
}, |
|||
ResolvedType = new ListGraphType(new NonNullGraphType(graphQLContext.GetAssetType())), |
|||
Resolver = new FuncFieldResolver<object>(c => |
|||
{ |
|||
var context = (GraphQLQueryContext)c.UserContext; |
|||
|
|||
var argTop = c.GetArgument("top", 20); |
|||
var argSkip = c.GetArgument("skip", 0); |
|||
var argQuery = c.GetArgument("search", string.Empty); |
|||
|
|||
return context.QueryAssetsAsync(argQuery, argSkip, argTop); |
|||
}), |
|||
Description = "Query assets items." |
|||
}); |
|||
} |
|||
|
|||
private void AddContentQuery(ISchemaEntity schema, IGraphType schemaType, string schemaName) |
|||
{ |
|||
AddField(new FieldType |
|||
{ |
|||
Name = $"query{schema.Name.ToPascalCase()}Contents", |
|||
Arguments = new QueryArguments |
|||
{ |
|||
new QueryArgument(typeof(IntGraphType)) |
|||
{ |
|||
Name = "top", |
|||
Description = "Optional number of contents to take.", |
|||
DefaultValue = 20 |
|||
}, |
|||
new QueryArgument(typeof(IntGraphType)) |
|||
{ |
|||
Name = "skip", |
|||
Description = "Optional number of contents to skip.", |
|||
DefaultValue = 0 |
|||
}, |
|||
new QueryArgument(typeof(StringGraphType)) |
|||
{ |
|||
Name = "filter", |
|||
Description = "Optional OData filter.", |
|||
DefaultValue = string.Empty |
|||
}, |
|||
new QueryArgument(typeof(StringGraphType)) |
|||
{ |
|||
Name = "search", |
|||
Description = "Optional OData full text search.", |
|||
DefaultValue = string.Empty |
|||
}, |
|||
new QueryArgument(typeof(StringGraphType)) |
|||
{ |
|||
Name = "orderby", |
|||
Description = "Optional OData order definition.", |
|||
DefaultValue = string.Empty |
|||
} |
|||
}, |
|||
ResolvedType = new ListGraphType(new NonNullGraphType(schemaType)), |
|||
Resolver = new FuncFieldResolver<object>(c => |
|||
{ |
|||
var context = (GraphQLQueryContext)c.UserContext; |
|||
var contentQuery = BuildODataQuery(c); |
|||
|
|||
return context.QueryContentsAsync(schema.Id.ToString(), contentQuery); |
|||
}), |
|||
Description = $"Query {schemaName} content items." |
|||
}); |
|||
} |
|||
|
|||
private static string BuildODataQuery(ResolveFieldContext c) |
|||
{ |
|||
var odataQuery = "?" + |
|||
string.Join("&", |
|||
c.Arguments |
|||
.Select(x => new { x.Key, Value = x.Value.ToString() }).Where(x => !string.IsNullOrWhiteSpace(x.Value)) |
|||
.Select(x => $"${x.Key}={x.Value}")); |
|||
|
|||
return odataQuery; |
|||
} |
|||
} |
|||
} |
|||
@ -1,37 +0,0 @@ |
|||
// ==========================================================================
|
|||
// NoopGraphType.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using GraphQL.Language.AST; |
|||
using GraphQL.Types; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types |
|||
{ |
|||
public sealed class NoopGraphType : ScalarGraphType |
|||
{ |
|||
public NoopGraphType(string name) |
|||
{ |
|||
Name = name; |
|||
} |
|||
|
|||
public override object Serialize(object value) |
|||
{ |
|||
return value; |
|||
} |
|||
|
|||
public override object ParseValue(object value) |
|||
{ |
|||
return value; |
|||
} |
|||
|
|||
public override object ParseLiteral(IValue value) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IContentEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents |
|||
{ |
|||
public interface IContentEntity : |
|||
IEntity, |
|||
IEntityWithAppRef, |
|||
IEntityWithCreatedBy, |
|||
IEntityWithLastModifiedBy, |
|||
IEntityWithVersion |
|||
{ |
|||
Status Status { get; } |
|||
|
|||
NamedContentData Data { get; } |
|||
} |
|||
} |
|||
@ -1,28 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IContentQueryService.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents |
|||
{ |
|||
public interface IContentQueryService |
|||
{ |
|||
Task<(ISchemaEntity Schema, long Total, IReadOnlyList<IContentEntity> Items)> QueryWithCountAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet<Guid> ids); |
|||
|
|||
Task<(ISchemaEntity Schema, long Total, IReadOnlyList<IContentEntity> Items)> QueryWithCountAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query); |
|||
|
|||
Task<(ISchemaEntity Schema, IContentEntity Content)> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id); |
|||
|
|||
Task<ISchemaEntity> FindSchemaAsync(IAppEntity app, string schemaIdOrName); |
|||
} |
|||
} |
|||
@ -1,146 +0,0 @@ |
|||
// ==========================================================================
|
|||
// QueryContext.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Assets; |
|||
using Squidex.Domain.Apps.Read.Assets.Repositories; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents |
|||
{ |
|||
public class QueryContext |
|||
{ |
|||
private readonly ConcurrentDictionary<Guid, IContentEntity> cachedContents = new ConcurrentDictionary<Guid, IContentEntity>(); |
|||
private readonly ConcurrentDictionary<Guid, IAssetEntity> cachedAssets = new ConcurrentDictionary<Guid, IAssetEntity>(); |
|||
private readonly IContentQueryService contentQuery; |
|||
private readonly IAssetRepository assetRepository; |
|||
private readonly IAppEntity app; |
|||
private readonly ClaimsPrincipal user; |
|||
|
|||
public QueryContext( |
|||
IAppEntity app, |
|||
IAssetRepository assetRepository, |
|||
IContentQueryService contentQuery, |
|||
ClaimsPrincipal user) |
|||
{ |
|||
Guard.NotNull(assetRepository, nameof(assetRepository)); |
|||
Guard.NotNull(contentQuery, nameof(contentQuery)); |
|||
Guard.NotNull(app, nameof(app)); |
|||
Guard.NotNull(user, nameof(user)); |
|||
|
|||
this.assetRepository = assetRepository; |
|||
this.contentQuery = contentQuery; |
|||
|
|||
this.user = user; |
|||
|
|||
this.app = app; |
|||
} |
|||
|
|||
public async Task<IAssetEntity> FindAssetAsync(Guid id) |
|||
{ |
|||
var asset = cachedAssets.GetOrDefault(id); |
|||
|
|||
if (asset == null) |
|||
{ |
|||
asset = await assetRepository.FindAssetAsync(id); |
|||
|
|||
if (asset != null) |
|||
{ |
|||
cachedAssets[asset.Id] = asset; |
|||
} |
|||
} |
|||
|
|||
return asset; |
|||
} |
|||
|
|||
public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id) |
|||
{ |
|||
var content = cachedContents.GetOrDefault(id); |
|||
|
|||
if (content == null) |
|||
{ |
|||
content = (await contentQuery.FindContentAsync(app, schemaId.ToString(), user, id)).Content; |
|||
|
|||
if (content != null) |
|||
{ |
|||
cachedContents[content.Id] = content; |
|||
} |
|||
} |
|||
|
|||
return content; |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IAssetEntity>> QueryAssetsAsync(string query, int skip = 0, int take = 10) |
|||
{ |
|||
var assets = await assetRepository.QueryAsync(app.Id, null, null, query, take, skip); |
|||
|
|||
foreach (var asset in assets) |
|||
{ |
|||
cachedAssets[asset.Id] = asset; |
|||
} |
|||
|
|||
return assets; |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IContentEntity>> QueryContentsAsync(string schemaIdOrName, string query) |
|||
{ |
|||
var contents = await contentQuery.QueryWithCountAsync(app, schemaIdOrName, user, false, query); |
|||
|
|||
foreach (var content in contents.Items) |
|||
{ |
|||
cachedContents[content.Id] = content; |
|||
} |
|||
|
|||
return contents.Items; |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(ICollection<Guid> ids) |
|||
{ |
|||
Guard.NotNull(ids, nameof(ids)); |
|||
|
|||
var notLoadedAssets = new HashSet<Guid>(ids.Where(id => !cachedAssets.ContainsKey(id))); |
|||
|
|||
if (notLoadedAssets.Count > 0) |
|||
{ |
|||
var assets = await assetRepository.QueryAsync(app.Id, null, notLoadedAssets, null, int.MaxValue); |
|||
|
|||
foreach (var asset in assets) |
|||
{ |
|||
cachedAssets[asset.Id] = asset; |
|||
} |
|||
} |
|||
|
|||
return ids.Select(id => cachedAssets.GetOrDefault(id)).Where(x => x != null).ToList(); |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IContentEntity>> GetReferencedContentsAsync(Guid schemaId, ICollection<Guid> ids) |
|||
{ |
|||
Guard.NotNull(ids, nameof(ids)); |
|||
|
|||
var notLoadedContents = new HashSet<Guid>(ids.Where(id => !cachedContents.ContainsKey(id))); |
|||
|
|||
if (notLoadedContents.Count > 0) |
|||
{ |
|||
var contents = await contentQuery.QueryWithCountAsync(app, schemaId.ToString(), user, false, notLoadedContents); |
|||
|
|||
foreach (var content in contents.Items) |
|||
{ |
|||
cachedContents[content.Id] = content; |
|||
} |
|||
} |
|||
|
|||
return ids.Select(id => cachedContents.GetOrDefault(id)).Where(x => x != null).ToList(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,33 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IContentRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.OData.UriParser; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.Repositories |
|||
{ |
|||
public interface IContentRepository |
|||
{ |
|||
Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids); |
|||
|
|||
Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery); |
|||
|
|||
Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> contentIds); |
|||
|
|||
Task<long> CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids); |
|||
|
|||
Task<long> CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery); |
|||
|
|||
Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id); |
|||
} |
|||
} |
|||
@ -1,90 +0,0 @@ |
|||
// ==========================================================================
|
|||
// EntityMapper.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Read |
|||
{ |
|||
public static class EntityMapper |
|||
{ |
|||
public static T Create<T>(SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater = null) where T : IEntity, new() |
|||
{ |
|||
var entity = new T(); |
|||
|
|||
SetId(headers, entity); |
|||
|
|||
SetVersion(headers, entity); |
|||
SetCreated(headers, entity); |
|||
SetCreatedBy(@event, entity); |
|||
|
|||
SetAppId(@event, entity); |
|||
|
|||
return entity.Update(@event, headers, updater); |
|||
} |
|||
|
|||
public static T Update<T>(this T entity, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater = null) where T : IEntity, new() |
|||
{ |
|||
SetVersion(headers, entity); |
|||
SetLastModified(headers, entity); |
|||
SetLastModifiedBy(@event, entity); |
|||
|
|||
updater?.Invoke(entity); |
|||
|
|||
return entity; |
|||
} |
|||
|
|||
private static void SetId(EnvelopeHeaders headers, IEntity entity) |
|||
{ |
|||
entity.Id = headers.AggregateId(); |
|||
} |
|||
|
|||
private static void SetCreated(EnvelopeHeaders headers, IEntity entity) |
|||
{ |
|||
entity.Created = headers.Timestamp(); |
|||
} |
|||
|
|||
private static void SetLastModified(EnvelopeHeaders headers, IEntity entity) |
|||
{ |
|||
entity.LastModified = headers.Timestamp(); |
|||
} |
|||
|
|||
private static void SetVersion(EnvelopeHeaders headers, IEntity entity) |
|||
{ |
|||
if (entity is IUpdateableEntityWithVersion withVersion) |
|||
{ |
|||
withVersion.Version = headers.EventStreamNumber(); |
|||
} |
|||
} |
|||
|
|||
private static void SetCreatedBy(SquidexEvent @event, IEntity entity) |
|||
{ |
|||
if (entity is IUpdateableEntityWithCreatedBy withCreatedBy && withCreatedBy) |
|||
{ |
|||
withCreatedBy.CreatedBy = @event.Actor; |
|||
} |
|||
} |
|||
|
|||
private static void SetLastModifiedBy(SquidexEvent @event, IEntity entity) |
|||
{ |
|||
if (entity is IUpdateableEntityWithLastModifiedBy withModifiedBy) |
|||
{ |
|||
withModifiedBy.LastModifiedBy = @event.Actor; |
|||
} |
|||
} |
|||
|
|||
private static void SetAppId(SquidexEvent @event, IEntity entity) |
|||
{ |
|||
if (entity is IUpdateableEntityWithAppRef app && @event is AppEvent appEvent) |
|||
{ |
|||
app.AppId = appEvent.AppId.Id; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,44 +0,0 @@ |
|||
// ==========================================================================
|
|||
// HistoryEventToStore.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.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; |
|||
} |
|||
} |
|||
} |
|||
@ -1,66 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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.Read.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); |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IHistoryEventEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.History |
|||
{ |
|||
public interface IHistoryEventEntity : IEntity |
|||
{ |
|||
Guid EventId { get; } |
|||
|
|||
RefToken Actor { get; } |
|||
|
|||
string Message { get; } |
|||
|
|||
long Version { get; } |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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.Read.History |
|||
{ |
|||
public interface IHistoryEventsCreator |
|||
{ |
|||
IReadOnlyDictionary<string, string> Texts { get; } |
|||
|
|||
Task<HistoryEventToStore> CreateEventAsync(Envelope<IEvent> @event); |
|||
} |
|||
} |
|||
@ -1,19 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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.Read.History.Repositories |
|||
{ |
|||
public interface IHistoryEventRepository |
|||
{ |
|||
Task<IReadOnlyList<IHistoryEventEntity>> QueryByChannelAsync(Guid appId, string channelPrefix, int count); |
|||
} |
|||
} |
|||
@ -1,34 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IApps.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.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
|
|||
namespace Squidex.Domain.Apps.Read |
|||
{ |
|||
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); |
|||
} |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using NodaTime; |
|||
|
|||
namespace Squidex.Domain.Apps.Read |
|||
{ |
|||
public interface IEntity |
|||
{ |
|||
Guid Id { get; set; } |
|||
|
|||
Instant Created { get; set; } |
|||
|
|||
Instant LastModified { get; set; } |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IEntityWithAppRef.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Read |
|||
{ |
|||
public interface IEntityWithAppRef |
|||
{ |
|||
Guid AppId { get; } |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IEntityWithCreatedBy.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read |
|||
{ |
|||
public interface IEntityWithCreatedBy |
|||
{ |
|||
RefToken CreatedBy { get; } |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IEntityWithLastModifiedBy.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read |
|||
{ |
|||
public interface IEntityWithLastModifiedBy |
|||
{ |
|||
RefToken LastModifiedBy { get; set; } |
|||
} |
|||
} |
|||
@ -1,15 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IEntityWithVersion.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Read |
|||
{ |
|||
public interface IEntityWithVersion |
|||
{ |
|||
long Version { get; } |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IUpdateableEntityWithAppRef.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Read |
|||
{ |
|||
public interface IUpdateableEntityWithAppRef |
|||
{ |
|||
Guid AppId { get; set; } |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IUpdateableEntityWithCreatedBy.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read |
|||
{ |
|||
public interface IUpdateableEntityWithCreatedBy |
|||
{ |
|||
RefToken CreatedBy { get; set; } |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IUpdateableEntityWithLastModifiedBy.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read |
|||
{ |
|||
public interface IUpdateableEntityWithLastModifiedBy |
|||
{ |
|||
RefToken LastModifiedBy { get; set; } |
|||
} |
|||
} |
|||
@ -1,15 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IUpdateableEntityWithVersion.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Read |
|||
{ |
|||
public interface IUpdateableEntityWithVersion |
|||
{ |
|||
long Version { get; set; } |
|||
} |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IRuleEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Rules |
|||
{ |
|||
public interface IRuleEntity : |
|||
IEntity, |
|||
IEntityWithAppRef, |
|||
IEntityWithCreatedBy, |
|||
IEntityWithLastModifiedBy, |
|||
IEntityWithVersion |
|||
{ |
|||
Rule RuleDef { get; } |
|||
} |
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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.Read.Rules |
|||
{ |
|||
public interface IRuleEventEntity : IEntity |
|||
{ |
|||
RuleJob Job { get; } |
|||
|
|||
Instant? NextAttempt { get; } |
|||
|
|||
RuleJobResult JobResult { get; } |
|||
|
|||
RuleResult Result { get; } |
|||
|
|||
int NumCalls { get; } |
|||
|
|||
string LastDump { get; } |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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.Read.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); |
|||
} |
|||
} |
|||
@ -1,157 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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.Read.Rules.Repositories; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.Timers; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.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; |
|||
} |
|||
} |
|||
} |
|||
@ -1,73 +0,0 @@ |
|||
// ==========================================================================
|
|||
// 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.Events; |
|||
using Squidex.Domain.Apps.Read.Rules.Repositories; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
// ==========================================================================
|
|||
// RuleJobResult.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Read.Rules |
|||
{ |
|||
public enum RuleJobResult |
|||
{ |
|||
Pending, |
|||
Success, |
|||
Retry, |
|||
Failed |
|||
} |
|||
} |
|||
@ -1,38 +0,0 @@ |
|||
// ==========================================================================
|
|||
// ISchemaEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Schemas |
|||
{ |
|||
public interface ISchemaEntity : |
|||
IEntity, |
|||
IEntityWithAppRef, |
|||
IEntityWithCreatedBy, |
|||
IEntityWithLastModifiedBy, |
|||
IEntityWithVersion |
|||
{ |
|||
string Name { get; } |
|||
|
|||
bool IsPublished { get; } |
|||
|
|||
bool IsDeleted { get; } |
|||
|
|||
string ScriptQuery { get; } |
|||
|
|||
string ScriptCreate { get; } |
|||
|
|||
string ScriptUpdate { get; } |
|||
|
|||
string ScriptDelete { get; } |
|||
|
|||
string ScriptChange { get; } |
|||
|
|||
Schema SchemaDef { get; } |
|||
} |
|||
} |
|||
@ -1,88 +0,0 @@ |
|||
// ==========================================================================
|
|||
// SchemaHistoryEventsCreator.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Domain.Apps.Events.Schemas; |
|||
using Squidex.Domain.Apps.Read.History; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Schemas |
|||
{ |
|||
public sealed class SchemaHistoryEventsCreator : HistoryEventsCreatorBase |
|||
{ |
|||
public SchemaHistoryEventsCreator(TypeNameRegistry typeNameRegistry) |
|||
: base(typeNameRegistry) |
|||
{ |
|||
AddEventMessage<SchemaCreated>( |
|||
"created schema {[Name]}"); |
|||
|
|||
AddEventMessage<SchemaUpdated>( |
|||
"updated schema {[Name]}"); |
|||
|
|||
AddEventMessage<SchemaDeleted>( |
|||
"deleted schema {[Name]}"); |
|||
|
|||
AddEventMessage<SchemaPublished>( |
|||
"published schema {[Name]}"); |
|||
|
|||
AddEventMessage<SchemaUnpublished>( |
|||
"unpublished schema {[Name]}"); |
|||
|
|||
AddEventMessage<SchemaFieldsReordered>( |
|||
"reordered fields of schema {[Name]}"); |
|||
|
|||
AddEventMessage<FieldAdded>( |
|||
"added field {[Field]} to schema {[Name]}"); |
|||
|
|||
AddEventMessage<FieldDeleted>( |
|||
"deleted field {[Field]} from schema {[Name]}"); |
|||
|
|||
AddEventMessage<FieldLocked>( |
|||
"has locked field {[Field]} of schema {[Name]}"); |
|||
|
|||
AddEventMessage<FieldHidden>( |
|||
"has hidden field {[Field]} of schema {[Name]}"); |
|||
|
|||
AddEventMessage<FieldShown>( |
|||
"has shown field {[Field]} of schema {[Name]}"); |
|||
|
|||
AddEventMessage<FieldDisabled>( |
|||
"disabled field {[Field]} of schema {[Name]}"); |
|||
|
|||
AddEventMessage<FieldEnabled>( |
|||
"disabled field {[Field]} of schema {[Name]}"); |
|||
|
|||
AddEventMessage<FieldUpdated>( |
|||
"has updated field {[Field]} of schema {[Name]}"); |
|||
|
|||
AddEventMessage<FieldDeleted>( |
|||
"deleted field {[Field]} of schema {[Name]}"); |
|||
} |
|||
|
|||
protected override Task<HistoryEventToStore> CreateEventCoreAsync(Envelope<IEvent> @event) |
|||
{ |
|||
if (@event.Payload is SchemaEvent schemaEvent) |
|||
{ |
|||
var channel = $"schemas.{schemaEvent.SchemaId.Name}"; |
|||
|
|||
var result = ForEvent(@event.Payload, channel).AddParameter("Name", schemaEvent.SchemaId.Name); |
|||
|
|||
if (schemaEvent is FieldEvent fieldEvent) |
|||
{ |
|||
result.AddParameter("Field", fieldEvent.FieldId.Name); |
|||
} |
|||
|
|||
return Task.FromResult(result); |
|||
} |
|||
|
|||
return Task.FromResult<HistoryEventToStore>(null); |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
</PropertyGroup> |
|||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
|||
<DebugType>full</DebugType> |
|||
<DebugSymbols>True</DebugSymbols> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Squidex.Domain.Apps.Core.Model\Squidex.Domain.Apps.Core.Model.csproj" /> |
|||
<ProjectReference Include="..\Squidex.Domain.Apps.Core.Operations\Squidex.Domain.Apps.Core.Operations.csproj" /> |
|||
<ProjectReference Include="..\Squidex.Domain.Apps.Events\Squidex.Domain.Apps.Events.csproj" /> |
|||
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<PackageReference Include="GraphQL" Version="0.17.3" /> |
|||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.0" /> |
|||
<PackageReference Include="NodaTime" Version="2.2.3" /> |
|||
<PackageReference Include="RefactoringEssentials" Version="5.4.0" /> |
|||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> |
|||
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.8.0" /> |
|||
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> |
|||
</ItemGroup> |
|||
<PropertyGroup> |
|||
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet> |
|||
</PropertyGroup> |
|||
</Project> |
|||
@ -1,87 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppProvider.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.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Domain.Apps.Read.State.Grains; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State |
|||
{ |
|||
public sealed class AppProvider : IAppProvider |
|||
{ |
|||
private readonly IStateFactory factory; |
|||
|
|||
public AppProvider(IStateFactory factory) |
|||
{ |
|||
Guard.NotNull(factory, nameof(factory)); |
|||
|
|||
this.factory = factory; |
|||
} |
|||
|
|||
public async Task<IAppEntity> GetAppAsync(string appName) |
|||
{ |
|||
var app = await factory.GetSynchronizedAsync<AppStateGrain>(appName); |
|||
|
|||
return await app.GetAppAsync(); |
|||
} |
|||
|
|||
public async Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(string appName, Guid id) |
|||
{ |
|||
var app = await factory.GetSynchronizedAsync<AppStateGrain>(appName); |
|||
|
|||
return await app.GetAppWithSchemaAsync(id); |
|||
} |
|||
|
|||
public async Task<List<IRuleEntity>> GetRulesAsync(string appName) |
|||
{ |
|||
var app = await factory.GetSynchronizedAsync<AppStateGrain>(appName); |
|||
|
|||
return await app.GetRulesAsync(); |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> GetSchemaAsync(string appName, Guid id, bool provideDeleted = false) |
|||
{ |
|||
var app = await factory.GetSynchronizedAsync<AppStateGrain>(appName); |
|||
|
|||
return await app.GetSchemaAsync(id, provideDeleted); |
|||
} |
|||
|
|||
public async Task<ISchemaEntity> GetSchemaAsync(string appName, string name, bool provideDeleted = false) |
|||
{ |
|||
var app = await factory.GetSynchronizedAsync<AppStateGrain>(appName); |
|||
|
|||
return await app.GetSchemaAsync(name, provideDeleted); |
|||
} |
|||
|
|||
public async Task<List<ISchemaEntity>> GetSchemasAsync(string appName) |
|||
{ |
|||
var app = await factory.GetSynchronizedAsync<AppStateGrain>(appName); |
|||
|
|||
return await app.GetSchemasAsync(); |
|||
} |
|||
|
|||
public async Task<List<IAppEntity>> GetUserApps(string userId) |
|||
{ |
|||
var appUser = await factory.GetSynchronizedAsync<AppUserGrain>(userId); |
|||
var appNames = await appUser.GetAppNamesAsync(); |
|||
|
|||
var tasks = appNames.Select(x => GetAppAsync(x)); |
|||
|
|||
var apps = await Task.WhenAll(tasks); |
|||
|
|||
return apps.Where(a => a != null && a.Contributors.ContainsKey(userId)).ToList(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,70 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppStateEventConsumer.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Domain.Apps.Events.Apps; |
|||
using Squidex.Domain.Apps.Read.State.Grains; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.States; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State |
|||
{ |
|||
public sealed class AppStateEventConsumer : IEventConsumer |
|||
{ |
|||
private readonly IStateFactory factory; |
|||
|
|||
public string Name |
|||
{ |
|||
get { return typeof(AppStateEventConsumer).Name; } |
|||
} |
|||
|
|||
public string EventsFilter |
|||
{ |
|||
get { return @"(^app-)|(^schema-)|(^rule\-)"; } |
|||
} |
|||
|
|||
public AppStateEventConsumer(IStateFactory factory) |
|||
{ |
|||
Guard.NotNull(factory, nameof(factory)); |
|||
|
|||
this.factory = factory; |
|||
} |
|||
|
|||
public Task ClearAsync() |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public async Task On(Envelope<IEvent> @event) |
|||
{ |
|||
if (@event.Payload is AppEvent appEvent) |
|||
{ |
|||
var appGrain = await factory.GetSynchronizedAsync<AppStateGrain>(appEvent.AppId.Name); |
|||
|
|||
await appGrain.HandleAsync(@event); |
|||
} |
|||
|
|||
if (@event.Payload is AppContributorAssigned contributorAssigned) |
|||
{ |
|||
var userGrain = await factory.GetSynchronizedAsync<AppUserGrain>(contributorAssigned.ContributorId); |
|||
|
|||
await userGrain.AddAppAsync(contributorAssigned.AppId.Name); |
|||
} |
|||
|
|||
if (@event.Payload is AppContributorRemoved contributorRemoved) |
|||
{ |
|||
var userGrain = await factory.GetSynchronizedAsync<AppUserGrain>(contributorRemoved.ContributorId); |
|||
|
|||
await userGrain.RemoveAppAsync(contributorRemoved.AppId.Name); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,134 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppStateGrain.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.Core.Schemas; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Domain.Apps.Events.Apps; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Grains |
|||
{ |
|||
public class AppStateGrain : IStatefulObject |
|||
{ |
|||
private readonly FieldRegistry fieldRegistry; |
|||
private IPersistence<AppStateGrainState> persistence; |
|||
private Exception exception; |
|||
private AppStateGrainState state; |
|||
|
|||
public AppStateGrain(FieldRegistry fieldRegistry) |
|||
{ |
|||
Guard.NotNull(fieldRegistry, nameof(fieldRegistry)); |
|||
|
|||
this.fieldRegistry = fieldRegistry; |
|||
} |
|||
|
|||
public async Task ActivateAsync(string key, IStore store) |
|||
{ |
|||
persistence = store.WithSnapshots<AppStateGrain, AppStateGrainState>(key, s => state = s); |
|||
|
|||
try |
|||
{ |
|||
await persistence.ReadAsync(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
exception = ex; |
|||
} |
|||
|
|||
if (state == null) |
|||
{ |
|||
state = new AppStateGrainState(); |
|||
} |
|||
|
|||
state.SetRegistry(fieldRegistry); |
|||
} |
|||
|
|||
public virtual Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid id) |
|||
{ |
|||
var schema = state.FindSchema(x => x.Id == id && !x.IsDeleted); |
|||
|
|||
return Task.FromResult((state.GetApp(), schema)); |
|||
} |
|||
|
|||
public virtual Task<IAppEntity> GetAppAsync() |
|||
{ |
|||
var result = state.GetApp(); |
|||
|
|||
return Task.FromResult(result); |
|||
} |
|||
|
|||
public virtual Task<List<IRuleEntity>> GetRulesAsync() |
|||
{ |
|||
var result = state.FindRules(); |
|||
|
|||
return Task.FromResult(result); |
|||
} |
|||
|
|||
public virtual Task<List<ISchemaEntity>> GetSchemasAsync() |
|||
{ |
|||
var result = state.FindSchemas(x => !x.IsDeleted); |
|||
|
|||
return Task.FromResult(result); |
|||
} |
|||
|
|||
public virtual Task<ISchemaEntity> GetSchemaAsync(Guid id, bool provideDeleted = false) |
|||
{ |
|||
var result = state.FindSchema(x => x.Id == id && (!x.IsDeleted || provideDeleted)); |
|||
|
|||
return Task.FromResult(result); |
|||
} |
|||
|
|||
public virtual Task<ISchemaEntity> GetSchemaAsync(string name, bool provideDeleted = false) |
|||
{ |
|||
var result = state.FindSchema(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted)); |
|||
|
|||
return Task.FromResult(result); |
|||
} |
|||
|
|||
public async virtual Task HandleAsync(Envelope<IEvent> message) |
|||
{ |
|||
if (exception != null) |
|||
{ |
|||
if (message.Payload is AppCreated) |
|||
{ |
|||
exception = null; |
|||
} |
|||
else |
|||
{ |
|||
throw exception; |
|||
} |
|||
} |
|||
|
|||
if (message.Payload is AppEvent appEvent && (state.App == null || state.App.Id == appEvent.AppId.Id)) |
|||
{ |
|||
try |
|||
{ |
|||
state = state.Apply(message); |
|||
|
|||
await persistence.WriteSnapshotAsync(state); |
|||
} |
|||
catch (InconsistentStateException) |
|||
{ |
|||
await persistence.ReadAsync(); |
|||
|
|||
state = state.Apply(message); |
|||
|
|||
await persistence.WriteSnapshotAsync(state); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,75 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppStateGrainState.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using Newtonsoft.Json; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Grains |
|||
{ |
|||
public sealed partial class AppStateGrainState : Cloneable<AppStateGrainState> |
|||
{ |
|||
private FieldRegistry registry; |
|||
|
|||
[JsonProperty] |
|||
public JsonAppEntity App { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public ImmutableDictionary<Guid, JsonRuleEntity> Rules { get; set; } = ImmutableDictionary<Guid, JsonRuleEntity>.Empty; |
|||
|
|||
[JsonProperty] |
|||
public ImmutableDictionary<Guid, JsonSchemaEntity> Schemas { get; set; } = ImmutableDictionary<Guid, JsonSchemaEntity>.Empty; |
|||
|
|||
public void SetRegistry(FieldRegistry registry) |
|||
{ |
|||
this.registry = registry; |
|||
} |
|||
|
|||
public IAppEntity GetApp() |
|||
{ |
|||
return App; |
|||
} |
|||
|
|||
public ISchemaEntity FindSchema(Func<JsonSchemaEntity, bool> filter) |
|||
{ |
|||
return Schemas?.Values.FirstOrDefault(filter); |
|||
} |
|||
|
|||
public List<ISchemaEntity> FindSchemas(Func<JsonSchemaEntity, bool> filter) |
|||
{ |
|||
return Schemas?.Values.Where(filter).OfType<ISchemaEntity>().ToList() ?? new List<ISchemaEntity>(); |
|||
} |
|||
|
|||
public List<IRuleEntity> FindRules() |
|||
{ |
|||
return Rules?.Values.OfType<IRuleEntity>().ToList() ?? new List<IRuleEntity>(); |
|||
} |
|||
|
|||
public AppStateGrainState Apply(Envelope<IEvent> envelope) |
|||
{ |
|||
return Clone(c => |
|||
{ |
|||
c.DispatchAction(envelope.Payload, envelope.Headers); |
|||
|
|||
if (c.App != null) |
|||
{ |
|||
c.App.Etag = Guid.NewGuid().ToString(); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -1,117 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppStateGrainState_Apps.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Domain.Apps.Events.Apps; |
|||
using Squidex.Domain.Apps.Events.Apps.Utils; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Grains |
|||
{ |
|||
public sealed partial class AppStateGrainState |
|||
{ |
|||
public void On(AppCreated @event, EnvelopeHeaders headers) |
|||
{ |
|||
App = EntityMapper.Create<JsonAppEntity>(@event, headers, a => |
|||
{ |
|||
SimpleMapper.Map(@event, a); |
|||
|
|||
a.LanguagesConfig = LanguagesConfig.Build(Language.EN); |
|||
}); |
|||
} |
|||
|
|||
public void On(AppLanguageAdded @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateApp(@event, headers, a => |
|||
{ |
|||
a.LanguagesConfig = a.LanguagesConfig.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(AppLanguageRemoved @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateApp(@event, headers, a => |
|||
{ |
|||
a.LanguagesConfig = a.LanguagesConfig.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(AppLanguageUpdated @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateApp(@event, headers, a => |
|||
{ |
|||
a.LanguagesConfig = a.LanguagesConfig.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(AppContributorAssigned @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateApp(@event, headers, a => |
|||
{ |
|||
a.Contributors = a.Contributors.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(AppContributorRemoved @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateApp(@event, headers, a => |
|||
{ |
|||
a.Contributors = a.Contributors.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(AppClientAttached @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateApp(@event, headers, a => |
|||
{ |
|||
a.Clients = a.Clients.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(AppClientUpdated @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateApp(@event, headers, a => |
|||
{ |
|||
a.Clients = a.Clients.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(AppClientRenamed @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateApp(@event, headers, a => |
|||
{ |
|||
a.Clients = a.Clients.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(AppClientRevoked @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateApp(@event, headers, a => |
|||
{ |
|||
a.Clients = a.Clients.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(AppPlanChanged @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateApp(@event, headers, a => |
|||
{ |
|||
SimpleMapper.Map(@event, a); |
|||
}); |
|||
} |
|||
|
|||
private void UpdateApp(AppEvent @event, EnvelopeHeaders headers, Action<JsonAppEntity> updater = null) |
|||
{ |
|||
App = App.Clone().Update(@event, headers, updater); |
|||
} |
|||
} |
|||
} |
|||
@ -1,65 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppStateGrainState_Rules.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Domain.Apps.Events.Rules; |
|||
using Squidex.Domain.Apps.Events.Rules.Utils; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Grains |
|||
{ |
|||
public sealed partial class AppStateGrainState |
|||
{ |
|||
public void On(RuleCreated @event, EnvelopeHeaders headers) |
|||
{ |
|||
var id = @event.RuleId; |
|||
|
|||
Rules = Rules.SetItem(id, EntityMapper.Create<JsonRuleEntity>(@event, headers, r => |
|||
{ |
|||
r.RuleDef = RuleEventDispatcher.Create(@event); |
|||
})); |
|||
} |
|||
|
|||
public void On(RuleUpdated @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateRule(@event, headers, r => |
|||
{ |
|||
r.RuleDef = r.RuleDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(RuleEnabled @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateRule(@event, headers, r => |
|||
{ |
|||
r.RuleDef = r.RuleDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(RuleDisabled @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateRule(@event, headers, r => |
|||
{ |
|||
r.RuleDef = r.RuleDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(RuleDeleted @event, EnvelopeHeaders headers) |
|||
{ |
|||
Rules = Rules.Remove(@event.RuleId); |
|||
} |
|||
|
|||
private void UpdateRule(RuleEvent @event, EnvelopeHeaders headers, Action<JsonRuleEntity> updater = null) |
|||
{ |
|||
var id = @event.RuleId; |
|||
|
|||
Rules = Rules.SetItem(id, x => x.Clone().Update(@event, headers, updater)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,162 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppStateGrainState_Schemas.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Domain.Apps.Events.Schemas; |
|||
using Squidex.Domain.Apps.Events.Schemas.Old; |
|||
using Squidex.Domain.Apps.Events.Schemas.Utils; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
#pragma warning disable CS0612 // Type or member is obsolete
|
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Grains |
|||
{ |
|||
public sealed partial class AppStateGrainState |
|||
{ |
|||
public void On(SchemaCreated @event, EnvelopeHeaders headers) |
|||
{ |
|||
var id = @event.SchemaId.Id; |
|||
|
|||
Schemas = Schemas.SetItem(id, EntityMapper.Create<JsonSchemaEntity>(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = SchemaEventDispatcher.Create(@event, registry); |
|||
|
|||
SimpleMapper.Map(@event, s); |
|||
})); |
|||
} |
|||
|
|||
public void On(SchemaPublished @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = s.SchemaDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(SchemaUnpublished @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = s.SchemaDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(ScriptsConfigured @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
SimpleMapper.Map(s, @event); |
|||
}); |
|||
} |
|||
|
|||
public void On(SchemaUpdated @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = s.SchemaDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(SchemaFieldsReordered @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = s.SchemaDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(FieldAdded @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = s.SchemaDef.Apply(@event, registry); |
|||
}); |
|||
} |
|||
|
|||
public void On(FieldUpdated @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = s.SchemaDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(FieldLocked @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = s.SchemaDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(FieldDisabled @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = s.SchemaDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(FieldEnabled @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = s.SchemaDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(FieldHidden @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = s.SchemaDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(FieldShown @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = s.SchemaDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(FieldDeleted @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers, s => |
|||
{ |
|||
s.SchemaDef = s.SchemaDef.Apply(@event); |
|||
}); |
|||
} |
|||
|
|||
public void On(WebhookAdded @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers); |
|||
} |
|||
|
|||
public void On(WebhookDeleted @event, EnvelopeHeaders headers) |
|||
{ |
|||
UpdateSchema(@event, headers); |
|||
} |
|||
|
|||
public void On(SchemaDeleted @event, EnvelopeHeaders headers) |
|||
{ |
|||
Schemas = Schemas.Remove(@event.SchemaId.Id); |
|||
} |
|||
|
|||
private void UpdateSchema(SchemaEvent @event, EnvelopeHeaders headers, Action<JsonSchemaEntity> updater = null) |
|||
{ |
|||
var id = @event.SchemaId.Id; |
|||
|
|||
Schemas = Schemas.SetItem(id, x => x.Clone().Update(@event, headers, updater)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,48 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppUserGrain.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.States; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Grains |
|||
{ |
|||
public sealed class AppUserGrain : IStatefulObject |
|||
{ |
|||
private IPersistence<AppUserGrainState> persistence; |
|||
private AppUserGrainState state = new AppUserGrainState(); |
|||
|
|||
public Task ActivateAsync(string key, IStore store) |
|||
{ |
|||
persistence = store.WithSnapshots<AppUserGrain, AppUserGrainState>(key, s => state = s); |
|||
|
|||
return persistence.ReadAsync(); |
|||
} |
|||
|
|||
public Task AddAppAsync(string appName) |
|||
{ |
|||
state = state.AddApp(appName); |
|||
|
|||
return persistence.WriteSnapshotAsync(state); |
|||
} |
|||
|
|||
public Task RemoveAppAsync(string appName) |
|||
{ |
|||
state = state.RemoveApp(appName); |
|||
|
|||
return persistence.WriteSnapshotAsync(state); |
|||
} |
|||
|
|||
public Task<List<string>> GetAppNamesAsync() |
|||
{ |
|||
return Task.FromResult(state.AppNames.ToList()); |
|||
} |
|||
} |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppUserGrainState.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Immutable; |
|||
using Newtonsoft.Json; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Grains |
|||
{ |
|||
public sealed class AppUserGrainState : Cloneable<AppUserGrainState> |
|||
{ |
|||
[JsonProperty] |
|||
public ImmutableHashSet<string> AppNames { get; set; } = ImmutableHashSet<string>.Empty; |
|||
|
|||
public AppUserGrainState AddApp(string appName) |
|||
{ |
|||
return Clone(c => c.AppNames = c.AppNames.Add(appName)); |
|||
} |
|||
|
|||
public AppUserGrainState RemoveApp(string appName) |
|||
{ |
|||
return Clone(c => c.AppNames = c.AppNames.Remove(appName)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,38 +0,0 @@ |
|||
// ==========================================================================
|
|||
// JsonAppEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Newtonsoft.Json; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Grains |
|||
{ |
|||
public sealed class JsonAppEntity : JsonEntity<JsonAppEntity>, IAppEntity |
|||
{ |
|||
[JsonProperty] |
|||
public string Name { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public string PlanId { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public string Etag { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public string PlanOwner { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public AppClients Clients { get; set; } = AppClients.Empty; |
|||
|
|||
[JsonProperty] |
|||
public AppContributors Contributors { get; set; } = AppContributors.Empty; |
|||
|
|||
[JsonProperty] |
|||
public LanguagesConfig LanguagesConfig { get; set; } |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
// ==========================================================================
|
|||
// JsonEntity.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.Read.State.Grains |
|||
{ |
|||
public abstract class JsonEntity<T> : Cloneable<T>, IUpdateableEntityWithVersion where T : Cloneable |
|||
{ |
|||
[JsonProperty] |
|||
public Guid Id { 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 => { }); |
|||
} |
|||
} |
|||
} |
|||
@ -1,36 +0,0 @@ |
|||
// ==========================================================================
|
|||
// JsonRuleEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Newtonsoft.Json; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Grains |
|||
{ |
|||
public sealed class JsonRuleEntity : |
|||
JsonEntity<JsonRuleEntity>, |
|||
IRuleEntity, |
|||
IUpdateableEntityWithAppRef, |
|||
IUpdateableEntityWithCreatedBy, |
|||
IUpdateableEntityWithLastModifiedBy |
|||
{ |
|||
[JsonProperty] |
|||
public Guid AppId { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public RefToken CreatedBy { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public RefToken LastModifiedBy { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public Rule RuleDef { get; set; } |
|||
} |
|||
} |
|||
@ -1,63 +0,0 @@ |
|||
// ==========================================================================
|
|||
// JsonSchemaEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Newtonsoft.Json; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.State.Grains |
|||
{ |
|||
public sealed class JsonSchemaEntity : |
|||
JsonEntity<JsonSchemaEntity>, |
|||
ISchemaEntity, |
|||
IUpdateableEntityWithAppRef, |
|||
IUpdateableEntityWithCreatedBy, |
|||
IUpdateableEntityWithLastModifiedBy |
|||
{ |
|||
[JsonProperty] |
|||
public string Name { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public Guid AppId { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public RefToken CreatedBy { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public RefToken LastModifiedBy { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public bool IsDeleted { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public string ScriptQuery { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public string ScriptCreate { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public string ScriptUpdate { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public string ScriptDelete { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public string ScriptChange { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public Schema SchemaDef { get; set; } |
|||
|
|||
[JsonIgnore] |
|||
public bool IsPublished |
|||
{ |
|||
get { return SchemaDef.IsPublished; } |
|||
} |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppAggregateCommand.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Write |
|||
{ |
|||
public class AppAggregateCommand : AppCommand, IAggregateCommand |
|||
{ |
|||
Guid IAggregateCommand.AggregateId |
|||
{ |
|||
get { return AppId.Id; } |
|||
} |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppCommand.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Write |
|||
{ |
|||
public abstract class AppCommand : SquidexCommand |
|||
{ |
|||
public NamedId<Guid> AppId { get; set; } |
|||
} |
|||
} |
|||
@ -1,174 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppCommandMiddleware.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Read; |
|||
using Squidex.Domain.Apps.Read.Apps.Services; |
|||
using Squidex.Domain.Apps.Write.Apps.Commands; |
|||
using Squidex.Domain.Apps.Write.Apps.Guards; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
using Squidex.Shared.Users; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.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 async Task On(CreateApp command, CommandContext context) |
|||
{ |
|||
await handler.CreateAsync<AppDomainObject>(context, async a => |
|||
{ |
|||
await GuardApp.CanCreate(command, appProvider); |
|||
|
|||
a.Create(command); |
|||
|
|||
context.Complete(EntityCreatedResult.Create(a.Id, a.Version)); |
|||
}); |
|||
} |
|||
|
|||
protected async Task On(AssignContributor command, CommandContext context) |
|||
{ |
|||
await handler.UpdateAsync<AppDomainObject>(context, async a => |
|||
{ |
|||
await GuardAppContributors.CanAssign(a.Contributors, command, userResolver, appPlansProvider.GetPlan(a.Plan?.PlanId)); |
|||
|
|||
a.AssignContributor(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(RemoveContributor command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<AppDomainObject>(context, a => |
|||
{ |
|||
GuardAppContributors.CanRemove(a.Contributors, command); |
|||
|
|||
a.RemoveContributor(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(AttachClient command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<AppDomainObject>(context, a => |
|||
{ |
|||
GuardAppClients.CanAttach(a.Clients, command); |
|||
|
|||
a.AttachClient(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(UpdateClient command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<AppDomainObject>(context, a => |
|||
{ |
|||
GuardAppClients.CanUpdate(a.Clients, command); |
|||
|
|||
a.UpdateClient(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(RevokeClient command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<AppDomainObject>(context, a => |
|||
{ |
|||
GuardAppClients.CanRevoke(a.Clients, command); |
|||
|
|||
a.RevokeClient(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(AddLanguage command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<AppDomainObject>(context, a => |
|||
{ |
|||
GuardAppLanguages.CanAdd(a.LanguagesConfig, command); |
|||
|
|||
a.AddLanguage(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(RemoveLanguage command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<AppDomainObject>(context, a => |
|||
{ |
|||
GuardAppLanguages.CanRemove(a.LanguagesConfig, command); |
|||
|
|||
a.RemoveLanguage(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(UpdateLanguage command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<AppDomainObject>(context, a => |
|||
{ |
|||
GuardAppLanguages.CanUpdate(a.LanguagesConfig, command); |
|||
|
|||
a.UpdateLanguage(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(ChangePlan command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<AppDomainObject>(context, async a => |
|||
{ |
|||
GuardApp.CanChangePlan(command, a.Plan, appPlansProvider); |
|||
|
|||
if (command.FromCallback) |
|||
{ |
|||
a.ChangePlan(command); |
|||
} |
|||
else |
|||
{ |
|||
var result = await appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, a.Id, a.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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,260 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AppDomainObject.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Domain.Apps.Events.Apps; |
|||
using Squidex.Domain.Apps.Events.Apps.Utils; |
|||
using Squidex.Domain.Apps.Write.Apps.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Commands; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Apps |
|||
{ |
|||
public class AppDomainObject : DomainObjectBase |
|||
{ |
|||
private AppContributors contributors = AppContributors.Empty; |
|||
private AppClients clients = AppClients.Empty; |
|||
private LanguagesConfig languagesConfig = LanguagesConfig.English; |
|||
private AppPlan plan; |
|||
private string name; |
|||
|
|||
public string Name |
|||
{ |
|||
get { return name; } |
|||
} |
|||
|
|||
public AppPlan Plan |
|||
{ |
|||
get { return plan; } |
|||
} |
|||
|
|||
public AppClients Clients |
|||
{ |
|||
get { return clients; } |
|||
} |
|||
|
|||
public AppContributors Contributors |
|||
{ |
|||
get { return contributors; } |
|||
} |
|||
|
|||
public LanguagesConfig LanguagesConfig |
|||
{ |
|||
get { return languagesConfig; } |
|||
} |
|||
|
|||
public AppDomainObject(Guid id, int version) |
|||
: base(id, version) |
|||
{ |
|||
} |
|||
|
|||
protected void On(AppCreated @event) |
|||
{ |
|||
name = @event.Name; |
|||
} |
|||
|
|||
protected void On(AppContributorAssigned @event) |
|||
{ |
|||
contributors = contributors.Apply(@event); |
|||
} |
|||
|
|||
protected void On(AppContributorRemoved @event) |
|||
{ |
|||
contributors = contributors.Apply(@event); |
|||
} |
|||
|
|||
protected void On(AppClientAttached @event) |
|||
{ |
|||
clients = clients.Apply(@event); |
|||
} |
|||
|
|||
protected void On(AppClientUpdated @event) |
|||
{ |
|||
clients = clients.Apply(@event); |
|||
} |
|||
|
|||
protected void On(AppClientRenamed @event) |
|||
{ |
|||
clients = clients.Apply(@event); |
|||
} |
|||
|
|||
protected void On(AppClientRevoked @event) |
|||
{ |
|||
clients = clients.Apply(@event); |
|||
} |
|||
|
|||
protected void On(AppLanguageAdded @event) |
|||
{ |
|||
languagesConfig = languagesConfig.Apply(@event); |
|||
} |
|||
|
|||
protected void On(AppLanguageRemoved @event) |
|||
{ |
|||
languagesConfig = languagesConfig.Apply(@event); |
|||
} |
|||
|
|||
protected void On(AppLanguageUpdated @event) |
|||
{ |
|||
languagesConfig = languagesConfig.Apply(@event); |
|||
} |
|||
|
|||
protected void On(AppPlanChanged @event) |
|||
{ |
|||
plan = string.IsNullOrWhiteSpace(@event.PlanId) ? null : new AppPlan(@event.Actor, @event.PlanId); |
|||
} |
|||
|
|||
protected override void DispatchEvent(Envelope<IEvent> @event) |
|||
{ |
|||
this.DispatchAction(@event.Payload); |
|||
} |
|||
|
|||
public AppDomainObject Create(CreateApp command) |
|||
{ |
|||
ThrowIfCreated(); |
|||
|
|||
var appId = new NamedId<Guid>(command.AppId, command.Name); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new AppCreated { AppId = appId })); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, CreateInitialOwner(appId, command))); |
|||
RaiseEvent(SimpleMapper.Map(command, CreateInitialLanguage(appId))); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public AppDomainObject UpdateClient(UpdateClient command) |
|||
{ |
|||
ThrowIfNotCreated(); |
|||
|
|||
if (!string.IsNullOrWhiteSpace(command.Name)) |
|||
{ |
|||
RaiseEvent(SimpleMapper.Map(command, new AppClientRenamed())); |
|||
} |
|||
|
|||
if (command.Permission.HasValue) |
|||
{ |
|||
RaiseEvent(SimpleMapper.Map(command, new AppClientUpdated { Permission = command.Permission.Value })); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public AppDomainObject AssignContributor(AssignContributor command) |
|||
{ |
|||
ThrowIfNotCreated(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new AppContributorAssigned())); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public AppDomainObject RemoveContributor(RemoveContributor command) |
|||
{ |
|||
ThrowIfNotCreated(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new AppContributorRemoved())); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public AppDomainObject AttachClient(AttachClient command) |
|||
{ |
|||
ThrowIfNotCreated(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new AppClientAttached())); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public AppDomainObject RevokeClient(RevokeClient command) |
|||
{ |
|||
ThrowIfNotCreated(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new AppClientRevoked())); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public AppDomainObject AddLanguage(AddLanguage command) |
|||
{ |
|||
ThrowIfNotCreated(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new AppLanguageAdded())); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public AppDomainObject RemoveLanguage(RemoveLanguage command) |
|||
{ |
|||
ThrowIfNotCreated(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new AppLanguageRemoved())); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public AppDomainObject UpdateLanguage(UpdateLanguage command) |
|||
{ |
|||
ThrowIfNotCreated(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new AppLanguageUpdated())); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public AppDomainObject ChangePlan(ChangePlan command) |
|||
{ |
|||
ThrowIfNotCreated(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new AppPlanChanged())); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
private void RaiseEvent(AppEvent @event) |
|||
{ |
|||
if (@event.AppId == null) |
|||
{ |
|||
@event.AppId = new NamedId<Guid>(Id, name); |
|||
} |
|||
|
|||
RaiseEvent(Envelope.Create(@event)); |
|||
} |
|||
|
|||
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(name)) |
|||
{ |
|||
throw new DomainException("App has not been created."); |
|||
} |
|||
} |
|||
|
|||
private void ThrowIfCreated() |
|||
{ |
|||
if (!string.IsNullOrWhiteSpace(name)) |
|||
{ |
|||
throw new DomainException("App has already been created."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AddLanguage.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Apps.Commands |
|||
{ |
|||
public sealed class AddLanguage : AppAggregateCommand |
|||
{ |
|||
public Language Language { get; set; } |
|||
} |
|||
} |
|||
@ -1,19 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AssignContributor.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Apps.Commands |
|||
{ |
|||
public sealed class AssignContributor : AppAggregateCommand |
|||
{ |
|||
public string ContributorId { get; set; } |
|||
|
|||
public AppContributorPermission Permission { get; set; } |
|||
} |
|||
} |
|||
@ -1,19 +0,0 @@ |
|||
// ==========================================================================
|
|||
// AttachClient.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Apps.Commands |
|||
{ |
|||
public sealed class AttachClient : AppAggregateCommand |
|||
{ |
|||
public string Id { get; set; } |
|||
|
|||
public string Secret { get; } = RandomHash.New(); |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
// ==========================================================================
|
|||
// ChangePlan.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Write.Apps.Commands |
|||
{ |
|||
public sealed class ChangePlan : AppAggregateCommand |
|||
{ |
|||
public bool FromCallback { get; set; } |
|||
|
|||
public string PlanId { get; set; } |
|||
} |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
// ==========================================================================
|
|||
// CreateApp.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.Commands; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.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(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,15 +0,0 @@ |
|||
// ==========================================================================
|
|||
// RemoveContributor.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Write.Apps.Commands |
|||
{ |
|||
public sealed class RemoveContributor : AppAggregateCommand |
|||
{ |
|||
public string ContributorId { get; set; } |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
// ==========================================================================
|
|||
// RemoveLanguage.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Apps.Commands |
|||
{ |
|||
public sealed class RemoveLanguage : AppAggregateCommand |
|||
{ |
|||
public Language Language { get; set; } |
|||
} |
|||
} |
|||
@ -1,15 +0,0 @@ |
|||
// ==========================================================================
|
|||
// RevokeClient.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Write.Apps.Commands |
|||
{ |
|||
public sealed class RevokeClient : AppAggregateCommand |
|||
{ |
|||
public string Id { get; set; } |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// ==========================================================================
|
|||
// RenameClient.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Apps.Commands |
|||
{ |
|||
public sealed class UpdateClient : AppAggregateCommand |
|||
{ |
|||
public string Id { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public AppClientPermission? Permission { get; set; } |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
// ==========================================================================
|
|||
// UpdateLanguage.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.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; } |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue