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