Browse Source

Chatbot support (#1001)

* Select client.

* Fixes to OpenAPI: Parameter order.

* Fix scripting.

* Fix content type.

* Save all files.

* Fix tests

* Improve errors.

* Fix schema name.

* Fix exception type.

* Fix versions.

* Fix script.

* Chatbot.

* Added missing files.

* Added a loading spinner.

* Fix migration

* Revert change.

* Fix login flow.

* Fix home page.

* Fix GraphQL.

* Add schema name.
pull/1003/head
Sebastian Stehle 3 years ago
committed by GitHub
parent
commit
ae96470356
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj
  2. 5
      backend/i18n/frontend_en.json
  3. 5
      backend/i18n/frontend_fr.json
  4. 5
      backend/i18n/frontend_it.json
  5. 5
      backend/i18n/frontend_nl.json
  6. 5
      backend/i18n/frontend_pt.json
  7. 5
      backend/i18n/frontend_zh.json
  8. 5
      backend/i18n/source/frontend_en.json
  9. 2
      backend/src/Migrations/Migrations.csproj
  10. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Component.cs
  11. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
  12. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/AddSchemaNames.cs
  13. 535
      backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.Designer.cs
  14. 6
      backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.resx
  15. 73
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs
  16. 114
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringAsyncJintExtension.cs
  17. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  18. 30
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Adapt.cs
  19. 4
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
  20. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/Tokenizer.cs
  21. 75
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs
  22. 38
      backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterJintExtension.cs
  23. 1
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs
  24. 3
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentInterfaceGraphType.cs
  25. 53
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs
  26. 25
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentInterfaceGraphType.cs
  27. 11
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs
  28. 44
      backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs
  29. 5
      backend/src/Squidex.Domain.Apps.Entities/Notifications/EmailUserNotifications.cs
  30. 8
      backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  31. 2
      backend/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj
  32. 4
      backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
  33. 2
      backend/src/Squidex.Domain.Users/DefaultXmlRepository.cs
  34. 8
      backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj
  35. 2
      backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs
  36. 4
      backend/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj
  37. 6
      backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
  38. 26
      backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  39. 2
      backend/src/Squidex.Infrastructure/Tasks/AsyncHelper.cs
  40. 2
      backend/src/Squidex.Infrastructure/Tasks/TaskExtensions.cs
  41. 2
      backend/src/Squidex.Shared/Squidex.Shared.csproj
  42. 3
      backend/src/Squidex.Web/Pipeline/CachingManager.cs
  43. 8
      backend/src/Squidex.Web/Squidex.Web.csproj
  44. 21
      backend/src/Squidex/Areas/Api/Controllers/Translations/Models/AskDto.cs
  45. 10
      backend/src/Squidex/Areas/Api/Controllers/Translations/Models/TranslationDto.cs
  46. 27
      backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs
  47. 90
      backend/src/Squidex/Config/Domain/AssetServices.cs
  48. 20
      backend/src/Squidex/Config/Domain/InfrastructureServices.cs
  49. 11
      backend/src/Squidex/Config/Domain/StoreServices.cs
  50. 48
      backend/src/Squidex/Squidex.csproj
  51. 13
      backend/src/Squidex/appsettings.json
  52. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs
  53. 150
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs
  54. 8
      backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj
  55. 25
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs
  56. 14
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj
  57. 8
      backend/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj
  58. 4
      backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonJsonSerializerTests.cs
  59. 11
      backend/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj
  60. 8
      backend/tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj
  61. 1
      frontend/src/app/features/content/declarations.ts
  62. 3
      frontend/src/app/features/content/module.ts
  63. 8
      frontend/src/app/features/content/pages/content/editor/content-field.component.html
  64. 2
      frontend/src/app/features/content/pages/content/editor/content-field.component.scss
  65. 4
      frontend/src/app/features/content/pages/content/editor/field-languages.component.html
  66. 56
      frontend/src/app/features/content/shared/forms/chat-dialog.component.html
  67. 6
      frontend/src/app/features/content/shared/forms/chat-dialog.component.scss
  68. 65
      frontend/src/app/features/content/shared/forms/chat-dialog.component.ts
  69. 13
      frontend/src/app/features/content/shared/forms/field-editor.component.html
  70. 10
      frontend/src/app/features/content/shared/forms/field-editor.component.ts
  71. 8
      frontend/src/app/features/settings/pages/templates/template.component.html
  72. 26
      frontend/src/app/shared/services/translations.service.spec.ts
  73. 12
      frontend/src/app/shared/services/translations.service.ts
  74. 12
      frontend/src/app/shell/pages/home/home-page.component.ts

18
backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj

@ -14,27 +14,27 @@
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.0.0-beta.3" /> <PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.0.0-beta.3" />
<PackageReference Include="Azure.Search.Documents" Version="11.4.0" /> <PackageReference Include="Azure.Search.Documents" Version="11.4.0" />
<PackageReference Include="Confluent.Apache.Avro" Version="1.7.7.7" /> <PackageReference Include="Confluent.Apache.Avro" Version="1.7.7.7" />
<PackageReference Include="Confluent.Kafka" Version="2.0.2" /> <PackageReference Include="Confluent.Kafka" Version="2.1.1" />
<PackageReference Include="Confluent.SchemaRegistry.Serdes" Version="1.3.0" /> <PackageReference Include="Confluent.SchemaRegistry.Serdes" Version="1.3.0" />
<PackageReference Include="CoreTweet" Version="1.0.0.483" /> <PackageReference Include="CoreTweet" Version="1.0.0.483" />
<PackageReference Include="Elasticsearch.Net" Version="7.17.5" /> <PackageReference Include="Elasticsearch.Net" Version="7.17.5" />
<PackageReference Include="Google.Cloud.Diagnostics.Common" Version="4.4.0" /> <PackageReference Include="Google.Cloud.Diagnostics.Common" Version="4.4.0" />
<PackageReference Include="Google.Cloud.Logging.V2" Version="3.6.0" /> <PackageReference Include="Google.Cloud.Logging.V2" Version="3.6.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Azure.CognitiveServices.Vision.ComputerVision" Version="7.0.1" /> <PackageReference Include="Microsoft.Azure.CognitiveServices.Vision.ComputerVision" Version="7.0.1" />
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.21.2" /> <PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.21.4" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.15.0" /> <PackageReference Include="Microsoft.OData.Core" Version="7.17.0" />
<PackageReference Include="NodaTime" Version="3.1.6" /> <PackageReference Include="NodaTime" Version="3.1.9" />
<PackageReference Include="OpenSearch.Net" Version="1.3.0" /> <PackageReference Include="OpenSearch.Net" Version="1.3.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.4.0" /> <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.5.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.4.0" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.5.0" />
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.4.0" /> <PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.5.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.4.0" /> <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.5.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.OpenTelemetry.Exporter.Stackdriver" Version="0.0.0-alpha.0.395" /> <PackageReference Include="Squidex.OpenTelemetry.Exporter.Stackdriver" Version="0.0.0-alpha.0.395" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />

5
backend/i18n/frontend_en.json

@ -166,6 +166,11 @@
"backups.started": "Backup started, it can take several minutes to complete.", "backups.started": "Backup started, it can take several minutes to complete.",
"backups.startedLabel": "Started", "backups.startedLabel": "Started",
"backups.startFailed": "Failed to start backup.", "backups.startFailed": "Failed to start backup.",
"chat.ask": "Ask",
"chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe, what you need.\n\n\nAlso add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.",
"chat.prompt": "Describe the content you want to generate",
"chat.title": "Chat Bot",
"chat.use": "Use",
"clients.add": "Add Client", "clients.add": "Add Client",
"clients.add.description": "Add a client to give other applications access to your content.", "clients.add.description": "Add a client to give other applications access to your content.",
"clients.add.title": "Add a new Client", "clients.add.title": "Add a new Client",

5
backend/i18n/frontend_fr.json

@ -166,6 +166,11 @@
"backups.started": "La sauvegarde a commencé, cela peut prendre plusieurs minutes.", "backups.started": "La sauvegarde a commencé, cela peut prendre plusieurs minutes.",
"backups.startedLabel": "Commencé", "backups.startedLabel": "Commencé",
"backups.startFailed": "Échec du démarrage de la sauvegarde.", "backups.startFailed": "Échec du démarrage de la sauvegarde.",
"chat.ask": "Ask",
"chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe, what you need.\n\n\nAlso add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.",
"chat.prompt": "Describe the content you want to generate",
"chat.title": "Chat Bot",
"chat.use": "Use",
"clients.add": "Ajouter un client", "clients.add": "Ajouter un client",
"clients.add.description": "Ajoutez un client pour permettre à d'autres applications d'accéder à votre contenu.", "clients.add.description": "Ajoutez un client pour permettre à d'autres applications d'accéder à votre contenu.",
"clients.add.title": "Ajouter un nouveau client", "clients.add.title": "Ajouter un nouveau client",

5
backend/i18n/frontend_it.json

@ -166,6 +166,11 @@
"backups.started": "Backup avviato, il suo completamento potrebbe richiedere alcuni minuti.", "backups.started": "Backup avviato, il suo completamento potrebbe richiedere alcuni minuti.",
"backups.startedLabel": "Avviato", "backups.startedLabel": "Avviato",
"backups.startFailed": "Non è stato possibile avviare il backup.", "backups.startFailed": "Non è stato possibile avviare il backup.",
"chat.ask": "Ask",
"chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe, what you need.\n\n\nAlso add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.",
"chat.prompt": "Describe the content you want to generate",
"chat.title": "Chat Bot",
"chat.use": "Use",
"clients.add": "Aggiungi un Client", "clients.add": "Aggiungi un Client",
"clients.add.description": "Add a client to give other applications access to your content.", "clients.add.description": "Add a client to give other applications access to your content.",
"clients.add.title": "Add a new Client", "clients.add.title": "Add a new Client",

5
backend/i18n/frontend_nl.json

@ -166,6 +166,11 @@
"backups.started": "Back-up gestart, het kan enkele minuten duren om te voltooien.", "backups.started": "Back-up gestart, het kan enkele minuten duren om te voltooien.",
"backups.startedLabel": "Gestart", "backups.startedLabel": "Gestart",
"backups.startFailed": "Starten van back-up is mislukt.", "backups.startFailed": "Starten van back-up is mislukt.",
"chat.ask": "Ask",
"chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe, what you need.\n\n\nAlso add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.",
"chat.prompt": "Describe the content you want to generate",
"chat.title": "Chat Bot",
"chat.use": "Use",
"clients.add": "Client toevoegen", "clients.add": "Client toevoegen",
"clients.add.description": "Voeg een client toe om andere applicaties toegang te geven tot uw inhoud.", "clients.add.description": "Voeg een client toe om andere applicaties toegang te geven tot uw inhoud.",
"clients.add.title": "Client toevoegen", "clients.add.title": "Client toevoegen",

5
backend/i18n/frontend_pt.json

@ -166,6 +166,11 @@
"backups.started": "O reforço começou, pode levar vários minutos para ser concluído.", "backups.started": "O reforço começou, pode levar vários minutos para ser concluído.",
"backups.startedLabel": "Começou", "backups.startedLabel": "Começou",
"backups.startFailed": "Falhou em começar o backup.", "backups.startFailed": "Falhou em começar o backup.",
"chat.ask": "Ask",
"chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe, what you need.\n\n\nAlso add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.",
"chat.prompt": "Describe the content you want to generate",
"chat.title": "Chat Bot",
"chat.use": "Use",
"clients.add": "Adicionar Cliente", "clients.add": "Adicionar Cliente",
"clients.add.description": "Adicione um cliente para dar a outras aplicações acesso ao seu conteúdo.", "clients.add.description": "Adicione um cliente para dar a outras aplicações acesso ao seu conteúdo.",
"clients.add.title": "Adicionar um novo Cliente", "clients.add.title": "Adicionar um novo Cliente",

5
backend/i18n/frontend_zh.json

@ -166,6 +166,11 @@
"backups.started": "备份已开始,可能需要几分钟才能完成。", "backups.started": "备份已开始,可能需要几分钟才能完成。",
"backups.startedLabel": "开始", "backups.startedLabel": "开始",
"backups.startFailed": "启动备份失败。", "backups.startFailed": "启动备份失败。",
"chat.ask": "Ask",
"chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe, what you need.\n\n\nAlso add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.",
"chat.prompt": "Describe the content you want to generate",
"chat.title": "Chat Bot",
"chat.use": "Use",
"clients.add": "添加客户端", "clients.add": "添加客户端",
"clients.add.description": "Add a client to give other applications access to your content.", "clients.add.description": "Add a client to give other applications access to your content.",
"clients.add.title": "Add a new Client", "clients.add.title": "Add a new Client",

5
backend/i18n/source/frontend_en.json

@ -166,6 +166,11 @@
"backups.started": "Backup started, it can take several minutes to complete.", "backups.started": "Backup started, it can take several minutes to complete.",
"backups.startedLabel": "Started", "backups.startedLabel": "Started",
"backups.startFailed": "Failed to start backup.", "backups.startFailed": "Failed to start backup.",
"chat.ask": "Ask",
"chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe, what you need.\n\n\nAlso add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.",
"chat.prompt": "Describe the content you want to generate",
"chat.title": "Chat Bot",
"chat.use": "Use",
"clients.add": "Add Client", "clients.add": "Add Client",
"clients.add.description": "Add a client to give other applications access to your content.", "clients.add.description": "Add a client to give other applications access to your content.",
"clients.add.title": "Add a new Client", "clients.add.title": "Add a new Client",

2
backend/src/Migrations/Migrations.csproj

@ -6,7 +6,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

2
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Component.cs

@ -18,6 +18,8 @@ public sealed record Component(string Type, JsonObject Data, Schema Schema)
{ {
public const string Discriminator = "schemaId"; public const string Discriminator = "schemaId";
public const string Descriptor = "schemaName";
public string Type { get; } = Guard.NotNullOrEmpty(Type); public string Type { get; } = Guard.NotNullOrEmpty(Type);
public Schema Schema { get; } = Guard.NotNull(Schema); public Schema Schema { get; } = Guard.NotNull(Schema);

2
backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj

@ -12,7 +12,7 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

4
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/AddSchemaNames.cs

@ -28,7 +28,7 @@ public sealed class AddSchemaNames : IContentItemConverter
return source; return source;
} }
if (source.ContainsKey("schemaName")) if (source.ContainsKey(Component.Descriptor))
{ {
return source; return source;
} }
@ -42,7 +42,7 @@ public sealed class AddSchemaNames : IContentItemConverter
if (components.TryGetValue(id, out var component)) if (components.TryGetValue(id, out var component))
{ {
source["schemaName"] = component.Name; source[Component.Descriptor] = component.Name;
} }
return source; return source;

535
backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.Designer.cs

@ -8,298 +8,317 @@
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace Squidex.Domain.Apps.Core.Properties; namespace Squidex.Domain.Apps.Core.Properties {
using System; using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary> /// <summary>
/// Returns the cached ResourceManager instance used by this class. /// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] // This class was auto-generated by the StronglyTypedResourceBuilder
internal static global::System.Resources.ResourceManager ResourceManager { // class via a tool like ResGen or Visual Studio.
get { // To add or remove a member, edit your .ResX file then rerun ResGen
if (object.ReferenceEquals(resourceMan, null)) { // with the /str option, or rebuild your VS project.
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Squidex.Domain.Apps.Core.Properties.Resources", typeof(Resources).Assembly); [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
resourceMan = temp; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Squidex.Domain.Apps.Core.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
} }
return resourceMan;
} }
}
/// <summary>
/// <summary> /// Overrides the current thread's CurrentUICulture property for all
/// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class.
/// resource lookups using this strongly typed resource class. /// </summary>
/// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture {
internal static global::System.Globalization.CultureInfo Culture { get {
get { return resourceCulture;
return resourceCulture; }
set {
resourceCulture = value;
}
} }
set {
resourceCulture = value; /// <summary>
/// Looks up a localized string similar to The download URL to the asset..
/// </summary>
internal static string ScriptingAssetContentAppUrl {
get {
return ResourceManager.GetString("ScriptingAssetContentAppUrl", resourceCulture);
}
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to The download URL to the asset using the file slug instead of the ID..
/// Looks up a localized string similar to The download URL to the asset.. /// </summary>
/// </summary> internal static string ScriptingAssetContentSlugUrl {
internal static string ScriptingAssetContentAppUrl { get {
get { return ResourceManager.GetString("ScriptingAssetContentSlugUrl", resourceCulture);
return ResourceManager.GetString("ScriptingAssetContentAppUrl", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to The download URL to the asset without the app name (deprecated)..
/// Looks up a localized string similar to The download URL to the asset using the file slug instead of the ID.. /// </summary>
/// </summary> internal static string ScriptingAssetContentUrl {
internal static string ScriptingAssetContentSlugUrl { get {
get { return ResourceManager.GetString("ScriptingAssetContentUrl", resourceCulture);
return ResourceManager.GetString("ScriptingAssetContentSlugUrl", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Counts the number of characters in a text. Useful in combination with html2Text or markdown2Text..
/// Looks up a localized string similar to The download URL to the asset without the app name (deprecated).. /// </summary>
/// </summary> internal static string ScriptingCharacterCount {
internal static string ScriptingAssetContentUrl { get {
get { return ResourceManager.GetString("ScriptingCharacterCount", resourceCulture);
return ResourceManager.GetString("ScriptingAssetContentUrl", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Completes the script when an async method is used..
/// Looks up a localized string similar to Counts the number of characters in a text. Useful in combination with html2Text or markdown2Text.. /// </summary>
/// </summary> internal static string ScriptingComplete {
internal static string ScriptingCharacterCount { get {
get { return ResourceManager.GetString("ScriptingComplete", resourceCulture);
return ResourceManager.GetString("ScriptingCharacterCount", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to The status of the content..
/// Looks up a localized string similar to Completes the script when an async method is used.. /// </summary>
/// </summary> internal static string ScriptingContentAction {
internal static string ScriptingComplete { get {
get { return ResourceManager.GetString("ScriptingContentAction", resourceCulture);
return ResourceManager.GetString("ScriptingComplete", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to The URL to the content in the UI..
/// Looks up a localized string similar to The status of the content.. /// </summary>
/// </summary> internal static string ScriptingContentUrl {
internal static string ScriptingContentAction { get {
get { return ResourceManager.GetString("ScriptingContentUrl", resourceCulture);
return ResourceManager.GetString("ScriptingContentAction", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Makes a DELETE request to the defined URL and parses the result as JSON. Headers are optional..
/// Looks up a localized string similar to The URL to the content in the UI.. /// </summary>
/// </summary> internal static string ScriptingDeleteJson {
internal static string ScriptingContentUrl { get {
get { return ResourceManager.GetString("ScriptingDeleteJson", resourceCulture);
return ResourceManager.GetString("ScriptingContentUrl", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Tell Squidex to not allow the current operation and to return a 400 (BadRequest)..
/// Looks up a localized string similar to Makes a DELETE request to the defined URL and parses the result as JSON. Headers are optional.. /// </summary>
/// </summary> internal static string ScriptingDisallow {
internal static string ScriptingDeleteJson { get {
get { return ResourceManager.GetString("ScriptingDisallow", resourceCulture);
return ResourceManager.GetString("ScriptingDeleteJson", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Formats a JavaScript date object using the specified pattern..
/// Looks up a localized string similar to Tell Squidex to not allow the current operation and to return a 400 (BadRequest).. /// </summary>
/// </summary> internal static string ScriptingFormatDate {
internal static string ScriptingDisallow { get {
get { return ResourceManager.GetString("ScriptingFormatDate", resourceCulture);
return ResourceManager.GetString("ScriptingDisallow", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Formats a JavaScript date object using the specified pattern..
/// Looks up a localized string similar to Formats a JavaScript date object using the specified pattern.. /// </summary>
/// </summary> internal static string ScriptingFormatTime {
internal static string ScriptingFormatDate { get {
get { return ResourceManager.GetString("ScriptingFormatTime", resourceCulture);
return ResourceManager.GetString("ScriptingFormatDate", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Uses OpenAI or other machine learning platforms to generate content from a prompt..
/// Looks up a localized string similar to Formats a JavaScript date object using the specified pattern.. /// </summary>
/// </summary> internal static string ScriptingGenerate {
internal static string ScriptingFormatTime { get {
get { return ResourceManager.GetString("ScriptingGenerate", resourceCulture);
return ResourceManager.GetString("ScriptingFormatTime", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Makes a GET request to the defined URL and parses the result as JSON. Headers are optional..
/// Looks up a localized string similar to Makes a GET request to the defined URL and parses the result as JSON. Headers are optional.. /// </summary>
/// </summary> internal static string ScriptingGetJSON {
internal static string ScriptingGetJSON { get {
get { return ResourceManager.GetString("ScriptingGetJSON", resourceCulture);
return ResourceManager.GetString("ScriptingGetJSON", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Generates a guid..
/// Looks up a localized string similar to Generates a guid.. /// </summary>
/// </summary> internal static string ScriptingGuid {
internal static string ScriptingGuid { get {
get { return ResourceManager.GetString("ScriptingGuid", resourceCulture);
return ResourceManager.GetString("ScriptingGuid", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Converts a HTML string to plain text..
/// Looks up a localized string similar to Converts a HTML string to plain text.. /// </summary>
/// </summary> internal static string ScriptingHtml2Text {
internal static string ScriptingHtml2Text { get {
get { return ResourceManager.GetString("ScriptingHtml2Text", resourceCulture);
return ResourceManager.GetString("ScriptingHtml2Text", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Converts a markdown string to plain text..
/// Looks up a localized string similar to Converts a markdown string to plain text.. /// </summary>
/// </summary> internal static string ScriptingMarkdown2Text {
internal static string ScriptingMarkdown2Text { get {
get { return ResourceManager.GetString("ScriptingMarkdown2Text", resourceCulture);
return ResourceManager.GetString("ScriptingMarkdown2Text", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Calculate the MD5 hash from a given string. Use this method for hashing passwords, when backwards compatibility is important..
/// Looks up a localized string similar to Calculate the MD5 hash from a given string. Use this method for hashing passwords, when backwards compatibility is important.. /// </summary>
/// </summary> internal static string ScriptingMD5 {
internal static string ScriptingMD5 { get {
get { return ResourceManager.GetString("ScriptingMD5", resourceCulture);
return ResourceManager.GetString("ScriptingMD5", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Makes a PATCH request to the defined URL and parses the result as JSON. Headers are optional..
/// Looks up a localized string similar to Makes a PATCH request to the defined URL and parses the result as JSON. Headers are optional.. /// </summary>
/// </summary> internal static string ScriptingPatchJson {
internal static string ScriptingPatchJson { get {
get { return ResourceManager.GetString("ScriptingPatchJson", resourceCulture);
return ResourceManager.GetString("ScriptingPatchJson", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Makes a POST request to the defined URL and parses the result as JSON. Headers are optional..
/// Looks up a localized string similar to Makes a POST request to the defined URL and parses the result as JSON. Headers are optional.. /// </summary>
/// </summary> internal static string ScriptingPostJSON {
internal static string ScriptingPostJSON { get {
get { return ResourceManager.GetString("ScriptingPostJSON", resourceCulture);
return ResourceManager.GetString("ScriptingPostJSON", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Makes a PUT request to the defined URL and parses the result as JSON. Headers are optional..
/// Looks up a localized string similar to Makes a PUT request to the defined URL and parses the result as JSON. Headers are optional.. /// </summary>
/// </summary> internal static string ScriptingPutJson {
internal static string ScriptingPutJson { get {
get { return ResourceManager.GetString("ScriptingPutJson", resourceCulture);
return ResourceManager.GetString("ScriptingPutJson", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Tell Squidex to reject the current operation and to return a 403 (Forbidden)..
/// Looks up a localized string similar to Tell Squidex to reject the current operation and to return a 403 (Forbidden).. /// </summary>
/// </summary> internal static string ScriptingReject {
internal static string ScriptingReject { get {
get { return ResourceManager.GetString("ScriptingReject", resourceCulture);
return ResourceManager.GetString("ScriptingReject", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Tell Squidex that you have modified the data and that the change should be applied..
/// Looks up a localized string similar to Tell Squidex that you have modified the data and that the change should be applied.. /// </summary>
/// </summary> internal static string ScriptingReplace {
internal static string ScriptingReplace { get {
get { return ResourceManager.GetString("ScriptingReplace", resourceCulture);
return ResourceManager.GetString("ScriptingReplace", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Calculate the SHA256 hash from a given string. Use this method for hashing passwords..
/// Looks up a localized string similar to Calculate the SHA256 hash from a given string. Use this method for hashing passwords.. /// </summary>
/// </summary> internal static string ScriptingSHA256 {
internal static string ScriptingSHA256 { get {
get { return ResourceManager.GetString("ScriptingSHA256", resourceCulture);
return ResourceManager.GetString("ScriptingSHA256", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Calculate the SHA256 hash from a given string. Use this method for hashing passwords..
/// Looks up a localized string similar to Calculate the SHA256 hash from a given string. Use this method for hashing passwords.. /// </summary>
/// </summary> internal static string ScriptingSHA512 {
internal static string ScriptingSHA512 { get {
get { return ResourceManager.GetString("ScriptingSHA512", resourceCulture);
return ResourceManager.GetString("ScriptingSHA512", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Calculates the slug of a text by removing all special characters and whitespaces to create a friendly term that can be used for SEO-friendly URLs..
/// Looks up a localized string similar to Calculates the slug of a text by removing all special characters and whitespaces to create a friendly term that can be used for SEO-friendly URLs.. /// </summary>
/// </summary> internal static string ScriptingSlugify {
internal static string ScriptingSlugify { get {
get { return ResourceManager.GetString("ScriptingSlugify", resourceCulture);
return ResourceManager.GetString("ScriptingSlugify", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Converts a text to camelCase..
/// Looks up a localized string similar to Converts a text to camelCase.. /// </summary>
/// </summary> internal static string ScriptingToCamelCase {
internal static string ScriptingToCamelCase { get {
get { return ResourceManager.GetString("ScriptingToCamelCase", resourceCulture);
return ResourceManager.GetString("ScriptingToCamelCase", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Converts a text to PascalCase.
/// Looks up a localized string similar to Converts a text to PascalCase. /// </summary>
/// </summary> internal static string ScriptingToPascalCase {
internal static string ScriptingToPascalCase { get {
get { return ResourceManager.GetString("ScriptingToPascalCase", resourceCulture);
return ResourceManager.GetString("ScriptingToPascalCase", resourceCulture); }
} }
}
/// <summary>
/// <summary> /// Looks up a localized string similar to Translate from the source text to the specified language..
/// Looks up a localized string similar to Counts the number of words in a text. Useful in combination with html2Text or markdown2Text.. /// </summary>
/// </summary> internal static string ScriptingTranslate {
internal static string ScriptingWordCount { get {
get { return ResourceManager.GetString("ScriptingTranslate", resourceCulture);
return ResourceManager.GetString("ScriptingWordCount", resourceCulture); }
}
/// <summary>
/// Looks up a localized string similar to Counts the number of words in a text. Useful in combination with html2Text or markdown2Text..
/// </summary>
internal static string ScriptingWordCount {
get {
return ResourceManager.GetString("ScriptingWordCount", resourceCulture);
}
} }
} }
} }

6
backend/src/Squidex.Domain.Apps.Core.Operations/Properties/Resources.resx

@ -150,6 +150,9 @@
<data name="ScriptingFormatTime" xml:space="preserve"> <data name="ScriptingFormatTime" xml:space="preserve">
<value>Formats a JavaScript date object using the specified pattern.</value> <value>Formats a JavaScript date object using the specified pattern.</value>
</data> </data>
<data name="ScriptingGenerate" xml:space="preserve">
<value>Uses OpenAI or other machine learning platforms to generate content from a prompt.</value>
</data>
<data name="ScriptingGetJSON" xml:space="preserve"> <data name="ScriptingGetJSON" xml:space="preserve">
<value>Makes a GET request to the defined URL and parses the result as JSON. Headers are optional.</value> <value>Makes a GET request to the defined URL and parses the result as JSON. Headers are optional.</value>
</data> </data>
@ -195,6 +198,9 @@
<data name="ScriptingToPascalCase" xml:space="preserve"> <data name="ScriptingToPascalCase" xml:space="preserve">
<value>Converts a text to PascalCase</value> <value>Converts a text to PascalCase</value>
</data> </data>
<data name="ScriptingTranslate" xml:space="preserve">
<value>Translate from the source text to the specified language.</value>
</data>
<data name="ScriptingWordCount" xml:space="preserve"> <data name="ScriptingWordCount" xml:space="preserve">
<value>Counts the number of words in a text. Useful in combination with html2Text or markdown2Text.</value> <value>Counts the number of words in a text. Useful in combination with html2Text or markdown2Text.</value>
</data> </data>

73
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs

@ -17,8 +17,8 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions;
public sealed class HttpJintExtension : IJintExtension, IScriptDescriptor public sealed class HttpJintExtension : IJintExtension, IScriptDescriptor
{ {
private delegate void HttpJson(string url, Action<JsValue> callback, JsValue? headers = null, bool ignoreError = false); private delegate void HttpJsonDelegate(string url, Action<JsValue> callback, JsValue? headers = null, bool ignoreError = false);
private delegate void HttpJsonWithBody(string url, JsValue post, Action<JsValue> callback, JsValue? headers = null, bool ignoreError = false); private delegate void HttpJsonWithBodyDelegate(string url, JsValue post, Action<JsValue> callback, JsValue? headers = null, bool ignoreError = false);
private readonly IHttpClientFactory httpClientFactory; private readonly IHttpClientFactory httpClientFactory;
public HttpJintExtension(IHttpClientFactory httpClientFactory) public HttpJintExtension(IHttpClientFactory httpClientFactory)
@ -35,32 +35,9 @@ public sealed class HttpJintExtension : IJintExtension, IScriptDescriptor
AddMethod(context, HttpMethod.Get, "getJSON"); AddMethod(context, HttpMethod.Get, "getJSON");
} }
public void Describe(AddDescription describe, ScriptScope scope)
{
if (!scope.HasFlag(ScriptScope.Async))
{
return;
}
describe(JsonType.Function, "getJSON(url, callback, headers?, ignoreError?)",
Resources.ScriptingGetJSON);
describe(JsonType.Function, "postJSON(url, body, callback, headers?, ignoreError?)",
Resources.ScriptingPostJSON);
describe(JsonType.Function, "putJSON(url, body, callback, headers?, ignoreError?)",
Resources.ScriptingPutJson);
describe(JsonType.Function, "patchJSON(url, body, callback, headers?, ignoreError?)",
Resources.ScriptingPatchJson);
describe(JsonType.Function, "deleteJSON(url, callback, headers?, ignoreError?)",
Resources.ScriptingDeleteJson);
}
private void AddMethod(ScriptExecutionContext context, HttpMethod method, string name) private void AddMethod(ScriptExecutionContext context, HttpMethod method, string name)
{ {
var action = new HttpJson((url, callback, headers, ignoreError) => var action = new HttpJsonDelegate((url, callback, headers, ignoreError) =>
{ {
Request(context, method, url, null, callback, headers, ignoreError); Request(context, method, url, null, callback, headers, ignoreError);
}); });
@ -70,7 +47,7 @@ public sealed class HttpJintExtension : IJintExtension, IScriptDescriptor
private void AddBodyMethod(ScriptExecutionContext context, HttpMethod method, string name) private void AddBodyMethod(ScriptExecutionContext context, HttpMethod method, string name)
{ {
var action = new HttpJsonWithBody((url, body, callback, headers, ignoreError) => var action = new HttpJsonWithBodyDelegate((url, body, callback, headers, ignoreError) =>
{ {
Request(context, method, url, body, callback, headers, ignoreError); Request(context, method, url, body, callback, headers, ignoreError);
}); });
@ -80,21 +57,20 @@ public sealed class HttpJintExtension : IJintExtension, IScriptDescriptor
private void Request(ScriptExecutionContext context, HttpMethod method, string url, JsValue? body, Action<JsValue> callback, JsValue? headers, bool ignoreError) private void Request(ScriptExecutionContext context, HttpMethod method, string url, JsValue? body, Action<JsValue> callback, JsValue? headers, bool ignoreError)
{ {
context.Schedule(async (scheduler, ct) => if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
{ {
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) throw new JavaScriptException("URL is not valid.");
{ }
throw new JavaScriptException("URL is not valid.");
}
if (callback == null) if (callback == null)
{ {
throw new JavaScriptException("Callback is not defined."); throw new JavaScriptException("Callback is not defined.");
} }
context.Schedule(async (scheduler, ct) =>
{
try try
{ {
var httpClient = httpClientFactory.CreateClient("Jint"); var httpClient = httpClientFactory.CreateClient("Jint");
var request = CreateRequest(context, method, uri, body, headers); var request = CreateRequest(context, method, uri, body, headers);
@ -186,4 +162,27 @@ public sealed class HttpJintExtension : IJintExtension, IScriptDescriptor
return jsonValue; return jsonValue;
} }
public void Describe(AddDescription describe, ScriptScope scope)
{
if (!scope.HasFlag(ScriptScope.Async))
{
return;
}
describe(JsonType.Function, "getJSON(url, callback, headers?, ignoreError?)",
Resources.ScriptingGetJSON);
describe(JsonType.Function, "postJSON(url, body, callback, headers?, ignoreError?)",
Resources.ScriptingPostJSON);
describe(JsonType.Function, "putJSON(url, body, callback, headers?, ignoreError?)",
Resources.ScriptingPutJson);
describe(JsonType.Function, "patchJSON(url, body, callback, headers?, ignoreError?)",
Resources.ScriptingPatchJson);
describe(JsonType.Function, "deleteJSON(url, callback, headers?, ignoreError?)",
Resources.ScriptingDeleteJson);
}
} }

114
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringAsyncJintExtension.cs

@ -0,0 +1,114 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Jint.Native;
using Jint.Runtime;
using Squidex.Domain.Apps.Core.Properties;
using Squidex.Text.ChatBots;
using Squidex.Text.Translations;
#pragma warning disable CA1826 // Do not use Enumerable methods on indexable collections
namespace Squidex.Domain.Apps.Core.Scripting.Extensions;
public sealed class StringAsyncJintExtension : IJintExtension, IScriptDescriptor
{
private delegate void TextGenerateDelegate(string prompt, Action<JsValue> callback);
private delegate void TextTranslateDelegate(string text, string language, Action<JsValue> callback, string sourceLanguage);
private readonly ITranslator translator;
private readonly IChatBot chatBot;
public StringAsyncJintExtension(ITranslator translator, IChatBot chatBot)
{
this.translator = translator;
this.chatBot = chatBot;
}
public void ExtendAsync(ScriptExecutionContext context)
{
var generate = new TextGenerateDelegate((prompt, callback) =>
{
Generate(context, prompt, callback);
});
var translate = new TextTranslateDelegate((text, language, callback, sourceLanguage) =>
{
Translate(context, text, language, callback, sourceLanguage);
});
context.Engine.SetValue("generate", generate);
context.Engine.SetValue("translate", translate);
}
private void Generate(ScriptExecutionContext context, string prompt, Action<JsValue> callback)
{
if (callback == null)
{
throw new JavaScriptException("Callback is not defined.");
}
context.Schedule(async (scheduler, ct) =>
{
try
{
if (string.IsNullOrWhiteSpace(prompt))
{
scheduler.Run(callback, JsValue.Null);
return;
}
var choices = await chatBot.AskQuestionAsync(prompt, ct);
scheduler.Run(callback, JsValue.FromObject(context.Engine, choices.FirstOrDefault()));
}
catch (Exception ex)
{
throw new JavaScriptException(ex.Message);
}
});
}
private void Translate(ScriptExecutionContext context, string text, string language, Action<JsValue> callback, string sourceLanguage)
{
if (callback == null)
{
throw new JavaScriptException("Callback is not defined.");
}
context.Schedule(async (scheduler, ct) =>
{
try
{
if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(language))
{
scheduler.Run(callback, JsValue.Null);
return;
}
var translation = await translator.TranslateAsync(text, language, sourceLanguage, ct);
scheduler.Run(callback, JsValue.FromObject(context.Engine, translation.Text));
}
catch (Exception ex)
{
throw new JavaScriptException(ex.Message);
}
});
}
public void Describe(AddDescription describe, ScriptScope scope)
{
if (scope.HasFlag(ScriptScope.Async))
{
describe(JsonType.Function, "generate(prompt, callback?",
Resources.ScriptingGenerate);
describe(JsonType.Function, "translate(text, language, callback, sourceLanguage?",
Resources.ScriptingTranslate);
}
}
}

4
backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -21,14 +21,14 @@
<PackageReference Include="Fluid.Core" Version="2.4.0" /> <PackageReference Include="Fluid.Core" Version="2.4.0" />
<PackageReference Include="GeoJSON.Net" Version="1.2.19" /> <PackageReference Include="GeoJSON.Net" Version="1.2.19" />
<PackageReference Include="Jint" Version="3.0.0-beta-2046" /> <PackageReference Include="Jint" Version="3.0.0-beta-2046" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="NJsonSchema" Version="10.9.0" /> <PackageReference Include="NJsonSchema" Version="10.9.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.Messaging.Subscriptions" Version="5.5.0" /> <PackageReference Include="Squidex.Messaging.Subscriptions" Version="5.9.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="7.0.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" /> <PackageReference Include="System.Linq.Async" Version="6.0.1" />

30
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Adapt.cs

@ -20,33 +20,17 @@ public static class Adapt
public static IReadOnlyDictionary<string, string> PropertyMap public static IReadOnlyDictionary<string, string> PropertyMap
{ {
get get => propertyMap ??=
{ BsonClassMap.LookupClassMap(typeof(MongoContentEntity)).AllMemberMaps
if (propertyMap == null) .ToDictionary(
{ x => x.MemberName,
propertyMap = x => x.ElementName,
BsonClassMap.LookupClassMap(typeof(MongoContentEntity)).AllMemberMaps StringComparer.OrdinalIgnoreCase);
.ToDictionary(
x => x.MemberName,
x => x.ElementName,
StringComparer.OrdinalIgnoreCase);
}
return propertyMap;
}
} }
public static IReadOnlyDictionary<string, PropertyPath> PathMap public static IReadOnlyDictionary<string, PropertyPath> PathMap
{ {
get get => pathMap ??= PropertyMap.ToDictionary(x => x.Key, x => (PropertyPath)x.Value);
{
if (pathMap == null)
{
pathMap = PropertyMap.ToDictionary(x => x.Key, x => (PropertyPath)x.Value);
}
return pathMap;
}
} }
public static PropertyPath MapPath(PropertyPath path) public static PropertyPath MapPath(PropertyPath path)

4
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj

@ -19,11 +19,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00015" /> <PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00015" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MongoDB.Driver" Version="2.19.0" /> <PackageReference Include="MongoDB.Driver" Version="2.20.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/Tokenizer.cs

@ -68,7 +68,7 @@ public static class Tokenizer
if (query.Length > 2 && query[2] == ':') if (query.Length > 2 && query[2] == ':')
{ {
textLanguage = query.Substring(0, 2); textLanguage = query[..2];
textReader.Read(); textReader.Read();
textReader.Read(); textReader.Read();
} }

75
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs

@ -38,36 +38,12 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
public void ExtendAsync(ScriptExecutionContext context) public void ExtendAsync(ScriptExecutionContext context)
{ {
AddAssetText(context); AddGetAssetText(context);
AddAssetBlurHash(context); AddGetAssetBlurHash(context);
AddAsset(context); AddGetAssetObject(context);
} }
public void Describe(AddDescription describe, ScriptScope scope) private void AddGetAssetObject(ScriptExecutionContext context)
{
if (!scope.HasFlag(ScriptScope.Async))
{
return;
}
describe(JsonType.Function, "getAsset(ids, callback)",
Resources.ScriptingGetAsset,
deprecationReason: Resources.ScriptingGetAssetDeprecated);
describe(JsonType.Function, "getAssetV2(ids, callback)",
Resources.ScriptingGetAssetV2);
describe(JsonType.Function, "getAssets(ids, callback)",
Resources.ScriptingGetAssets);
describe(JsonType.Function, "getAssetText(asset, callback, encoding?)",
Resources.ScriptingGetAssetText);
describe(JsonType.Function, "getAssetBlurHash(asset, callback, x?, y?)",
Resources.ScriptingGetBlurHash);
}
private void AddAsset(ScriptExecutionContext context)
{ {
if (!context.TryGetValue<DomainId>("appId", out var appId)) if (!context.TryGetValue<DomainId>("appId", out var appId))
{ {
@ -94,7 +70,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
context.Engine.SetValue("getAssets", getAssets); context.Engine.SetValue("getAssets", getAssets);
} }
private void AddAssetText(ScriptExecutionContext context) private void AddGetAssetText(ScriptExecutionContext context)
{ {
var action = new GetAssetTextDelegate((references, callback, encoding) => var action = new GetAssetTextDelegate((references, callback, encoding) =>
{ {
@ -104,7 +80,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
context.Engine.SetValue("getAssetText", action); context.Engine.SetValue("getAssetText", action);
} }
private void AddAssetBlurHash(ScriptExecutionContext context) private void AddGetAssetBlurHash(ScriptExecutionContext context)
{ {
var getBlurHash = new GetBlurHashDelegate((input, callback, componentX, componentY) => var getBlurHash = new GetBlurHashDelegate((input, callback, componentX, componentY) =>
{ {
@ -116,7 +92,10 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
private void GetText(ScriptExecutionContext context, JsValue input, Action<JsValue> callback, JsValue? encoding) private void GetText(ScriptExecutionContext context, JsValue input, Action<JsValue> callback, JsValue? encoding)
{ {
Guard.NotNull(callback); if (callback == null)
{
throw new JavaScriptException("Callback is not defined.");
}
context.Schedule(async (scheduler, ct) => context.Schedule(async (scheduler, ct) =>
{ {
@ -166,7 +145,10 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
private void GetBlurHash(ScriptExecutionContext context, JsValue input, Action<JsValue> callback, JsValue? componentX, JsValue? componentY) private void GetBlurHash(ScriptExecutionContext context, JsValue input, Action<JsValue> callback, JsValue? componentX, JsValue? componentY)
{ {
Guard.NotNull(callback); if (callback == null)
{
throw new JavaScriptException("Callback is not defined.");
}
context.Schedule(async (scheduler, ct) => context.Schedule(async (scheduler, ct) =>
{ {
@ -229,7 +211,10 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
private void GetAssets(ScriptExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback) private void GetAssets(ScriptExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback)
{ {
Guard.NotNull(callback); if (callback == null)
{
throw new JavaScriptException("Callback is not defined.");
}
context.Schedule(async (scheduler, ct) => context.Schedule(async (scheduler, ct) =>
{ {
@ -311,4 +296,28 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor
return app; return app;
} }
public void Describe(AddDescription describe, ScriptScope scope)
{
if (!scope.HasFlag(ScriptScope.Async))
{
return;
}
describe(JsonType.Function, "getAsset(ids, callback)",
Resources.ScriptingGetAsset,
deprecationReason: Resources.ScriptingGetAssetDeprecated);
describe(JsonType.Function, "getAssetV2(ids, callback)",
Resources.ScriptingGetAssetV2);
describe(JsonType.Function, "getAssets(ids, callback)",
Resources.ScriptingGetAssets);
describe(JsonType.Function, "getAssetText(asset, callback, encoding?)",
Resources.ScriptingGetAssetText);
describe(JsonType.Function, "getAssetBlurHash(asset, callback, x?, y?)",
Resources.ScriptingGetBlurHash);
}
} }

38
backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterJintExtension.cs

@ -15,8 +15,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Counter;
public sealed class CounterJintExtension : IJintExtension, IScriptDescriptor public sealed class CounterJintExtension : IJintExtension, IScriptDescriptor
{ {
private delegate long CounterReset(string name, long value = 0); private delegate long CounterResetDelegate(string name, long value = 0);
private delegate void CounterResetV2(string name, Action<JsValue>? callback = null, long value = 0); private delegate void CounterResetV2Delegate(string name, Action<JsValue>? callback = null, long value = 0);
private readonly ICounterService counterService; private readonly ICounterService counterService;
public CounterJintExtension(ICounterService counterService) public CounterJintExtension(ICounterService counterService)
@ -38,7 +38,7 @@ public sealed class CounterJintExtension : IJintExtension, IScriptDescriptor
context.Engine.SetValue("incrementCounter", increment); context.Engine.SetValue("incrementCounter", increment);
var reset = new CounterReset((name, value) => var reset = new CounterResetDelegate((name, value) =>
{ {
return Reset(appId, name, value); return Reset(appId, name, value);
}); });
@ -60,7 +60,7 @@ public sealed class CounterJintExtension : IJintExtension, IScriptDescriptor
context.Engine.SetValue("incrementCounterV2", increment); context.Engine.SetValue("incrementCounterV2", increment);
var reset = new CounterResetV2((name, callback, value) => var reset = new CounterResetV2Delegate((name, callback, value) =>
{ {
ResetV2(context, appId, name, callback, value); ResetV2(context, appId, name, callback, value);
}); });
@ -68,21 +68,6 @@ public sealed class CounterJintExtension : IJintExtension, IScriptDescriptor
context.Engine.SetValue("resetCounterV2", reset); context.Engine.SetValue("resetCounterV2", reset);
} }
public void Describe(AddDescription describe, ScriptScope scope)
{
describe(JsonType.Function, "incrementCounter(name)",
Resources.ScriptingIncrementCounter);
describe(JsonType.Function, "incrementCounterV2(name, callback?)",
Resources.ScriptingIncrementCounterV2);
describe(JsonType.Function, "resetCounter(name, value?)",
Resources.ScriptingResetCounter);
describe(JsonType.Function, "resetCounter(name, callback?, value?)",
Resources.ScriptingResetCounterV2);
}
private long Increment(DomainId appId, string name) private long Increment(DomainId appId, string name)
{ {
return AsyncHelper.Sync(() => counterService.IncrementAsync(appId, name)); return AsyncHelper.Sync(() => counterService.IncrementAsync(appId, name));
@ -118,4 +103,19 @@ public sealed class CounterJintExtension : IJintExtension, IScriptDescriptor
} }
}); });
} }
public void Describe(AddDescription describe, ScriptScope scope)
{
describe(JsonType.Function, "incrementCounter(name)",
Resources.ScriptingIncrementCounter);
describe(JsonType.Function, "incrementCounterV2(name, callback?)",
Resources.ScriptingIncrementCounterV2);
describe(JsonType.Function, "resetCounter(name, value?)",
Resources.ScriptingResetCounter);
describe(JsonType.Function, "resetCounter(name, callback?, value?)",
Resources.ScriptingResetCounterV2);
}
} }

1
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs

@ -27,6 +27,7 @@ internal sealed class ComponentGraphType : ObjectGraphType<JsonObject>
Description = $"The structure of the {schemaInfo.DisplayName} component schema."; Description = $"The structure of the {schemaInfo.DisplayName} component schema.";
AddField(ContentFields.SchemaId); AddField(ContentFields.SchemaId);
AddField(ContentFields.SchemaName);
foreach (var fieldInfo in schemaInfo.Fields) foreach (var fieldInfo in schemaInfo.Fields)
{ {

3
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentInterfaceGraphType.cs

@ -17,7 +17,8 @@ internal sealed class ComponentInterfaceGraphType : InterfaceGraphType<JsonObjec
// The name is used for equal comparison. Therefore it is important to treat it as readonly. // The name is used for equal comparison. Therefore it is important to treat it as readonly.
Name = "Component"; Name = "Component";
AddField(ContentFields.SchemaId); AddField(ContentFields.SchemaIdNoResolver);
AddField(ContentFields.SchemaNameNoResolver);
Description = "The structure of all content types."; Description = "The structure of all content types.";
} }

53
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs

@ -7,6 +7,7 @@
using GraphQL.Resolvers; using GraphQL.Resolvers;
using GraphQL.Types; using GraphQL.Types;
using Namotion.Reflection;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.ExtractReferenceIds;
@ -43,6 +44,8 @@ internal static class ContentFields
Description = FieldDescriptions.EntityId Description = FieldDescriptions.EntityId
}; };
public static readonly FieldType IdNoResolver = Id.WithouthResolver();
public static readonly FieldType Version = new FieldType public static readonly FieldType Version = new FieldType
{ {
Name = "version", Name = "version",
@ -51,6 +54,8 @@ internal static class ContentFields
Description = FieldDescriptions.EntityVersion Description = FieldDescriptions.EntityVersion
}; };
public static readonly FieldType VersionNoResolver = Version.WithouthResolver();
public static readonly FieldType Created = new FieldType public static readonly FieldType Created = new FieldType
{ {
Name = "created", Name = "created",
@ -59,6 +64,8 @@ internal static class ContentFields
Description = FieldDescriptions.EntityCreated Description = FieldDescriptions.EntityCreated
}; };
public static readonly FieldType CreatedNoResolver = Created.WithouthResolver();
public static readonly FieldType CreatedBy = new FieldType public static readonly FieldType CreatedBy = new FieldType
{ {
Name = "createdBy", Name = "createdBy",
@ -67,6 +74,8 @@ internal static class ContentFields
Description = FieldDescriptions.EntityCreatedBy Description = FieldDescriptions.EntityCreatedBy
}; };
public static readonly FieldType CreatedByNoResolver = CreatedBy.WithouthResolver();
public static readonly FieldType CreatedByUser = new FieldType public static readonly FieldType CreatedByUser = new FieldType
{ {
Name = "createdByUser", Name = "createdByUser",
@ -75,6 +84,8 @@ internal static class ContentFields
Description = FieldDescriptions.EntityCreatedBy Description = FieldDescriptions.EntityCreatedBy
}; };
public static readonly FieldType CreatedByUserNoResolver = CreatedByUser.WithouthResolver();
public static readonly FieldType LastModified = new FieldType public static readonly FieldType LastModified = new FieldType
{ {
Name = "lastModified", Name = "lastModified",
@ -83,6 +94,8 @@ internal static class ContentFields
Description = FieldDescriptions.EntityLastModified Description = FieldDescriptions.EntityLastModified
}; };
public static readonly FieldType LastModifiedNoResolver = LastModified.WithouthResolver();
public static readonly FieldType LastModifiedBy = new FieldType public static readonly FieldType LastModifiedBy = new FieldType
{ {
Name = "lastModifiedBy", Name = "lastModifiedBy",
@ -91,6 +104,8 @@ internal static class ContentFields
Description = FieldDescriptions.EntityLastModifiedBy Description = FieldDescriptions.EntityLastModifiedBy
}; };
public static readonly FieldType LastModifiedByNoResolver = LastModifiedBy.WithouthResolver();
public static readonly FieldType LastModifiedByUser = new FieldType public static readonly FieldType LastModifiedByUser = new FieldType
{ {
Name = "lastModifiedByUser", Name = "lastModifiedByUser",
@ -99,6 +114,8 @@ internal static class ContentFields
Description = FieldDescriptions.EntityLastModifiedBy Description = FieldDescriptions.EntityLastModifiedBy
}; };
public static readonly FieldType LastModifiedByUserNoResolver = LastModifiedByUser.WithouthResolver();
public static readonly FieldType Status = new FieldType public static readonly FieldType Status = new FieldType
{ {
Name = "status", Name = "status",
@ -107,6 +124,8 @@ internal static class ContentFields
Description = FieldDescriptions.ContentStatus Description = FieldDescriptions.ContentStatus
}; };
public static readonly FieldType StatusNoResolver = Status.WithouthResolver();
public static readonly FieldType StatusColor = new FieldType public static readonly FieldType StatusColor = new FieldType
{ {
Name = "statusColor", Name = "statusColor",
@ -115,6 +134,8 @@ internal static class ContentFields
Description = FieldDescriptions.ContentStatusColor Description = FieldDescriptions.ContentStatusColor
}; };
public static readonly FieldType StatusColorNoResolver = StatusColor.WithouthResolver();
public static readonly FieldType NewStatus = new FieldType public static readonly FieldType NewStatus = new FieldType
{ {
Name = "newStatus", Name = "newStatus",
@ -123,6 +144,8 @@ internal static class ContentFields
Description = FieldDescriptions.ContentNewStatus Description = FieldDescriptions.ContentNewStatus
}; };
public static readonly FieldType NewStatusNoResolver = NewStatus.WithouthResolver();
public static readonly FieldType NewStatusColor = new FieldType public static readonly FieldType NewStatusColor = new FieldType
{ {
Name = "newStatusColor", Name = "newStatusColor",
@ -131,6 +154,8 @@ internal static class ContentFields
Description = FieldDescriptions.ContentStatusColor Description = FieldDescriptions.ContentStatusColor
}; };
public static readonly FieldType NewStatusColorNoResolver = NewStatusColor.WithouthResolver();
public static readonly FieldType SchemaId = new FieldType public static readonly FieldType SchemaId = new FieldType
{ {
Name = "schemaId", Name = "schemaId",
@ -139,6 +164,18 @@ internal static class ContentFields
Description = FieldDescriptions.ContentSchemaId Description = FieldDescriptions.ContentSchemaId
}; };
public static readonly FieldType SchemaIdNoResolver = SchemaId.WithouthResolver();
public static readonly FieldType SchemaName = new FieldType
{
Name = "schemaName",
ResolvedType = Scalars.String,
Resolver = Resolve(x => GetSchemaName(x)),
Description = FieldDescriptions.ContentSchemaName
};
public static readonly FieldType SchemaNameNoResolver = SchemaName.WithouthResolver();
public static readonly FieldType Url = new FieldType public static readonly FieldType Url = new FieldType
{ {
Name = "url", Name = "url",
@ -147,6 +184,8 @@ internal static class ContentFields
Description = FieldDescriptions.ContentUrl Description = FieldDescriptions.ContentUrl
}; };
public static readonly FieldType UrlNoResolver = Url.WithouthResolver();
public static readonly FieldType EditToken = new FieldType public static readonly FieldType EditToken = new FieldType
{ {
Name = "editToken", Name = "editToken",
@ -155,6 +194,8 @@ internal static class ContentFields
Description = FieldDescriptions.EditToken Description = FieldDescriptions.EditToken
}; };
public static readonly FieldType EditTokenNoResolver = EditToken.WithouthResolver();
public static readonly FieldType DataDynamic = new FieldType public static readonly FieldType DataDynamic = new FieldType
{ {
Name = "data__dynamic", Name = "data__dynamic",
@ -163,6 +204,8 @@ internal static class ContentFields
Description = FieldDescriptions.ContentData Description = FieldDescriptions.ContentData
}; };
public static readonly FieldType DataDynamicNoResolver = DataDynamic.WithouthResolver();
public static readonly FieldType StringFieldText = new FieldType public static readonly FieldType StringFieldText = new FieldType
{ {
Name = "text", Name = "text",
@ -188,4 +231,14 @@ internal static class ContentFields
{ {
return Resolvers.Sync(resolver); return Resolvers.Sync(resolver);
} }
private static string? GetSchemaName(JsonObject component)
{
if (component.TryGetValue("schemaName", out var name) && name.Type == JsonValueType.String)
{
return name.ToString();
}
return null;
}
} }

25
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentInterfaceGraphType.cs

@ -16,18 +16,19 @@ internal sealed class ContentInterfaceGraphType : InterfaceGraphType<IEnrichedCo
// The name is used for equal comparison. Therefore it is important to treat it as readonly. // The name is used for equal comparison. Therefore it is important to treat it as readonly.
Name = "Content"; Name = "Content";
AddField(ContentFields.Id); AddField(ContentFields.IdNoResolver);
AddField(ContentFields.Version); AddField(ContentFields.VersionNoResolver);
AddField(ContentFields.Created); AddField(ContentFields.CreatedNoResolver);
AddField(ContentFields.CreatedBy); AddField(ContentFields.CreatedByNoResolver);
AddField(ContentFields.LastModified); AddField(ContentFields.LastModifiedNoResolver);
AddField(ContentFields.LastModifiedBy); AddField(ContentFields.LastModifiedByNoResolver);
AddField(ContentFields.Status); AddField(ContentFields.EditTokenNoResolver);
AddField(ContentFields.StatusColor); AddField(ContentFields.StatusNoResolver);
AddField(ContentFields.NewStatus); AddField(ContentFields.StatusColorNoResolver);
AddField(ContentFields.NewStatusColor); AddField(ContentFields.NewStatusNoResolver);
AddField(ContentFields.DataDynamic); AddField(ContentFields.NewStatusColorNoResolver);
AddField(ContentFields.Url); AddField(ContentFields.DataDynamicNoResolver);
AddField(ContentFields.UrlNoResolver);
Description = "The structure of all content types."; Description = "The structure of all content types.";
} }

11
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs

@ -17,6 +17,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
public static class SharedExtensions public static class SharedExtensions
{ {
internal static FieldType WithouthResolver(this FieldType source)
{
return new FieldType
{
Name = source.Name,
ResolvedType = source.ResolvedType,
Resolver = null,
Description = source.Name
};
}
internal static string BuildODataQuery(this IResolveFieldContext context) internal static string BuildODataQuery(this IResolveFieldContext context)
{ {
var sb = DefaultPools.StringBuilder.Get(); var sb = DefaultPools.StringBuilder.Get();

44
backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs

@ -54,28 +54,13 @@ public sealed class ReferencesJintExtension : IJintExtension, IScriptDescriptor
context.Engine.SetValue("getReferences", getReferences); context.Engine.SetValue("getReferences", getReferences);
} }
public void Describe(AddDescription describe, ScriptScope scope) private void GetReferences(ScriptExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback)
{ {
if (!scope.HasFlag(ScriptScope.Async)) if (callback == null)
{ {
return; throw new JavaScriptException("Callback is not defined.");
} }
describe(JsonType.Function, "getReference(id, callback)",
Resources.ScriptingGetReference,
deprecationReason: Resources.ScriptingGetReferenceDeprecated);
describe(JsonType.Function, "getReferenceV2(id, callback)",
Resources.ScriptingGetReferenceV2);
describe(JsonType.Function, "getReferences(ids, callback)",
Resources.ScriptingGetReferences);
}
private void GetReferences(ScriptExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback)
{
Guard.NotNull(callback);
context.Schedule(async (scheduler, ct) => context.Schedule(async (scheduler, ct) =>
{ {
var ids = references.ToIds(); var ids = references.ToIds();
@ -110,7 +95,10 @@ public sealed class ReferencesJintExtension : IJintExtension, IScriptDescriptor
private void GetReference(ScriptExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback) private void GetReference(ScriptExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback)
{ {
Guard.NotNull(callback); if (callback == null)
{
throw new JavaScriptException("Callback is not defined.");
}
context.Schedule(async (scheduler, ct) => context.Schedule(async (scheduler, ct) =>
{ {
@ -157,4 +145,22 @@ public sealed class ReferencesJintExtension : IJintExtension, IScriptDescriptor
return app; return app;
} }
public void Describe(AddDescription describe, ScriptScope scope)
{
if (!scope.HasFlag(ScriptScope.Async))
{
return;
}
describe(JsonType.Function, "getReference(id, callback)",
Resources.ScriptingGetReference,
deprecationReason: Resources.ScriptingGetReferenceDeprecated);
describe(JsonType.Function, "getReferenceV2(id, callback)",
Resources.ScriptingGetReferenceV2);
describe(JsonType.Function, "getReferences(ids, callback)",
Resources.ScriptingGetReferences);
}
} }

5
backend/src/Squidex.Domain.Apps.Entities/Notifications/EmailUserNotifications.cs

@ -14,6 +14,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Email; using Squidex.Infrastructure.Email;
using Squidex.Shared.Identity; using Squidex.Shared.Identity;
using Squidex.Shared.Users; using Squidex.Shared.Users;
using System.Globalization;
namespace Squidex.Domain.Apps.Entities.Notifications; namespace Squidex.Domain.Apps.Entities.Notifications;
@ -187,12 +188,12 @@ public sealed class EmailUserNotifications : IUserNotifications
if (vars.ApiCallsLimit != null) if (vars.ApiCallsLimit != null)
{ {
text = text.Replace("$API_CALLS_LIMIT", vars.ApiCallsLimit.ToString(), StringComparison.Ordinal); text = text.Replace("$API_CALLS_LIMIT", vars.ApiCallsLimit.Value.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal);
} }
if (vars.ApiCalls != null) if (vars.ApiCalls != null)
{ {
text = text.Replace("$API_CALLS", vars.ApiCalls.ToString(), StringComparison.Ordinal); text = text.Replace("$API_CALLS", vars.ApiCalls.Value.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal);
} }
text = text.Replace("$UI_URL", vars.URL, StringComparison.Ordinal); text = text.Replace("$UI_URL", vars.URL, StringComparison.Ordinal);

8
backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj

@ -20,16 +20,16 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CsvHelper" Version="30.0.1" /> <PackageReference Include="CsvHelper" Version="30.0.1" />
<PackageReference Include="GraphQL" Version="7.3.0" /> <PackageReference Include="GraphQL" Version="7.5.0" />
<PackageReference Include="GraphQL.DataLoader" Version="7.3.0" /> <PackageReference Include="GraphQL.DataLoader" Version="7.5.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Notifo.SDK" Version="1.7.4" /> <PackageReference Include="Notifo.SDK" Version="1.7.4" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.CLI.Core" Version="10.3.0" /> <PackageReference Include="Squidex.CLI.Core" Version="11.0.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="7.0.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />

2
backend/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj

@ -14,7 +14,7 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

4
backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj

@ -19,12 +19,12 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" /> <ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="MongoDB.Driver" Version="2.19.0" /> <PackageReference Include="MongoDB.Driver" Version="2.20.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" /> <PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" />

2
backend/src/Squidex.Domain.Users/DefaultXmlRepository.cs

@ -50,7 +50,9 @@ public sealed class DefaultXmlRepository : IXmlRepository
{ {
var state = new State(element); var state = new State(element);
#pragma warning disable MA0134 // Observe result of async calls
store.WriteAsync(new SnapshotWriteJob<State>(DomainId.Create(friendlyName), state, 0)); store.WriteAsync(new SnapshotWriteJob<State>(DomainId.Create(friendlyName), state, 0));
#pragma warning restore MA0134 // Observe result of async calls
} }
private async Task<IReadOnlyCollection<XElement>> GetAllElementsAsync() private async Task<IReadOnlyCollection<XElement>> GetAllElementsAsync()

8
backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj

@ -17,14 +17,14 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" /> <ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="IdentityModel" Version="6.0.0" /> <PackageReference Include="IdentityModel" Version="6.1.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="7.0.4" /> <PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="7.0.8" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="OpenIddict.AspNetCore" Version="4.1.0" /> <PackageReference Include="OpenIddict.AspNetCore" Version="4.5.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="SharpPwned.NET" Version="2.0.1" /> <PackageReference Include="SharpPwned.NET" Version="2.0.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />

2
backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs

@ -25,6 +25,7 @@ internal sealed class GetEventStoreSubscription : IEventSubscription
string? prefix, string? prefix,
string? streamFilter) string? streamFilter)
{ {
#pragma warning disable MA0134 // Observe result of async calls
Task.Run(async () => Task.Run(async () =>
{ {
var ct = cts.Token; var ct = cts.Token;
@ -69,6 +70,7 @@ internal sealed class GetEventStoreSubscription : IEventSubscription
cancellationToken: ct); cancellationToken: ct);
} }
}, cts.Token); }, cts.Token);
#pragma warning restore MA0134 // Observe result of async calls
} }
public void Dispose() public void Dispose()

4
backend/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj

@ -14,8 +14,8 @@
<PackageReference Include="EventStore.Client.Grpc.PersistentSubscriptions" Version="23.0.0" /> <PackageReference Include="EventStore.Client.Grpc.PersistentSubscriptions" Version="23.0.0" />
<PackageReference Include="EventStore.Client.Grpc.ProjectionManagement" Version="23.0.0" /> <PackageReference Include="EventStore.Client.Grpc.ProjectionManagement" Version="23.0.0" />
<PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.0.0" /> <PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.0.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.52.0" /> <PackageReference Include="Grpc.Net.Client" Version="2.54.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

6
backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj

@ -14,12 +14,12 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MongoDB.Driver" Version="2.19.0" /> <PackageReference Include="MongoDB.Driver" Version="2.20.0" />
<PackageReference Include="MongoDB.Driver.GridFS" Version="2.19.0" /> <PackageReference Include="MongoDB.Driver.GridFS" Version="2.20.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="7.0.0" /> <PackageReference Include="System.Threading.Tasks.Dataflow" Version="7.0.0" />

26
backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -11,30 +11,30 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MailKit" Version="3.6.0" /> <PackageReference Include="MailKit" Version="4.1.0" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" /> <PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="7.0.4" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="7.0.8" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" /> <PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageReference Include="Microsoft.OData.Core" Version="7.15.0" /> <PackageReference Include="Microsoft.OData.Core" Version="7.17.0" />
<PackageReference Include="NodaTime" Version="3.1.6" /> <PackageReference Include="NodaTime" Version="3.1.9" />
<PackageReference Include="OpenTelemetry.Api" Version="1.4.0" /> <PackageReference Include="OpenTelemetry.Api" Version="1.5.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.Assets" Version="5.5.0" /> <PackageReference Include="Squidex.Assets" Version="5.9.0" />
<PackageReference Include="Squidex.Caching" Version="5.5.0" /> <PackageReference Include="Squidex.Caching" Version="5.9.0" />
<PackageReference Include="Squidex.Hosting.Abstractions" Version="5.5.0" /> <PackageReference Include="Squidex.Hosting.Abstractions" Version="5.9.0" />
<PackageReference Include="Squidex.Log" Version="5.5.0" /> <PackageReference Include="Squidex.Log" Version="5.9.0" />
<PackageReference Include="Squidex.Messaging" Version="5.5.0" /> <PackageReference Include="Squidex.Messaging" Version="5.9.0" />
<PackageReference Include="Squidex.Text" Version="5.5.0" /> <PackageReference Include="Squidex.Text" Version="5.9.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="7.0.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" /> <PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="System.Reactive" Version="5.0.0" /> <PackageReference Include="System.Reactive" Version="6.0.0" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" /> <PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
<PackageReference Include="System.Security.Claims" Version="4.3.0" /> <PackageReference Include="System.Security.Claims" Version="4.3.0" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="7.0.0" /> <PackageReference Include="System.Threading.Tasks.Dataflow" Version="7.0.0" />

2
backend/src/Squidex.Infrastructure/Tasks/AsyncHelper.cs

@ -54,6 +54,7 @@ public static class AsyncHelper
public static void Batch<TIn>(this Channel<object> source, Channel<object> target, int batchSize, int timeout, public static void Batch<TIn>(this Channel<object> source, Channel<object> target, int batchSize, int timeout,
CancellationToken ct = default) CancellationToken ct = default)
{ {
#pragma warning disable MA0134 // Observe result of async calls
Task.Run(async () => Task.Run(async () =>
{ {
var batch = new List<TIn>(batchSize); var batch = new List<TIn>(batchSize);
@ -94,5 +95,6 @@ public static class AsyncHelper
await TrySendAsync(); await TrySendAsync();
}, ct).ContinueWith(x => target.Writer.TryComplete(x.Exception)); }, ct).ContinueWith(x => target.Writer.TryComplete(x.Exception));
#pragma warning restore MA0134 // Observe result of async calls
} }
} }

2
backend/src/Squidex.Infrastructure/Tasks/TaskExtensions.cs

@ -23,12 +23,14 @@ public static class TaskExtensions
} }
else else
{ {
#pragma warning disable MA0134 // Observe result of async calls
task.ContinueWith( task.ContinueWith(
IgnoreTaskContinuation, IgnoreTaskContinuation,
CancellationToken.None, CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously, TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default); TaskScheduler.Default);
#pragma warning restore MA0134 // Observe result of async calls
} }
} }

2
backend/src/Squidex.Shared/Squidex.Shared.csproj

@ -9,7 +9,7 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

3
backend/src/Squidex.Web/Pipeline/CachingManager.cs

@ -16,6 +16,8 @@ using Microsoft.Net.Http.Headers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Caching;
#pragma warning disable CA1822 // Mark members as static
namespace Squidex.Web.Pipeline; namespace Squidex.Web.Pipeline;
public sealed class CachingManager : IRequestCache public sealed class CachingManager : IRequestCache
@ -197,7 +199,6 @@ public sealed class CachingManager : IRequestCache
public CachingManager(IHttpContextAccessor httpContextAccessor, IOptions<CachingOptions> cachingOptions) public CachingManager(IHttpContextAccessor httpContextAccessor, IOptions<CachingOptions> cachingOptions)
{ {
this.httpContextAccessor = httpContextAccessor; this.httpContextAccessor = httpContextAccessor;
this.cachingOptions = cachingOptions.Value; this.cachingOptions = cachingOptions.Value;
stringBuilderPool = new DefaultObjectPool<StringBuilder>(new StringBuilderPooledObjectPolicy stringBuilderPool = new DefaultObjectPool<StringBuilder>(new StringBuilderPooledObjectPolicy

8
backend/src/Squidex.Web/Squidex.Web.csproj

@ -13,10 +13,10 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="GraphQL" Version="7.3.0" /> <PackageReference Include="GraphQL" Version="7.5.0" />
<PackageReference Include="GraphQL.SystemTextJson" Version="7.3.0" /> <PackageReference Include="GraphQL.SystemTextJson" Version="7.5.0" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="7.3.0" /> <PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="7.4.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

21
backend/src/Squidex/Areas/Api/Controllers/Translations/Models/AskDto.cs

@ -0,0 +1,21 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Translations.Models;
[OpenApiRequest]
public sealed class AskDto
{
/// <summary>
/// The text to ask.
/// </summary>
[LocalizedRequired]
public string Prompt { get; set; }
}

10
backend/src/Squidex/Areas/Api/Controllers/Translations/Models/TranslationDto.cs

@ -15,7 +15,13 @@ public sealed class TranslationDto
/// <summary> /// <summary>
/// The result of the translation. /// The result of the translation.
/// </summary> /// </summary>
public TranslationResultCode Result { get; set; } public TranslationStatus Status { get; set; }
/// <summary>
/// The result of the translation.
/// </summary>
[Obsolete("Use Status property now.")]
public TranslationStatus Result => Status;
/// <summary> /// <summary>
/// The translated text. /// The translated text.
@ -24,6 +30,6 @@ public sealed class TranslationDto
public static TranslationDto FromDomain(TranslationResult translation) public static TranslationDto FromDomain(TranslationResult translation)
{ {
return SimpleMapper.Map(translation, new TranslationDto { Result = translation.Code }); return SimpleMapper.Map(translation, new TranslationDto());
} }
} }

27
backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs

@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.Translations.Models; using Squidex.Areas.Api.Controllers.Translations.Models;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Text.ChatBots;
using Squidex.Text.Translations; using Squidex.Text.Translations;
using Squidex.Web; using Squidex.Web;
@ -17,15 +18,18 @@ namespace Squidex.Areas.Api.Controllers.Translations;
/// <summary> /// <summary>
/// Manage translations. /// Manage translations.
/// </summary> /// </summary>
[ApiModelValidation(true)]
[ApiExplorerSettings(GroupName = nameof(Translations))] [ApiExplorerSettings(GroupName = nameof(Translations))]
public sealed class TranslationsController : ApiController public sealed class TranslationsController : ApiController
{ {
private readonly ITranslator translator; private readonly ITranslator translator;
private readonly IChatBot chatBot;
public TranslationsController(ICommandBus commandBus, ITranslator translator) public TranslationsController(ICommandBus commandBus, ITranslator translator, IChatBot chatBot)
: base(commandBus) : base(commandBus)
{ {
this.translator = translator; this.translator = translator;
this.chatBot = chatBot;
} }
/// <summary> /// <summary>
@ -38,7 +42,7 @@ public sealed class TranslationsController : ApiController
[Route("apps/{app}/translations/")] [Route("apps/{app}/translations/")]
[ProducesResponseType(typeof(TranslationDto), StatusCodes.Status200OK)] [ProducesResponseType(typeof(TranslationDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppTranslate)] [ApiPermissionOrAnonymous(PermissionIds.AppTranslate)]
[ApiCosts(0)] [ApiCosts(10)]
public async Task<IActionResult> PostTranslation(string app, [FromBody] TranslateDto request) public async Task<IActionResult> PostTranslation(string app, [FromBody] TranslateDto request)
{ {
var result = await translator.TranslateAsync(request.Text, request.TargetLanguage, request.SourceLanguage, HttpContext.RequestAborted); var result = await translator.TranslateAsync(request.Text, request.TargetLanguage, request.SourceLanguage, HttpContext.RequestAborted);
@ -46,4 +50,23 @@ public sealed class TranslationsController : ApiController
return Ok(response); return Ok(response);
} }
/// <summary>
/// Asks the chatbot a question a text.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="request">The question request.</param>
/// <response code="200">Question asked.</response>.
[HttpPost]
[Route("apps/{app}/ask/")]
[ProducesResponseType(typeof(string[]), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppTranslate)]
[ApiCosts(10)]
public async Task<IActionResult> PostQuestion(string app, [FromBody] AskDto request)
{
var result = await chatBot.AskQuestionAsync(request.Prompt, HttpContext.RequestAborted);
var response = result;
return Ok(response);
}
} }

90
backend/src/Squidex/Config/Domain/AssetServices.cs

@ -49,15 +49,6 @@ public static class AssetServices
services.AddTransientAs<AssetCache>() services.AddTransientAs<AssetCache>()
.As<IAssetCache>(); .As<IAssetCache>();
services.AddSingletonAs<AssetTusRunner>()
.AsSelf();
services.AddSingletonAs<AssetTusStore>()
.As<ITusStore>().As<ITusExpirationStore>();
services.AddSingletonAs(c => InMemoryFileLockProvider.Instance)
.As<ITusFileLockProvider>();
services.AddSingletonAs<RebuildFiles>() services.AddSingletonAs<RebuildFiles>()
.AsSelf(); .AsSelf();
@ -108,6 +99,8 @@ public static class AssetServices
services.AddSingletonAs<SvgAssetMetadataSource>() services.AddSingletonAs<SvgAssetMetadataSource>()
.As<IAssetMetadataSource>(); .As<IAssetMetadataSource>();
services.AddAssetTus();
} }
public static void AddSquidexAssetInfrastructure(this IServiceCollection services, IConfiguration config) public static void AddSquidexAssetInfrastructure(this IServiceCollection services, IConfiguration config)
@ -116,43 +109,27 @@ public static class AssetServices
{ {
["Default"] = () => ["Default"] = () =>
{ {
services.AddSingletonAs<NoopAssetStore>() services.AddFolderAssetStore(config);
.AsOptional<IAssetStore>();
}, },
["Folder"] = () => ["Folder"] = () =>
{ {
var path = config.GetRequiredValue("assetStore:folder:path"); services.AddFolderAssetStore(config);
services.AddSingletonAs(c => new FolderAssetStore(path, c.GetRequiredService<ILogger<FolderAssetStore>>()))
.As<IAssetStore>();
}, },
["GoogleCloud"] = () => ["GoogleCloud"] = () =>
{ {
var options = new GoogleCloudAssetOptions services.AddGoogleCloudAssetStore(config);
{
BucketName = config.GetRequiredValue("assetStore:googleCloud:bucket")
};
services.AddSingletonAs(c => new GoogleCloudAssetStore(options))
.As<IAssetStore>();
}, },
["AzureBlob"] = () => ["AzureBlob"] = () =>
{ {
var options = new AzureBlobAssetOptions services.AddAzureBlobAssetStore(config);
{
ConnectionString = config.GetRequiredValue("assetStore:azureBlob:connectionString"),
ContainerName = config.GetRequiredValue("assetStore:azureBlob:containerName")
};
services.AddSingletonAs(c => new AzureBlobAssetStore(options))
.As<IAssetStore>();
}, },
["AmazonS3"] = () => ["AmazonS3"] = () =>
{ {
var amazonS3Options = config.GetSection("assetStore:amazonS3").Get<AmazonS3AssetOptions>() ?? new (); services.AddAmazonS3AssetStore(config);
},
services.AddSingletonAs(c => new AmazonS3AssetStore(amazonS3Options)) ["FTP"] = () =>
.As<IAssetStore>(); {
services.AddFTPAssetStore(config);
}, },
["MongoDb"] = () => ["MongoDb"] = () =>
{ {
@ -160,48 +137,17 @@ public static class AssetServices
var mongoDatabaseName = config.GetRequiredValue("assetStore:mongoDb:database"); var mongoDatabaseName = config.GetRequiredValue("assetStore:mongoDb:database");
var mongoGridFsBucketName = config.GetRequiredValue("assetStore:mongoDb:bucket"); var mongoGridFsBucketName = config.GetRequiredValue("assetStore:mongoDb:bucket");
services.AddSingletonAs(c => services.AddMongoAssetStore(c =>
{
var mongoClient = StoreServices.GetMongoClient(mongoConfiguration);
var mongoDatabase = mongoClient.GetDatabase(mongoDatabaseName);
var gridFsbucket = new GridFSBucket<string>(mongoDatabase, new GridFSBucketOptions
{
BucketName = mongoGridFsBucketName
});
return new MongoGridFsAssetStore(gridFsbucket);
})
.As<IAssetStore>();
},
["Ftp"] = () =>
{
var serverHost = config.GetRequiredValue("assetStore:ftp:serverHost");
var serverPort = config.GetOptionalValue<int>("assetStore:ftp:serverPort", 21);
var username = config.GetRequiredValue("assetStore:ftp:username");
var password = config.GetRequiredValue("assetStore:ftp:password");
var options = new FTPAssetOptions
{ {
Path = config.GetOptionalValue("assetStore:ftp:path", "/")! var mongoClient = StoreServices.GetMongoClient(mongoConfiguration);
}; var mongoDatabase = mongoClient.GetDatabase(mongoDatabaseName);
services.AddSingletonAs(c => return new GridFSBucket<string>(mongoDatabase, new GridFSBucketOptions
{ {
var factory = new Func<AsyncFtpClient>(() => new AsyncFtpClient(serverHost, username, password, serverPort)); BucketName = mongoGridFsBucketName
});
return new FTPAssetStore(factory, options, c.GetRequiredService<ILogger<FTPAssetStore>>()); });
})
.As<IAssetStore>();
} }
}); });
services.AddSingletonAs<IInitializable>(c =>
{
var service = c.GetRequiredService<IAssetStore>();
return new DelegateInitializer(service.GetType().Name, service.InitializeAsync);
});
} }
} }

20
backend/src/Squidex/Config/Domain/InfrastructureServices.cs

@ -86,6 +86,9 @@ public static class InfrastructureServices
services.AddSingletonAs<StringWordsJintExtension>() services.AddSingletonAs<StringWordsJintExtension>()
.As<IJintExtension>().As<IScriptDescriptor>(); .As<IJintExtension>().As<IScriptDescriptor>();
services.AddSingletonAs<StringAsyncJintExtension>()
.As<IJintExtension>().As<IScriptDescriptor>();
services.AddSingletonAs<HttpJintExtension>() services.AddSingletonAs<HttpJintExtension>()
.As<IJintExtension>().As<IScriptDescriptor>(); .As<IJintExtension>().As<IScriptDescriptor>();
@ -127,26 +130,15 @@ public static class InfrastructureServices
public static void AddSquidexTranslation(this IServiceCollection services, IConfiguration config) public static void AddSquidexTranslation(this IServiceCollection services, IConfiguration config)
{ {
services.Configure<GoogleCloudTranslationOptions>(config,
"translations:googleCloud");
services.Configure<DeepLOptions>(config,
"translations:deepL");
services.Configure<LanguagesOptions>(config, services.Configure<LanguagesOptions>(config,
"languages"); "languages");
services.AddSingletonAs<LanguagesInitializer>() services.AddSingletonAs<LanguagesInitializer>()
.AsSelf(); .AsSelf();
services.AddSingletonAs(c => new DeepLTranslationService(c.GetRequiredService<IOptions<DeepLOptions>>().Value)) services.AddDeepLTranslations(config);
.As<ITranslationService>(); services.AddGoogleCloudTranslations(config);
services.AddOpenAIChatBot(config);
services.AddSingletonAs(c => new GoogleCloudTranslationService(c.GetRequiredService<IOptions<GoogleCloudTranslationOptions>>().Value))
.As<ITranslationService>();
services.AddSingletonAs<Translator>()
.As<ITranslator>();
} }
public static void AddSquidexLocalization(this IServiceCollection services) public static void AddSquidexLocalization(this IServiceCollection services)

11
backend/src/Squidex/Config/Domain/StoreServices.cs

@ -67,6 +67,7 @@ public static class StoreServices
var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database")!; var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database")!;
var mongoContentDatabaseName = config.GetOptionalValue("store:mongoDb:contentDatabase", mongoDatabaseName)!; var mongoContentDatabaseName = config.GetOptionalValue("store:mongoDb:contentDatabase", mongoDatabaseName)!;
services.AddMongoAssetKeyValueStore();
services.AddSingleton(typeof(ISnapshotStore<>), typeof(MongoSnapshotStore<>)); services.AddSingleton(typeof(ISnapshotStore<>), typeof(MongoSnapshotStore<>));
services.AddSingletonAs(c => GetMongoClient(mongoConfiguration)) services.AddSingletonAs(c => GetMongoClient(mongoConfiguration))
@ -111,9 +112,6 @@ public static class StoreServices
services.AddHealthChecks() services.AddHealthChecks()
.AddCheck<MongoHealthCheck>("MongoDB", tags: new[] { "node" }); .AddCheck<MongoHealthCheck>("MongoDB", tags: new[] { "node" });
services.AddSingletonAs<MongoAssetKeyValueStore<TusMetadata>>()
.As<IAssetKeyValueStore<TusMetadata>>();
services.AddSingletonAs<MongoRequestLogRepository>() services.AddSingletonAs<MongoRequestLogRepository>()
.As<IRequestLogRepository>(); .As<IRequestLogRepository>();
@ -208,13 +206,6 @@ public static class StoreServices
services.AddSingleton(typeof(IPersistenceFactory<>), services.AddSingleton(typeof(IPersistenceFactory<>),
typeof(Store<>)); typeof(Store<>));
services.AddSingletonAs<IInitializable>(c =>
{
var service = c.GetRequiredService<IAssetKeyValueStore<TusMetadata>>();
return new DelegateInitializer(service.GetType().Name, service.InitializeAsync);
});
} }
public static IMongoClient GetMongoClient(string configuration) public static IMongoClient GetMongoClient(string configuration)

48
backend/src/Squidex/Squidex.csproj

@ -34,24 +34,24 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="7.0.0" /> <PackageReference Include="AspNet.Security.OAuth.GitHub" Version="7.0.2" />
<PackageReference Include="GraphQL" Version="7.3.0" /> <PackageReference Include="GraphQL" Version="7.5.0" />
<PackageReference Include="GraphQL.MicrosoftDI" Version="7.3.0" /> <PackageReference Include="GraphQL.MicrosoftDI" Version="7.5.0" />
<PackageReference Include="GraphQL.SystemTextJson" Version="7.3.0" /> <PackageReference Include="GraphQL.SystemTextJson" Version="7.5.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="7.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="7.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="7.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="7.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="7.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="7.0.8" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="7.0.4" /> <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="7.0.8" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" /> <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis.RulesetToEditorconfigConverter" Version="3.3.3" /> <PackageReference Include="Microsoft.CodeAnalysis.RulesetToEditorconfigConverter" Version="3.3.3" />
<PackageReference Include="Microsoft.Data.Edm" Version="5.8.5" /> <PackageReference Include="Microsoft.Data.Edm" Version="5.8.5" />
<PackageReference Include="Microsoft.OData.Core" Version="7.15.0" /> <PackageReference Include="Microsoft.OData.Core" Version="7.17.0" />
<PackageReference Include="MongoDB.Driver" Version="2.19.0" /> <PackageReference Include="MongoDB.Driver" Version="2.20.0" />
<PackageReference Include="MongoDB.Driver.Core.Extensions.OpenTelemetry" Version="1.0.0" /> <PackageReference Include="MongoDB.Driver.Core.Extensions.OpenTelemetry" Version="1.0.0" />
<PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="3.0.0" /> <PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="3.0.0" />
<PackageReference Include="NJsonSchema" Version="10.9.0" /> <PackageReference Include="NJsonSchema" Version="10.9.0" />
@ -61,19 +61,19 @@
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc7" /> <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc7" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc7" /> <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc7" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="ReportGenerator" Version="5.1.19" PrivateAssets="all" /> <PackageReference Include="ReportGenerator" Version="5.1.22" PrivateAssets="all" />
<PackageReference Include="Squidex.Assets.Azure" Version="5.5.0" /> <PackageReference Include="Squidex.Assets.Azure" Version="5.9.0" />
<PackageReference Include="Squidex.Assets.GoogleCloud" Version="5.5.0" /> <PackageReference Include="Squidex.Assets.GoogleCloud" Version="5.9.0" />
<PackageReference Include="Squidex.Assets.FTP" Version="5.5.0" /> <PackageReference Include="Squidex.Assets.FTP" Version="5.9.0" />
<PackageReference Include="Squidex.Assets.ImageMagick" Version="5.5.0" /> <PackageReference Include="Squidex.Assets.ImageMagick" Version="5.9.0" />
<PackageReference Include="Squidex.Assets.ImageSharp" Version="5.5.0" /> <PackageReference Include="Squidex.Assets.ImageSharp" Version="5.9.0" />
<PackageReference Include="Squidex.Assets.Mongo" Version="5.5.0" /> <PackageReference Include="Squidex.Assets.Mongo" Version="5.9.0" />
<PackageReference Include="Squidex.Assets.S3" Version="5.5.0" /> <PackageReference Include="Squidex.Assets.S3" Version="5.9.0" />
<PackageReference Include="Squidex.Assets.TusAdapter" Version="5.5.0" /> <PackageReference Include="Squidex.Assets.TusAdapter" Version="5.9.0" />
<PackageReference Include="Squidex.ClientLibrary" Version="16.0.0" /> <PackageReference Include="Squidex.ClientLibrary" Version="16.0.0" />
<PackageReference Include="Squidex.Hosting" Version="5.5.0" /> <PackageReference Include="Squidex.Hosting" Version="5.9.0" />
<PackageReference Include="Squidex.Messaging.All" Version="5.5.0" /> <PackageReference Include="Squidex.Messaging.All" Version="5.9.0" />
<PackageReference Include="Squidex.Messaging.Subscriptions" Version="5.5.0" /> <PackageReference Include="Squidex.Messaging.Subscriptions" Version="5.9.0" />
<PackageReference Include="Squidex.OpenIddict.MongoDb" Version="4.0.1-dev" /> <PackageReference Include="Squidex.OpenIddict.MongoDb" Version="4.0.1-dev" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
</ItemGroup> </ItemGroup>

13
backend/src/Squidex/appsettings.json

@ -550,11 +550,11 @@
"suppressXFrameOptionsHeader": false, "suppressXFrameOptionsHeader": false,
// Initial admin user. // Initial admin user.
"adminEmail": "", "adminEmail": "hello@squidex.io",
"adminPassword": "", "adminPassword": "1q2w3e$R",
// Recreate the admin if it does not exist or the password does not match. // Recreate the admin if it does not exist or the password does not match.
"adminRecreate": false, "adminRecreate": true,
// Client with all admin permissions. // Client with all admin permissions.
"adminClientId": "", "adminClientId": "",
@ -624,6 +624,13 @@
} }
}, },
"chatbot": {
"openai": {
// The OpenAI API Key.
"apiKey": ""
}
},
"rebuild": { "rebuild": {
// Set to true to rebuild apps. // Set to true to rebuild apps.
"apps": false, "apps": false,

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs

@ -17,8 +17,6 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent;
public class ContentConversionTests public class ContentConversionTests
{ {
private readonly Schema schema; private readonly Schema schema;
private readonly LanguagesConfig languages = LanguagesConfig.English.Set(Language.DE);
private readonly Language language = Language.DE;
private readonly ResolvedComponents components; private readonly ResolvedComponents components;
public ContentConversionTests() public ContentConversionTests()

150
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs

@ -7,6 +7,7 @@
using System.Net; using System.Net;
using System.Text; using System.Text;
using Jint.Runtime;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
@ -15,12 +16,16 @@ using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
using Squidex.Text.ChatBots;
using Squidex.Text.Translations;
namespace Squidex.Domain.Apps.Core.Operations.Scripting; namespace Squidex.Domain.Apps.Core.Operations.Scripting;
public class JintScriptEngineHelperTests : IClassFixture<TranslationsFixture> public class JintScriptEngineHelperTests : IClassFixture<TranslationsFixture>
{ {
private readonly IHttpClientFactory httpClientFactory = A.Fake<IHttpClientFactory>(); private readonly IHttpClientFactory httpClientFactory = A.Fake<IHttpClientFactory>();
private readonly ITranslator translator = A.Fake<ITranslator>();
private readonly IChatBot chatBot = A.Fake<IChatBot>();
private readonly JintScriptEngine sut; private readonly JintScriptEngine sut;
public JintScriptEngineHelperTests() public JintScriptEngineHelperTests()
@ -30,7 +35,8 @@ public class JintScriptEngineHelperTests : IClassFixture<TranslationsFixture>
new DateTimeJintExtension(), new DateTimeJintExtension(),
new HttpJintExtension(httpClientFactory), new HttpJintExtension(httpClientFactory),
new StringJintExtension(), new StringJintExtension(),
new StringWordsJintExtension() new StringWordsJintExtension(),
new StringAsyncJintExtension(translator, chatBot)
}; };
sut = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())), sut = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())),
@ -611,6 +617,148 @@ public class JintScriptEngineHelperTests : IClassFixture<TranslationsFixture>
Assert.Equal(expectedResult, actual); Assert.Equal(expectedResult, actual);
} }
[Fact]
public async Task Should_generate_content()
{
A.CallTo(() => chatBot.AskQuestionAsync("prompt", A<CancellationToken>._))
.Returns(new List<string> { "Generated" });
var vars = new ScriptVars
{
};
const string script = @"
generate('prompt', function(actual) {
complete(actual);
});
";
var actual = await sut.ExecuteAsync(vars, script);
Assert.Equal("Generated", actual.ToString());
}
[Theory]
[InlineData("null")]
[InlineData("''")]
[InlineData("' '")]
public async Task Should_return_null_string_on_generate_if_prompt_is_invalid(string input)
{
var vars = new ScriptVars
{
};
var script = $@"
generate({input}, function(actual) {{
complete(actual);
}});
";
var actual = await sut.ExecuteAsync(vars, script);
Assert.Equal(JsonValue.Null, actual);
A.CallTo(() => chatBot.AskQuestionAsync(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_throw_exception_on_generate_if_callback_is_null()
{
var vars = new ScriptVars
{
};
const string script = @"
generate('prompt', null);
";
await Assert.ThrowsAsync<ValidationException>(() => sut.ExecuteAsync(vars, script));
}
[Fact]
public async Task Should_translate_content()
{
A.CallTo(() => translator.TranslateAsync("text", "en", "it", A<CancellationToken>._))
.Returns(TranslationResult.Success("Translated", "it"));
var vars = new ScriptVars
{
};
const string script = @"
translate('text', 'en', function(actual) {
complete(actual);
}, 'it');
";
var actual = await sut.ExecuteAsync(vars, script);
Assert.Equal("Translated", actual.ToString());
}
[Theory]
[InlineData("null")]
[InlineData("''")]
[InlineData("' '")]
public async Task Should_return_null_string_on_translate_if_input_is_invalid(string input)
{
var vars = new ScriptVars
{
};
var script = $@"
translate({input}, 'en', function(actual) {{
complete(actual);
}});
";
var actual = await sut.ExecuteAsync(vars, script);
Assert.Equal(JsonValue.Null, actual);
A.CallTo(() => translator.TranslateAsync(A<string>._, A<string>._, A<string>._, A<CancellationToken>._))
.MustNotHaveHappened();
}
[Theory]
[InlineData("null")]
[InlineData("''")]
[InlineData("' '")]
public async Task Should_return_null_string_on_input_if_target_language_is_invalid(string input)
{
var vars = new ScriptVars
{
};
var script = $@"
translate('text', {input}, function(actual) {{
complete(actual);
}});
";
var actual = await sut.ExecuteAsync(vars, script);
Assert.Equal(JsonValue.Null, actual);
A.CallTo(() => translator.TranslateAsync(A<string>._, A<string>._, A<string>._, A<CancellationToken>._))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_throw_exception_on_translate_if_callback_is_null()
{
var vars = new ScriptVars
{
};
const string script = @"
translate('text', 'en', null);
";
await Assert.ThrowsAsync<ValidationException>(() => sut.ExecuteAsync(vars, script));
}
private MockupHttpHandler SetupRequest(HttpStatusCode statusCode = HttpStatusCode.OK) private MockupHttpHandler SetupRequest(HttpStatusCode statusCode = HttpStatusCode.OK)
{ {
var httpResponse = new HttpResponseMessage(statusCode) var httpResponse = new HttpResponseMessage(statusCode)

8
backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj

@ -14,15 +14,15 @@
<ProjectReference Include="..\..\src\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\..\src\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FakeItEasy" Version="7.3.1" /> <PackageReference Include="FakeItEasy" Version="7.4.0" />
<PackageReference Include="FluentAssertions" Version="6.10.0" /> <PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="3.0.0" /> <PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="3.0.0" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.0.0" /> <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.0.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />

25
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs

@ -7,6 +7,7 @@
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
@ -87,6 +88,7 @@ public static class TestContent
myComponent { myComponent {
iv { iv {
schemaId schemaId
schemaName
schemaRef1Field schemaRef1Field
} }
} }
@ -98,10 +100,12 @@ public static class TestContent
__typename __typename
... on MyRefSchema1Component { ... on MyRefSchema1Component {
schemaId schemaId
schemaName
schemaRef1Field schemaRef1Field
} }
... on MyRefSchema2Component { ... on MyRefSchema2Component {
schemaId schemaId
schemaName
schemaRef2Field schemaRef2Field
} }
} }
@ -172,6 +176,7 @@ public static class TestContent
myComponent__Dynamic myComponent__Dynamic
myComponent { myComponent {
schemaId schemaId
schemaName
schemaRef1Field schemaRef1Field
} }
myComponents__Dynamic myComponents__Dynamic
@ -179,10 +184,12 @@ public static class TestContent
__typename __typename
... on MyRefSchema1Component { ... on MyRefSchema1Component {
schemaId schemaId
schemaName
schemaRef1Field schemaRef1Field
} }
... on MyRefSchema2Component { ... on MyRefSchema2Component {
schemaId schemaId
schemaName
schemaRef2Field schemaRef2Field
} }
} }
@ -244,6 +251,7 @@ public static class TestContent
.AddInvariant( .AddInvariant(
JsonValue.Object() JsonValue.Object()
.Add(Component.Discriminator, TestSchemas.Ref1.Id) .Add(Component.Discriminator, TestSchemas.Ref1.Id)
.Add(Component.Descriptor, TestSchemas.Ref1.SchemaDef.Name)
.Add("schemaRef1Field", "Component1"))) .Add("schemaRef1Field", "Component1")))
.AddField("my-components", .AddField("my-components",
new ContentFieldData() new ContentFieldData()
@ -251,9 +259,11 @@ public static class TestContent
JsonValue.Array( JsonValue.Array(
JsonValue.Object() JsonValue.Object()
.Add(Component.Discriminator, TestSchemas.Ref1.Id) .Add(Component.Discriminator, TestSchemas.Ref1.Id)
.Add(Component.Descriptor, TestSchemas.Ref1.SchemaDef.Name)
.Add("schemaRef1Field", "Component1"), .Add("schemaRef1Field", "Component1"),
JsonValue.Object() JsonValue.Object()
.Add(Component.Discriminator, TestSchemas.Ref2.Id) .Add(Component.Discriminator, TestSchemas.Ref2.Id)
.Add(Component.Descriptor, TestSchemas.Ref2.SchemaDef.Name)
.Add("schemaRef2Field", "Component2")))) .Add("schemaRef2Field", "Component2"))))
.AddField("my-json", .AddField("my-json",
new ContentFieldData() new ContentFieldData()
@ -484,6 +494,7 @@ public static class TestContent
iv = new Dictionary<string, object> iv = new Dictionary<string, object>
{ {
["schemaId"] = TestSchemas.Ref1.Id.ToString(), ["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaName"] = TestSchemas.Ref1.SchemaDef.Name,
["schemaRef1Field"] = "Component1" ["schemaRef1Field"] = "Component1"
} }
}, },
@ -494,11 +505,13 @@ public static class TestContent
new Dictionary<string, object> new Dictionary<string, object>
{ {
["schemaId"] = TestSchemas.Ref1.Id.ToString(), ["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaName"] = TestSchemas.Ref1.SchemaDef.Name,
["schemaRef1Field"] = "Component1" ["schemaRef1Field"] = "Component1"
}, },
new Dictionary<string, object> new Dictionary<string, object>
{ {
["schemaId"] = TestSchemas.Ref2.Id.ToString(), ["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaName"] = TestSchemas.Ref2.SchemaDef.Name,
["schemaRef2Field"] = "Component2" ["schemaRef2Field"] = "Component2"
} }
} }
@ -648,6 +661,7 @@ public static class TestContent
iv = new Dictionary<string, object> iv = new Dictionary<string, object>
{ {
["schemaId"] = TestSchemas.Ref1.Id.ToString(), ["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaName"] = TestSchemas.Ref1.SchemaDef.Name,
["schemaRef1Field"] = "Component1" ["schemaRef1Field"] = "Component1"
} }
}, },
@ -656,6 +670,7 @@ public static class TestContent
iv = new Dictionary<string, object> iv = new Dictionary<string, object>
{ {
["schemaId"] = TestSchemas.Ref1.Id.ToString(), ["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaName"] = TestSchemas.Ref1.SchemaDef.Name,
["schemaRef1Field"] = "Component1" ["schemaRef1Field"] = "Component1"
} }
}, },
@ -666,11 +681,13 @@ public static class TestContent
new Dictionary<string, object> new Dictionary<string, object>
{ {
["schemaId"] = TestSchemas.Ref1.Id.ToString(), ["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaName"] = TestSchemas.Ref1.SchemaDef.Name,
["schemaRef1Field"] = "Component1" ["schemaRef1Field"] = "Component1"
}, },
new Dictionary<string, object> new Dictionary<string, object>
{ {
["schemaId"] = TestSchemas.Ref2.Id.ToString(), ["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaName"] = TestSchemas.Ref2.SchemaDef.Name,
["schemaRef2Field"] = "Component2" ["schemaRef2Field"] = "Component2"
} }
} }
@ -683,12 +700,14 @@ public static class TestContent
{ {
["__typename"] = "MyRefSchema1Component", ["__typename"] = "MyRefSchema1Component",
["schemaId"] = TestSchemas.Ref1.Id.ToString(), ["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaName"] = TestSchemas.Ref1.SchemaDef.Name,
["schemaRef1Field"] = "Component1" ["schemaRef1Field"] = "Component1"
}, },
new Dictionary<string, object> new Dictionary<string, object>
{ {
["__typename"] = "MyRefSchema2Component", ["__typename"] = "MyRefSchema2Component",
["schemaId"] = TestSchemas.Ref2.Id.ToString(), ["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaName"] = TestSchemas.Ref2.SchemaDef.Name,
["schemaRef2Field"] = "Component2" ["schemaRef2Field"] = "Component2"
} }
} }
@ -771,11 +790,13 @@ public static class TestContent
["myComponent__Dynamic"] = new Dictionary<string, object> ["myComponent__Dynamic"] = new Dictionary<string, object>
{ {
["schemaId"] = TestSchemas.Ref1.Id.ToString(), ["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaName"] = TestSchemas.Ref1.SchemaDef.Name,
["schemaRef1Field"] = "Component1" ["schemaRef1Field"] = "Component1"
}, },
["myComponent"] = new Dictionary<string, object> ["myComponent"] = new Dictionary<string, object>
{ {
["schemaId"] = TestSchemas.Ref1.Id.ToString(), ["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaName"] = TestSchemas.Ref1.SchemaDef.Name,
["schemaRef1Field"] = "Component1" ["schemaRef1Field"] = "Component1"
}, },
["myComponents__Dynamic"] = new[] ["myComponents__Dynamic"] = new[]
@ -783,11 +804,13 @@ public static class TestContent
new Dictionary<string, object> new Dictionary<string, object>
{ {
["schemaId"] = TestSchemas.Ref1.Id.ToString(), ["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaName"] = TestSchemas.Ref1.SchemaDef.Name,
["schemaRef1Field"] = "Component1" ["schemaRef1Field"] = "Component1"
}, },
new Dictionary<string, object> new Dictionary<string, object>
{ {
["schemaId"] = TestSchemas.Ref2.Id.ToString(), ["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaName"] = TestSchemas.Ref2.SchemaDef.Name,
["schemaRef2Field"] = "Component2" ["schemaRef2Field"] = "Component2"
} }
}, },
@ -797,12 +820,14 @@ public static class TestContent
{ {
["__typename"] = "MyRefSchema1Component", ["__typename"] = "MyRefSchema1Component",
["schemaId"] = TestSchemas.Ref1.Id.ToString(), ["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaName"] = TestSchemas.Ref1.SchemaDef.Name,
["schemaRef1Field"] = "Component1" ["schemaRef1Field"] = "Component1"
}, },
new Dictionary<string, object> new Dictionary<string, object>
{ {
["__typename"] = "MyRefSchema2Component", ["__typename"] = "MyRefSchema2Component",
["schemaId"] = TestSchemas.Ref2.Id.ToString(), ["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaName"] = TestSchemas.Ref2.SchemaDef.Name,
["schemaRef2Field"] = "Component2" ["schemaRef2Field"] = "Component2"
} }
}, },

14
backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj

@ -21,21 +21,21 @@
<ProjectReference Include="..\Squidex.Infrastructure.Tests\Squidex.Infrastructure.Tests.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure.Tests\Squidex.Infrastructure.Tests.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FakeItEasy" Version="7.3.1" /> <PackageReference Include="FakeItEasy" Version="7.4.0" />
<PackageReference Include="FluentAssertions" Version="6.10.0" /> <PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="GraphQL" Version="7.3.0" /> <PackageReference Include="GraphQL" Version="7.5.0" />
<PackageReference Include="GraphQL.SystemTextJson" Version="7.3.0" /> <PackageReference Include="GraphQL.SystemTextJson" Version="7.5.0" />
<PackageReference Include="Lorem.Universal.Net" Version="4.0.80" /> <PackageReference Include="Lorem.Universal.Net" Version="4.0.80" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" /> <PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.0.1" /> <PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.0.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Reactive.Linq" Version="5.0.0" /> <PackageReference Include="System.Reactive.Linq" Version="6.0.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">

8
backend/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj

@ -14,13 +14,13 @@
<ProjectReference Include="..\..\src\Squidex.Shared\Squidex.Shared.csproj" /> <ProjectReference Include="..\..\src\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FakeItEasy" Version="7.3.1" /> <PackageReference Include="FakeItEasy" Version="7.4.0" />
<PackageReference Include="FluentAssertions" Version="6.10.0" /> <PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2" />

4
backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonJsonSerializerTests.cs

@ -89,8 +89,8 @@ public class BsonJsonSerializerTests
Bool = true, Bool = true,
Byte = 0x2, Byte = 0x2,
Bytes = new byte[] { 0x10, 0x12, 0x13 }, Bytes = new byte[] { 0x10, 0x12, 0x13 },
DateTimeOffset = DateTime.Today, DateTimeOffset = new DateTimeOffset(2022, 12, 11, 10, 9, 8, TimeSpan.Zero),
DateTime = DateTime.UtcNow.Date, DateTime = new DateTime(2022, 12, 11),
Float32 = 32.5f, Float32 = 32.5f,
Float64 = 32.5d, Float64 = 32.5d,
Guid = Guid.NewGuid(), Guid = Guid.NewGuid(),

11
backend/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj

@ -14,14 +14,17 @@
<ProjectReference Include="..\..\src\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\..\src\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FakeItEasy" Version="7.3.1" /> <PackageReference Include="FakeItEasy" Version="7.4.0" />
<PackageReference Include="FluentAssertions" Version="6.10.0" /> <PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.0.0" /> <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.0.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />

8
backend/tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj

@ -12,13 +12,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FakeItEasy" Version="7.3.1" /> <PackageReference Include="FakeItEasy" Version="7.4.0" />
<PackageReference Include="FluentAssertions" Version="6.10.0" /> <PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.22"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.62">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">

1
frontend/src/app/features/content/declarations.ts

@ -28,6 +28,7 @@ export * from './shared/due-time-selector.component';
export * from './shared/forms/array-editor.component'; export * from './shared/forms/array-editor.component';
export * from './shared/forms/array-item.component'; export * from './shared/forms/array-item.component';
export * from './shared/forms/assets-editor.component'; export * from './shared/forms/assets-editor.component';
export * from './shared/forms/chat-dialog.component';
export * from './shared/forms/component-section.component'; export * from './shared/forms/component-section.component';
export * from './shared/forms/component.component'; export * from './shared/forms/component.component';
export * from './shared/forms/field-editor.component'; export * from './shared/forms/field-editor.component';

3
frontend/src/app/features/content/module.ts

@ -9,7 +9,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { VirtualScrollerModule } from 'ngx-virtual-scroller'; import { VirtualScrollerModule } from 'ngx-virtual-scroller';
import { CanDeactivateGuard, ContentMustExistGuard, LoadLanguagesGuard, LoadSchemasGuard, SchemaMustExistPublishedGuard, SchemaMustNotBeSingletonGuard, SqxFrameworkModule, SqxSharedModule } from '@app/shared'; import { CanDeactivateGuard, ContentMustExistGuard, LoadLanguagesGuard, LoadSchemasGuard, SchemaMustExistPublishedGuard, SchemaMustNotBeSingletonGuard, SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { ArrayEditorComponent, ArrayItemComponent, AssetsEditorComponent, CalendarPageComponent, CommentsPageComponent, ComponentComponent, ComponentSectionComponent, ContentComponent, ContentCreatorComponent, ContentEditorComponent, ContentEventComponent, ContentExtensionComponent, ContentFieldComponent, ContentHistoryPageComponent, ContentInspectionComponent, ContentPageComponent, ContentReferencesComponent, ContentSectionComponent, ContentsFiltersPageComponent, ContentsPageComponent, CustomViewEditorComponent, DueTimeSelectorComponent, FieldCopyButtonComponent, FieldEditorComponent, FieldLanguagesComponent, IFrameEditorComponent, PreviewButtonComponent, ReferenceDropdownComponent, ReferenceItemComponent, ReferencesCheckboxesComponent, ReferencesEditorComponent, ReferencesPageComponent, ReferencesTagsComponent, SchemasPageComponent, SidebarPageComponent, StockPhotoEditorComponent } from './declarations'; import { ArrayEditorComponent, ArrayItemComponent, AssetsEditorComponent, CalendarPageComponent, ChatDialogComponent, CommentsPageComponent, ComponentComponent, ComponentSectionComponent, ContentComponent, ContentCreatorComponent, ContentEditorComponent, ContentEventComponent, ContentExtensionComponent, ContentFieldComponent, ContentHistoryPageComponent, ContentInspectionComponent, ContentPageComponent, ContentReferencesComponent, ContentSectionComponent, ContentsFiltersPageComponent, ContentsPageComponent, CustomViewEditorComponent, DueTimeSelectorComponent, FieldCopyButtonComponent, FieldEditorComponent, FieldLanguagesComponent, IFrameEditorComponent, PreviewButtonComponent, ReferenceDropdownComponent, ReferenceItemComponent, ReferencesCheckboxesComponent, ReferencesEditorComponent, ReferencesPageComponent, ReferencesTagsComponent, SchemasPageComponent, SidebarPageComponent, StockPhotoEditorComponent } from './declarations';
const routes: Routes = [ const routes: Routes = [
{ {
@ -97,6 +97,7 @@ const routes: Routes = [
ArrayItemComponent, ArrayItemComponent,
AssetsEditorComponent, AssetsEditorComponent,
CalendarPageComponent, CalendarPageComponent,
ChatDialogComponent,
CommentsPageComponent, CommentsPageComponent,
ComponentComponent, ComponentComponent,
ComponentSectionComponent, ComponentSectionComponent,

8
frontend/src/app/features/content/pages/content/editor/content-field.component.html

@ -4,10 +4,6 @@
<div class="languages-container"> <div class="languages-container">
<div class="languages-buttons"> <div class="languages-buttons">
<div class="languages-inner"> <div class="languages-inner">
<button *ngIf="!formModel.field.isDisabled && isTranslatable" type="button" class="btn btn-text-secondary btn-sm no-focus-shadow me-1" title="i18n:contents.autotranslate" (click)="translate()" tabindex="-1">
<i class="icon-translate"></i>
</button>
<sqx-field-languages <sqx-field-languages
[formModel]="formModel" [formModel]="formModel"
(languageChange)="languageChange.emit($event)" (languageChange)="languageChange.emit($event)"
@ -18,6 +14,10 @@
</sqx-field-languages> </sqx-field-languages>
<sqx-field-copy-button [formModel]="formModel" [languages]="languages"></sqx-field-copy-button> <sqx-field-copy-button [formModel]="formModel" [languages]="languages"></sqx-field-copy-button>
<button *ngIf="!formModel.field.isDisabled && isTranslatable" type="button" class="btn btn-sm btn-outline-secondary force no-focus-shadow ms-1" title="i18n:contents.autotranslate" (click)="translate()" tabindex="-1">
<i class="icon-translate"></i>
</button>
</div> </div>
</div> </div>
</div> </div>

2
frontend/src/app/features/content/pages/content/editor/content-field.component.scss

@ -31,7 +31,7 @@
} }
&-buttons { &-buttons {
@include absolute(-.75rem, 4.25rem, null, 0); @include absolute(-.75rem, 6.5rem, null, 0);
overflow: hidden; overflow: hidden;
} }

4
frontend/src/app/features/content/pages/content/editor/field-languages.component.html

@ -1,5 +1,5 @@
<ng-container *ngIf="formModel.field.isLocalizable && languages.length > 1"> <ng-container *ngIf="formModel.field.isLocalizable && languages.length > 1">
<button *ngIf="!formModel.field.properties.isComplexUI" type="button" class="btn btn-text-secondary btn-sm no-focus-shadow me-1" (click)="toggleShowAllControls()"> <button *ngIf="!formModel.field.properties.isComplexUI" type="button" class="btn btn-sm btn-outline-secondary force no-focus-shadow" (click)="toggleShowAllControls()">
<ng-container *ngIf="showAllControls; else singleLanguage"> <ng-container *ngIf="showAllControls; else singleLanguage">
<span>{{ 'contents.languageModeSingle' | sqxTranslate }}</span> <span>{{ 'contents.languageModeSingle' | sqxTranslate }}</span>
</ng-container> </ng-container>
@ -10,7 +10,7 @@
</button> </button>
<ng-container *ngIf="formModel.field.properties.isComplexUI || !showAllControls"> <ng-container *ngIf="formModel.field.properties.isComplexUI || !showAllControls">
<div class="button-container"> <div class="button-container ms-1">
<sqx-language-selector size="sm" #buttonLanguages <sqx-language-selector size="sm" #buttonLanguages
[exists]="formModel.translationStatus | async" [exists]="formModel.translationStatus | async"
(languageChange)="languageChange.emit($event)" (languageChange)="languageChange.emit($event)"

56
frontend/src/app/features/content/shared/forms/chat-dialog.component.html

@ -0,0 +1,56 @@
<sqx-modal-dialog size="lg" (close)="close.emit()">
<ng-container title>
{{ 'chat.title' | sqxTranslate }}
</ng-container>
<ng-container content>
<sqx-form-alert [marginBottom]="4">
<span [sqxMarkdown]="'chat.description' | sqxTranslate" [inline]="true"></span>
</sqx-form-alert>
<form (ngSubmit)="ask()">
<div class="row row-cols-0 g-2">
<div class="col">
<input class="form-control" placeholder="{{ 'chat.prompt' | sqxTranslate }}"
sqxFocusOnInit
(ngModelChange)="setQuestion($event)"
[ngModel]="snapshot.chatQuestion"
[ngModelOptions]="{ standalone: true }"
[disabled]="snapshot.isRunning" />
</div>
<div class="col-auto">
<button type="submit" class="btn btn-secondary" [disabled]="snapshot.isRunning">
{{ 'chat.ask' | sqxTranslate }}
</button>
</div>
</div>
</form>
<ng-container *ngIf="!snapshot.isRunning; else loading">
<ng-container *ngIf="snapshot.chatAnswers">
<h4 class="mt-4">{{ 'chat.answers' | sqxTranslate }}</h4>
<div *ngIf="snapshot.chatAnswers?.length === 0">
{{ 'chat.noAnswers' | sqxTranslate }}
</div>
<div class="row g-2 answer" *ngFor="let answer of (snapshot.chatAnswers || [])">
<div class="col">
<textarea class="form-control" readonly [ngModel]="answer"></textarea>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary" (click)="complete.emit(answer)">
{{ 'chat.use' | sqxTranslate }}
</button>
</div>
</div>
</ng-container>
</ng-container>
<ng-template #loading>
<div class="mt-4">
<sqx-loader></sqx-loader>
</div>
</ng-template>
</ng-container>
</sqx-modal-dialog>

6
frontend/src/app/features/content/shared/forms/chat-dialog.component.scss

@ -0,0 +1,6 @@
@import 'mixins';
@import 'vars';
textarea {
height: 100px;
}

65
frontend/src/app/features/content/shared/forms/chat-dialog.component.ts

@ -0,0 +1,65 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectorRef, Component, EventEmitter, Output } from '@angular/core';
import { AppsState, StatefulComponent, TranslationsService } from '@app/shared';
interface State {
// True, when running
isRunning: boolean;
// The questions.
chatQuestion: string;
// The answers.
chatAnswers?: ReadonlyArray<string>;
}
@Component({
selector: 'sqx-chat-dialog',
styleUrls: ['./chat-dialog.component.scss'],
templateUrl: './chat-dialog.component.html',
})
export class ChatDialogComponent extends StatefulComponent<State> {
@Output()
public close = new EventEmitter();
@Output()
public complete = new EventEmitter<string>();
constructor(changeDetector: ChangeDetectorRef,
private readonly appsState: AppsState,
private readonly translator: TranslationsService,
) {
super(changeDetector, {
isRunning: false,
chatQuestion: '',
chatAnswers: undefined,
});
}
public setQuestion(chatQuestion: string) {
this.next({ chatQuestion });
}
public ask() {
this.next({ isRunning: true });
this.translator.ask(this.appsState.appName, { prompt: this.snapshot.chatQuestion })
.subscribe({
next: chatAnswers => {
this.next({ chatAnswers });
},
error: () => {
this.next({ chatAnswers: [] });
},
complete: () => {
this.next({ isRunning: false });
},
});
}
}

13
frontend/src/app/features/content/shared/forms/field-editor.component.html

@ -1,11 +1,15 @@
<div class="field" [class.expanded]="isExpanded" *ngIf="formModel"> <div class="field" [class.expanded]="isExpanded" *ngIf="formModel">
<div class="buttons-container" *ngIf="canUnset"> <div class="buttons-container" *ngIf="canUnset">
<div class="buttons"> <div class="buttons">
<button type="button" class="btn btn-sm btn-outline-secondary force" title="i18n:contents.fieldFullscreen" (click)="toggleExpanded()" tabindex="-1"> <button type="button" class="btn btn-sm btn-outline-secondary force no-focus-shadow" (click)="chatDialog.show()" tabindex="-1">
AI
</button>
<button type="button" class="btn btn-sm btn-outline-secondary force no-focus-shadow ms-1" title="i18n:contents.fieldFullscreen" (click)="toggleExpanded()" tabindex="-1">
<i class="icon-fullscreen"></i> <i class="icon-fullscreen"></i>
</button> </button>
<button type="button" class="btn btn-sm btn-outline-secondary btn-clear force ms-1" [disabled]="isEmpty | async" tabindex="-1" <button type="button" class="btn btn-sm btn-outline-secondary btn-clear force no-focus-shadow ms-1" [disabled]="isEmpty | async" tabindex="-1"
(sqxConfirmClick)="unset()" (sqxConfirmClick)="unset()"
confirmTitle="i18n:contents.unsetValueConfirmTitle" confirmTitle="i18n:contents.unsetValueConfirmTitle"
confirmText="i18n:contents.unsetValueConfirmText" confirmText="i18n:contents.unsetValueConfirmText"
@ -244,3 +248,8 @@
<span [sqxMarkdown]="field.properties.hints" [optional]="true" [inline]="true"></span> <span [sqxMarkdown]="field.properties.hints" [optional]="true" [inline]="true"></span>
</sqx-form-hint> </sqx-form-hint>
</div> </div>
<ng-container *sqxModal="chatDialog">
<sqx-chat-dialog (close)="chatDialog.hide()" (complete)="setValue($event)"></sqx-chat-dialog>
</ng-container>

10
frontend/src/app/features/content/shared/forms/field-editor.component.ts

@ -8,7 +8,7 @@
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { AbstractControl } from '@angular/forms'; import { AbstractControl } from '@angular/forms';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { AbstractContentForm, AppLanguageDto, EditContentForm, FieldDto, hasNoValue$, MathHelper, TypedSimpleChanges, Types } from '@app/shared'; import { AbstractContentForm, AppLanguageDto, DialogModel, EditContentForm, FieldDto, hasNoValue$, MathHelper, TypedSimpleChanges, Types } from '@app/shared';
@Component({ @Component({
selector: 'sqx-field-editor[form][formContext][formLevel][formModel][isComparing][language][languages]', selector: 'sqx-field-editor[form][formContext][formLevel][formModel][isComparing][language][languages]',
@ -57,6 +57,8 @@ export class FieldEditorComponent {
public isEmpty?: Observable<boolean>; public isEmpty?: Observable<boolean>;
public isExpanded = false; public isExpanded = false;
public chatDialog = new DialogModel();
public get field() { public get field() {
return this.formModel.field; return this.formModel.field;
} }
@ -92,4 +94,10 @@ export class FieldEditorComponent {
public unset() { public unset() {
this.formModel.unset(); this.formModel.unset();
} }
public setValue(value: any) {
this.formModel.setValue(value);
this.chatDialog.hide();
}
} }

8
frontend/src/app/features/settings/pages/templates/template.component.html

@ -20,7 +20,13 @@
</div> </div>
<div class="table-items-row-details-tab"> <div class="table-items-row-details-tab">
<div class="help" [innerHTML]="details | async | sqxMarkdown | sqxSafeHtml"></div> <ng-container *ngIf="details | async; let loadedDetails; else loading">
<div class="help" [innerHTML]="loadedDetails | sqxMarkdown | sqxSafeHtml"></div>
</ng-container>
<ng-template #loading>
<sqx-loader></sqx-loader>
</ng-template>
</div> </div>
</div> </div>
</div> </div>

26
frontend/src/app/shared/services/translations.service.spec.ts

@ -47,4 +47,30 @@ describe('TranslationsService', () => {
expect(translation!).toEqual(new TranslationDto('Translated', 'Hallo')); expect(translation!).toEqual(new TranslationDto('Translated', 'Hallo'));
})); }));
it('should make post request to ask question',
inject([TranslationsService, HttpTestingController], (translationsService: TranslationsService, httpMock: HttpTestingController) => {
const dto = { prompt: 'My Question' };
let answers: ReadonlyArray<string>;
translationsService.ask('my-app', dto).subscribe(result => {
answers = result;
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/ask');
expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toBeNull();
req.flush([
'Answer1',
'Answer2',
]);
expect(answers!).toEqual([
'Answer1',
'Answer2',
]);
}));
}); });

12
frontend/src/app/shared/services/translations.service.ts

@ -30,6 +30,11 @@ export type TranslateDto = Readonly<{
targetLanguage: string; targetLanguage: string;
}>; }>;
export type AskDto = Readonly<{
// The question to ask.
prompt: string;
}>;
@Injectable() @Injectable()
export class TranslationsService { export class TranslationsService {
constructor( constructor(
@ -47,6 +52,13 @@ export class TranslationsService {
}), }),
pretifyError('i18n:translate.translateFailed')); pretifyError('i18n:translate.translateFailed'));
} }
public ask(appName: string, request: AskDto): Observable<ReadonlyArray<string>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/ask`);
return this.http.post<any>(url, request).pipe(
pretifyError('i18n:chatBot.questionFailed'));
}
} }
function parseTranslation(body: any): TranslationDto { function parseTranslation(body: any): TranslationDto {

12
frontend/src/app/shell/pages/home/home-page.component.ts

@ -38,9 +38,17 @@ export class HomePageComponent {
} }
try { try {
const path = await this.authService.loginPopup(redirectPath) || '/app'; let path = await this.authService.loginPopup(redirectPath);
this.router.navigateByUrl(path, { replaceUrl: true }); if (!path) {
path = '/app';
}
const success = await this.router.navigateByUrl(path, { replaceUrl: true });
if (!success) {
this.router.navigate(['/app'], { replaceUrl: true });
}
} catch { } catch {
this.router.navigate(['/'], { replaceUrl: true }); this.router.navigate(['/'], { replaceUrl: true });
} }

Loading…
Cancel
Save