diff --git a/backend/extensions/Squidex.Extensions/Actions/RuleHelper.cs b/backend/extensions/Squidex.Extensions/Actions/RuleHelper.cs index 920dfe93a..06f3de5b1 100644 --- a/backend/extensions/Squidex.Extensions/Actions/RuleHelper.cs +++ b/backend/extensions/Squidex.Extensions/Actions/RuleHelper.cs @@ -18,9 +18,10 @@ namespace Squidex.Extensions.Actions { if (!string.IsNullOrWhiteSpace(expression)) { - var vars = new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = new EventScriptVars { - ["event"] = @event + Event = @event }; return scriptEngine.Evaluate(vars, expression); diff --git a/backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs index 38e2a4829..b7da1629a 100644 --- a/backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs @@ -33,9 +33,10 @@ namespace Squidex.Extensions.Actions.Script protected override async Task ExecuteJobAsync(ScriptJob job, CancellationToken ct = default) { - var vars = new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = new EventScriptVars { - ["event"] = job.Event + Event = job.Event }; var result = await scriptEngine.ExecuteAsync(vars, job.Script, ct: ct); diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index 731138e14..1c8f16c6b 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -261,6 +261,7 @@ "common.disable": "Disable", "common.disabled": "Disabled", "common.displayName": "Display Name", + "common.documentation": "Documentation", "common.edit": "Edit", "common.editing": "Editing", "common.email": "Email", @@ -658,6 +659,7 @@ "rules.actionData": "Action Data", "rules.actionHint": "The selection of the action type cannot be changed later.", "rules.addSchema": "Add Schema", + "rules.advancedFormattingHint": "You can use advanced formatting", "rules.cancelFailed": "Failed to cancel rule. Please reload.", "rules.conditionHint": "Optional condition as javascript expression", "rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example", diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json index 4ec2324e0..a27bf885a 100644 --- a/backend/i18n/frontend_it.json +++ b/backend/i18n/frontend_it.json @@ -261,6 +261,7 @@ "common.disable": "Disable", "common.disabled": "Disabled", "common.displayName": "Nome visualizzato", + "common.documentation": "Documentation", "common.edit": "Modifica", "common.editing": "Editing", "common.email": "Email", @@ -658,6 +659,7 @@ "rules.actionData": "Action Data", "rules.actionHint": "The selection of the action type cannot be changed later.", "rules.addSchema": "Add Schema", + "rules.advancedFormattingHint": "You can use advanced formatting", "rules.cancelFailed": "Non è stato possibile eliminare la regola. Per favore ricarica.", "rules.conditionHint": "Optional condition as javascript expression", "rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example", diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json index 9f4567ab8..d51ee4539 100644 --- a/backend/i18n/frontend_nl.json +++ b/backend/i18n/frontend_nl.json @@ -261,6 +261,7 @@ "common.disable": "Uitzetten", "common.disabled": "Uitgezet", "common.displayName": "Weergavenaam", + "common.documentation": "Documentation", "common.edit": "Bewerken", "common.editing": "Bewerken", "common.email": "E-mail", @@ -658,6 +659,7 @@ "rules.actionData": "Actiegegevens", "rules.actionHint": "De selectie van het actietype kan later niet worden gewijzigd.", "rules.addSchema": "Add Schema", + "rules.advancedFormattingHint": "You can use advanced formatting", "rules.cancelFailed": "Annuleren van regel is mislukt. Laad opnieuw.", "rules.conditionHint": "Optional condition as javascript expression", "rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example", diff --git a/backend/i18n/frontend_zh.json b/backend/i18n/frontend_zh.json index b73c6aa04..fc1c5cfff 100644 --- a/backend/i18n/frontend_zh.json +++ b/backend/i18n/frontend_zh.json @@ -261,6 +261,7 @@ "common.disable": "Disable", "common.disabled": "已禁用", "common.displayName": "显示名称", + "common.documentation": "Documentation", "common.edit": "编辑", "common.editing": "Editing", "common.email": "电子邮件", @@ -658,6 +659,7 @@ "rules.actionData": "动作数据", "rules.actionHint": "动作类型的选择以后不能更改。", "rules.addSchema": "Add Schema", + "rules.advancedFormattingHint": "You can use advanced formatting", "rules.cancelFailed": "取消规则失败,请重新加载。", "rules.conditionHint": "Optional condition as javascript expression", "rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example", diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index 731138e14..1c8f16c6b 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -261,6 +261,7 @@ "common.disable": "Disable", "common.disabled": "Disabled", "common.displayName": "Display Name", + "common.documentation": "Documentation", "common.edit": "Edit", "common.editing": "Editing", "common.email": "Email", @@ -658,6 +659,7 @@ "rules.actionData": "Action Data", "rules.actionHint": "The selection of the action type cannot be changed later.", "rules.addSchema": "Add Schema", + "rules.advancedFormattingHint": "You can use advanced formatting", "rules.cancelFailed": "Failed to cancel rule. Please reload.", "rules.conditionHint": "Optional condition as javascript expression", "rules.conditionHint2": "Conditions are javascript expressions that define when to trigger, for example", diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptionAttribute.cs b/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptionAttribute.cs new file mode 100644 index 000000000..2b9311ba0 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptionAttribute.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core +{ + [AttributeUsage(AttributeTargets.Property)] + public sealed class FieldDescriptionAttribute : Attribute + { + public string Name { get; } + + public FieldDescriptionAttribute(string name) + { + Name = name; + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs b/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs index f0e515514..b87d6169e 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs @@ -60,6 +60,33 @@ namespace Squidex.Domain.Apps.Core { } } + /// + /// Looks up a localized string similar to The user or client that triggered the event or command.. + /// + public static string Actor { + get { + return ResourceManager.GetString("Actor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The ID of this actor.. + /// + public static string ActorIdentifier { + get { + return ResourceManager.GetString("ActorIdentifier", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type of this actor.. + /// + public static string ActorType { + get { + return ResourceManager.GetString("ActorType", resourceCulture); + } + } + /// /// Looks up a localized string similar to The ID of the current app.. /// @@ -303,6 +330,33 @@ namespace Squidex.Domain.Apps.Core { } } + /// + /// Looks up a localized string similar to The user when someone is mentioned with '@'.. + /// + public static string CommentMentionedUser { + get { + return ResourceManager.GetString("CommentMentionedUser", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The text of the comment.. + /// + public static string CommentText { + get { + return ResourceManager.GetString("CommentText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The URL pointing to the source of the comment.. + /// + public static string CommentUrl { + get { + return ResourceManager.GetString("CommentUrl", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0} field ({1} component).. /// @@ -321,6 +375,15 @@ namespace Squidex.Domain.Apps.Core { } } + /// + /// Looks up a localized string similar to The content.. + /// + public static string Content { + get { + return ResourceManager.GetString("Content", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0} nested field.. /// @@ -627,6 +690,42 @@ namespace Squidex.Domain.Apps.Core { } } + /// + /// Looks up a localized string similar to The actual event.. + /// + public static string Event { + get { + return ResourceManager.GetString("Event", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The name of the event.. + /// + public static string EventName { + get { + return ResourceManager.GetString("EventName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The event timestamp.. + /// + public static string EventTimestamp { + get { + return ResourceManager.GetString("EventTimestamp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type of the event, e.g. 'Created' or 'Updated'.. + /// + public static string EventType { + get { + return ResourceManager.GetString("EventType", resourceCulture); + } + } + /// /// Looks up a localized string similar to The path to the json value.. /// @@ -636,6 +735,24 @@ namespace Squidex.Domain.Apps.Core { } } + /// + /// Looks up a localized string similar to The ID part of this ID.. + /// + public static string NamedId { + get { + return ResourceManager.GetString("NamedId", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The name part of this ID.. + /// + public static string NamedName { + get { + return ResourceManager.GetString("NamedName", resourceCulture); + } + } + /// /// Looks up a localized string similar to The current operation.. /// @@ -717,6 +834,33 @@ namespace Squidex.Domain.Apps.Core { } } + /// + /// Looks up a localized string similar to The ID of the schema.. + /// + public static string SchemaId { + get { + return ResourceManager.GetString("SchemaId", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The current number of calls.. + /// + public static string UsageCallsCurrent { + get { + return ResourceManager.GetString("UsageCallsCurrent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configured usage limit.. + /// + public static string UsageCallsLimit { + get { + return ResourceManager.GetString("UsageCallsLimit", resourceCulture); + } + } + /// /// Looks up a localized string similar to Information about the current user.. /// @@ -727,7 +871,7 @@ namespace Squidex.Domain.Apps.Core { } /// - /// Looks up a localized string similar to The additional properties of the user.. + /// Looks up a localized string similar to The additional properties of this user.. /// public static string UserClaims { get { @@ -736,7 +880,7 @@ namespace Squidex.Domain.Apps.Core { } /// - /// Looks up a localized string similar to The display name of the user.. + /// Looks up a localized string similar to The display name of this user.. /// public static string UserDisplayName { get { @@ -745,7 +889,7 @@ namespace Squidex.Domain.Apps.Core { } /// - /// Looks up a localized string similar to The email address of the current user.. + /// Looks up a localized string similar to The email address ofthis user.. /// public static string UserEmail { get { @@ -754,7 +898,7 @@ namespace Squidex.Domain.Apps.Core { } /// - /// Looks up a localized string similar to The ID of the user.. + /// Looks up a localized string similar to The ID of this user.. /// public static string UserId { get { @@ -763,7 +907,7 @@ namespace Squidex.Domain.Apps.Core { } /// - /// Looks up a localized string similar to True when the current user is a client, which is typically the case when the request is made from the API.. + /// Looks up a localized string similar to True when this user is a client, which is typically the case when the request is made from the API.. /// public static string UserIsClient { get { @@ -772,7 +916,7 @@ namespace Squidex.Domain.Apps.Core { } /// - /// Looks up a localized string similar to True when the current user is a user, which is typically the case when the request is made in the UI.. + /// Looks up a localized string similar to True when this user is a user, which is typically the case when the request is made in the UI.. /// public static string UserIsUser { get { diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx b/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx index b8d8bf241..783b3a224 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx +++ b/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx @@ -117,6 +117,15 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The user or client that triggered the event or command. + + + The ID of this actor. + + + The type of this actor. + The ID of the current app. @@ -198,12 +207,24 @@ The executed command. + + The user when someone is mentioned with '@'. + + + The text of the comment. + + + The URL pointing to the source of the comment. + {0} field ({1} component). The schema id to identity the component type. + + The content. + {0} nested field. @@ -306,9 +327,27 @@ The version of the objec. + + The actual event. + + + The name of the event. + + + The event timestamp. + + + The type of the event, e.g. 'Created' or 'Updated'. + The path to the json value. + + The ID part of this ID. + + + The name part of this ID. + The current operation. @@ -336,26 +375,35 @@ The optional version of the content to retrieve an older instance (not cached). + + The ID of the schema. + + + The current number of calls. + + + The configured usage limit. + Information about the current user. - The additional properties of the user. + The additional properties of this user. - The display name of the user. + The display name of this user. - The email address of the current user. + The email address ofthis user. - The ID of the user. + The ID of this user. - True when the current user is a client, which is typically the case when the request is made from the API. + True when this user is a client, which is typically the case when the request is made from the API. - True when the current user is a user, which is typically the case when the request is made in the UI. + True when this user is a user, which is typically the case when the request is made in the UI. The list of additional properties that have the name 'name'. diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs index 083dc1f0a..55f424e7b 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs @@ -13,32 +13,61 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public sealed class EnrichedAssetEvent : EnrichedUserEventBase, IEnrichedEntityEvent { + [FieldDescription(nameof(FieldDescriptions.EventType))] public EnrichedAssetEventType Type { get; set; } + [FieldDescription(nameof(FieldDescriptions.EntityId))] public DomainId Id { get; set; } + [FieldDescription(nameof(FieldDescriptions.EntityCreated))] public Instant Created { get; set; } + [FieldDescription(nameof(FieldDescriptions.EntityLastModified))] public Instant LastModified { get; set; } + [FieldDescription(nameof(FieldDescriptions.EntityCreatedBy))] public RefToken CreatedBy { get; set; } + [FieldDescription(nameof(FieldDescriptions.EntityLastModifiedBy))] public RefToken LastModifiedBy { get; set; } + [FieldDescription(nameof(FieldDescriptions.AssetParentId))] + public DomainId ParentId { get; } + + [FieldDescription(nameof(FieldDescriptions.AssetMimeType))] public string MimeType { get; set; } + [FieldDescription(nameof(FieldDescriptions.AssetFileName))] public string FileName { get; set; } + [FieldDescription(nameof(FieldDescriptions.AssetFileHash))] + public string FileHash { get; set; } + + [FieldDescription(nameof(FieldDescriptions.AssetSlug))] + public string Slug { get; set; } + + [FieldDescription(nameof(FieldDescriptions.AssetFileVersion))] public long FileVersion { get; set; } + [FieldDescription(nameof(FieldDescriptions.AssetFileSize))] public long FileSize { get; set; } + [FieldDescription(nameof(FieldDescriptions.AssetIsProtected))] + public bool IsProtected { get; set; } + + [FieldDescription(nameof(FieldDescriptions.AssetPixelWidth))] public int? PixelWidth { get; set; } + [FieldDescription(nameof(FieldDescriptions.AssetPixelHeight))] public int? PixelHeight { get; set; } + [FieldDescription(nameof(FieldDescriptions.AssetType))] public AssetType AssetType { get; set; } + [FieldDescription(nameof(FieldDescriptions.AssetMetadata))] + public AssetMetadata Metadata { get; } + + [FieldDescription(nameof(FieldDescriptions.AssetIsImage))] public bool IsImage { get => AssetType == AssetType.Image; diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs index 64a739fce..89665cd89 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs @@ -9,16 +9,19 @@ using System.Runtime.Serialization; using Squidex.Shared.Users; #pragma warning disable CA1822 // Mark members as static +#pragma warning disable SA1133 // Do not combine attributes namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public sealed class EnrichedCommentEvent : EnrichedUserEventBase { + [FieldDescription(nameof(FieldDescriptions.CommentText))] public string Text { get; set; } + [FieldDescription(nameof(FieldDescriptions.CommentUrl))] public Uri? Url { get; set; } - [IgnoreDataMember] + [FieldDescription(nameof(FieldDescriptions.CommentMentionedUser)), IgnoreDataMember] public IUser MentionedUser { get; set; } [IgnoreDataMember] diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs index 2466057a6..804517dbe 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs @@ -13,24 +13,34 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public sealed class EnrichedContentEvent : EnrichedSchemaEventBase, IEnrichedEntityEvent { + [FieldDescription(nameof(FieldDescriptions.EventType))] public EnrichedContentEventType Type { get; set; } + [FieldDescription(nameof(FieldDescriptions.EntityId))] public DomainId Id { get; set; } + [FieldDescription(nameof(FieldDescriptions.EntityCreated))] public Instant Created { get; set; } + [FieldDescription(nameof(FieldDescriptions.EntityLastModified))] public Instant LastModified { get; set; } + [FieldDescription(nameof(FieldDescriptions.EntityCreatedBy))] public RefToken CreatedBy { get; set; } + [FieldDescription(nameof(FieldDescriptions.EntityLastModifiedBy))] public RefToken LastModifiedBy { get; set; } + [FieldDescription(nameof(FieldDescriptions.ContentData))] public ContentData Data { get; set; } + [FieldDescription(nameof(FieldDescriptions.ContentDataOld))] public ContentData? DataOld { get; set; } + [FieldDescription(nameof(FieldDescriptions.ContentStatus))] public Status Status { get; set; } + [FieldDescription(nameof(FieldDescriptions.ContentNewStatus))] public Status? NewStatus { get; set; } public override long Partition diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedEvent.cs index 2a8376c2e..ec5f8aeb5 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedEvent.cs @@ -12,12 +12,16 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public abstract class EnrichedEvent { + [FieldDescription(nameof(FieldDescriptions.AppId))] public NamedId AppId { get; set; } + [FieldDescription(nameof(FieldDescriptions.EventTimestamp))] public Instant Timestamp { get; set; } + [FieldDescription(nameof(FieldDescriptions.EventName))] public string Name { get; set; } + [FieldDescription(nameof(FieldDescriptions.EntityVersion))] public long Version { get; set; } public abstract long Partition { get; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs index 82a940be9..7a20b0adb 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs @@ -11,6 +11,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public sealed class EnrichedSchemaEvent : EnrichedSchemaEventBase, IEnrichedEntityEvent { + [FieldDescription(nameof(FieldDescriptions.EventType))] public EnrichedSchemaEventType Type { get; set; } public DomainId Id diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventBase.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventBase.cs index 484c15edf..9bf33879f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventBase.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventBase.cs @@ -11,6 +11,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public abstract class EnrichedSchemaEventBase : EnrichedUserEventBase { + [FieldDescription(nameof(FieldDescriptions.EntityVersion))] public NamedId SchemaId { get; set; } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUsageExceededEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUsageExceededEvent.cs index c9794bc60..15eaf96a0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUsageExceededEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUsageExceededEvent.cs @@ -9,8 +9,10 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public sealed class EnrichedUsageExceededEvent : EnrichedEvent { + [FieldDescription(nameof(FieldDescriptions.UsageCallsCurrent))] public long CallsCurrent { get; set; } + [FieldDescription(nameof(FieldDescriptions.UsageCallsLimit))] public long CallsLimit { get; set; } public override long Partition diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUserEventBase.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUserEventBase.cs index 3acc50943..724d3b475 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUserEventBase.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUserEventBase.cs @@ -10,14 +10,16 @@ using Squidex.Infrastructure; using Squidex.Shared.Users; #pragma warning disable CA1822 // Mark members as static +#pragma warning disable SA1133 // Do not combine attributes namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents { public abstract class EnrichedUserEventBase : EnrichedEvent { + [FieldDescription(nameof(FieldDescriptions.Actor))] public RefToken Actor { get; set; } - [IgnoreDataMember] + [FieldDescription(nameof(FieldDescriptions.User)), IgnoreDataMember] public IUser? User { get; set; } public bool ShouldSerializeUser() diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventJintExtension.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventJintExtension.cs index 98b0ed6fd..266a6941c 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventJintExtension.cs @@ -90,7 +90,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Extensions if (scope.HasFlag(ScriptScope.AssetTrigger)) { describe(JsonType.Function, "assetContentUrl", - Resources.ScriptingAssetContentUrl); + Resources.ScriptingAssetContentUrl); describe(JsonType.Function, "assetContentAppUrl", Resources.ScriptingAssetContentAppUrl); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs index a55bf3377..e2f76c472 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs @@ -103,9 +103,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules if (TryGetScript(text.Trim(), out var script)) { - var vars = new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = new EventScriptVars { - ["event"] = @event + Event = @event }; #pragma warning disable MA0042 // Do not use blocking calls in an async method diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.Designer.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.Designer.cs index 1977af222..19db21a60 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.Designer.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.Designer.cs @@ -133,7 +133,7 @@ namespace Squidex.Domain.Apps.Core.Properties { } /// - /// Looks up a localized string similar to Tell Squidex to not allow the current operation and to return a 403 (Forbidden).. + /// Looks up a localized string similar to Tell Squidex to not allow the current operation and to return a 400 (BadRequest).. /// internal static string ScriptingDisallow { get { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.resx b/backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.resx index a3d70a319..f81ab5834 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.resx +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.resx @@ -142,7 +142,7 @@ Makes a DELETE request to the defined URL and parses the result as JSON. Headers are optional. - Tell Squidex to not allow the current operation and to return a 403 (Forbidden). + Tell Squidex to not allow the current operation and to return a 400 (BadRequest). Formats a JavaScript date object using the specified pattern. diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetCommandScriptVars.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetCommandScriptVars.cs new file mode 100644 index 000000000..b50b53b72 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetCommandScriptVars.cs @@ -0,0 +1,82 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Assets; +using Squidex.Domain.Apps.Core.Scripting.Internal; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Scripting +{ + public sealed class AssetCommandScriptVars : ScriptVars + { + [FieldDescription(nameof(FieldDescriptions.AssetParentId))] + public DomainId ParentId + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetFileHash))] + public string? FileHash + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetFileName))] + public string? FileName + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetSlug))] + public string? FileSlug + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetMimeType))] + public string? MimeType + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetParentPath))] + public Array? ParentPath + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetMetadata))] + public AssetMetadata? Metadata + { + set => SetValue(value != null ? new AssetMetadataWrapper(value) : null); + } + + [FieldDescription(nameof(FieldDescriptions.AssetTags))] + public HashSet? Tags + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetFileSize))] + public long FileSize + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetIsProtected))] + public bool? IsProtected + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.EntityRequestDeletePermanent))] + public bool? Permanent + { + set => SetValue(value); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetEntityScriptVars.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetEntityScriptVars.cs new file mode 100644 index 000000000..e28d84ab2 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetEntityScriptVars.cs @@ -0,0 +1,83 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.ObjectModel; +using Squidex.Domain.Apps.Core.Assets; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; + +namespace Squidex.Domain.Apps.Core.Scripting +{ + public sealed class AssetEntityScriptVars : ScriptVars + { + [FieldDescription(nameof(FieldDescriptions.AssetParentId))] + public DomainId ParentId + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetFileHash))] + public string? FileHash + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetFileName))] + public string? FileName + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetSlug))] + public string? FileSlug + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetMimeType))] + public string? MimeType + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetParentPath))] + public Array? ParentPath + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetMetadata))] + public AssetMetadata? Metadata + { + set => SetValue(value != null ? new ReadOnlyDictionary(value) : null); + } + + [FieldDescription(nameof(FieldDescriptions.AssetTags))] + public HashSet? Tags + { + set => SetValue(value != null ? new ReadOnlyCollection(value.ToList()) : null); + } + + [FieldDescription(nameof(FieldDescriptions.AssetFileSize))] + public long FileSize + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetFileVersion))] + public long FileVersion + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AssetIsProtected))] + public bool? IsProtected + { + set => SetValue(value); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetScriptVars.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetScriptVars.cs new file mode 100644 index 000000000..0f716d23f --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetScriptVars.cs @@ -0,0 +1,57 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Security.Claims; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Scripting +{ + public sealed class AssetScriptVars : ScriptVars + { + [FieldDescription(nameof(FieldDescriptions.AppId))] + public DomainId AppId + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.EntityId))] + public DomainId AssetId + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AppName))] + public string AppName + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.Operation))] + public string Operation + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.Command))] + public AssetCommandScriptVars Command + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.Asset))] + public AssetEntityScriptVars Asset + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.User))] + public ClaimsPrincipal? User + { + set => SetValue(value); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentScriptVars.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentScriptVars.cs new file mode 100644 index 000000000..4fcea8597 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentScriptVars.cs @@ -0,0 +1,101 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Security.Claims; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Scripting +{ + public sealed class ContentScriptVars : DataScriptVars + { + [FieldDescription(nameof(FieldDescriptions.AppId))] + public DomainId AppId + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.SchemaId))] + public DomainId SchemaId + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.EntityId))] + public DomainId ContentId + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.AppName))] + public string AppName + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.ContentSchemaName))] + public string SchemaName + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.Operation))] + public string Operation + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.EntityRequestDeletePermanent))] + public bool Permanent + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.User))] + public ClaimsPrincipal? User + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.ContentStatus))] + public Status Status + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.ContentStatusOld))] + public Status StatusOld + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.ContentStatusOld))] + public Status OldStatus + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.ContentData))] + public ContentData? DataOld + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.ContentDataOld))] + public ContentData? OldData + { + set => SetValue(value); + } + + [FieldDescription(nameof(FieldDescriptions.ContentData))] + public override ContentData? Data + { + get => GetValue(); + set => SetValue(value); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/DataScriptVars.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/DataScriptVars.cs new file mode 100644 index 000000000..1f25f23a7 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/DataScriptVars.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Contents; + +namespace Squidex.Domain.Apps.Core.Scripting +{ + public class DataScriptVars : ScriptVars + { + public virtual ContentData? Data + { + get => GetValue(); + set => SetValue(value); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/EventScriptVars.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/EventScriptVars.cs new file mode 100644 index 000000000..41f157249 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/EventScriptVars.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; + +namespace Squidex.Domain.Apps.Core.Scripting +{ + public sealed class EventScriptVars : ScriptVars + { + public EnrichedEvent Event + { + set => SetValue(value); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs index 5732c9b7f..207996e92 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs @@ -36,6 +36,11 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions public void Describe(AddDescription describe, ScriptScope scope) { + if (!scope.HasFlag(ScriptScope.Async)) + { + return; + } + describe(JsonType.Function, "getJSON(url, callback, headers?)", Resources.ScriptingGetJSON); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs index 6db8031d2..b748fb797 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs @@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.Scripting Task ExecuteAsync(ScriptVars vars, string script, ScriptOptions options = default, CancellationToken ct = default); - Task TransformAsync(ScriptVars vars, string script, ScriptOptions options = default, + Task TransformAsync(DataScriptVars vars, string script, ScriptOptions options = default, CancellationToken ct = default); IJsonValue Execute(ScriptVars vars, string script, ScriptOptions options = default); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/AssetMetadataWrapper.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/AssetMetadataWrapper.cs new file mode 100644 index 000000000..b5b34e169 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/AssetMetadataWrapper.cs @@ -0,0 +1,125 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using Squidex.Domain.Apps.Core.Assets; +using Squidex.Infrastructure.Json.Objects; + +namespace Squidex.Domain.Apps.Core.Scripting.Internal +{ + internal sealed class AssetMetadataWrapper : IDictionary + { + private readonly AssetMetadata metadata; + + public int Count + { + get => metadata.Count; + } + + public ICollection Keys + { + get => metadata.Keys; + } + + public ICollection Values + { + get => metadata.Values.Cast().ToList(); + } + + public object? this[string key] + { + get => metadata[key]; + set => metadata[key] = JsonValue.Create(value); + } + + public bool IsReadOnly + { + get => false; + } + + public AssetMetadataWrapper(AssetMetadata metadata) + { + this.metadata = metadata; + } + + public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value) + { + if (metadata.TryGetValue(key, out var temp)) + { + value = temp; + return true; + } + else + { + value = null; + return false; + } + } + + public void Add(string key, object? value) + { + metadata.Add(key, JsonValue.Create(value)); + } + + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + public bool Remove(string key) + { + return metadata.Remove(key); + } + + public bool Remove(KeyValuePair item) + { + return false; + } + + public void Clear() + { + metadata.Clear(); + } + + public bool Contains(KeyValuePair item) + { + return false; + } + + public bool ContainsKey(string key) + { + return metadata.ContainsKey(key); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + var i = arrayIndex; + + foreach (var item in metadata) + { + if (i >= array.Length) + { + break; + } + + array[i] = new KeyValuePair(item.Key, item.Value); + i++; + } + } + + public IEnumerator> GetEnumerator() + { + return metadata.Select(x => new KeyValuePair(x.Key, x.Value)).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)metadata).GetEnumerator(); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs index 2eef28679..67c427afa 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Core.Scripting } } - public async Task TransformAsync(ScriptVars vars, string script, ScriptOptions options = default, + public async Task TransformAsync(DataScriptVars vars, string script, ScriptOptions options = default, CancellationToken ct = default) { Guard.NotNull(vars); @@ -194,20 +194,25 @@ namespace Squidex.Domain.Apps.Core.Scripting public void Describe(AddDescription describe, ScriptScope scope) { + if (scope.HasFlag(ScriptScope.ContentTrigger) || scope.HasFlag(ScriptScope.AssetTrigger)) + { + return; + } + if (scope.HasFlag(ScriptScope.Transform) || scope.HasFlag(ScriptScope.ContentScript)) { describe(JsonType.Function, "replace()", Resources.ScriptingReplace); } - describe(JsonType.Function, "disallow()", + describe(JsonType.Function, "disallow(reason)", Resources.ScriptingDisallow); - describe(JsonType.Function, "complete()", - Resources.ScriptingComplete); - describe(JsonType.Function, "reject(reason)", Resources.ScriptingReject); + + describe(JsonType.Function, "complete()", + Resources.ScriptingComplete); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContext.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContext.cs index b4f9cdf8f..e1f0c9759 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContext.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContext.cs @@ -17,11 +17,6 @@ namespace Squidex.Domain.Apps.Core.Scripting { } - public ScriptContext(ScriptContext source) - : base(source, StringComparer.OrdinalIgnoreCase) - { - } - public bool TryGetValue(string key, [MaybeNullWhen(false)] out T value) { Guard.NotNull(key); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptScope.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptScope.cs index 38b355f74..15fbe8efb 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptScope.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptScope.cs @@ -10,10 +10,14 @@ namespace Squidex.Domain.Apps.Core.Scripting [Flags] public enum ScriptScope { + Async, AssetScript, AssetTrigger, ContentScript, ContentTrigger, - Transform + Transform, + SchemaTrigger, + UsageTrigger, + CommentTrigger } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs index 7113eb459..964f553f7 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs @@ -6,27 +6,11 @@ // ========================================================================== using System.Runtime.CompilerServices; -using Squidex.Domain.Apps.Core.Contents; namespace Squidex.Domain.Apps.Core.Scripting { - public sealed class ScriptVars : ScriptContext + public class ScriptVars : ScriptContext { - public ScriptVars() - { - } - - public ScriptVars(ScriptVars source) - : base(source) - { - } - - public ContentData? Data - { - get => GetValue(); - set => SetValue(value); - } - public void SetValue(object? value, [CallerMemberName] string? key = null) { if (key != null) diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs index c0c01dd72..7cb761163 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs @@ -5,195 +5,330 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections; +using System.Globalization; +using System.Reflection; +using System.Security.Claims; +using System.Text.RegularExpressions; +using NodaTime; +using Squidex.Domain.Apps.Core.Assets; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Queries; +using Squidex.Infrastructure.Reflection.Internal; +using Squidex.Shared.Users; +using Squidex.Text; namespace Squidex.Domain.Apps.Core.Scripting { public sealed class ScriptingCompleter { private readonly IEnumerable descriptors; + private static readonly FilterSchema DynamicData = new FilterSchema(FilterSchemaType.Object) + { + Fields = ReadonlyList.Create( + new FilterField(new FilterSchema(FilterSchemaType.Object), "my-field"), + new FilterField(FilterSchema.String, "my-field.iv")) + }; public ScriptingCompleter(IEnumerable descriptors) { this.descriptors = descriptors; } + public IReadOnlyList Trigger(string type) + { + Guard.NotNull(type); + + switch (type) + { + case "AssetChanged": + return AssetTrigger(); + case "Comment": + return CommentTrigger(); + case "ContentChanged": + return ContentTrigger(DynamicData); + case "SchemaChanged": + return SchemaTrigger(); + case "Usage": + return UsageTrigger(); + default: + return new List(); + } + } + public IReadOnlyList ContentScript(FilterSchema dataSchema) { Guard.NotNull(dataSchema); - return new Process(descriptors).Content(dataSchema, ScriptScope.ContentScript | ScriptScope.Transform); + return new Process(descriptors, dataSchema.Flatten()).ContentScript(); } public IReadOnlyList ContentTrigger(FilterSchema dataSchema) { Guard.NotNull(dataSchema); - return new Process(descriptors).Content(dataSchema, ScriptScope.ContentTrigger); + return new Process(descriptors, dataSchema.Flatten()).ContentTrigger(); } public IReadOnlyList AssetScript() { - return new Process(descriptors).Asset(ScriptScope.AssetScript); + return new Process(descriptors).AssetScript(); } public IReadOnlyList AssetTrigger() { - return new Process(descriptors).Asset(ScriptScope.AssetTrigger); + return new Process(descriptors).AssetTrigger(); + } + + public IReadOnlyList CommentTrigger() + { + return new Process(descriptors).CommentTrigger(); + } + + public IReadOnlyList SchemaTrigger() + { + return new Process(descriptors).SchemaTrigger(); + } + + public IReadOnlyList UsageTrigger() + { + return new Process(descriptors).UsageTrigger(); } private sealed class Process { + private static readonly Regex PropertyRegex = new Regex(@"^(?!\d)[\w$]+$", RegexOptions.Compiled); private readonly Stack prefixes = new Stack(); - private readonly HashSet result = new HashSet(); + private readonly Dictionary result = new Dictionary(); private readonly IEnumerable descriptors; + private readonly FilterSchema? dataSchema; - public Process(IEnumerable descriptors) + public Process(IEnumerable descriptors, FilterSchema? dataSchema = null) { this.descriptors = descriptors; + this.dataSchema = dataSchema; } - public IReadOnlyList Content(FilterSchema dataSchema, ScriptScope scope) + private IReadOnlyList Build() { - AddShared(scope); + return result.Values.OrderBy(x => x.Path).ToList(); + } - AddObject("ctx", FieldDescriptions.Context, () => - { - AddString("contentId", - FieldDescriptions.EntityId); + public IReadOnlyList SchemaTrigger() + { + AddHelpers(ScriptScope.SchemaTrigger); - AddString("status", - FieldDescriptions.ContentStatus); + AddObject("event", FieldDescriptions.Event, () => + { + AddType(typeof(EnrichedSchemaEvent)); + }); - AddString("statusOld", - FieldDescriptions.ContentStatusOld); + return Build(); + } - AddObject("data", FieldDescriptions.ContentData, () => - { - AddData(dataSchema); - }); + public IReadOnlyList CommentTrigger() + { + AddHelpers(ScriptScope.CommentTrigger); - AddObject("dataOld", FieldDescriptions.ContentDataOld, () => - { - AddData(dataSchema); - }); + AddObject("event", FieldDescriptions.Event, () => + { + AddType(typeof(EnrichedCommentEvent)); }); - return result.OrderBy(x => x.Path).ToList(); + return Build(); } - public IReadOnlyList Asset(ScriptScope scope) + public IReadOnlyList UsageTrigger() { - AddShared(scope); + AddHelpers(ScriptScope.UsageTrigger); - AddObject("ctx", FieldDescriptions.Context, () => + AddObject("event", FieldDescriptions.Event, () => { - AddString("assetId", - FieldDescriptions.EntityId); + AddType(typeof(EnrichedUsageExceededEvent)); + }); - AddObject("asset", - FieldDescriptions.Asset, () => - { - AddSharedAsset(); + return Build(); + } - AddNumber("fileVersion", - FieldDescriptions.AssetFileVersion); - }); + public IReadOnlyList ContentScript() + { + var scope = ScriptScope.ContentScript | ScriptScope.Transform | ScriptScope.Async; - AddObject("command", - FieldDescriptions.Command, () => - { - AddSharedAsset(); + AddHelpers(scope); - AddBoolean("permanent", - FieldDescriptions.EntityRequestDeletePermanent); - }); + AddObject("ctx", FieldDescriptions.Context, () => + { + AddType(typeof(ContentScriptVars)); }); - return result.OrderBy(x => x.Path).ToList(); + return Build(); } - private void AddSharedAsset() + public IReadOnlyList ContentTrigger() { - AddString("fileHash", - FieldDescriptions.AssetFileHash); - - AddString("fileName", - FieldDescriptions.AssetFileName); + var scope = ScriptScope.ContentTrigger; - AddString("fileSize", - FieldDescriptions.AssetFileSize); + AddHelpers(scope); - AddString("fileSlug", - FieldDescriptions.AssetSlug); + AddObject("event", FieldDescriptions.Event, () => + { + AddType(typeof(EnrichedContentEvent)); + }); - AddString("mimeType", - FieldDescriptions.AssetMimeType); + return Build(); + } - AddBoolean("isProtected", - FieldDescriptions.AssetIsProtected); + public IReadOnlyList AssetTrigger() + { + AddHelpers(ScriptScope.AssetTrigger); - AddString("parentId", - FieldDescriptions.AssetParentId); + AddObject("event", FieldDescriptions.Event, () => + { + AddType(typeof(EnrichedAssetEvent)); + }); - AddArray("parentPath", - FieldDescriptions.AssetParentPath); + return Build(); + } - AddArray("tags", - FieldDescriptions.AssetTags); + public IReadOnlyList AssetScript() + { + AddHelpers(ScriptScope.AssetScript | ScriptScope.Async); - AddObject("metadata", - FieldDescriptions.AssetMetadata, () => + AddObject("ctx", FieldDescriptions.Event, () => { - AddArray("name", - FieldDescriptions.AssetMetadataValue); + AddType(typeof(AssetScriptVars)); }); + + return Build(); } - private void AddShared(ScriptScope scope) + private void AddHelpers(ScriptScope scope) { foreach (var descriptor in descriptors) { descriptor.Describe(Add, scope); } + } - AddString("appId", - FieldDescriptions.AppId); - - AddString("appName", - FieldDescriptions.AppName); + private void AddType(Type type) + { + foreach (var (name, description, propertyTypeOrNullable) in GetFields(type)) + { + var propertyType = Nullable.GetUnderlyingType(propertyTypeOrNullable) ?? propertyTypeOrNullable; - AddString("operation", - FieldDescriptions.Operation); + if (propertyType.IsEnum || + propertyType == typeof(string) || + propertyType == typeof(DomainId) || + propertyType == typeof(Instant) || + propertyType == typeof(Status)) + { + AddString(name, description); + } + else if (propertyType == typeof(int) || propertyType == typeof(long)) + { + AddNumber(name, description); + } + else if (propertyType == typeof(bool)) + { + AddBoolean(name, description); + } + else if (propertyType == typeof(AssetMetadata)) + { + AddObject(name, description, () => + { + AddString("my-name", + FieldDescriptions.AssetMetadataValue); + }); + } + else if (propertyType == typeof(NamedId)) + { + AddObject(name, description, () => + { + AddString("id", + FieldDescriptions.NamedId); + + AddString("name", + FieldDescriptions.NamedName); + }); + } + else if (propertyType == typeof(RefToken)) + { + AddObject(name, description, () => + { + AddString("identifier", + FieldDescriptions.ActorIdentifier); + + AddString("type", + FieldDescriptions.ActorType); + }); + } + else if (propertyType == typeof(IUser) || propertyType == typeof(ClaimsPrincipal)) + { + AddObject(name, description, () => + { + AddString("id", + FieldDescriptions.UserId); + + AddString("email", + FieldDescriptions.UserEmail); + + AddBoolean("isClient", + FieldDescriptions.UserIsClient); + + AddBoolean("isUser", + FieldDescriptions.UserIsUser); + + AddObject("claims", FieldDescriptions.UserClaims, () => + { + AddArray("name", + FieldDescriptions.UsersClaimsValue); + }); + }); + } + else if (propertyType == typeof(ContentData) && dataSchema != null) + { + AddObject(name, description, () => + { + AddData(); + }); + } + else if (GetFields(propertyType).Any()) + { + AddObject(name, description, () => + { + AddType(propertyType); + }); + } + else if (propertyType.GetInterfaces().Contains(typeof(IEnumerable))) + { + AddArray(name, description); + } + } + } - AddObject("user", - FieldDescriptions.User, () => + private static IEnumerable<(string Name, string Description, Type Type)> GetFields(Type type) + { + foreach (var property in type.GetPublicProperties()) { - AddString("id", - FieldDescriptions.UserId); - - AddString("email", - FieldDescriptions.UserEmail); + var descriptionKey = property.GetCustomAttribute()?.Name; - AddBoolean("isClient", - FieldDescriptions.UserIsClient); + if (descriptionKey == null) + { + continue; + } - AddBoolean("isUser", - FieldDescriptions.UserIsUser); + var description = FieldDescriptions.ResourceManager.GetString(descriptionKey, CultureInfo.InvariantCulture)!; - AddObject("claims", - FieldDescriptions.UserClaims, () => - { - AddArray("name", - FieldDescriptions.UsersClaimsValue); - }); - }); + yield return (property.Name.ToCamelCase(), description, property.PropertyType); + } } - private void AddData(FilterSchema dataSchema) + private void AddData() { - if (dataSchema.Fields == null) + if (dataSchema?.Fields == null) { return; } @@ -268,9 +403,11 @@ namespace Squidex.Domain.Apps.Core.Scripting private void Add(JsonType type, string? name, string? description) { - if (name != null) + var parts = name?.Split('.') ?? Array.Empty(); + + foreach (var part in parts) { - prefixes.Push(name); + PushPrefix(part); } if (prefixes.Count == 0) @@ -278,11 +415,11 @@ namespace Squidex.Domain.Apps.Core.Scripting return; } - var path = string.Join('.', prefixes.Reverse()); + var path = string.Concat(prefixes.Reverse()); - result.Add(new ScriptingValue(path, type, description)); + result[path] = new ScriptingValue(path, type, description); - if (name != null) + for (int i = 0; i < parts.Length; i++) { prefixes.Pop(); } @@ -292,16 +429,36 @@ namespace Squidex.Domain.Apps.Core.Scripting { Add(JsonType.Object, name, description); - prefixes.Push(name); - try + var parts = name.Split('.'); + + foreach (var part in parts) { - inner(); + PushPrefix(part); } - finally + + inner(); + + for (int i = 0; i < parts.Length; i++) { prefixes.Pop(); } } + + private void PushPrefix(string name) + { + if (prefixes.Count == 0) + { + prefixes.Push(name); + } + else if (PropertyRegex.IsMatch(name)) + { + prefixes.Push($".{name}"); + } + else + { + prefixes.Push($"['{name}']"); + } + } } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs index 3d3b45b98..0e36de17a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs @@ -56,6 +56,8 @@ namespace Squidex.Domain.Apps.Entities.Assets SimpleMapper.Map(asset, result); result.Actor = asset.LastModifiedBy; + result.PixelHeight = asset.Metadata?.GetPixelHeight(); + result.PixelWidth = asset.Metadata?.GetPixelWidth(); result.Name = "AssetQueried"; yield return result; @@ -78,6 +80,8 @@ namespace Squidex.Domain.Apps.Entities.Assets { SimpleMapper.Map(asset, result); + result.PixelHeight = asset.Metadata?.GetPixelHeight(); + result.PixelWidth = asset.Metadata?.GetPixelWidth(); result.AssetType = asset.Type; } @@ -109,9 +113,10 @@ namespace Squidex.Domain.Apps.Entities.Assets return true; } - var vars = new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = new EventScriptVars { - ["event"] = @event + Event = @event }; return scriptEngine.Evaluate(vars, trigger.Condition); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs index afdcf6730..35496edad 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs @@ -42,6 +42,11 @@ namespace Squidex.Domain.Apps.Entities.Assets public void Describe(AddDescription describe, ScriptScope scope) { + if (!scope.HasFlag(ScriptScope.Async)) + { + return; + } + describe(JsonType.Function, "getAssets(ids, callback)", Resources.ScriptingGetAssets); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ScriptingExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ScriptingExtensions.cs index 9e9a93fd8..dd40e608d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ScriptingExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ScriptingExtensions.cs @@ -5,12 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.ObjectModel; -using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure; -using Squidex.Infrastructure.Json.Objects; using Squidex.Text; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards @@ -24,29 +21,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards CanReject = true }; - private static class ScriptKeys - { - public const string AppId = "appId"; - public const string AppName = "appName"; - public const string Asset = "asset"; - public const string AssetId = "assetId"; - public const string Command = "command"; - public const string FileHash = "fileHash"; - public const string FileName = "fileName"; - public const string FileSize = "fileSize"; - public const string FileSlug = "fileSlug"; - public const string FileVersion = "fileVersion"; - public const string IsProtected = "isProtected"; - public const string Metadata = "metadata"; - public const string MimeType = "mimeType"; - public const string Operation = "operation"; - public const string ParentId = "parentId"; - public const string ParentPath = "parentPath"; - public const string Permanent = "permanent"; - public const string Tags = "tags"; - public const string User = "User"; - } - public static async Task ExecuteCreateScriptAsync(this AssetOperation operation, CreateAsset create) { var script = operation.App.AssetScripts?.Create; @@ -58,23 +32,23 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards var parentPath = await GetPathAsync(operation, create.ParentId); - // Tags and metadata are mutable and can be changed from the scripts, but not replaced. - var vars = new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = new AssetScriptVars { - // Use a dictionary for better performance, because no reflection is involved. - [ScriptKeys.Command] = new Dictionary + // Tags and metadata are mutable and can be changed from the scripts, but not replaced. + Command = new AssetCommandScriptVars { - [ScriptKeys.Metadata] = create.Metadata.Mutable(), - [ScriptKeys.FileHash] = create.FileHash, - [ScriptKeys.FileName] = create.File.FileName, - [ScriptKeys.FileSize] = create.File.FileSize, - [ScriptKeys.FileSlug] = create.File.FileName.Slugify(), - [ScriptKeys.MimeType] = create.File.MimeType, - [ScriptKeys.ParentId] = create.ParentId, - [ScriptKeys.ParentPath] = parentPath, - [ScriptKeys.Tags] = create.Tags + FileHash = create.FileHash, + FileName = create.File.FileName, + FileSlug = create.File.FileName.Slugify(), + FileSize = create.File.FileSize, + Metadata = create.Metadata, + MimeType = create.File.MimeType, + ParentId = create.ParentId, + ParentPath = parentPath, + Tags = create.Tags }, - [ScriptKeys.Operation] = "Create" + Operation = "Create" }; await ExecuteScriptAsync(operation, script, vars); @@ -89,20 +63,20 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards return Task.CompletedTask; } - // Tags and metadata are mutable and can be changed from the scripts, but not replaced. - var vars = new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = new AssetScriptVars { - // Use a dictionary for better performance, because no reflection is involved. - [ScriptKeys.Command] = new Dictionary + // Tags and metadata are mutable and can be changed from the scripts, but not replaced. + Command = new AssetCommandScriptVars { - [ScriptKeys.Metadata] = update.Metadata.Mutable(), - [ScriptKeys.FileHash] = update.FileHash, - [ScriptKeys.FileName] = update.File.FileName, - [ScriptKeys.FileSize] = update.File.FileSize, - [ScriptKeys.MimeType] = update.File.MimeType, - [ScriptKeys.Tags] = update.Tags + Metadata = update.Metadata, + FileHash = update.FileHash, + FileName = update.File.FileName, + FileSize = update.File.FileSize, + MimeType = update.File.MimeType, + Tags = update.Tags }, - [ScriptKeys.Operation] = "Update" + Operation = "Update" }; return ExecuteScriptAsync(operation, script, vars); @@ -117,18 +91,19 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards return Task.CompletedTask; } - // Tags are mutable and can be changed from the scripts, but not replaced. - var vars = new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = new AssetScriptVars { - // Use a dictionary for better performance, because no reflection is involved. - [ScriptKeys.Command] = new Dictionary + // Tags are mutable and can be changed from the scripts, but not replaced. + Command = new AssetCommandScriptVars { - [ScriptKeys.Metadata] = annotate.Metadata?.Mutable(), - [ScriptKeys.FileName] = annotate.FileName, - [ScriptKeys.FileSlug] = annotate.Slug, - [ScriptKeys.Tags] = annotate.Tags + IsProtected = annotate.IsProtected, + Metadata = annotate.Metadata, + FileName = annotate.FileName, + FileSlug = annotate.Slug, + Tags = annotate.Tags }, - [ScriptKeys.Operation] = "Annotate" + Operation = "Annotate" }; return ExecuteScriptAsync(operation, script, vars); @@ -145,15 +120,15 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards var parentPath = await GetPathAsync(operation, move.ParentId); - var vars = new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = new AssetScriptVars { - // Use a dictionary for better performance, because no reflection is involved. - [ScriptKeys.Command] = new Dictionary + Command = new AssetCommandScriptVars { - [ScriptKeys.ParentId] = move.ParentId, - [ScriptKeys.ParentPath] = parentPath + ParentId = move.ParentId, + ParentPath = parentPath }, - [ScriptKeys.Operation] = "Move" + Operation = "Move" }; await ExecuteScriptAsync(operation, script, vars); @@ -168,53 +143,53 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards return Task.CompletedTask; } - var vars = new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = new AssetScriptVars { - // Use a dictionary for better performance, because no reflection is involved. - [ScriptKeys.Command] = new Dictionary + Command = new AssetCommandScriptVars { - [ScriptKeys.Permanent] = delete.Permanent + Permanent = delete.Permanent }, - [ScriptKeys.Operation] = "Delete" + Operation = "Delete" }; return ExecuteScriptAsync(operation, script, vars); } - private static async Task ExecuteScriptAsync(AssetOperation operation, string script, ScriptVars vars) + private static async Task ExecuteScriptAsync(AssetOperation operation, string script, AssetScriptVars vars) { var snapshot = operation.Snapshot; var parentPath = await GetPathAsync(operation, snapshot.ParentId); - // Use a dictionary for better performance, because no reflection is involved. - var asset = new Dictionary + // Script vars are just wrappers over dictionaries for better performance. + var asset = new AssetEntityScriptVars { - [ScriptKeys.Metadata] = snapshot.ReadonlyMetadata(), - [ScriptKeys.FileHash] = snapshot.FileHash, - [ScriptKeys.FileName] = snapshot.FileName, - [ScriptKeys.FileSize] = snapshot.FileSize, - [ScriptKeys.FileSlug] = snapshot.Slug, - [ScriptKeys.FileVersion] = snapshot.FileVersion, - [ScriptKeys.IsProtected] = snapshot.IsProtected, - [ScriptKeys.MimeType] = snapshot.MimeType, - [ScriptKeys.ParentId] = snapshot.ParentId, - [ScriptKeys.ParentPath] = parentPath, - [ScriptKeys.Tags] = snapshot.ReadonlyTags() + Metadata = snapshot.Metadata, + FileHash = snapshot.FileHash, + FileName = snapshot.FileName, + FileSize = snapshot.FileSize, + FileSlug = snapshot.Slug, + FileVersion = snapshot.FileVersion, + IsProtected = snapshot.IsProtected, + MimeType = snapshot.MimeType, + ParentId = snapshot.ParentId, + ParentPath = parentPath, + Tags = snapshot.Tags }; - vars[ScriptKeys.AppId] = operation.App.Id; - vars[ScriptKeys.AppName] = operation.App.Name; - vars[ScriptKeys.AssetId] = operation.CommandId; - vars[ScriptKeys.Asset] = asset; - vars[ScriptKeys.User] = operation.User; + vars.AppId = operation.App.Id; + vars.AppName = operation.App.Name; + vars.AssetId = operation.CommandId; + vars.Asset = asset; + vars.User = operation.User; var scriptEngine = operation.Resolve(); await scriptEngine.ExecuteAsync(vars, script, Options); } - private static async Task GetPathAsync(AssetOperation operation, DomainId parentId) + private static async Task GetPathAsync(AssetOperation operation, DomainId parentId) { if (parentId == default) { @@ -222,40 +197,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards } var assetQuery = operation.Resolve(); + var assetPath = await assetQuery.FindAssetFolderAsync(operation.App.Id, parentId); - var path = await assetQuery.FindAssetFolderAsync(operation.App.Id, parentId); - - return path.Select(x => new { id = x.Id, folderName = x.FolderName }).ToList(); - } - - private static object? Mutable(this AssetMetadata metadata) - { - if (metadata == null) - { - return null; - } - - return new ScriptMetadataWrapper(metadata); - } - - private static object? ReadonlyMetadata(this IAssetEntity asset) - { - if (asset.Metadata == null) - { - return null; - } - - return new ReadOnlyDictionary(asset.Metadata); - } - - private static object? ReadonlyTags(this IAssetEntity asset) - { - if (asset.Tags == null) - { - return null; - } - - return new ReadOnlyCollection(asset.Tags.ToList()); + return assetPath.Select(x => new { id = x.Id, folderName = x.FolderName }).ToArray(); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs index 1f80f9f32..c1edfa6ef 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Comments/CommentTriggerHandler.cs @@ -74,9 +74,10 @@ namespace Squidex.Domain.Apps.Entities.Comments return true; } - var vars = new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = new EventScriptVars { - ["event"] = @event + Event = @event }; return scriptEngine.Evaluate(vars, trigger.Condition); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs index 98cb4df0e..3441912ba 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentChangedTriggerHandler.cs @@ -215,9 +215,10 @@ namespace Squidex.Domain.Apps.Entities.Contents return true; } - var vars = new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = new EventScriptVars { - ["event"] = @event + Event = @event }; return scriptEngine.Evaluate(vars, schema.Condition); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs index 452bf0ac0..23702ac5c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs @@ -413,7 +413,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject if (!c.DoNotScript) { - await operation.ExecuteDeleteScriptAsync(); + await operation.ExecuteDeleteScriptAsync(c.Permanent); } if (c.CheckReferrers) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ScriptingExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ScriptingExtensions.cs index 5ed1747ca..ed689be45 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ScriptingExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ScriptingExtensions.cs @@ -19,25 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards CanReject = true }; - private static class ScriptKeys - { - public const string AppId = "appId"; - public const string AppName = "appName"; - public const string Command = "command"; - public const string Content = "content"; - public const string ContentId = "contentId"; - public const string Data = "data"; - public const string DataOld = "dataOld"; - public const string OldData = "oldData"; - public const string OldStatus = "oldStatus"; - public const string Operation = "operation"; - public const string SchemaId = "achemaId"; - public const string SchemaName = "achemaName"; - public const string Status = "status"; - public const string StatusOld = "statusOld"; - public const string User = "user"; - } - public static Task ExecuteCreateScriptAsync(this ContentOperation operation, ContentData data, Status status) { var script = operation.SchemaDef.Scripts.Create; @@ -47,15 +28,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards return Task.FromResult(data); } - var vars = Enrich(operation, new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = Enrich(operation, new ContentScriptVars { - [ScriptKeys.Data] = data, - [ScriptKeys.DataOld] = null, - [ScriptKeys.OldData] = null, - [ScriptKeys.OldStatus] = default(Status), - [ScriptKeys.Operation] = "Create", - [ScriptKeys.Status] = status, - [ScriptKeys.StatusOld] = default(Status) + Data = data, + DataOld = null, + OldData = null, + OldStatus = default, + Operation = "Create", + Status = status, + StatusOld = default }); return TransformAsync(operation, script, vars); @@ -70,15 +52,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards return Task.FromResult(data); } - var vars = Enrich(operation, new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = Enrich(operation, new ContentScriptVars { - [ScriptKeys.Data] = data, - [ScriptKeys.DataOld] = operation.Snapshot.Data, - [ScriptKeys.OldData] = operation.Snapshot.Data, - [ScriptKeys.OldStatus] = data, - [ScriptKeys.Operation] = "Update", - [ScriptKeys.Status] = operation.Snapshot.EditingStatus(), - [ScriptKeys.StatusOld] = default(Status) + Data = data, + DataOld = operation.Snapshot.Data, + OldData = operation.Snapshot.Data, + OldStatus = operation.Snapshot.Status, + Operation = "Update", + Status = operation.Snapshot.EditingStatus(), + StatusOld = default }); return TransformAsync(operation, script, vars); @@ -93,24 +76,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards return Task.FromResult(operation.Snapshot.Data); } - // Clone the data so we do not change it by accident. - var data = operation.Snapshot.Data.Clone(); - - var vars = Enrich(operation, new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = Enrich(operation, new ContentScriptVars { - [ScriptKeys.Data] = data, - [ScriptKeys.DataOld] = null, - [ScriptKeys.OldData] = null, - [ScriptKeys.OldStatus] = operation.Snapshot.EditingStatus(), - [ScriptKeys.Operation] = change.ToString(), - [ScriptKeys.Status] = status, - [ScriptKeys.StatusOld] = operation.Snapshot.EditingStatus() + Data = operation.Snapshot.Data.Clone(), + DataOld = null, + OldData = null, + OldStatus = operation.Snapshot.EditingStatus(), + Operation = change.ToString(), + Status = status, + StatusOld = operation.Snapshot.EditingStatus() }); return TransformAsync(operation, script, vars); } - public static Task ExecuteDeleteScriptAsync(this ContentOperation operation) + public static Task ExecuteDeleteScriptAsync(this ContentOperation operation, bool permanent) { var script = operation.SchemaDef.Scripts.Delete; @@ -119,40 +100,40 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards return Task.CompletedTask; } - var vars = Enrich(operation, new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = Enrich(operation, new ContentScriptVars { - [ScriptKeys.Data] = operation.Snapshot.Data, - [ScriptKeys.DataOld] = null, - [ScriptKeys.OldData] = null, - [ScriptKeys.OldStatus] = operation.Snapshot.EditingStatus(), - [ScriptKeys.Operation] = "Delete", - [ScriptKeys.Status] = operation.Snapshot.EditingStatus(), - [ScriptKeys.StatusOld] = default(Status) + Data = operation.Snapshot.Data, + DataOld = null, + OldData = null, + OldStatus = operation.Snapshot.EditingStatus(), + Operation = "Delete", + Permanent = permanent, + Status = operation.Snapshot.EditingStatus(), + StatusOld = default }); return ExecuteAsync(operation, script, vars); } - private static async Task TransformAsync(ContentOperation operation, string script, ScriptVars vars) + private static async Task TransformAsync(ContentOperation operation, string script, ContentScriptVars vars) { return await operation.Resolve().TransformAsync(vars, script, Options); } - private static async Task ExecuteAsync(ContentOperation operation, string script, ScriptVars vars) + private static async Task ExecuteAsync(ContentOperation operation, string script, ContentScriptVars vars) { await operation.Resolve().ExecuteAsync(vars, script, Options); } - private static ScriptVars Enrich(ContentOperation operation, ScriptVars vars) + private static ContentScriptVars Enrich(ContentOperation operation, ContentScriptVars vars) { - vars[ScriptKeys.AppId] = operation.App.Id; - vars[ScriptKeys.AppName] = operation.App.Name; - vars[ScriptKeys.Command] = operation.Command; - vars[ScriptKeys.Content] = operation.Snapshot; - vars[ScriptKeys.ContentId] = operation.CommandId; - vars[ScriptKeys.SchemaId] = operation.Schema.Id; - vars[ScriptKeys.SchemaName] = operation.Schema.SchemaDef.Name; - vars[ScriptKeys.User] = operation.User; + vars.AppId = operation.App.Id; + vars.AppName = operation.App.Name; + vars.ContentId = operation.CommandId; + vars.SchemaId = operation.Schema.Id; + vars.SchemaName = operation.Schema.SchemaDef.Name; + vars.User = operation.User; return vars; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs index cd816567b..7b36c74c0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs @@ -115,9 +115,9 @@ namespace Squidex.Domain.Apps.Entities.Contents if (!string.IsNullOrWhiteSpace(condition?.Expression) && data != null) { - var vars = new ScriptVars + var vars = new DataScriptVars { - ["data"] = data + Data = data }; return scriptEngine.Evaluate(vars, condition.Expression); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs index 1a0903ec7..736bed454 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs @@ -14,16 +14,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps { private readonly IScriptEngine scriptEngine; - private static class ScriptKeys - { - public const string AppId = "appId"; - public const string AppName = "appName"; - public const string ContentId = "contentId"; - public const string Data = "data"; - public const string Operation = "operation"; - public const string User = "user"; - } - public ScriptContent(IScriptEngine scriptEngine) { this.scriptEngine = scriptEngine; @@ -48,11 +38,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps continue; } - var vars = new ScriptVars + var vars = new ContentScriptVars { - [ScriptKeys.AppId] = context.App.Id, - [ScriptKeys.AppName] = context.App.Name, - [ScriptKeys.User] = context.User + AppId = schema.AppId.Id, + AppName = schema.AppId.Name, + SchemaId = schema.Id, + SchemaName = schema.SchemaDef.Name, + User = context.User }; var preScript = schema.SchemaDef.Scripts.QueryPre; @@ -74,15 +66,26 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps } } - private async Task TransformAsync(ScriptVars rootVars, string script, ContentEntity content, + private async Task TransformAsync(ContentScriptVars sharedVars, string script, ContentEntity content, CancellationToken ct) { - var vars = new ScriptVars(rootVars) + var vars = new ContentScriptVars { - [ScriptKeys.ContentId] = content.Id, - [ScriptKeys.Data] = content.Data, + ContentId = content.Id, + Data = content.Data, + DataOld = default, + Status = content.Status, + StatusOld = default }; + foreach (var (key, value) in sharedVars) + { + if (!vars.ContainsKey(key)) + { + vars[key] = value; + } + } + var options = new ScriptOptions { AsContext = true diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs index fae5e94c8..386d9dd6b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs @@ -49,6 +49,11 @@ namespace Squidex.Domain.Apps.Entities.Contents public void Describe(AddDescription describe, ScriptScope scope) { + if (!scope.HasFlag(ScriptScope.Async)) + { + return; + } + describe(JsonType.Function, "getReferences(ids, callback)", Resources.ScriptingGetReferences); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs index 543239ee0..f1f9e3b38 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaChangedTriggerHandler.cs @@ -79,7 +79,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas return true; } - var vars = new ScriptVars + // Script vars are just wrappers over dictionaries for better performance. + var vars = new EventScriptVars { ["event"] = @event }; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppAssetsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppAssetsController.cs index fa3a109e3..70b67f225 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppAssetsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppAssetsController.cs @@ -30,7 +30,7 @@ namespace Squidex.Areas.Api.Controllers.Apps /// /// The name of the app to get the asset scripts for. /// - /// 200 => App asset scripts returned. + /// 200 => Asset scripts returned. /// 404 => App not found. /// [HttpGet] @@ -38,7 +38,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [ProducesResponseType(typeof(AssetScriptsDto), StatusCodes.Status200OK)] [ApiPermissionOrAnonymous(Permissions.AppAssetSScriptsRead)] [ApiCosts(0)] - public IActionResult GetScripts(string app) + public IActionResult GetAssetScripts(string app) { var response = Deferred.Response(() => { @@ -54,8 +54,8 @@ namespace Squidex.Areas.Api.Controllers.Apps /// The name of the app to update. /// The values to update. /// - /// 200 => App updated. - /// 400 => App request not valid. + /// 200 => Asset scripts updated. + /// 400 => Asset request not valid. /// 404 => App not found. /// [HttpPut] @@ -63,7 +63,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [ProducesResponseType(typeof(AssetScriptsDto), StatusCodes.Status200OK)] [ApiPermissionOrAnonymous(Permissions.AppAssetsScriptsUpdate)] [ApiCosts(0)] - public async Task PutScripts(string app, [FromBody] UpdateAssetScriptsDto request) + public async Task PutAssetScripts(string app, [FromBody] UpdateAssetScriptsDto request) { var response = await InvokeCommandAsync(request.ToCommand()); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs index e4cbc2c40..2c13c3a68 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs @@ -225,7 +225,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models if (resources.IsAllowed(Shared.Permissions.AppAssetsScriptsUpdate, Name, additional: permissions)) { - AddDeleteLink("assets/scripts", resources.Url(x => nameof(x.GetScripts), values)); + AddDeleteLink("assets/scripts", resources.Url(x => nameof(x.GetAssetScripts), values)); } AddGetLink("settings", resources.Url(x => nameof(x.GetSettings), values)); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AssetScriptsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AssetScriptsDto.cs index d1982b276..50e56a667 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AssetScriptsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AssetScriptsDto.cs @@ -58,7 +58,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models if (resources.CanUpdateAssetsScripts) { - AddPutLink("update", resources.Url(x => nameof(x.PutScripts), values)); + AddPutLink("update", resources.Url(x => nameof(x.PutAssetScripts), values)); } return this; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index ad2eda78d..830eaa3c8 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -425,9 +425,9 @@ namespace Squidex.Areas.Api.Controllers.Assets [ApiPermissionOrAnonymous] [ApiCosts(1)] [OpenApiIgnore] - public IActionResult GetScriptCompletion(string app, string schema) + public IActionResult GetScriptCompletion(string app, string schema, + [FromServices] ScriptingCompleter completer) { - var completer = HttpContext.RequestServices.GetRequiredService(); var completion = completer.AssetScript(); return Ok(completion); @@ -438,9 +438,9 @@ namespace Squidex.Areas.Api.Controllers.Assets [ApiPermissionOrAnonymous] [ApiCosts(1)] [OpenApiIgnore] - public IActionResult GetScriptTriggerCompletion(string app, string schema) + public IActionResult GetScriptTriggerCompletion(string app, string schema, + [FromServices] ScriptingCompleter completer) { - var completer = HttpContext.RequestServices.GetRequiredService(); var completion = completer.AssetTrigger(); return Ok(completion); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index 202b82875..cb3fd2e04 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -6,7 +6,6 @@ // ========================================================================== using Microsoft.AspNetCore.Mvc; -using Microsoft.Net.Http.Headers; using Squidex.Areas.Api.Controllers.Contents.Models; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs index 830bc34d9..b7b3c5140 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs @@ -9,8 +9,10 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using NodaTime; +using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Rules.Models; using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.Commands; @@ -469,6 +471,19 @@ namespace Squidex.Areas.Api.Controllers.Rules return Content(schema.ToJson(), "application/json"); } + [HttpGet] + [Route("apps/{app}/rules/completion/{triggerType}")] + [ApiPermissionOrAnonymous] + [ApiCosts(1)] + [OpenApiIgnore] + public IActionResult GetScriptCompletion(string app, string triggerType, + [FromServices] ScriptingCompleter completer) + { + var completion = completer.Trigger(triggerType); + + return Ok(completion); + } + private async Task InvokeCommandAsync(ICommand command) { var context = await CommandBus.PublishAsync(command); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index 435983aa9..0d98d1d0c 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -335,9 +335,9 @@ namespace Squidex.Areas.Api.Controllers.Schemas [ApiPermissionOrAnonymous] [ApiCosts(1)] [OpenApiIgnore] - public async Task GetScriptCompletion(string app, string schema) + public async Task GetScriptCompletion(string app, string schema, + [FromServices] ScriptingCompleter completer) { - var completer = HttpContext.RequestServices.GetRequiredService(); var completion = completer.ContentScript(await BuildModel()); return Ok(completion); @@ -348,9 +348,9 @@ namespace Squidex.Areas.Api.Controllers.Schemas [ApiPermissionOrAnonymous] [ApiCosts(1)] [OpenApiIgnore] - public async Task GetScriptTriggerCompletion(string app, string schema) + public async Task GetScriptTriggerCompletion(string app, string schema, + [FromServices] ScriptingCompleter completer) { - var completer = HttpContext.RequestServices.GetRequiredService(); var completion = completer.ContentTrigger(await BuildModel()); return Ok(completion); @@ -374,7 +374,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var components = await appProvider.GetComponentsAsync(Schema, HttpContext.RequestAborted); - return Schema.SchemaDef.BuildDataSchema(App.PartitionResolver(), components).Flatten(); + return Schema.SchemaDef.BuildDataSchema(App.PartitionResolver(), components); } private Task GetSchemaAsync(string schema) diff --git a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs index 5e98a8abf..36c1caaab 100644 --- a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -67,7 +67,7 @@ namespace Squidex.Config.Domain .AsSelf(); services.AddSingletonAs() - .As(); + .As().As(); services.AddSingletonAs() .As(); diff --git a/backend/src/Squidex/Config/Domain/QueryServices.cs b/backend/src/Squidex/Config/Domain/QueryServices.cs index ff54b3564..0cef3d645 100644 --- a/backend/src/Squidex/Config/Domain/QueryServices.cs +++ b/backend/src/Squidex/Config/Domain/QueryServices.cs @@ -7,7 +7,6 @@ using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Entities.Contents.GraphQL; -using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives; using Squidex.Web.Services; diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs index b0789dd57..28537028e 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs @@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting { var content = new ContentData(); - var vars = new ScriptVars + var vars = new DataScriptVars { ["data"] = content }; @@ -122,7 +122,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(10.0)); - var vars = new ScriptVars + var vars = new DataScriptVars { ["data"] = content }; @@ -150,13 +150,13 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting throw 'Error'; "; - await Assert.ThrowsAsync(() => sut.TransformAsync(new ScriptVars(), script)); + await Assert.ThrowsAsync(() => sut.TransformAsync(new DataScriptVars(), script)); } [Fact] public async Task TransformAsync_should_throw_exception_if_script_failed() { - var vars = new ScriptVars + var vars = new DataScriptVars { ["data"] = new ContentData() }; @@ -171,7 +171,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task TransformAsync_should_return_original_content_if_not_replaced() { - var vars = new ScriptVars + var vars = new DataScriptVars { ["data"] = new ContentData() }; @@ -188,7 +188,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task TransformAsync_should_return_original_content_if_not_replaced_async() { - var vars = new ScriptVars + var vars = new DataScriptVars { ["data"] = new ContentData() }; @@ -217,7 +217,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant("MyOperation")); - var vars = new ScriptVars + var vars = new DataScriptVars { ["data"] = content, ["dataOld"] = null, @@ -248,7 +248,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(42)); - var vars = new ScriptVars + var vars = new DataScriptVars { ["data"] = content, ["dataOld"] = null, @@ -274,7 +274,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task TransformAsync_should_not_ignore_transformation_if_async_not_set() { - var vars = new ScriptVars + var vars = new DataScriptVars { ["data"] = new ContentData(), ["dataOld"] = null, @@ -300,7 +300,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task TransformAsync_should_not_timeout_if_replace_never_called() { - var vars = new ScriptVars + var vars = new DataScriptVars { ["data"] = new ContentData(), ["dataOld"] = null, @@ -338,7 +338,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(10.0)); - var vars = new ScriptVars + var vars = new DataScriptVars { ["data"] = content }; @@ -385,7 +385,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting userIdentity.AddClaim(new Claim(OpenIdClaims.ClientId, "2")); - var vars = new ScriptVars + var vars = new DataScriptVars { ["data"] = content, ["dataOld"] = oldContent, @@ -539,7 +539,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task Should_share_vars_between_execution_for_transform() { - var vars = new ScriptVars + var vars = new DataScriptVars { ["value"] = 13 }; @@ -557,11 +557,19 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting sut.Execute(vars, script1, new ScriptOptions { AsContext = true }); #pragma warning restore MA0042 // Do not use blocking calls in an async method - var vars2 = new ScriptVars(vars) + var vars2 = new DataScriptVars() { - Data = new ContentData() + ["data"] = new ContentData() }; + foreach (var (key, value) in vars) + { + if (!vars2.ContainsKey(key)) + { + vars2[key] = value; + } + } + var result = await sut.TransformAsync(vars2, script2, new ScriptOptions { AsContext = true }); Assert.Equal(JsonValue.Create(28), result["test"]!["iv"]); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs new file mode 100644 index 000000000..67da1ec29 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs @@ -0,0 +1,380 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using FakeItEasy; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.GenerateFilters; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.Scripting; +using Squidex.Infrastructure.Queries; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Operations.Scripting +{ + public class ScriptingCompleterTests + { + private readonly IScriptDescriptor scriptDescriptor1 = A.Fake(); + private readonly IScriptDescriptor scriptDescriptor2 = A.Fake(); + private readonly FilterSchema dataSchema; + private readonly ScriptingCompleter sut; + + public ScriptingCompleterTests() + { + var schema = + new Schema("simple") + .AddString(1, "my-field", Partitioning.Invariant); + + dataSchema = schema.BuildDataSchema(LanguagesConfig.English.ToResolver(), ResolvedComponents.Empty); + + sut = new ScriptingCompleter(new[] { scriptDescriptor1, scriptDescriptor2 }); + } + + [Fact] + public void Should_calls_descriptors() + { + sut.UsageTrigger(); + + A.CallTo(() => scriptDescriptor1.Describe(A._, A._)) + .MustHaveHappened(); + + A.CallTo(() => scriptDescriptor2.Describe(A._, A._)) + .MustHaveHappened(); + } + + [Fact] + public void Should_describe_content_script() + { + var result = sut.ContentScript(dataSchema); + + AssertCompletion(result, + PresetUser("ctx.user"), + new[] + { + "ctx", + "ctx.appId", + "ctx.appName", + "ctx.contentId", + "ctx.data", + "ctx.data['my-field']", + "ctx.data['my-field'].iv", + "ctx.dataOld", + "ctx.dataOld['my-field']", + "ctx.dataOld['my-field'].iv", + "ctx.oldData", + "ctx.oldData['my-field']", + "ctx.oldData['my-field'].iv", + "ctx.oldStatus", + "ctx.operation", + "ctx.permanent", + "ctx.schemaId", + "ctx.schemaName", + "ctx.status", + "ctx.statusOld" + }); + } + + [Fact] + public void Should_describe_asset_script() + { + var result = sut.AssetScript(); + + AssertCompletion(result, + PresetUser("ctx.user"), + new[] + { + "ctx", + "ctx.appId", + "ctx.appName", + "ctx.asset", + "ctx.asset.fileHash", + "ctx.asset.fileName", + "ctx.asset.fileSize", + "ctx.asset.fileSlug", + "ctx.asset.fileVersion", + "ctx.asset.isProtected", + "ctx.asset.metadata", + "ctx.asset.metadata['my-name']", + "ctx.asset.mimeType", + "ctx.asset.parentId", + "ctx.asset.parentPath", + "ctx.asset.tags", + "ctx.assetId", + "ctx.command", + "ctx.command.fileHash", + "ctx.command.fileName", + "ctx.command.fileSize", + "ctx.command.fileSlug", + "ctx.command.isProtected", + "ctx.command.metadata", + "ctx.command.metadata['my-name']", + "ctx.command.mimeType", + "ctx.command.parentId", + "ctx.command.parentPath", + "ctx.command.permanent", + "ctx.command.tags", + "ctx.operation", + }); + } + + [Fact] + public void Should_describe_content_trigger() + { + var result = sut.ContentTrigger(dataSchema); + + AssertContentTrigger(result); + } + + [Fact] + public void Should_describe_dynamic_content_trigger() + { + var result = sut.Trigger("ContentChanged"); + + AssertContentTrigger(result); + } + + private static void AssertContentTrigger(IReadOnlyList result) + { + AssertCompletion(result, + PresetActor("event.actor"), + PresetUser("event.user"), + new[] + { + "event", + "event.appId", + "event.appId.id", + "event.appId.name", + "event.created", + "event.createdBy", + "event.createdBy.identifier", + "event.createdBy.type", + "event.data", + "event.data['my-field']", + "event.data['my-field'].iv", + "event.dataOld", + "event.dataOld['my-field']", + "event.dataOld['my-field'].iv", + "event.lastModified", + "event.lastModifiedBy", + "event.lastModifiedBy.identifier", + "event.lastModifiedBy.type", + "event.id", + "event.name", + "event.newStatus", + "event.schemaId", + "event.schemaId.id", + "event.schemaId.name", + "event.status", + "event.timestamp", + "event.type", + "event.version" + }); + } + + [Fact] + public void Should_describe_asset_trigger() + { + var result = sut.AssetTrigger(); + + AssertAssetTrigger(result); + } + + [Fact] + public void Should_describe_dynamicasset_trigger() + { + var result = sut.Trigger("AssetChanged"); + + AssertAssetTrigger(result); + } + + private void AssertAssetTrigger(IReadOnlyList result) + { + AssertCompletion(result, + PresetActor("event.actor"), + PresetUser("event.user"), + new[] + { + "event", + "event.appId", + "event.appId.id", + "event.appId.name", + "event.assetType", + "event.created", + "event.createdBy", + "event.createdBy.identifier", + "event.createdBy.type", + "event.fileHash", + "event.fileName", + "event.fileSize", + "event.fileVersion", + "event.isImage", + "event.isProtected", + "event.lastModified", + "event.lastModifiedBy", + "event.lastModifiedBy.identifier", + "event.lastModifiedBy.type", + "event.id", + "event.metadata", + "event.metadata['my-name']", + "event.mimeType", + "event.name", + "event.parentId", + "event.pixelHeight", + "event.pixelWidth", + "event.slug", + "event.timestamp", + "event.type", + "event.version" + }); + } + + [Fact] + public void Should_describe_comment_trigger() + { + var result = sut.CommentTrigger(); + + AssertCommentTrigger(result); + } + + [Fact] + public void Should_describe_dynamic_comment_trigger() + { + var result = sut.Trigger("Comment"); + + AssertCommentTrigger(result); + } + + private static void AssertCommentTrigger(IReadOnlyList result) + { + AssertCompletion(result, + PresetActor("event.actor"), + PresetUser("event.user"), + PresetUser("event.mentionedUser"), + new[] + { + "event", + "event.appId", + "event.appId.id", + "event.appId.name", + "event.name", + "event.text", + "event.timestamp", + "event.version" + }); + } + + [Fact] + public void Should_describe_schema_trigger() + { + var result = sut.SchemaTrigger(); + + AssertSchemaTrigger(result); + } + + [Fact] + public void Should_describe_dynamic_schema_trigger() + { + var result = sut.Trigger("SchemaChanged"); + + AssertSchemaTrigger(result); + } + + private static void AssertSchemaTrigger(IReadOnlyList result) + { + AssertCompletion(result, + PresetActor("event.actor"), + PresetUser("event.user"), + new[] + { + "event", + "event.appId", + "event.appId.id", + "event.appId.name", + "event.name", + "event.schemaId", + "event.schemaId.id", + "event.schemaId.name", + "event.timestamp", + "event.type", + "event.version" + }); + } + + [Fact] + public void Should_describe_usage_trigger() + { + var result = sut.UsageTrigger(); + + AssertUsageTrigger(result); + } + + [Fact] + public void Should_describe_dynamic_usage_trigger() + { + var result = sut.Trigger("Usage"); + + AssertUsageTrigger(result); + } + + private static void AssertUsageTrigger(IReadOnlyList result) + { + AssertCompletion(result, + new[] + { + "event", + "event.appId", + "event.appId.id", + "event.appId.name", + "event.callsCurrent", + "event.callsLimit", + "event.name", + "event.timestamp", + "event.version" + }); + } + + private static void AssertCompletion(IReadOnlyList result, params string[][] expected) + { + var allExpected = expected.SelectMany(x => x).ToArray(); + + var paths = result.Select(x => x.Path).ToArray(); + + foreach (var value in paths) + { + Assert.Contains(value, allExpected); + } + + foreach (var value in allExpected) + { + Assert.Contains(value, paths); + } + } + + private static string[] PresetActor(string path) + { + return new[] + { + $"{path}", + $"{path}.identifier", + $"{path}.type" + }; + } + + private static string[] PresetUser(string path) + { + return new[] + { + $"{path}", + $"{path}.claims", + $"{path}.claims.name", + $"{path}.email", + $"{path}.id", + $"{path}.isClient", + $"{path}.isUser", + }; + } + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs index 5b2448030..401346096 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs @@ -99,8 +99,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId, false, default)) .Returns((app, schema)); - A.CallTo(() => scriptEngine.TransformAsync(A._, A._, ScriptOptions(), default)) - .ReturnsLazily(x => Task.FromResult(x.GetArgument(0)!.Data!)); + A.CallTo(() => scriptEngine.TransformAsync(A._, A._, ScriptOptions(), default)) + .ReturnsLazily(x => Task.FromResult(x.GetArgument(0)!.Data!)); patched = patch.MergeInto(data); @@ -150,9 +150,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); - A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions(), default)) .MustNotHaveHappened(); } @@ -173,9 +173,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); - A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions(), default)) .MustNotHaveHappened(); } @@ -197,9 +197,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Archived, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -250,9 +250,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); - A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions(), default)) .MustNotHaveHappened(); } @@ -273,9 +273,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentCreated { Data = data, Status = Status.Draft }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); - A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions(), default)) .MustNotHaveHappened(); } @@ -297,9 +297,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Archived, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -321,7 +321,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentUpdated { Data = otherData }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(otherData, data, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -343,7 +343,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentUpdated { Data = otherData }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(otherData, data, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -367,9 +367,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(otherData, data, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(otherData, null, Status.Archived, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, null, Status.Archived, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -423,7 +423,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentUpdated { Data = otherData }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(otherData, data, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -447,7 +447,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentUpdated { Data = otherData }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(otherData, data, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(otherData, data, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -464,7 +464,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject Assert.Single(LastEvents); - A.CallTo(() => scriptEngine.TransformAsync(A._, "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(A._, "", ScriptOptions(), default)) .MustNotHaveHappened(); } @@ -496,7 +496,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentUpdated { Data = patched }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(patched, data, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(patched, data, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -520,7 +520,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentUpdated { Data = patched }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(patched, data, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(patched, data, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -537,7 +537,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject Assert.Single(LastEvents); - A.CallTo(() => scriptEngine.TransformAsync(A._, "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(A._, "", ScriptOptions(), default)) .MustNotHaveHappened(); } @@ -559,7 +559,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Archived, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -581,7 +581,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Archived, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -604,7 +604,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Unpublished }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Draft, Status.Published), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft, Status.Published), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -613,7 +613,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject { var command = new ChangeContentStatus { Status = Status.Draft }; - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Draft, Status.Published), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft, Status.Published), "", ScriptOptions(), default)) .Returns(otherData); await ExecuteCreateAsync(); @@ -632,7 +632,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Unpublished }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Draft, Status.Published), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Draft, Status.Published), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -656,7 +656,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentStatusChanged { Change = StatusChange.Change, Status = Status.Archived }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Archived, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Archived, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -680,7 +680,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentStatusChanged { Change = StatusChange.Published, Status = Status.Published }) ); - A.CallTo(() => scriptEngine.TransformAsync(ScriptVars(data, null, Status.Published, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(DataScriptVars(data, null, Status.Published, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -707,7 +707,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentStatusScheduled { Status = Status.Published, DueTime = dueTime }) ); - A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions(), default)) .MustNotHaveHappened(); } @@ -735,7 +735,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) ); - A.CallTo(() => scriptEngine.TransformAsync(A._, "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.TransformAsync(A._, "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -763,7 +763,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentSchedulingCancelled()) ); - A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions(), default)) .MustNotHaveHappened(); } @@ -845,7 +845,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject CreateContentEvent(new ContentDeleted()) ); - A.CallTo(() => scriptEngine.ExecuteAsync(ScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.ExecuteAsync(DataScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -863,7 +863,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject Assert.Equal(EtagVersion.Empty, sut.Snapshot.Version); Assert.Empty(LastEvents); - A.CallTo(() => scriptEngine.ExecuteAsync(ScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) + A.CallTo(() => scriptEngine.ExecuteAsync(DataScriptVars(data, null, Status.Draft), "", ScriptOptions(), default)) .MustHaveHappened(); } @@ -969,17 +969,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject return A.That.Matches(x => x.CanDisallow && x.CanReject && x.AsContext); } - private ScriptVars ScriptVars(ContentData? newData, ContentData? oldData, Status newStatus) + private DataScriptVars DataScriptVars(ContentData? newData, ContentData? oldData, Status newStatus) { - return A.That.Matches(x => Matches(x, newData, oldData, newStatus, default)); + return A.That.Matches(x => Matches(x, newData, oldData, newStatus, default)); } - private ScriptVars ScriptVars(ContentData? newData, ContentData? oldData, Status newStatus, Status oldStatus) + private DataScriptVars DataScriptVars(ContentData? newData, ContentData? oldData, Status newStatus, Status oldStatus) { - return A.That.Matches(x => Matches(x, newData, oldData, newStatus, oldStatus)); + return A.That.Matches(x => Matches(x, newData, oldData, newStatus, oldStatus)); } - private bool Matches(ScriptVars x, ContentData? newData, ContentData? oldData, Status newStatus, Status oldStatus) + private bool Matches(DataScriptVars x, ContentData? newData, ContentData? oldData, Status newStatus, Status oldStatus) { return Equals(x["contentId"], contentId) && diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index ca7899852..cf41dbb5f 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -19,7 +19,6 @@ using Squidex.Caching; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Assets; -using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives; using Squidex.Domain.Apps.Entities.Contents.TestData; using Squidex.Domain.Apps.Entities.Schemas; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs index 984d2e0a0..82a48a5af 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs @@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries await sut.EnrichAsync(ctx, new[] { content }, provider, default); - A.CallTo(() => scriptEngine.TransformAsync(A._, A._, ScriptOptions(), A._)) + A.CallTo(() => scriptEngine.TransformAsync(A._, A._, ScriptOptions(), A._)) .MustNotHaveHappened(); } @@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries await sut.EnrichAsync(ctx, new[] { content }, provider, default); - A.CallTo(() => scriptEngine.TransformAsync(A._, A._, ScriptOptions(), A._)) + A.CallTo(() => scriptEngine.TransformAsync(A._, A._, ScriptOptions(), A._)) .MustNotHaveHappened(); } @@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries var content = new ContentEntity { Data = oldData, SchemaId = schemaId }; - A.CallTo(() => scriptEngine.TransformAsync(A._, "my-query", ScriptOptions(), A._)) + A.CallTo(() => scriptEngine.TransformAsync(A._, "my-query", ScriptOptions(), A._)) .Returns(new ContentData()); await sut.EnrichAsync(ctx, new[] { content }, provider, default); @@ -82,7 +82,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries Assert.NotSame(oldData, content.Data); A.CallTo(() => scriptEngine.TransformAsync( - A.That.Matches(x => + A.That.Matches(x => Equals(x["user"], ctx.User) && Equals(x["data"], oldData) && Equals(x["contentId"], content.Id)), diff --git a/frontend/src/app/features/rules/pages/rule/rule-page.component.html b/frontend/src/app/features/rules/pages/rule/rule-page.component.html index 71fc0a30c..ddea59a68 100644 --- a/frontend/src/app/features/rules/pages/rule/rule-page.component.html +++ b/frontend/src/app/features/rules/pages/rule/rule-page.component.html @@ -133,7 +133,12 @@ {{ 'rules.actionHint' | sqxTranslate }} - + + diff --git a/frontend/src/app/features/rules/shared/actions/formattable-input.component.html b/frontend/src/app/features/rules/shared/actions/formattable-input.component.html index 3c59f7e30..9bd80e7a9 100644 --- a/frontend/src/app/features/rules/shared/actions/formattable-input.component.html +++ b/frontend/src/app/features/rules/shared/actions/formattable-input.component.html @@ -17,5 +17,5 @@ - + \ No newline at end of file diff --git a/frontend/src/app/features/rules/shared/actions/formattable-input.component.scss b/frontend/src/app/features/rules/shared/actions/formattable-input.component.scss index 0ee76b356..a26b21682 100644 --- a/frontend/src/app/features/rules/shared/actions/formattable-input.component.scss +++ b/frontend/src/app/features/rules/shared/actions/formattable-input.component.scss @@ -25,7 +25,7 @@ color: $color-text-decent; &.active { - color: $color-text; + color: $color-black; } } } \ No newline at end of file diff --git a/frontend/src/app/features/rules/shared/actions/formattable-input.component.ts b/frontend/src/app/features/rules/shared/actions/formattable-input.component.ts index 82aa23c13..d950ccd66 100644 --- a/frontend/src/app/features/rules/shared/actions/formattable-input.component.ts +++ b/frontend/src/app/features/rules/shared/actions/formattable-input.component.ts @@ -37,6 +37,9 @@ export class FormattableInputComponent implements ControlValueAccessor, AfterVie @Input() public formattable = true; + @Input() + public completion: ReadonlyArray<{ path: string; description: string }> | undefined | null; + @ViewChild(DefaultValueAccessor) public inputEditor!: DefaultValueAccessor; @@ -50,6 +53,10 @@ export class FormattableInputComponent implements ControlValueAccessor, AfterVie public aceMode = 'ace/editor/text'; + public get actualCompletion() { + return this.mode === 'Script' ? this.completion : null; + } + public get valueAccessor(): ControlValueAccessor { return this.codeEditor || this.inputEditor; } diff --git a/frontend/src/app/features/rules/shared/actions/generic-action.component.html b/frontend/src/app/features/rules/shared/actions/generic-action.component.html index 578f7468c..b01df742c 100644 --- a/frontend/src/app/features/rules/shared/actions/generic-action.component.html +++ b/frontend/src/app/features/rules/shared/actions/generic-action.component.html @@ -12,10 +12,10 @@ - + - +
@@ -37,10 +37,10 @@ - +
- You can use advanced formatting: Documentation + {{ 'i18n:rules.advancedFormattingHint' | sqxTranslate }}: {{ 'i18n:common.documentation' | sqxTranslate }}
diff --git a/frontend/src/app/features/rules/shared/actions/generic-action.component.ts b/frontend/src/app/features/rules/shared/actions/generic-action.component.ts index 92746fbd2..d5d09007f 100644 --- a/frontend/src/app/features/rules/shared/actions/generic-action.component.ts +++ b/frontend/src/app/features/rules/shared/actions/generic-action.component.ts @@ -5,16 +5,43 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { ActionForm } from '@app/shared'; +import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { EMPTY, Observable, shareReplay } from 'rxjs'; +import { ActionForm, RuleCompletions, RulesService } from '@app/shared'; @Component({ - selector: 'sqx-generic-action[actionForm]', + selector: 'sqx-generic-action[actionForm][appName][trigger][triggerType]', styleUrls: ['./generic-action.component.scss'], templateUrl: './generic-action.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class GenericActionComponent { +export class GenericActionComponent implements OnChanges { @Input() public actionForm!: ActionForm; + + @Input() + public appName!: string; + + @Input() + public trigger!: any; + + @Input() + public triggerType: string | undefined | null; + + public ruleCompletions: Observable = EMPTY; + + constructor( + private readonly rulesService: RulesService, + ) { + } + + public ngOnChanges(changes: SimpleChanges) { + if (changes['appName'] || changes['triggerType']) { + if (this.triggerType) { + this.ruleCompletions = this.rulesService.getCompletions(this.appName, this.triggerType).pipe(shareReplay(1)); + } else { + this.ruleCompletions = EMPTY; + } + } + } } diff --git a/frontend/src/app/features/settings/pages/asset-scripts/asset-scripts-page.component.ts b/frontend/src/app/features/settings/pages/asset-scripts/asset-scripts-page.component.ts index 30c3f7718..7ed0d3c16 100644 --- a/frontend/src/app/features/settings/pages/asset-scripts/asset-scripts-page.component.ts +++ b/frontend/src/app/features/settings/pages/asset-scripts/asset-scripts-page.component.ts @@ -15,7 +15,7 @@ import { AppsState, AssetCompletions, AssetScriptsState, AssetsService, EditAsse templateUrl: './asset-scripts-page.component.html', }) export class AssetScriptsPageComponent extends ResourceOwner implements OnInit { - public assetScript = 'create'; + public assetScript = 'Annotate'; public assetCompletions: Observable = EMPTY; public editForm = new EditAssetScriptsForm(); diff --git a/frontend/src/app/features/settings/pages/backups/backups-page.component.ts b/frontend/src/app/features/settings/pages/backups/backups-page.component.ts index d5b85844e..2fbb774fd 100644 --- a/frontend/src/app/features/settings/pages/backups/backups-page.component.ts +++ b/frontend/src/app/features/settings/pages/backups/backups-page.component.ts @@ -24,7 +24,7 @@ export class BackupsPageComponent extends ResourceOwner implements OnInit { } public ngOnInit() { - this.backupsState.load(); + this.backupsState.load(true); this.own(timer(3000, 3000).pipe(switchMap(() => this.backupsState.load(false, true)))); } diff --git a/frontend/src/app/framework/angular/markdown.directive.ts b/frontend/src/app/framework/angular/markdown.directive.ts index ee8303c52..48dd250ca 100644 --- a/frontend/src/app/framework/angular/markdown.directive.ts +++ b/frontend/src/app/framework/angular/markdown.directive.ts @@ -35,6 +35,9 @@ export class MarkdownDirective implements OnChanges { @Input() public inline = true; + @Input() + public html = false; + @Input() public optional = false; @@ -59,7 +62,7 @@ export class MarkdownDirective implements OnChanges { html = marked(this.markdown, { renderer }); } - if (!html || html === this.markdown || html.indexOf('<') < 0) { + if (!this.html && (!html || html === this.markdown || html.indexOf('<') < 0)) { this.renderer.setProperty(this.element.nativeElement, 'textContent', html); } else { this.renderer.setProperty(this.element.nativeElement, 'innerHTML', html); diff --git a/frontend/src/app/shared/services/assets.service.spec.ts b/frontend/src/app/shared/services/assets.service.spec.ts index 76cea1d27..681a56bef 100644 --- a/frontend/src/app/shared/services/assets.service.spec.ts +++ b/frontend/src/app/shared/services/assets.service.spec.ts @@ -7,7 +7,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { inject, TestBed } from '@angular/core/testing'; -import { AnalyticsService, ApiUrlConfig, AssetDto, AssetFolderDto, AssetFoldersDto, AssetsDto, AssetsService, DateTime, ErrorDto, MathHelper, Resource, ResourceLinks, sanitize, Version } from '@app/shared/internal'; +import { AnalyticsService, ApiUrlConfig, AssetCompletions, AssetDto, AssetFolderDto, AssetFoldersDto, AssetsDto, AssetsService, DateTime, ErrorDto, MathHelper, Resource, ResourceLinks, sanitize, Version } from '@app/shared/internal'; describe('AssetsService', () => { const version = new Version('1'); @@ -453,6 +453,24 @@ describe('AssetsService', () => { expect(assetFolder!).toEqual(createAssetFolder(22)); })); + it('should make get request to get completions', + inject([AssetsService, HttpTestingController], (assetsService: AssetsService, httpMock: HttpTestingController) => { + let completions: AssetCompletions; + + assetsService.getCompletions('my-app').subscribe(result => { + completions = result; + }); + + const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets/completion'); + + expect(req.request.method).toEqual('GET'); + expect(req.request.headers.get('If-Match')).toBeNull(); + + req.flush([]); + + expect(completions!).toEqual([]); + })); + function assetResponse(id: number, suffix = '', parentId?: string) { parentId = parentId || MathHelper.EMPTY_GUID; diff --git a/frontend/src/app/shared/services/rules.service.spec.ts b/frontend/src/app/shared/services/rules.service.spec.ts index 73026082d..da0ffe492 100644 --- a/frontend/src/app/shared/services/rules.service.spec.ts +++ b/frontend/src/app/shared/services/rules.service.spec.ts @@ -8,6 +8,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { inject, TestBed } from '@angular/core/testing'; import { AnalyticsService, ApiUrlConfig, DateTime, Resource, ResourceLinks, RuleDto, RuleElementDto, RuleElementPropertyDto, RuleEventDto, RuleEventsDto, RulesDto, RulesService, Version } from '@app/shared/internal'; +import { RuleCompletions } from '..'; import { SimulatedRuleEventDto, SimulatedRuleEventsDto } from './rules.service'; describe('RulesService', () => { @@ -361,6 +362,24 @@ describe('RulesService', () => { req.flush({}); })); + it('should make get request to get completions', + inject([RulesService, HttpTestingController], (rulesService: RulesService, httpMock: HttpTestingController) => { + let completions: RuleCompletions; + + rulesService.getCompletions('my-app', 'TriggerType').subscribe(result => { + completions = result; + }); + + const req = httpMock.expectOne('http://service/p/api/apps/my-app/rules/completion/TriggerType'); + + expect(req.request.method).toEqual('GET'); + expect(req.request.headers.get('If-Match')).toBeNull(); + + req.flush([]); + + expect(completions!).toEqual([]); + })); + function ruleResponse(id: number, suffix = '') { const key = `${id}${suffix}`; diff --git a/frontend/src/app/shared/services/rules.service.ts b/frontend/src/app/shared/services/rules.service.ts index 077ffa0f7..70c064fad 100644 --- a/frontend/src/app/shared/services/rules.service.ts +++ b/frontend/src/app/shared/services/rules.service.ts @@ -210,6 +210,9 @@ export class SimulatedRuleEventDto { } } +export type RuleCompletions = + ReadonlyArray<{ path: string; description: string; type: string }>; + export type ActionsDto = Readonly<{ [name: string]: RuleElementDto }>; @@ -380,6 +383,12 @@ export class RulesService { }), pretifyError('i18n:rules.ruleEvents.cancelFailed')); } + + public getCompletions(appName: string, actionType: string): Observable { + const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/completion/${actionType}`); + + return this.http.get(url); + } } function parseSimulatedEvents(response: { items: any[]; total: number } & Resource) { diff --git a/frontend/src/app/shared/services/schemas.service.spec.ts b/frontend/src/app/shared/services/schemas.service.spec.ts index 585765878..efd1853ad 100644 --- a/frontend/src/app/shared/services/schemas.service.spec.ts +++ b/frontend/src/app/shared/services/schemas.service.spec.ts @@ -8,6 +8,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { inject, TestBed } from '@angular/core/testing'; import { AnalyticsService, ApiUrlConfig, createProperties, DateTime, FieldRule, NestedFieldDto, Resource, ResourceLinks, RootFieldDto, SchemaDto, SchemaPropertiesDto, SchemasDto, SchemasService, Version } from '@app/shared/internal'; +import { SchemaCompletions } from '..'; describe('SchemasService', () => { const version = new Version('1'); @@ -578,6 +579,24 @@ describe('SchemasService', () => { req.flush({}); })); + it('should make get request to get completions', + inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { + let completions: SchemaCompletions; + + schemasService.getCompletions('my-app', 'my-schema').subscribe(result => { + completions = result; + }); + + const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/completion'); + + expect(req.request.method).toEqual('GET'); + expect(req.request.headers.get('If-Match')).toBeNull(); + + req.flush([]); + + expect(completions!).toEqual([]); + })); + function schemaPropertiesResponse(id: number, suffix = '') { const key = `${id}${suffix}`; diff --git a/frontend/src/app/shared/state/apps.state.ts b/frontend/src/app/shared/state/apps.state.ts index d66a91f40..8d07be453 100644 --- a/frontend/src/app/shared/state/apps.state.ts +++ b/frontend/src/app/shared/state/apps.state.ts @@ -33,14 +33,14 @@ export class AppsState extends State { public selectedSettings = this.project(s => s.selectedSettings); - public get appName() { - return this.snapshot.selectedApp?.name || ''; - } - public get appId() { return this.snapshot.selectedApp?.id || ''; } + public get appName() { + return this.snapshot.selectedApp?.name || ''; + } + constructor( private readonly appsService: AppsService, private readonly dialogs: DialogService, diff --git a/frontend/src/app/shared/state/asset-scripts.state.ts b/frontend/src/app/shared/state/asset-scripts.state.ts index 66f6adecb..a39a69d1a 100644 --- a/frontend/src/app/shared/state/asset-scripts.state.ts +++ b/frontend/src/app/shared/state/asset-scripts.state.ts @@ -46,6 +46,14 @@ export class AssetScriptsState extends State { public canUpdate = this.project(x => x.canUpdate === true); + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + constructor( private readonly appsState: AppsState, private readonly appsService: AppsService, @@ -100,10 +108,6 @@ export class AssetScriptsState extends State { }, 'Loading Success / Updated'); } - private get appName() { - return this.appsState.appName; - } - private get version() { return this.snapshot.version; } diff --git a/frontend/src/app/shared/state/asset-uploader.state.ts b/frontend/src/app/shared/state/asset-uploader.state.ts index 787a3f321..3fc37ee46 100644 --- a/frontend/src/app/shared/state/asset-uploader.state.ts +++ b/frontend/src/app/shared/state/asset-uploader.state.ts @@ -44,6 +44,14 @@ export class AssetUploaderState extends State { public uploads = this.project(x => x.uploads); + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + constructor( private readonly appsState: AppsState, private readonly assetsService: AssetsService, @@ -148,8 +156,4 @@ export class AssetUploaderState extends State { return { ...s, uploads }; }, 'Upload Started'); } - - private get appName() { - return this.appsState.appName; - } } diff --git a/frontend/src/app/shared/state/assets.state.ts b/frontend/src/app/shared/state/assets.state.ts index 52ae6bab4..02d146b68 100644 --- a/frontend/src/app/shared/state/assets.state.ts +++ b/frontend/src/app/shared/state/assets.state.ts @@ -113,6 +113,18 @@ export abstract class AssetsStateBase extends State { public canRenameTag = this.project(x => x.canRenameTag === true); + public get parentId() { + return this.snapshot.parentId; + } + + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + protected constructor(name: string, private readonly appsState: AppsState, private readonly assetsService: AssetsService, @@ -414,14 +426,6 @@ export abstract class AssetsStateBase extends State { return this.loadInternal(false); } - - public get parentId() { - return this.snapshot.parentId; - } - - private get appName() { - return this.appsState.appName; - } } function getTagNames(tags: object): ReadonlyArray { diff --git a/frontend/src/app/shared/state/backups.state.ts b/frontend/src/app/shared/state/backups.state.ts index 337391b68..1753ece1e 100644 --- a/frontend/src/app/shared/state/backups.state.ts +++ b/frontend/src/app/shared/state/backups.state.ts @@ -45,6 +45,14 @@ export class BackupsState extends State { public canCreate = this.project(x => x.canCreate === true); + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + constructor( private readonly appsState: AppsState, private readonly backupsService: BackupsService, @@ -100,8 +108,4 @@ export class BackupsState extends State { }), shareSubscribed(this.dialogs)); } - - private get appName() { - return this.appsState.appName; - } } diff --git a/frontend/src/app/shared/state/clients.state.ts b/frontend/src/app/shared/state/clients.state.ts index 7c3e40677..22976bb58 100644 --- a/frontend/src/app/shared/state/clients.state.ts +++ b/frontend/src/app/shared/state/clients.state.ts @@ -45,6 +45,14 @@ export class ClientsState extends State { public canCreate = this.project(x => x.canCreate === true); + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + constructor( private readonly appsState: AppsState, private readonly clientsService: ClientsService, @@ -114,10 +122,6 @@ export class ClientsState extends State { }, 'Loading Success / Updated'); } - private get appName() { - return this.appsState.appName; - } - private get version() { return this.snapshot.version; } diff --git a/frontend/src/app/shared/state/contents.state.ts b/frontend/src/app/shared/state/contents.state.ts index 19d4c23e5..1ca153769 100644 --- a/frontend/src/app/shared/state/contents.state.ts +++ b/frontend/src/app/shared/state/contents.state.ts @@ -81,14 +81,14 @@ export abstract class ContentsStateBase extends State { public statusQueries = this.projectFrom(this.statuses, getStatusQueries); - public get appName() { - return this.appsState.appName; - } - public get appId() { return this.appsState.appId; } + public get appName() { + return this.appsState.appName; + } + protected constructor(name: string, private readonly appsState: AppsState, private readonly contentsService: ContentsService, diff --git a/frontend/src/app/shared/state/contributors.state.ts b/frontend/src/app/shared/state/contributors.state.ts index 0939ff624..28be99ac6 100644 --- a/frontend/src/app/shared/state/contributors.state.ts +++ b/frontend/src/app/shared/state/contributors.state.ts @@ -59,6 +59,10 @@ export class ContributorsState extends State { return this.appsState.appId; } + public get appName() { + return this.appsState.appName; + } + constructor( private readonly appsState: AppsState, private readonly contributorsService: ContributorsService, @@ -150,10 +154,6 @@ export class ContributorsState extends State { }, 'Loading Success / Updated'); } - private get appName() { - return this.appsState.appName; - } - private get version() { return this.snapshot.version; } diff --git a/frontend/src/app/shared/state/languages.state.ts b/frontend/src/app/shared/state/languages.state.ts index 21d339795..6eae3bd98 100644 --- a/frontend/src/app/shared/state/languages.state.ts +++ b/frontend/src/app/shared/state/languages.state.ts @@ -76,6 +76,14 @@ export class LanguagesState extends State { public canCreate = this.project(x => x.canCreate === true); + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + constructor( private readonly appLanguagesService: AppLanguagesService, private readonly appsState: AppsState, @@ -165,10 +173,6 @@ export class LanguagesState extends State { }, 'Loading Success / Updated'); } - private get appName() { - return this.appsState.appName; - } - private get version() { return this.snapshot.version; } diff --git a/frontend/src/app/shared/state/plans.state.ts b/frontend/src/app/shared/state/plans.state.ts index 0eef9ca6b..c3c750904 100644 --- a/frontend/src/app/shared/state/plans.state.ts +++ b/frontend/src/app/shared/state/plans.state.ts @@ -64,12 +64,16 @@ export class PlansState extends State { public hasPortal = this.project(x => x.hasPortal); - public window = window; - public get appId() { return this.appsState.appId; } + public get appName() { + return this.appsState.appName; + } + + public window = window; + constructor( private readonly appsState: AppsState, private readonly authState: AuthService, @@ -130,10 +134,6 @@ export class PlansState extends State { shareSubscribed(this.dialogs)); } - private get appName() { - return this.appsState.appName; - } - private get userId() { return this.authState.user!.id; } diff --git a/frontend/src/app/shared/state/roles.state.ts b/frontend/src/app/shared/state/roles.state.ts index 0fc2b438d..4bda077c4 100644 --- a/frontend/src/app/shared/state/roles.state.ts +++ b/frontend/src/app/shared/state/roles.state.ts @@ -51,6 +51,14 @@ export class RolesState extends State { public canCreate = this.project(x => x.canCreate === true); + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + constructor( private readonly appsState: AppsState, private readonly dialogs: DialogService, @@ -120,10 +128,6 @@ export class RolesState extends State { }, 'Loading Success / Updated'); } - private get appName() { - return this.appsState.appName; - } - private get version() { return this.snapshot.version; } diff --git a/frontend/src/app/shared/state/rule-events.state.ts b/frontend/src/app/shared/state/rule-events.state.ts index 3c108f480..28a5db6bb 100644 --- a/frontend/src/app/shared/state/rule-events.state.ts +++ b/frontend/src/app/shared/state/rule-events.state.ts @@ -43,6 +43,14 @@ export class RuleEventsState extends State { public canCancelAll = this.project(x => hasAnyLink(x.links, 'cancel')); + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + constructor( private readonly appsState: AppsState, private readonly dialogs: DialogService, @@ -140,10 +148,6 @@ export class RuleEventsState extends State { return this.loadInternal(false); } - - private get appName() { - return this.appsState.appName; - } } const setCancelled = (event: RuleEventDto) => diff --git a/frontend/src/app/shared/state/rule-simulator.state.ts b/frontend/src/app/shared/state/rule-simulator.state.ts index 7e7ad6fe0..7f517c861 100644 --- a/frontend/src/app/shared/state/rule-simulator.state.ts +++ b/frontend/src/app/shared/state/rule-simulator.state.ts @@ -34,6 +34,14 @@ export class RuleSimulatorState extends State { public isLoading = this.project(x => x.isLoading === true); + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + constructor( private readonly appsState: AppsState, private readonly dialogs: DialogService, @@ -86,8 +94,4 @@ export class RuleSimulatorState extends State { public selectRule(ruleId?: string) { this.resetState({ ruleId }, 'Select Rule'); } - - private get appName() { - return this.appsState.appName; - } } diff --git a/frontend/src/app/shared/state/rules.state.ts b/frontend/src/app/shared/state/rules.state.ts index b6358191c..0c1b4938a 100644 --- a/frontend/src/app/shared/state/rules.state.ts +++ b/frontend/src/app/shared/state/rules.state.ts @@ -69,6 +69,14 @@ export class RulesState extends State { public runningRule = this.projectFrom2(this.rules, this.runningRuleId, (r, id) => r.find(x => x.id === id)); + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + constructor( private readonly appsState: AppsState, private readonly dialogs: DialogService, @@ -216,8 +224,4 @@ export class RulesState extends State { return { ...s, rules, selectedRule }; }, 'Updated'); } - - private get appName() { - return this.appsState.appName; - } } diff --git a/frontend/src/app/shared/state/schemas.state.ts b/frontend/src/app/shared/state/schemas.state.ts index e00b83cbf..9d04cec24 100644 --- a/frontend/src/app/shared/state/schemas.state.ts +++ b/frontend/src/app/shared/state/schemas.state.ts @@ -62,6 +62,14 @@ export class SchemasState extends State { public categoryNames = this.projectFrom2(this.schemas, this.addedCategories, (s, c) => buildCategoryNames(c, s)); + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + public get schemaId() { return this.snapshot.selectedSchema?.id || ''; } @@ -352,10 +360,6 @@ export class SchemasState extends State { this.dialogs.notifyInfo('i18n:common.nothingChanged'); } } - - private get appName() { - return this.appsState.appName; - } } function getField(x: SchemaDto, request: AddFieldDto, parent?: RootFieldDto | null): FieldDto { diff --git a/frontend/src/app/shared/state/ui.state.ts b/frontend/src/app/shared/state/ui.state.ts index e105b5b10..2282e0823 100644 --- a/frontend/src/app/shared/state/ui.state.ts +++ b/frontend/src/app/shared/state/ui.state.ts @@ -84,6 +84,14 @@ export class UIState extends State { distinctUntilChanged()); } + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + constructor( private readonly appsState: AppsState, private readonly uiService: UIService, @@ -208,10 +216,6 @@ export class UIState extends State { return false; } - - private get appName() { - return this.appsState.appName; - } } function getValue(setting: Settings | undefined | null, path: string, defaultValue: T) { diff --git a/frontend/src/app/shared/state/workflows.state.ts b/frontend/src/app/shared/state/workflows.state.ts index 4b5b905ce..739e04204 100644 --- a/frontend/src/app/shared/state/workflows.state.ts +++ b/frontend/src/app/shared/state/workflows.state.ts @@ -49,6 +49,14 @@ export class WorkflowsState extends State { public canCreate = this.project(x => x.canCreate === true); + public get appId() { + return this.appsState.appId; + } + + public get appName() { + return this.appsState.appName; + } + constructor( private readonly appsState: AppsState, private readonly dialogs: DialogService, @@ -121,10 +129,6 @@ export class WorkflowsState extends State { }, 'Loading Success / Updated'); } - private get appName() { - return this.appsState.appName; - } - private get version() { return this.snapshot.version; }