Browse Source

Merge branch 'release/4.x'

# Conflicts:
#	backend/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs
#	backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs
#	backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/BulkUpdateContents.cs
#	backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs
#	backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs
#	backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/IUpsertCommand.cs
#	backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs
#	backend/src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs
#	backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs
#	backend/src/Squidex.Infrastructure/Translations/ResourcesLocalizer.cs
#	backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
#	backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateWorkflowDto.cs
#	backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs
#	backend/src/Squidex/Areas/Api/Controllers/QueryDto.cs
#	backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/ReferencesValidatorTests.cs
#	backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValidatorTests.cs
#	backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DefaultAppLogStoreTests.cs
#	backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs
#	backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs
#	backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveReferencesTests.cs
#	backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferenceFluidExtensionTests.cs
#	backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/CachingTextIndexerStateTests.cs
#	backend/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs
pull/590/head
Sebastian 5 years ago
parent
commit
745440a444
  1. 2
      backend/extensions/Squidex.Extensions/APM/Datadog/DatadogPlugin.cs
  2. 2
      backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs
  3. 10
      backend/i18n/frontend_en.json
  4. 10
      backend/i18n/frontend_it.json
  5. 10
      backend/i18n/frontend_nl.json
  6. 2
      backend/i18n/source/backend_en.json
  7. 2
      backend/i18n/source/backend_it.json
  8. 2
      backend/i18n/source/backend_nl.json
  9. 10
      backend/i18n/source/frontend_en.json
  10. 2
      backend/src/Migrations/OldEvents/SchemaCreated.cs
  11. 11
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs
  12. 13
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs
  13. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs
  14. 21
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonRole.cs
  15. 72
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RoleConverter.cs
  16. 22
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs
  17. 65
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs
  18. 76
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs
  19. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs
  20. 10
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  21. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOperations.cs
  22. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndex.cs
  23. 11
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs
  24. 3
      backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs
  25. 4
      backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateRole.cs
  26. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs
  27. 6
      backend/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs
  28. 12
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs
  29. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs
  30. 3
      backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/BulkUpdateContents.cs
  31. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs
  32. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  33. 10
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentActions.cs
  34. 5
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureFieldRules.cs
  35. 7
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs
  36. 9
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/IUpsertCommand.cs
  37. 4
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs
  38. 7
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs
  39. 4
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs
  40. 12
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs
  41. 4
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs
  42. 11
      backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs
  43. 3
      backend/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs
  44. 3
      backend/src/Squidex.Domain.Apps.Events/Apps/AppLanguageUpdated.cs
  45. 6
      backend/src/Squidex.Domain.Apps.Events/Apps/AppPlanChanged.cs
  46. 9
      backend/src/Squidex.Domain.Apps.Events/Apps/AppRoleUpdated.cs
  47. 4
      backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreatedField.cs
  48. 3
      backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaFieldsReordered.cs
  49. 4
      backend/src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs
  50. 18
      backend/src/Squidex.Infrastructure/Assets/ImageFormat.cs
  51. 2
      backend/src/Squidex.Infrastructure/Assets/ImageInfo.cs
  52. 57
      backend/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs
  53. 10
      backend/src/Squidex.Infrastructure/Assets/ResizeOptions.cs
  54. 12
      backend/src/Squidex.Infrastructure/EventSourcing/Grains/BatchSubscriber.cs
  55. 2
      backend/src/Squidex.Infrastructure/Log/ISemanticLog.cs
  56. 6
      backend/src/Squidex.Infrastructure/Tasks/AsyncHelper.cs
  57. 22
      backend/src/Squidex.Shared/Permissions.cs
  58. 2
      backend/src/Squidex.Shared/Texts.it.resx
  59. 2
      backend/src/Squidex.Shared/Texts.nl.resx
  60. 2
      backend/src/Squidex.Shared/Texts.resx
  61. 22
      backend/src/Squidex.Web/Pipeline/AppResolver.cs
  62. 25
      backend/src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs
  63. 2
      backend/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs
  64. 2
      backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs
  65. 2
      backend/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs
  66. 14
      backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
  67. 37
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
  68. 15
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs
  69. 3
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateLanguageDto.cs
  70. 8
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateRoleDto.cs
  71. 2
      backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs
  72. 6
      backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetContentQueryDto.cs
  73. 8
      backend/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs
  74. 5
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/BulkUpdateDto.cs
  75. 1
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/GraphQLGetDto.cs
  76. 1
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/GraphQLPostDto.cs
  77. 2
      backend/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs
  78. 2
      backend/src/Squidex/Areas/Api/Controllers/Ping/PingController.cs
  79. 3
      backend/src/Squidex/Areas/Api/Controllers/QueryDto.cs
  80. 5
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureFieldRulesDto.cs
  81. 3
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/ReorderFieldsDto.cs
  82. 22
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaDto.cs
  83. 3
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaFieldDto.cs
  84. 4
      backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs
  85. 4
      backend/src/Squidex/Areas/Api/Controllers/Search/SearchController.cs
  86. 8
      backend/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs
  87. 4
      backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs
  88. 2
      backend/src/Squidex/Areas/IdentityServer/Views/Account/Login.cshtml
  89. 2
      backend/src/Squidex/Areas/IdentityServer/Views/Error/Error.cshtml
  90. 2
      backend/src/Squidex/Areas/IdentityServer/Views/Profile/Profile.cshtml
  91. 1
      backend/src/Squidex/Config/Domain/SerializationServices.cs
  92. 22
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RoleTests.cs
  93. 39
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesJsonTests.cs
  94. 51
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs
  95. 10
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs
  96. 4
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/ExpressionsAttribute.cs
  97. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterCompareTests.cs
  98. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs
  99. 3
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs
  100. 3
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs

2
backend/extensions/Squidex.Extensions/APM/Datadog/DatadogPlugin.cs

@ -15,7 +15,7 @@ using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Plugins; using Squidex.Infrastructure.Plugins;
namespace Squidex.Extensions.APM.Datadoq namespace Squidex.Extensions.APM.Datadog
{ {
public sealed class DatadogPlugin : IPlugin, IStartupFilter public sealed class DatadogPlugin : IPlugin, IStartupFilter
{ {

2
backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs

@ -161,7 +161,7 @@ namespace Squidex.Extensions.Actions.Kafka
{ {
try try
{ {
var schema = (RecordSchema)Avro.Schema.Parse(avroSchema); var schema = (RecordSchema)Schema.Parse(avroSchema);
var jsonObject = jsonSerializer.Deserialize<JsonObject>(json); var jsonObject = jsonSerializer.Deserialize<JsonObject>(json);

10
backend/i18n/frontend_en.json

@ -550,6 +550,16 @@
"roles.deleteConfirmTitle": "Do you really want to delete the role?", "roles.deleteConfirmTitle": "Do you really want to delete the role?",
"roles.loadFailed": "Failed to load roles. Please reload.", "roles.loadFailed": "Failed to load roles. Please reload.",
"roles.loadPermissionsFailed": "Failed to load permissions. Please reload.", "roles.loadPermissionsFailed": "Failed to load permissions. Please reload.",
"roles.permissions": "Permissions",
"roles.permissionsDescription": "Permissions restrict the allowed operations and queries at API level and are a security feature.",
"roles.permissionsPlaceholder": "Start typing to search for permissions",
"roles.properties": "Properties",
"roles.properties.hideAPI": "Hide API",
"roles.properties.hideAssets": "Hide Assets",
"roles.properties.hideContents": "Hide {schema} Contents",
"roles.properties.hideSchemas": "Hide Schemas",
"roles.properties.hideSettings": "Hide Settings",
"roles.propertiesDescription": "Properties describe the behavior of the Management UI, but do not provide security for the API.",
"roles.refreshTooltip": "Refresh roles (CTRL + SHIFT + R)", "roles.refreshTooltip": "Refresh roles (CTRL + SHIFT + R)",
"roles.reloaded": "Roles reloaded.", "roles.reloaded": "Roles reloaded.",
"roles.revokeFailed": "Failed to revoke role. Please reload.", "roles.revokeFailed": "Failed to revoke role. Please reload.",

10
backend/i18n/frontend_it.json

@ -550,6 +550,16 @@
"roles.deleteConfirmTitle": "Sei sicuro di voler eliminare il ruolo?", "roles.deleteConfirmTitle": "Sei sicuro di voler eliminare il ruolo?",
"roles.loadFailed": "Non è stato possibile caricare i ruoli. Per favore ricarica.", "roles.loadFailed": "Non è stato possibile caricare i ruoli. Per favore ricarica.",
"roles.loadPermissionsFailed": "Non è stato possibile caricare i permessi. Per favore ricarica.", "roles.loadPermissionsFailed": "Non è stato possibile caricare i permessi. Per favore ricarica.",
"roles.permissions": "Permissions",
"roles.permissionsDescription": "Permissions restrict the allowed operations and queries at API level and are a security feature.",
"roles.permissionsPlaceholder": "Start typing to search for permissions",
"roles.properties": "Properties",
"roles.properties.hideAPI": "Hide API",
"roles.properties.hideAssets": "Hide Assets",
"roles.properties.hideContents": "Hide {schema} Contents",
"roles.properties.hideSchemas": "Hide Schemas",
"roles.properties.hideSettings": "Hide Settings",
"roles.propertiesDescription": "Properties describe the behavior of the Management UI, but do not provide security for the API.",
"roles.refreshTooltip": "Aggiorna i ruoli (CTRL + SHIFT + R)", "roles.refreshTooltip": "Aggiorna i ruoli (CTRL + SHIFT + R)",
"roles.reloaded": "Ruoli ricaricati.", "roles.reloaded": "Ruoli ricaricati.",
"roles.revokeFailed": "Non è stato possibile rimuovere il ruolo. Per favore ricarica.", "roles.revokeFailed": "Non è stato possibile rimuovere il ruolo. Per favore ricarica.",

10
backend/i18n/frontend_nl.json

@ -550,6 +550,16 @@
"roles.deleteConfirmTitle": "Wil je de rol echt verwijderen?", "roles.deleteConfirmTitle": "Wil je de rol echt verwijderen?",
"roles.loadFailed": "Laden van rollen is mislukt. Laad opnieuw.", "roles.loadFailed": "Laden van rollen is mislukt. Laad opnieuw.",
"roles.loadPermissionsFailed": "Kan machtigingen niet laden. Laad opnieuw.", "roles.loadPermissionsFailed": "Kan machtigingen niet laden. Laad opnieuw.",
"roles.permissions": "Permissions",
"roles.permissionsDescription": "Permissions restrict the allowed operations and queries at API level and are a security feature.",
"roles.permissionsPlaceholder": "Start typing to search for permissions",
"roles.properties": "Properties",
"roles.properties.hideAPI": "Hide API",
"roles.properties.hideAssets": "Hide Assets",
"roles.properties.hideContents": "Hide {schema} Contents",
"roles.properties.hideSchemas": "Hide Schemas",
"roles.properties.hideSettings": "Hide Settings",
"roles.propertiesDescription": "Properties describe the behavior of the Management UI, but do not provide security for the API.",
"roles.refreshTooltip": "Ververs rollen (CTRL + SHIFT + R)", "roles.refreshTooltip": "Ververs rollen (CTRL + SHIFT + R)",
"roles.reloaded": "Rollen opnieuw geladen.", "roles.reloaded": "Rollen opnieuw geladen.",
"roles.revokeFailed": "Kan rol niet intrekken. Laad opnieuw.", "roles.revokeFailed": "Kan rol niet intrekken. Laad opnieuw.",

2
backend/i18n/source/backend_en.json

@ -66,7 +66,7 @@
"common.httpValidationError": "Validation error", "common.httpValidationError": "Validation error",
"common.initialStep": "Initial step", "common.initialStep": "Initial step",
"common.jsError": "Failed to execute script with Javascript error: {message}", "common.jsError": "Failed to execute script with Javascript error: {message}",
"common.jsNotAlloweed": "Script has forbidden the operation.", "common.jsNotAllowed": "Script has forbidden the operation.",
"common.jsParseError": "Failed to execute script with Javascript syntax error: {message}", "common.jsParseError": "Failed to execute script with Javascript syntax error: {message}",
"common.jsRejected": "Script rejected the operation.", "common.jsRejected": "Script rejected the operation.",
"common.language": "Language code", "common.language": "Language code",

2
backend/i18n/source/backend_it.json

@ -66,7 +66,7 @@
"common.httpValidationError": "Errore di validazione", "common.httpValidationError": "Errore di validazione",
"common.initialStep": "Step iniziale", "common.initialStep": "Step iniziale",
"common.jsError": "Esecuzione dello script fallita, Errore Javascript: {message}", "common.jsError": "Esecuzione dello script fallita, Errore Javascript: {message}",
"common.jsNotAlloweed": "Uno script ha proibito l'operazione.", "common.jsNotAllowed": "Uno script ha proibito l'operazione.",
"common.jsParseError": "Esecuzione dello script fallita, errore di sintassi nel Javascript: {message}", "common.jsParseError": "Esecuzione dello script fallita, errore di sintassi nel Javascript: {message}",
"common.jsRejected": "Lo script ha rifiutato l'operazione.", "common.jsRejected": "Lo script ha rifiutato l'operazione.",
"common.language": "Codice della lingua", "common.language": "Codice della lingua",

2
backend/i18n/source/backend_nl.json

@ -61,7 +61,7 @@
"common.httpValidationError": "Validatiefout", "common.httpValidationError": "Validatiefout",
"common.initialStep": "Eerste stap", "common.initialStep": "Eerste stap",
"common.jsError": "Kan script niet uitvoeren met Javascript-fout: {message}", "common.jsError": "Kan script niet uitvoeren met Javascript-fout: {message}",
"common.jsNotAlloweed": "Script heeft de bewerking verboden.", "common.jsNotAllowed": "Script heeft de bewerking verboden.",
"common.jsParseError": "Kan script niet uitvoeren met Javascript-syntaxisfout: {message}", "common.jsParseError": "Kan script niet uitvoeren met Javascript-syntaxisfout: {message}",
"common.jsRejected": "Script heeft de bewerking afgewezen.", "common.jsRejected": "Script heeft de bewerking afgewezen.",
"common.language": "Taalcode", "common.language": "Taalcode",

10
backend/i18n/source/frontend_en.json

@ -550,6 +550,16 @@
"roles.deleteConfirmTitle": "Do you really want to delete the role?", "roles.deleteConfirmTitle": "Do you really want to delete the role?",
"roles.loadFailed": "Failed to load roles. Please reload.", "roles.loadFailed": "Failed to load roles. Please reload.",
"roles.loadPermissionsFailed": "Failed to load permissions. Please reload.", "roles.loadPermissionsFailed": "Failed to load permissions. Please reload.",
"roles.permissions": "Permissions",
"roles.permissionsDescription": "Permissions restrict the allowed operations and queries at API level and are a security feature.",
"roles.permissionsPlaceholder": "Start typing to search for permissions",
"roles.properties": "Properties",
"roles.properties.hideAPI": "Hide API",
"roles.properties.hideAssets": "Hide Assets",
"roles.properties.hideContents": "Hide {schema} Contents",
"roles.properties.hideSchemas": "Hide Schemas",
"roles.properties.hideSettings": "Hide Settings",
"roles.propertiesDescription": "Properties describe the behavior of the Management UI, but do not provide security for the API.",
"roles.refreshTooltip": "Refresh roles (CTRL + SHIFT + R)", "roles.refreshTooltip": "Refresh roles (CTRL + SHIFT + R)",
"roles.reloaded": "Roles reloaded.", "roles.reloaded": "Roles reloaded.",
"roles.revokeFailed": "Failed to revoke role. Please reload.", "roles.revokeFailed": "Failed to revoke role. Please reload.",

2
backend/src/Migrations/OldEvents/SchemaCreated.cs

@ -52,7 +52,7 @@ namespace Migrations.OldEvents
var field = eventField.Properties.CreateRootField(totalFields, eventField.Name, partitioning); var field = eventField.Properties.CreateRootField(totalFields, eventField.Name, partitioning);
if (field is ArrayField arrayField && eventField.Nested?.Count > 0) if (field is ArrayField arrayField && eventField.Nested?.Length > 0)
{ {
foreach (var nestedEventField in eventField.Nested) foreach (var nestedEventField in eventField.Nested)
{ {

11
backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs

@ -36,16 +36,21 @@ namespace Squidex.Domain.Apps.Core.Apps
Role = role; Role = role;
ApiCallsLimit = apiCallsLimit; ApiCallsLimit = apiCallsLimit;
ApiTrafficLimit = apiTrafficLimit; ApiTrafficLimit = apiTrafficLimit;
AllowAnonymous = allowAnonymous; AllowAnonymous = allowAnonymous;
} }
[Pure] [Pure]
public AppClient Update(string? name, string? role, long? apiCallsLimit, long? apiTrafficLimit, bool? allowAnonymous) public AppClient Update(string? name, string? role,
long? apiCallsLimit,
long? apiTrafficLimit,
bool? allowAnonymous)
{ {
return new AppClient(name.Or(Name), Secret, role.Or(Role), apiCallsLimit ?? ApiCallsLimit, apiTrafficLimit ?? ApiTrafficLimit, allowAnonymous ?? AllowAnonymous); return new AppClient(name.Or(Name), Secret, role.Or(Role),
apiCallsLimit ?? ApiCallsLimit,
apiTrafficLimit ?? ApiTrafficLimit,
allowAnonymous ?? AllowAnonymous);
} }
} }
} }

13
backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs

@ -53,11 +53,16 @@ namespace Squidex.Domain.Apps.Core.Apps
throw new ArgumentException("Id already exists.", nameof(id)); throw new ArgumentException("Id already exists.", nameof(id));
} }
return Add(id, new AppClient(id, secret, Role.Editor)); var newClient = new AppClient(id, secret, Role.Editor);
return Add(id, newClient);
} }
[Pure] [Pure]
public AppClients Update(string id, string? name = null, string? role = null, long? apiCallsLimit = null, long? apiTrafficLimit = null, bool? allowAnonymous = false) public AppClients Update(string id, string? name = null, string? role = null,
long? apiCallsLimit = null,
long? apiTrafficLimit = null,
bool? allowAnonymous = false)
{ {
Guard.NotNullOrEmpty(id, nameof(id)); Guard.NotNullOrEmpty(id, nameof(id));
@ -66,7 +71,9 @@ namespace Squidex.Domain.Apps.Core.Apps
return this; return this;
} }
return With<AppClients>(id, client.Update(name, role, apiCallsLimit, apiTrafficLimit, allowAnonymous)); var newClient = client.Update(name, role, apiCallsLimit, apiTrafficLimit, allowAnonymous);
return With<AppClients>(id, newClient);
} }
} }
} }

4
backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs

@ -47,7 +47,9 @@ namespace Squidex.Domain.Apps.Core.Apps
return this; return this;
} }
return With<AppPatterns>(id, appPattern.Update(name, pattern, message)); var newPattern = appPattern.Update(name, pattern, message);
return With<AppPatterns>(id, newPattern);
} }
} }
} }

21
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonRole.cs

@ -0,0 +1,21 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class JsonRole
{
[JsonProperty]
public string[] Permissions { get; set; }
[JsonProperty]
public JsonObject Properties { get; set; }
}
}

72
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RoleConverter.cs

@ -0,0 +1,72 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class RoleConverter : JsonClassConverter<JsonRole>
{
protected override void WriteValue(JsonWriter writer, JsonRole value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("permissions");
serializer.Serialize(writer, value.Permissions);
writer.WritePropertyName("properties");
serializer.Serialize(writer, value.Properties);
writer.WriteEndObject();
}
protected override JsonRole ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var permissions = Array.Empty<string>();
var properties = (JsonObject?)null;
if (reader.TokenType == JsonToken.StartArray)
{
permissions = serializer.Deserialize<string[]>(reader)!;
}
else
{
while (reader.Read() && reader.TokenType != JsonToken.EndObject)
{
if (reader.TokenType == JsonToken.PropertyName)
{
var propertyName = reader.Value!.ToString()!;
if (!reader.Read())
{
throw new JsonSerializationException("Unexpected end when reading role.");
}
switch (propertyName.ToLowerInvariant())
{
case "permissions":
permissions = serializer.Deserialize<string[]>(reader)!;
break;
case "properties":
properties = serializer.Deserialize<JsonObject>(reader)!;
break;
}
}
}
}
return new JsonRole
{
Permissions = permissions,
Properties = properties ?? JsonValue.Object()
};
}
}
}

22
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs

@ -18,11 +18,15 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
protected override void WriteValue(JsonWriter writer, Roles value, JsonSerializer serializer) protected override void WriteValue(JsonWriter writer, Roles value, JsonSerializer serializer)
{ {
var json = new Dictionary<string, string[]>(value.CustomCount); var json = new Dictionary<string, JsonRole>(value.CustomCount);
foreach (var role in value.Custom) foreach (var role in value.Custom)
{ {
json.Add(role.Name, role.Permissions.ToIds().ToArray()); json.Add(role.Name, new JsonRole
{
Permissions = role.Permissions.ToIds().ToArray(),
Properties = role.Properties
});
} }
serializer.Serialize(writer, json); serializer.Serialize(writer, json);
@ -30,14 +34,24 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
protected override Roles ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) protected override Roles ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
var json = serializer.Deserialize<Dictionary<string, string[]>>(reader)!; var json = serializer.Deserialize<Dictionary<string, JsonRole>>(reader)!;
if (json.Count == 0) if (json.Count == 0)
{ {
return Roles.Empty; return Roles.Empty;
} }
return new Roles(json.ToDictionary(x => x.Key, x => new Role(x.Key, new PermissionSet(x.Value)))); return new Roles(json.ToDictionary(x => x.Key, x =>
{
var permissions = PermissionSet.Empty;
if (x.Value.Permissions.Length > 0)
{
permissions = new PermissionSet(x.Value.Permissions);
}
return new Role(x.Key, permissions, x.Value.Properties);
}));
} }
} }
} }

65
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs

@ -7,9 +7,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Linq; using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using P = Squidex.Shared.Permissions; using P = Squidex.Shared.Permissions;
@ -18,36 +20,68 @@ namespace Squidex.Domain.Apps.Core.Apps
[Equals(DoNotAddEqualityOperators = true)] [Equals(DoNotAddEqualityOperators = true)]
public sealed class Role : Named public sealed class Role : Named
{ {
private static readonly HashSet<string> ExtraPermissions = new HashSet<string>
{
P.AppComments,
P.AppContributorsRead,
P.AppHistory,
P.AppHistory,
P.AppLanguagesRead,
P.AppPatternsRead,
P.AppPing,
P.AppRolesRead,
P.AppSchemasRead,
P.AppSearch,
P.AppTranslate,
P.AppUsage
};
public const string Editor = "Editor"; public const string Editor = "Editor";
public const string Developer = "Developer"; public const string Developer = "Developer";
public const string Owner = "Owner"; public const string Owner = "Owner";
public const string Reader = "Reader"; public const string Reader = "Reader";
public static readonly ReadOnlyCollection<string> EmptyProperties = new ReadOnlyCollection<string>(new List<string>());
public PermissionSet Permissions { get; } public PermissionSet Permissions { get; }
public JsonObject Properties { get; }
[IgnoreDuringEquals] [IgnoreDuringEquals]
public bool IsDefault public bool IsDefault
{ {
get { return Roles.IsDefault(this); } get { return Roles.IsDefault(this); }
} }
public Role(string name, PermissionSet permissions) public Role(string name, PermissionSet permissions, JsonObject properties)
: base(name) : base(name)
{ {
Guard.NotNull(permissions, nameof(permissions)); Guard.NotNull(permissions, nameof(permissions));
Guard.NotNull(properties, nameof(properties));
Permissions = permissions; Permissions = permissions;
Properties = properties;
}
public static Role WithPermissions(string role, params string[] permissions)
{
return new Role(role, new PermissionSet(permissions), JsonValue.Object());
}
public static Role WithProperties(string role, JsonObject properties)
{
return new Role(role, PermissionSet.Empty, properties);
} }
public Role(string name, params string[] permissions) public static Role Create(string role)
: this(name, new PermissionSet(permissions))
{ {
return new Role(role, PermissionSet.Empty, JsonValue.Object());
} }
[Pure] [Pure]
public Role Update(string[] permissions) public Role Update(PermissionSet? permissions, JsonObject? properties)
{ {
return new Role(Name, new PermissionSet(permissions)); return new Role(Name, permissions ?? Permissions, properties ?? Properties);
} }
public bool Equals(string name) public bool Equals(string name)
@ -55,12 +89,11 @@ namespace Squidex.Domain.Apps.Core.Apps
return name != null && name.Equals(Name, StringComparison.Ordinal); return name != null && name.Equals(Name, StringComparison.Ordinal);
} }
public Role ForApp(string app) public Role ForApp(string app, bool isFrontend = false)
{ {
var result = new HashSet<Permission> Guard.NotNullOrEmpty(app, nameof(app));
{
P.ForApp(P.AppCommon, app) var result = new HashSet<Permission>();
};
if (Permissions.Any()) if (Permissions.Any())
{ {
@ -72,7 +105,17 @@ namespace Squidex.Domain.Apps.Core.Apps
} }
} }
return new Role(Name, new PermissionSet(result)); if (isFrontend)
{
foreach (var extraPermissionId in ExtraPermissions)
{
var extraPermission = P.ForApp(extraPermissionId, app);
result.Add(extraPermission);
}
}
return new Role(Name, new PermissionSet(result), Properties);
} }
} }
} }

76
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs

@ -12,6 +12,7 @@ using System.Diagnostics.Contracts;
using System.Linq; using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using Squidex.Shared; using Squidex.Shared;
@ -24,28 +25,37 @@ namespace Squidex.Domain.Apps.Core.Apps
public static readonly IReadOnlyDictionary<string, Role> Defaults = new Dictionary<string, Role> public static readonly IReadOnlyDictionary<string, Role> Defaults = new Dictionary<string, Role>
{ {
[Role.Owner] = [Role.Owner] =
new Role(Role.Owner, new PermissionSet( new Role(Role.Owner,
Clean(Permissions.App))), new PermissionSet(
Clean(Permissions.App)),
JsonValue.Object()),
[Role.Reader] = [Role.Reader] =
new Role(Role.Reader, new PermissionSet( new Role(Role.Reader,
Clean(Permissions.AppAssetsRead), new PermissionSet(
Clean(Permissions.AppContentsRead))), Clean(Permissions.AppAssetsRead),
Clean(Permissions.AppContentsRead)),
JsonValue.Object()
.Add("ui.api.hide", true)),
[Role.Editor] = [Role.Editor] =
new Role(Role.Editor, new PermissionSet( new Role(Role.Editor,
Clean(Permissions.AppAssets), new PermissionSet(
Clean(Permissions.AppContents), Clean(Permissions.AppAssets),
Clean(Permissions.AppRolesRead), Clean(Permissions.AppContents),
Clean(Permissions.AppWorkflowsRead))), Clean(Permissions.AppRolesRead),
Clean(Permissions.AppWorkflowsRead)),
JsonValue.Object()
.Add("ui.api.hide", true)),
[Role.Developer] = [Role.Developer] =
new Role(Role.Developer, new PermissionSet( new Role(Role.Developer,
Clean(Permissions.AppApi), new PermissionSet(
Clean(Permissions.AppAssets), Clean(Permissions.AppAssets),
Clean(Permissions.AppContents), Clean(Permissions.AppContents),
Clean(Permissions.AppPatterns), Clean(Permissions.AppPatterns),
Clean(Permissions.AppRolesRead), Clean(Permissions.AppRolesRead),
Clean(Permissions.AppRules), Clean(Permissions.AppRules),
Clean(Permissions.AppSchemas), Clean(Permissions.AppSchemas),
Clean(Permissions.AppWorkflows))) Clean(Permissions.AppWorkflows)),
JsonValue.Object())
}; };
public static readonly Roles Empty = new Roles(new ImmutableDictionary<string, Role>()); public static readonly Roles Empty = new Roles(new ImmutableDictionary<string, Role>());
@ -89,8 +99,6 @@ namespace Squidex.Domain.Apps.Core.Apps
[Pure] [Pure]
public Roles Add(string name) public Roles Add(string name)
{ {
var newRole = new Role(name);
if (inner.ContainsKey(name)) if (inner.ContainsKey(name))
{ {
return this; return this;
@ -101,21 +109,24 @@ namespace Squidex.Domain.Apps.Core.Apps
return this; return this;
} }
var newRole = Role.Create(name);
return Create(inner.With(name, newRole)); return Create(inner.With(name, newRole));
} }
[Pure] [Pure]
public Roles Update(string name, params string[] permissions) public Roles Update(string name, PermissionSet? permissions = null, JsonObject? properties = null)
{ {
Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(name, nameof(name));
Guard.NotNull(permissions, nameof(permissions));
if (!inner.TryGetValue(name, out var role)) if (!inner.TryGetValue(name, out var role))
{ {
return this; return this;
} }
return Create(inner.With(name, role.Update(permissions))); var newRole = role.Update(permissions, properties);
return Create(inner.With(name, newRole));
} }
public static bool IsDefault(string role) public static bool IsDefault(string role)
@ -138,19 +149,22 @@ namespace Squidex.Domain.Apps.Core.Apps
return inner.ContainsKey(name) || Defaults.ContainsKey(name); return inner.ContainsKey(name) || Defaults.ContainsKey(name);
} }
public bool TryGet(string app, string name, [MaybeNullWhen(false)] out Role value) public bool TryGet(string app, string name, bool isFrontend, [MaybeNullWhen(false)] out Role value)
{ {
Guard.NotNull(app, nameof(app)); Guard.NotNull(app, nameof(app));
if (Defaults.TryGetValue(name, out var role) || inner.TryGetValue(name, out role)) value = null!;
if (Defaults.TryGetValue(name, out var role))
{ {
value = role.ForApp(app); value = role.ForApp(app, isFrontend && name != Role.Owner);
return true; }
else if (inner.TryGetValue(name, out role))
{
value = role.ForApp(app, isFrontend);
} }
value = null!; return value != null;
return false;
} }
private static string Clean(string permission) private static string Clean(string permission)

2
backend/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs

@ -213,7 +213,7 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization
if (sourceNames.SetEquals(targetNames) && !sourceNames.SequenceEqual(targetNames)) if (sourceNames.SetEquals(targetNames) && !sourceNames.SequenceEqual(targetNames))
{ {
var fieldIds = targetNames.Select(x => sourceIds.Find(y => y.Name == x)!.Id).ToList(); var fieldIds = targetNames.Select(x => sourceIds.Find(y => y.Name == x)!.Id).ToArray();
yield return new SchemaFieldsReordered { FieldIds = fieldIds, ParentFieldId = parentId }; yield return new SchemaFieldsReordered { FieldIds = fieldIds, ParentFieldId = parentId };
} }

10
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs

@ -30,9 +30,9 @@ namespace Squidex.Domain.Apps.Core.Scripting
private readonly IJintExtension[] extensions; private readonly IJintExtension[] extensions;
private readonly Parser parser; private readonly Parser parser;
public TimeSpan Timeout { get; set; } = TimeSpan.FromMilliseconds(200); public TimeSpan TimeoutScript { get; set; } = TimeSpan.FromMilliseconds(200);
public TimeSpan ExecutionTimeout { get; set; } = TimeSpan.FromMilliseconds(4000); public TimeSpan TimeoutExecution { get; set; } = TimeSpan.FromMilliseconds(4000);
public JintScriptEngine(IMemoryCache memoryCache, IEnumerable<IJintExtension>? extensions = null) public JintScriptEngine(IMemoryCache memoryCache, IEnumerable<IJintExtension>? extensions = null)
{ {
@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
Guard.NotNull(vars, nameof(vars)); Guard.NotNull(vars, nameof(vars));
Guard.NotNullOrEmpty(script, nameof(script)); Guard.NotNullOrEmpty(script, nameof(script));
using (var cts = new CancellationTokenSource(ExecutionTimeout)) using (var cts = new CancellationTokenSource(TimeoutExecution))
{ {
var tcs = new TaskCompletionSource<IJsonValue>(); var tcs = new TaskCompletionSource<IJsonValue>();
@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
Guard.NotNull(vars, nameof(vars)); Guard.NotNull(vars, nameof(vars));
Guard.NotNullOrEmpty(script, nameof(script)); Guard.NotNullOrEmpty(script, nameof(script));
using (var cts = new CancellationTokenSource(ExecutionTimeout)) using (var cts = new CancellationTokenSource(TimeoutExecution))
{ {
var tcs = new TaskCompletionSource<NamedContentData>(); var tcs = new TaskCompletionSource<NamedContentData>();
@ -140,7 +140,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
options.AddObjectConverter(DefaultConverter.Instance); options.AddObjectConverter(DefaultConverter.Instance);
options.SetReferencesResolver(NullPropagation.Instance); options.SetReferencesResolver(NullPropagation.Instance);
options.Strict(); options.Strict();
options.TimeoutInterval(Timeout); options.TimeoutInterval(TimeoutScript);
}); });
if (options.CanDisallow) if (options.CanDisallow)

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOperations.cs

@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
private static readonly MessageDelegate Disallow = message => private static readonly MessageDelegate Disallow = message =>
{ {
message = !string.IsNullOrWhiteSpace(message) ? message : T.Get("common.jsNotAlloweed"); message = !string.IsNullOrWhiteSpace(message) ? message : T.Get("common.jsNotAllowed");
throw new DomainForbiddenException(message); throw new DomainForbiddenException(message);
}; };

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndex.cs

@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText
{ {
switch (command) switch (command)
{ {
case DeleteIndexEntry delete: case DeleteIndexEntry _:
writes.Add( writes.Add(
new DeleteOneModel<MongoTextIndexEntity>( new DeleteOneModel<MongoTextIndexEntity>(
Filter.Eq(x => x.DocId, command.DocId))); Filter.Eq(x => x.DocId, command.DocId)));

11
backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs

@ -56,19 +56,16 @@ namespace Squidex.Domain.Apps.Entities.Apps
Search("Clients", Permissions.AppClientsRead, Search("Clients", Permissions.AppClientsRead,
urlGenerator.ClientsUI, SearchResultType.Setting); urlGenerator.ClientsUI, SearchResultType.Setting);
Search("Contents", Permissions.AppCommon,
urlGenerator.ContentsUI, SearchResultType.Content);
Search("Contributors", Permissions.AppContributorsRead, Search("Contributors", Permissions.AppContributorsRead,
urlGenerator.ContributorsUI, SearchResultType.Setting); urlGenerator.ContributorsUI, SearchResultType.Setting);
Search("Dashboard", Permissions.AppCommon, Search("Dashboard", Permissions.AppUsage,
urlGenerator.DashboardUI, SearchResultType.Dashboard); urlGenerator.DashboardUI, SearchResultType.Dashboard);
Search("Languages", Permissions.AppCommon, Search("Languages", Permissions.AppLanguagesRead,
urlGenerator.LanguagesUI, SearchResultType.Setting); urlGenerator.LanguagesUI, SearchResultType.Setting);
Search("Patterns", Permissions.AppCommon, Search("Patterns", Permissions.AppPatternsRead,
urlGenerator.PatternsUI, SearchResultType.Setting); urlGenerator.PatternsUI, SearchResultType.Setting);
Search("Roles", Permissions.AppRolesRead, Search("Roles", Permissions.AppRolesRead,
@ -77,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
Search("Rules", Permissions.AppRulesRead, Search("Rules", Permissions.AppRulesRead,
urlGenerator.RulesUI, SearchResultType.Rule); urlGenerator.RulesUI, SearchResultType.Rule);
Search("Schemas", Permissions.AppCommon, Search("Schemas", Permissions.AppSchemasRead,
urlGenerator.SchemasUI, SearchResultType.Schema); urlGenerator.SchemasUI, SearchResultType.Schema);
Search("Subscription", Permissions.AppPlansRead, Search("Subscription", Permissions.AppPlansRead,

3
backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
@ -18,6 +17,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands
public bool IsMaster { get; set; } public bool IsMaster { get; set; }
public List<Language>? Fallback { get; set; } public Language[]? Fallback { get; set; }
} }
} }

4
backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateRole.cs

@ -5,6 +5,8 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class UpdateRole : AppUpdateCommand public sealed class UpdateRole : AppUpdateCommand
@ -12,5 +14,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands
public string Name { get; set; } public string Name { get; set; }
public string[] Permissions { get; set; } public string[] Permissions { get; set; }
public JsonObject? Properties { get; set; }
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs

@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
e(T.Get("apps.languages.masterLanguageNotOptional"), nameof(command.IsMaster)); e(T.Get("apps.languages.masterLanguageNotOptional"), nameof(command.IsMaster));
} }
if (command.Fallback?.Count > 0) if (command.Fallback?.Length > 0)
{ {
e(T.Get("apps.languages.masterLanguageNoFallbacks"), nameof(command.Fallback)); e(T.Get("apps.languages.masterLanguageNoFallbacks"), nameof(command.Fallback));
} }

6
backend/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs

@ -78,7 +78,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.State
return UpdateImage(e, ev => null); return UpdateImage(e, ev => null);
case AppPlanChanged e when Is.Change(Plan?.PlanId, e.PlanId): case AppPlanChanged e when Is.Change(Plan?.PlanId, e.PlanId):
return UpdatePlan(e, ev => new AppPlan(ev.Actor, ev.PlanId)); return UpdatePlan(e, ev => ev.ToAppPlan());
case AppPlanReset e when Plan != null: case AppPlanReset e when Plan != null:
return UpdatePlan(e, ev => null); return UpdatePlan(e, ev => null);
@ -120,7 +120,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.State
return UpdateRoles(e, (ev, r) => r.Add(ev.Name)); return UpdateRoles(e, (ev, r) => r.Add(ev.Name));
case AppRoleUpdated e: case AppRoleUpdated e:
return UpdateRoles(e, (ev, r) => r.Update(ev.Name, ev.Permissions)); return UpdateRoles(e, (ev, r) => r.Update(ev.Name, ev.ToPermissions(), ev.Properties));
case AppRoleDeleted e: case AppRoleDeleted e:
return UpdateRoles(e, (ev, r) => r.Remove(ev.Name)); return UpdateRoles(e, (ev, r) => r.Remove(ev.Name));
@ -134,7 +134,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.State
case AppLanguageUpdated e: case AppLanguageUpdated e:
return UpdateLanguages(e, (ev, l) => return UpdateLanguages(e, (ev, l) =>
{ {
l = l.Set(ev.Language, ev.IsOptional, ev.Fallback?.ToArray()); l = l.Set(ev.Language, ev.IsOptional, ev.Fallback);
if (ev.IsMaster) if (ev.IsMaster)
{ {

12
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs

@ -6,7 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic; using System.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Text; using Squidex.Text;
@ -144,8 +144,14 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
} }
}; };
command.Fields ??= new List<UpsertSchemaField>(); if (command.Fields == null)
command.Fields.Add(field); {
command.Fields = new[] { field };
}
else
{
command.Fields = command.Fields.Union(Enumerable.Repeat(field, 1)).ToArray();
}
return field; return field;
} }

6
backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs

@ -36,12 +36,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
if (context.Command is BulkUpdateContents bulkUpdates) if (context.Command is BulkUpdateContents bulkUpdates)
{ {
if (bulkUpdates.Jobs?.Count > 0) if (bulkUpdates.Jobs?.Length > 0)
{ {
var requestContext = contextProvider.Context.WithoutContentEnrichment().WithUnpublished(true); var requestContext = contextProvider.Context.WithoutContentEnrichment().WithUnpublished(true);
var requestedSchema = bulkUpdates.SchemaId.Name; var requestedSchema = bulkUpdates.SchemaId.Name;
var results = new BulkUpdateResultItem[bulkUpdates.Jobs.Count]; var results = new BulkUpdateResultItem[bulkUpdates.Jobs.Length];
var actionBlock = new ActionBlock<int>(async index => var actionBlock = new ActionBlock<int>(async index =>
{ {
@ -115,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2) MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2)
}); });
for (var i = 0; i < bulkUpdates.Jobs.Count; i++) for (var i = 0; i < bulkUpdates.Jobs.Length; i++)
{ {
await actionBlock.SendAsync(i); await actionBlock.SendAsync(i);
} }

3
backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/BulkUpdateContents.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.Commands namespace Squidex.Domain.Apps.Entities.Contents.Commands
@ -24,6 +23,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands
public bool OptimizeValidation { get; set; } public bool OptimizeValidation { get; set; }
public List<BulkUpdateJob>? Jobs { get; set; } public BulkUpdateJob[]? Jobs { get; set; }
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs

@ -114,7 +114,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
CheckErrors(validator); CheckErrors(validator);
} }
private void CheckErrors(ContentValidator validator) private static void CheckErrors(ContentValidator validator)
{ {
if (validator.Errors.Count > 0) if (validator.Errors.Count > 0)
{ {

2
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs

@ -33,8 +33,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public ICommandBus CommandBus { get; } public ICommandBus CommandBus { get; }
public ISemanticLog Log { get; }
public GraphQLExecutionContext(Context context, IServiceProvider resolver) public GraphQLExecutionContext(Context context, IServiceProvider resolver)
: base(context : base(context
.WithoutCleanup() .WithoutCleanup()

10
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentActions.cs

@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
} }
}; };
public static readonly ValueResolver Resolver = new ValueResolver((value, c) => public static readonly ValueResolver Resolver = (value, c) =>
{ {
if (c.Arguments.TryGetValue("path", out var p) && p is string path) if (c.Arguments.TryGetValue("path", out var p) && p is string path)
{ {
@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
} }
return value; return value;
}); };
} }
public static readonly QueryArguments JsonPath = new QueryArguments public static readonly QueryArguments JsonPath = new QueryArguments
@ -124,7 +124,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Description = "Optional OData full text search.", Description = "Optional OData full text search.",
DefaultValue = string.Empty, DefaultValue = string.Empty,
ResolvedType = AllTypes.String ResolvedType = AllTypes.String
}, }
}; };
} }
@ -152,7 +152,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Name = "data", Name = "data",
Description = "The data for the content.", Description = "The data for the content.",
DefaultValue = null, DefaultValue = null,
ResolvedType = new NonNullGraphType(inputType), ResolvedType = new NonNullGraphType(inputType)
}, },
new QueryArgument(AllTypes.None) new QueryArgument(AllTypes.None)
{ {
@ -209,7 +209,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
Name = "data", Name = "data",
Description = "The data for the content.", Description = "The data for the content.",
DefaultValue = null, DefaultValue = null,
ResolvedType = new NonNullGraphType(inputType), ResolvedType = new NonNullGraphType(inputType)
}, },
new QueryArgument(AllTypes.None) new QueryArgument(AllTypes.None)
{ {

5
backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureFieldRules.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
@ -13,11 +12,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class ConfigureFieldRules : SchemaUpdateCommand public sealed class ConfigureFieldRules : SchemaUpdateCommand
{ {
public List<FieldRuleCommand>? FieldRules { get; set; } public FieldRuleCommand[]? FieldRules { get; set; }
public FieldRules ToFieldRules() public FieldRules ToFieldRules()
{ {
if (FieldRules?.Count > 0) if (FieldRules?.Length > 0)
{ {
return new FieldRules(FieldRules.Select(x => x.ToFieldRule()).ToList()); return new FieldRules(FieldRules.Select(x => x.ToFieldRule()).ToList());
} }

7
backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs

@ -9,8 +9,7 @@ using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using FieldRules = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.FieldRuleCommand>; using SchemaField = Squidex.Domain.Apps.Entities.Schemas.Commands.UpsertSchemaField;
using SchemaFields = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.UpsertSchemaField>;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
@ -26,13 +25,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands
public bool IsSingleton { get; set; } public bool IsSingleton { get; set; }
public SchemaFields Fields { get; set; } public SchemaField[]? Fields { get; set; }
public FieldNames? FieldsInReferences { get; set; } public FieldNames? FieldsInReferences { get; set; }
public FieldNames? FieldsInLists { get; set; } public FieldNames? FieldsInLists { get; set; }
public FieldRules? FieldRules { get; set; } public FieldRuleCommand[]? FieldRules { get; set; }
public SchemaScripts? Scripts { get; set; } public SchemaScripts? Scripts { get; set; }

9
backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/IUpsertCommand.cs

@ -9,8 +9,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using FieldRules = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.FieldRuleCommand>; using SchemaField = Squidex.Domain.Apps.Entities.Schemas.Commands.UpsertSchemaField;
using SchemaFields = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.UpsertSchemaField>;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
@ -20,13 +19,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands
string Category { get; set; } string Category { get; set; }
SchemaFields Fields { get; set; } SchemaField[]? Fields { get; set; }
FieldNames? FieldsInReferences { get; set; } FieldNames? FieldsInReferences { get; set; }
FieldNames? FieldsInLists { get; set; } FieldNames? FieldsInLists { get; set; }
FieldRules? FieldRules { get; set; } FieldRuleCommand[]? FieldRules { get; set; }
SchemaScripts? Scripts { get; set; } SchemaScripts? Scripts { get; set; }
@ -85,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands
var field = eventField.Properties.CreateRootField(totalFields, eventField.Name, partitioning); var field = eventField.Properties.CreateRootField(totalFields, eventField.Name, partitioning);
if (field is ArrayField arrayField && eventField.Nested?.Count > 0) if (field is ArrayField arrayField && eventField.Nested?.Length > 0)
{ {
foreach (var nestedEventField in eventField.Nested) foreach (var nestedEventField in eventField.Nested)
{ {

4
backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs

@ -5,12 +5,10 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class ReorderFields : ParentFieldCommand public sealed class ReorderFields : ParentFieldCommand
{ {
public List<long> FieldIds { get; set; } public long[] FieldIds { get; set; }
} }
} }

7
backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs

@ -8,8 +8,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using FieldRules = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.FieldRuleCommand>; using SchemaField = Squidex.Domain.Apps.Entities.Schemas.Commands.UpsertSchemaField;
using SchemaFields = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.UpsertSchemaField>;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
@ -23,13 +22,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands
public string Category { get; set; } public string Category { get; set; }
public SchemaFields Fields { get; set; } public SchemaField[]? Fields { get; set; }
public FieldNames? FieldsInReferences { get; set; } public FieldNames? FieldsInReferences { get; set; }
public FieldNames? FieldsInLists { get; set; } public FieldNames? FieldsInLists { get; set; }
public FieldRules? FieldRules { get; set; } public FieldRuleCommand[]? FieldRules { get; set; }
public SchemaScripts? Scripts { get; set; } public SchemaScripts? Scripts { get; set; }

4
backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs

@ -5,14 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class UpsertSchemaField : UpsertSchemaFieldBase public sealed class UpsertSchemaField : UpsertSchemaFieldBase
{ {
public string Partitioning { get; set; } public string Partitioning { get; set; }
public List<UpsertSchemaNestedField> Nested { get; set; } public UpsertSchemaNestedField[]? Nested { get; set; }
} }
} }

12
backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs

@ -143,7 +143,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
private static void ValidateUpsert(IUpsertCommand command, AddValidation e) private static void ValidateUpsert(IUpsertCommand command, AddValidation e)
{ {
if (command.Fields?.Count > 0) if (command.Fields?.Length > 0)
{ {
command.Fields.Foreach((field, fieldIndex) => command.Fields.Foreach((field, fieldIndex) =>
{ {
@ -182,7 +182,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
ValidateField(field, prefix, e); ValidateField(field, prefix, e);
if (field.Nested?.Count > 0) if (field.Nested?.Length > 0)
{ {
if (field.Properties is ArrayFieldProperties) if (field.Properties is ArrayFieldProperties)
{ {
@ -193,7 +193,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
ValidateNestedField(nestedField, nestedPrefix, e); ValidateNestedField(nestedField, nestedPrefix, e);
}); });
} }
else if (field.Nested.Count > 0) else if (field.Nested.Length > 0)
{ {
e(T.Get("schemas.onlyArraysHaveNested"), $"{prefix}.{nameof(field.Partitioning)}"); e(T.Get("schemas.onlyArraysHaveNested"), $"{prefix}.{nameof(field.Partitioning)}");
} }
@ -292,7 +292,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
} }
} }
private static void ValidateFieldRules(List<FieldRuleCommand>? fieldRules, string path, AddValidation e) private static void ValidateFieldRules(FieldRuleCommand[]? fieldRules, string path, AddValidation e)
{ {
fieldRules?.Foreach((rule, ruleIndex) => fieldRules?.Foreach((rule, ruleIndex) =>
{ {
@ -318,7 +318,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
var fieldPrefix = $"{path}[{fieldIndex}]"; var fieldPrefix = $"{path}[{fieldIndex}]";
var field = command?.Fields?.Find(x => x.Name == fieldName); var field = command?.Fields?.FirstOrDefault(x => x.Name == fieldName);
if (string.IsNullOrWhiteSpace(fieldName)) if (string.IsNullOrWhiteSpace(fieldName))
{ {
@ -356,7 +356,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
private static void ValidateFieldIds<TField>(ReorderFields c, IReadOnlyDictionary<long, TField> fields, AddValidation e) private static void ValidateFieldIds<TField>(ReorderFields c, IReadOnlyDictionary<long, TField> fields, AddValidation e)
{ {
if (c.FieldIds != null && (c.FieldIds.Count != fields.Count || c.FieldIds.Any(x => !fields.ContainsKey(x)))) if (c.FieldIds != null && (c.FieldIds.Length != fields.Count || c.FieldIds.Any(x => !fields.ContainsKey(x))))
{ {
e(T.Get("schemas.fieldsNotCovered"), nameof(c.FieldIds)); e(T.Get("schemas.fieldsNotCovered"), nameof(c.FieldIds));
} }

4
backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs

@ -211,12 +211,12 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
return schema; return schema;
} }
private string GetCacheKey(DomainId appId, string name) private static string GetCacheKey(DomainId appId, string name)
{ {
return $"SCHEMAS_NAME_{appId}_{name}"; return $"SCHEMAS_NAME_{appId}_{name}";
} }
private string GetCacheKey(DomainId appId, DomainId id) private static string GetCacheKey(DomainId appId, DomainId id)
{ {
return $"SCHEMAS_ID_{appId}_{id}"; return $"SCHEMAS_ID_{appId}_{id}";
} }

11
backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs

@ -59,17 +59,6 @@ namespace Squidex.Domain.Apps.Entities.Schemas
Add(c); Add(c);
long id;
if (c.ParentFieldId == null)
{
id = Snapshot.SchemaDef.FieldsByName[c.Name].Id;
}
else
{
id = ((IArrayField)Snapshot.SchemaDef.FieldsById[c.ParentFieldId.Value]).FieldsByName[c.Name].Id;
}
return Snapshot; return Snapshot;
}); });

3
backend/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Linq;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Events.Schemas; using Squidex.Domain.Apps.Events.Schemas;
@ -138,7 +139,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State
case SchemaFieldsReordered e: case SchemaFieldsReordered e:
{ {
SchemaDef = SchemaDef.ReorderFields(e.FieldIds, e.ParentFieldId?.Id); SchemaDef = SchemaDef.ReorderFields(e.FieldIds.ToList(), e.ParentFieldId?.Id);
break; break;
} }

3
backend/src/Squidex.Domain.Apps.Events/Apps/AppLanguageUpdated.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -20,6 +19,6 @@ namespace Squidex.Domain.Apps.Events.Apps
public bool IsMaster { get; set; } public bool IsMaster { get; set; }
public List<Language>? Fallback { get; set; } public Language[]? Fallback { get; set; }
} }
} }

6
backend/src/Squidex.Domain.Apps.Events/Apps/AppPlanChanged.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Apps namespace Squidex.Domain.Apps.Events.Apps
@ -13,5 +14,10 @@ namespace Squidex.Domain.Apps.Events.Apps
public sealed class AppPlanChanged : AppEvent public sealed class AppPlanChanged : AppEvent
{ {
public string PlanId { get; set; } public string PlanId { get; set; }
public AppPlan ToAppPlan()
{
return new AppPlan(Actor, PlanId);
}
} }
} }

9
backend/src/Squidex.Domain.Apps.Events/Apps/AppRoleUpdated.cs

@ -6,6 +6,8 @@
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Security;
namespace Squidex.Domain.Apps.Events.Apps namespace Squidex.Domain.Apps.Events.Apps
{ {
@ -15,5 +17,12 @@ namespace Squidex.Domain.Apps.Events.Apps
public string Name { get; set; } public string Name { get; set; }
public string[] Permissions { get; set; } public string[] Permissions { get; set; }
public JsonObject? Properties { get; set; }
public PermissionSet ToPermissions()
{
return new PermissionSet(Permissions);
}
} }
} }

4
backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreatedField.cs

@ -5,14 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Events.Schemas namespace Squidex.Domain.Apps.Events.Schemas
{ {
public sealed class SchemaCreatedField : SchemaCreatedFieldBase public sealed class SchemaCreatedField : SchemaCreatedFieldBase
{ {
public string Partitioning { get; set; } public string Partitioning { get; set; }
public List<SchemaCreatedNestedField> Nested { get; set; } public SchemaCreatedNestedField[] Nested { get; set; }
} }
} }

3
backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaFieldsReordered.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Schemas namespace Squidex.Domain.Apps.Events.Schemas
@ -13,6 +12,6 @@ namespace Squidex.Domain.Apps.Events.Schemas
[EventType(nameof(SchemaFieldsReordered))] [EventType(nameof(SchemaFieldsReordered))]
public sealed class SchemaFieldsReordered : ParentFieldEvent public sealed class SchemaFieldsReordered : ParentFieldEvent
{ {
public List<long> FieldIds { get; set; } public long[] FieldIds { get; set; }
} }
} }

4
backend/src/Squidex.Infrastructure.MongoDb/Assets/MongoGridFsAssetStore.cs

@ -49,9 +49,9 @@ namespace Squidex.Infrastructure.Assets
{ {
var name = GetFileName(fileName, nameof(fileName)); var name = GetFileName(fileName, nameof(fileName));
var find = await bucket.FindAsync(Builders<GridFSFileInfo<string>>.Filter.Eq(x => x.Id, name), cancellationToken: ct); var query = await bucket.FindAsync(Builders<GridFSFileInfo<string>>.Filter.Eq(x => x.Id, name), cancellationToken: ct);
var file = await find.FirstOrDefaultAsync(ct); var file = await query.FirstOrDefaultAsync(cancellationToken: ct);
if (file == null) if (file == null)
{ {

18
backend/src/Squidex.Infrastructure/Assets/ImageFormat.cs

@ -0,0 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Assets
{
public enum ImageFormat
{
Auto,
PNG,
JPEG,
TGA,
GIF
}
}

2
backend/src/Squidex.Infrastructure/Assets/ImageInfo.cs

@ -9,6 +9,8 @@ namespace Squidex.Infrastructure.Assets
{ {
public sealed class ImageInfo public sealed class ImageInfo
{ {
public string? Format { get; set; }
public int PixelWidth { get; } public int PixelWidth { get; }
public int PixelHeight { get; } public int PixelHeight { get; }

57
backend/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs

@ -10,7 +10,11 @@ using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using ISResizeMode = SixLabors.ImageSharp.Processing.ResizeMode; using ISResizeMode = SixLabors.ImageSharp.Processing.ResizeMode;
@ -30,7 +34,7 @@ namespace Squidex.Infrastructure.Assets.ImageSharp
if (!options.IsValid) if (!options.IsValid)
{ {
source.CopyTo(destination); await source.CopyToAsync(destination);
return; return;
} }
@ -44,17 +48,7 @@ namespace Squidex.Infrastructure.Assets.ImageSharp
{ {
using (var image = Image.Load(source, out var format)) using (var image = Image.Load(source, out var format))
{ {
var encoder = Configuration.Default.ImageFormatsManager.FindEncoder(format); var encoder = GetEncoder(options, format);
if (encoder == null)
{
throw new NotSupportedException();
}
if (options.Quality.HasValue && (encoder is JpegEncoder || !options.KeepFormat))
{
encoder = new JpegEncoder { Quality = options.Quality.Value };
}
image.Mutate(x => x.AutoOrient()); image.Mutate(x => x.AutoOrient());
@ -90,7 +84,7 @@ namespace Squidex.Infrastructure.Assets.ImageSharp
image.Mutate(x => x.Resize(resizeOptions)); image.Mutate(x => x.Resize(resizeOptions));
} }
image.Save(destination, encoder); await image.SaveAsync(destination, encoder);
} }
} }
finally finally
@ -99,6 +93,39 @@ namespace Squidex.Infrastructure.Assets.ImageSharp
} }
} }
private static IImageEncoder GetEncoder(ResizeOptions options, IImageFormat? format)
{
var encoder = Configuration.Default.ImageFormatsManager.FindEncoder(format);
if (encoder == null)
{
throw new NotSupportedException();
}
if (options.Quality.HasValue && (encoder is JpegEncoder || !options.KeepFormat) && options.Format == ImageFormat.Auto)
{
encoder = new JpegEncoder { Quality = options.Quality.Value };
}
else if (options.Format == ImageFormat.JPEG)
{
encoder = new JpegEncoder();
}
else if (options.Format == ImageFormat.PNG)
{
encoder = new PngEncoder();
}
else if (options.Format == ImageFormat.TGA)
{
encoder = new TgaEncoder();
}
else if (options.Format == ImageFormat.GIF)
{
encoder = new GifEncoder();
}
return encoder;
}
public Task<ImageInfo?> GetImageInfoAsync(Stream source) public Task<ImageInfo?> GetImageInfoAsync(Stream source)
{ {
Guard.NotNull(source, nameof(source)); Guard.NotNull(source, nameof(source));
@ -107,11 +134,13 @@ namespace Squidex.Infrastructure.Assets.ImageSharp
try try
{ {
var image = Image.Identify(source); var image = Image.Identify(source, out var format);
if (image != null) if (image != null)
{ {
result = GetImageInfo(image); result = GetImageInfo(image);
result.Format = format.Name;
} }
} }
catch catch

10
backend/src/Squidex.Infrastructure/Assets/ResizeOptions.cs

@ -11,6 +11,8 @@ namespace Squidex.Infrastructure.Assets
{ {
public sealed class ResizeOptions public sealed class ResizeOptions
{ {
public ImageFormat Format { get; set; }
public ResizeMode Mode { get; set; } public ResizeMode Mode { get; set; }
public int? Width { get; set; } public int? Width { get; set; }
@ -27,7 +29,7 @@ namespace Squidex.Infrastructure.Assets
public bool IsValid public bool IsValid
{ {
get { return Width > 0 || Height > 0 || Quality > 0; } get { return Width > 0 || Height > 0 || Quality > 0 || Format != ImageFormat.Auto; }
} }
public override string ToString() public override string ToString()
@ -58,6 +60,12 @@ namespace Squidex.Infrastructure.Assets
sb.Append(FocusY); sb.Append(FocusY);
} }
if (Format != ImageFormat.Auto)
{
sb.Append("_format_");
sb.Append(Format.ToString());
}
return sb.ToString(); return sb.ToString();
} }
} }

12
backend/src/Squidex.Infrastructure/EventSourcing/Grains/BatchSubscriber.cs

@ -22,7 +22,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
private readonly IEventSubscription eventSubscription; private readonly IEventSubscription eventSubscription;
private readonly IDataflowBlock pipelineEnd; private readonly IDataflowBlock pipelineEnd;
public object Sender public object? Sender
{ {
get { return eventSubscription.Sender!; } get { return eventSubscription.Sender!; }
} }
@ -86,21 +86,21 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
var handle = new ActionBlock<IList<Job>>(async jobs => var handle = new ActionBlock<IList<Job>>(async jobs =>
{ {
var sender = eventSubscription.Sender;
foreach (var jobsBySender in jobs.GroupBy<Job, object>(x => x.Sender)) foreach (var jobsBySender in jobs.GroupBy<Job, object>(x => x.Sender))
{ {
var sender = jobsBySender.Key; if (sender != null && ReferenceEquals(jobsBySender.Key, sender))
if (ReferenceEquals(sender, eventSubscription.Sender))
{ {
var exception = jobs.FirstOrDefault(x => x.Exception != null)?.Exception; var exception = jobs.FirstOrDefault(x => x.Exception != null)?.Exception;
if (exception != null) if (exception != null)
{ {
await grain.OnErrorAsync(Sender, exception); await grain.OnErrorAsync(sender, exception);
} }
else else
{ {
await grain.OnEventsAsync(Sender, GetEvents(jobsBySender), GetPosition(jobsBySender)); await grain.OnEventsAsync(sender, GetEvents(jobsBySender), GetPosition(jobsBySender));
} }
} }
} }

2
backend/src/Squidex.Infrastructure/Log/ISemanticLog.cs

@ -11,7 +11,7 @@ namespace Squidex.Infrastructure.Log
{ {
public delegate void LogFormatter(IObjectWriter writer); public delegate void LogFormatter(IObjectWriter writer);
public delegate void LogFormatter<T>(T context, IObjectWriter writer); public delegate void LogFormatter<in T>(T context, IObjectWriter writer);
public interface ISemanticLog public interface ISemanticLog
{ {

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

@ -60,12 +60,12 @@ namespace Squidex.Infrastructure.Tasks
var timer = new Timer(_ => batchBlock.TriggerBatch()); var timer = new Timer(_ => batchBlock.TriggerBatch());
var timerBlock = new TransformBlock<T, T>((T value) => var timerBlock = new TransformBlock<T, T>(value =>
{ {
timer.Change(timeout, Timeout.Infinite); timer.Change(timeout, Timeout.Infinite);
return value; return value;
}, new ExecutionDataflowBlockOptions() }, new ExecutionDataflowBlockOptions
{ {
BoundedCapacity = 1, BoundedCapacity = 1,
CancellationToken = dataflowBlockOptions.CancellationToken, CancellationToken = dataflowBlockOptions.CancellationToken,
@ -76,7 +76,7 @@ namespace Squidex.Infrastructure.Tasks
TaskScheduler = dataflowBlockOptions.TaskScheduler TaskScheduler = dataflowBlockOptions.TaskScheduler
}); });
timerBlock.LinkTo(batchBlock, new DataflowLinkOptions() timerBlock.LinkTo(batchBlock, new DataflowLinkOptions
{ {
PropagateCompletion = true PropagateCompletion = true
}); });

22
backend/src/Squidex.Shared/Permissions.cs

@ -50,13 +50,28 @@ namespace Squidex.Shared
public const string AdminUsersLock = "squidex.admin.users.lock"; public const string AdminUsersLock = "squidex.admin.users.lock";
public const string App = "squidex.apps.{app}"; public const string App = "squidex.apps.{app}";
public const string AppCommon = "squidex.apps.{app}.common";
public const string AppDelete = "squidex.apps.{app}.delete"; public const string AppDelete = "squidex.apps.{app}.delete";
public const string AppUpdate = "squidex.apps.{app}.update"; public const string AppUpdate = "squidex.apps.{app}.update";
public const string AppUpdateImage = "squidex.apps.{app}.update"; public const string AppUpdateImage = "squidex.apps.{app}.update";
public const string AppUpdateGeneral = "squidex.apps.{app}.general"; public const string AppUpdateGeneral = "squidex.apps.{app}.general";
public const string AppHistory = "squidex.apps.{app}.history";
public const string AppPing = "squidex.apps.{app}.ping";
public const string AppSearch = "squidex.apps.{app}.search";
public const string AppTranslate = "squidex.apps.{app}.translate";
public const string AppUsage = "squidex.apps.{app}.usage";
public const string AppComments = "squidex.apps.{app}.comments";
public const string AppCommentsRead = "squidex.apps.{app}.comments.read";
public const string AppCommentsCreate = "squidex.apps.{app}.comments.create";
public const string AppCommentsUpdate = "squidex.apps.{app}.comments.update";
public const string AppCommentsDelete = "squidex.apps.{app}.comments.delete";
public const string AppClients = "squidex.apps.{app}.clients"; public const string AppClients = "squidex.apps.{app}.clients";
public const string AppClientsRead = "squidex.apps.{app}.clients.read"; public const string AppClientsRead = "squidex.apps.{app}.clients.read";
public const string AppClientsCreate = "squidex.apps.{app}.clients.create"; public const string AppClientsCreate = "squidex.apps.{app}.clients.create";
@ -69,6 +84,7 @@ namespace Squidex.Shared
public const string AppContributorsRevoke = "squidex.apps.{app}.contributors.revoke"; public const string AppContributorsRevoke = "squidex.apps.{app}.contributors.revoke";
public const string AppLanguages = "squidex.apps.{app}.languages"; public const string AppLanguages = "squidex.apps.{app}.languages";
public const string AppLanguagesRead = "squidex.apps.{app}.languages.read";
public const string AppLanguagesCreate = "squidex.apps.{app}.languages.create"; public const string AppLanguagesCreate = "squidex.apps.{app}.languages.create";
public const string AppLanguagesUpdate = "squidex.apps.{app}.languages.update"; public const string AppLanguagesUpdate = "squidex.apps.{app}.languages.update";
public const string AppLanguagesDelete = "squidex.apps.{app}.languages.delete"; public const string AppLanguagesDelete = "squidex.apps.{app}.languages.delete";
@ -80,6 +96,7 @@ namespace Squidex.Shared
public const string AppRolesDelete = "squidex.apps.{app}.roles.delete"; public const string AppRolesDelete = "squidex.apps.{app}.roles.delete";
public const string AppPatterns = "squidex.apps.{app}.patterns"; public const string AppPatterns = "squidex.apps.{app}.patterns";
public const string AppPatternsRead = "squidex.apps.{app}.patterns.read";
public const string AppPatternsCreate = "squidex.apps.{app}.patterns.create"; public const string AppPatternsCreate = "squidex.apps.{app}.patterns.create";
public const string AppPatternsUpdate = "squidex.apps.{app}.patterns.update"; public const string AppPatternsUpdate = "squidex.apps.{app}.patterns.update";
public const string AppPatternsDelete = "squidex.apps.{app}.patterns.delete"; public const string AppPatternsDelete = "squidex.apps.{app}.patterns.delete";
@ -115,6 +132,7 @@ namespace Squidex.Shared
public const string AppRulesDelete = "squidex.apps.{app}.rules.delete"; public const string AppRulesDelete = "squidex.apps.{app}.rules.delete";
public const string AppSchemas = "squidex.apps.{app}.schemas"; public const string AppSchemas = "squidex.apps.{app}.schemas";
public const string AppSchemasRead = "squidex.apps.{app}.schemas.read";
public const string AppSchemasCreate = "squidex.apps.{app}.schemas.create"; public const string AppSchemasCreate = "squidex.apps.{app}.schemas.create";
public const string AppSchemasUpdate = "squidex.apps.{app}.schemas.{name}.update"; public const string AppSchemasUpdate = "squidex.apps.{app}.schemas.{name}.update";
public const string AppSchemasScripts = "squidex.apps.{app}.schemas.{name}.scripts"; public const string AppSchemasScripts = "squidex.apps.{app}.schemas.{name}.scripts";
@ -130,8 +148,6 @@ namespace Squidex.Shared
public const string AppContentsVersionDelete = "squidex.apps.{app}.contents.{name}.version.delete"; public const string AppContentsVersionDelete = "squidex.apps.{app}.contents.{name}.version.delete";
public const string AppContentsDelete = "squidex.apps.{app}.contents.{name}.delete"; public const string AppContentsDelete = "squidex.apps.{app}.contents.{name}.delete";
public const string AppApi = "squidex.apps.{app}.api";
static Permissions() static Permissions()
{ {
foreach (var field in typeof(Permissions).GetFields(BindingFlags.Public | BindingFlags.Static)) foreach (var field in typeof(Permissions).GetFields(BindingFlags.Public | BindingFlags.Static))

2
backend/src/Squidex.Shared/Texts.it.resx

@ -283,7 +283,7 @@
<data name="common.jsError" xml:space="preserve"> <data name="common.jsError" xml:space="preserve">
<value>Esecuzione dello script fallita, Errore Javascript: {message}</value> <value>Esecuzione dello script fallita, Errore Javascript: {message}</value>
</data> </data>
<data name="common.jsNotAlloweed" xml:space="preserve"> <data name="common.jsNotAllowed" xml:space="preserve">
<value>Uno script ha proibito l'operazione.</value> <value>Uno script ha proibito l'operazione.</value>
</data> </data>
<data name="common.jsParseError" xml:space="preserve"> <data name="common.jsParseError" xml:space="preserve">

2
backend/src/Squidex.Shared/Texts.nl.resx

@ -283,7 +283,7 @@
<data name="common.jsError" xml:space="preserve"> <data name="common.jsError" xml:space="preserve">
<value>Kan script niet uitvoeren met Javascript-fout: {message}</value> <value>Kan script niet uitvoeren met Javascript-fout: {message}</value>
</data> </data>
<data name="common.jsNotAlloweed" xml:space="preserve"> <data name="common.jsNotAllowed" xml:space="preserve">
<value>Script heeft de bewerking verboden.</value> <value>Script heeft de bewerking verboden.</value>
</data> </data>
<data name="common.jsParseError" xml:space="preserve"> <data name="common.jsParseError" xml:space="preserve">

2
backend/src/Squidex.Shared/Texts.resx

@ -283,7 +283,7 @@
<data name="common.jsError" xml:space="preserve"> <data name="common.jsError" xml:space="preserve">
<value>Failed to execute script with Javascript error: {message}</value> <value>Failed to execute script with Javascript error: {message}</value>
</data> </data>
<data name="common.jsNotAlloweed" xml:space="preserve"> <data name="common.jsNotAllowed" xml:space="preserve">
<value>Script has forbidden the operation.</value> <value>Script has forbidden the operation.</value>
</data> </data>
<data name="common.jsParseError" xml:space="preserve"> <data name="common.jsParseError" xml:space="preserve">

22
backend/src/Squidex.Web/Pipeline/AppResolver.cs

@ -41,9 +41,9 @@ namespace Squidex.Web.Pipeline
if (!string.IsNullOrWhiteSpace(appName)) if (!string.IsNullOrWhiteSpace(appName))
{ {
var canCache = !user.IsInClient(DefaultClients.Frontend); var isFrontend = user.IsInClient(DefaultClients.Frontend);
var app = await appProvider.GetAppAsync(appName, canCache); var app = await appProvider.GetAppAsync(appName, !isFrontend);
if (app == null) if (app == null)
{ {
@ -51,16 +51,16 @@ namespace Squidex.Web.Pipeline
return; return;
} }
var (role, permissions) = FindByOpenIdSubject(app, user); var (role, permissions) = FindByOpenIdSubject(app, user, isFrontend);
if (permissions == null) if (permissions == null)
{ {
(role, permissions) = FindByOpenIdClient(app, user); (role, permissions) = FindByOpenIdClient(app, user, isFrontend);
} }
if (permissions == null) if (permissions == null)
{ {
(role, permissions) = FindAnonymousClient(app); (role, permissions) = FindAnonymousClient(app, isFrontend);
} }
if (permissions != null) if (permissions != null)
@ -132,7 +132,7 @@ namespace Squidex.Web.Pipeline
return context.ActionDescriptor.EndpointMetadata.Any(x => x is AllowAnonymousAttribute); return context.ActionDescriptor.EndpointMetadata.Any(x => x is AllowAnonymousAttribute);
} }
private static (string?, PermissionSet?) FindByOpenIdClient(IAppEntity app, ClaimsPrincipal user) private static (string?, PermissionSet?) FindByOpenIdClient(IAppEntity app, ClaimsPrincipal user, bool isFrontend)
{ {
var (appName, clientId) = user.GetClient(); var (appName, clientId) = user.GetClient();
@ -141,7 +141,7 @@ namespace Squidex.Web.Pipeline
return (null, null); return (null, null);
} }
if (clientId != null && app.Clients.TryGetValue(clientId, out var client) && app.Roles.TryGet(app.Name, client.Role, out var role)) if (clientId != null && app.Clients.TryGetValue(clientId, out var client) && app.Roles.TryGet(app.Name, client.Role, isFrontend, out var role))
{ {
return (client.Role, role.Permissions); return (client.Role, role.Permissions);
} }
@ -149,11 +149,11 @@ namespace Squidex.Web.Pipeline
return (null, null); return (null, null);
} }
private static (string?, PermissionSet?) FindAnonymousClient(IAppEntity app) private static (string?, PermissionSet?) FindAnonymousClient(IAppEntity app, bool isFrontend)
{ {
var client = app.Clients.Values.FirstOrDefault(x => x.AllowAnonymous); var client = app.Clients.Values.FirstOrDefault(x => x.AllowAnonymous);
if (client != null && app.Roles.TryGet(app.Name, client.Role, out var role)) if (client != null && app.Roles.TryGet(app.Name, client.Role, isFrontend, out var role))
{ {
return (client.Role, role.Permissions); return (client.Role, role.Permissions);
} }
@ -161,11 +161,11 @@ namespace Squidex.Web.Pipeline
return (null, null); return (null, null);
} }
private static (string?, PermissionSet?) FindByOpenIdSubject(IAppEntity app, ClaimsPrincipal user) private static (string?, PermissionSet?) FindByOpenIdSubject(IAppEntity app, ClaimsPrincipal user, bool isFrontend)
{ {
var subjectId = user.OpenIdSubject(); var subjectId = user.OpenIdSubject();
if (subjectId != null && app.Contributors.TryGetValue(subjectId, out var roleName) && app.Roles.TryGet(app.Name, roleName, out var role)) if (subjectId != null && app.Contributors.TryGetValue(subjectId, out var roleName) && app.Roles.TryGet(app.Name, roleName, isFrontend, out var role))
{ {
return (roleName, role.Permissions); return (roleName, role.Permissions);
} }

25
backend/src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs

@ -17,6 +17,7 @@ using Squidex.Areas.Api.Controllers.Rules.Models;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Areas.Api.Config.OpenApi namespace Squidex.Areas.Api.Config.OpenApi
{ {
@ -77,18 +78,24 @@ namespace Squidex.Areas.Api.Config.OpenApi
CreateStringMap<RefToken>(), CreateStringMap<RefToken>(),
CreateStringMap<Status>(), CreateStringMap<Status>(),
new PrimitiveTypeMapper(typeof(AssetMetadata), schema => CreateObjectMap<JsonObject>(),
{ CreateObjectMap<AssetMetadata>()
schema.Type = JsonObjectType.Object;
schema.AdditionalPropertiesSchema = new JsonSchema
{
Description = "Any JSON type"
};
})
}; };
} }
private static ITypeMapper CreateObjectMap<T>()
{
return new PrimitiveTypeMapper(typeof(T), schema =>
{
schema.Type = JsonObjectType.Object;
schema.AdditionalPropertiesSchema = new JsonSchema
{
Description = "Any JSON type"
};
});
}
private static ITypeMapper CreateStringMap<T>(string? format = null) private static ITypeMapper CreateStringMap<T>(string? format = null)
{ {
return new PrimitiveTypeMapper(typeof(T), schema => return new PrimitiveTypeMapper(typeof(T), schema =>

2
backend/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs

@ -48,7 +48,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpGet] [HttpGet]
[Route("apps/{app}/contributors/")] [Route("apps/{app}/contributors/")]
[ProducesResponseType(typeof(ContributorsDto), 200)] [ProducesResponseType(typeof(ContributorsDto), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppContributorsRead)]
[ApiCosts(0)] [ApiCosts(0)]
public IActionResult GetContributors(string app) public IActionResult GetContributors(string app)
{ {

2
backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs

@ -41,7 +41,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpGet] [HttpGet]
[Route("apps/{app}/languages/")] [Route("apps/{app}/languages/")]
[ProducesResponseType(typeof(AppLanguagesDto), 200)] [ProducesResponseType(typeof(AppLanguagesDto), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppLanguagesRead)]
[ApiCosts(0)] [ApiCosts(0)]
public IActionResult GetLanguages(string app) public IActionResult GetLanguages(string app)
{ {

2
backend/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs

@ -42,7 +42,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[HttpGet] [HttpGet]
[Route("apps/{app}/patterns/")] [Route("apps/{app}/patterns/")]
[ProducesResponseType(typeof(PatternsDto), 200)] [ProducesResponseType(typeof(PatternsDto), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppPatternsRead)]
[ApiCosts(0)] [ApiCosts(0)]
public IActionResult GetPatterns(string app) public IActionResult GetPatterns(string app)
{ {

14
backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs

@ -80,7 +80,9 @@ namespace Squidex.Areas.Api.Controllers.Apps
var response = Deferred.Response(() => var response = Deferred.Response(() =>
{ {
return apps.OrderBy(x => x.Name).Select(a => AppDto.FromApp(a, userOrClientId, appPlansProvider, Resources)).ToArray(); var isFrontend = HttpContext.User.IsInClient(DefaultClients.Frontend);
return apps.OrderBy(x => x.Name).Select(a => AppDto.FromApp(a, userOrClientId, isFrontend, appPlansProvider, Resources)).ToArray();
}); });
Response.Headers[HeaderNames.ETag] = apps.ToEtag(); Response.Headers[HeaderNames.ETag] = apps.ToEtag();
@ -107,7 +109,9 @@ namespace Squidex.Areas.Api.Controllers.Apps
{ {
var userOrClientId = HttpContext.User.UserOrClientId()!; var userOrClientId = HttpContext.User.UserOrClientId()!;
return AppDto.FromApp(App, userOrClientId, appPlansProvider, Resources); var isFrontend = HttpContext.User.IsInClient(DefaultClients.Frontend);
return AppDto.FromApp(App, userOrClientId, isFrontend, appPlansProvider, Resources);
}); });
Response.Headers[HeaderNames.ETag] = App.ToEtag(); Response.Headers[HeaderNames.ETag] = App.ToEtag();
@ -212,7 +216,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
try try
{ {
await assetStore.DownloadAsync(resizedAsset, body); await assetStore.DownloadAsync(resizedAsset, body, ct: ct);
} }
catch (AssetNotFoundException) catch (AssetNotFoundException)
{ {
@ -298,8 +302,10 @@ namespace Squidex.Areas.Api.Controllers.Apps
var userOrClientId = HttpContext.User.UserOrClientId()!; var userOrClientId = HttpContext.User.UserOrClientId()!;
var isFrontend = HttpContext.User.IsInClient(DefaultClients.Frontend);
var result = context.Result<IAppEntity>(); var result = context.Result<IAppEntity>();
var response = AppDto.FromApp(result, userOrClientId, appPlansProvider, Resources); var response = AppDto.FromApp(result, userOrClientId, isFrontend, appPlansProvider, Resources);
return response; return response;
} }

37
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using NodaTime; using NodaTime;
using Squidex.Areas.Api.Controllers.Assets; using Squidex.Areas.Api.Controllers.Assets;
@ -16,6 +17,7 @@ using Squidex.Areas.Api.Controllers.Schemas;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Plans; using Squidex.Domain.Apps.Entities.Apps.Plans;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
@ -73,6 +75,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// <summary> /// <summary>
/// Indicates if the user can access the api. /// Indicates if the user can access the api.
/// </summary> /// </summary>
[Obsolete("Usage role properties")]
public bool CanAccessApi { get; set; } public bool CanAccessApi { get; set; }
/// <summary> /// <summary>
@ -90,17 +93,30 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// </summary> /// </summary>
public string? PlanUpgrade { get; set; } public string? PlanUpgrade { get; set; }
public static AppDto FromApp(IAppEntity app, string userId, IAppPlansProvider plans, Resources resources) /// <summary>
/// The properties from the role.
/// </summary>
[LocalizedRequired]
public JsonObject RoleProperties { get; set; }
public static AppDto FromApp(IAppEntity app, string userId, bool isFrontend, IAppPlansProvider plans, Resources resources)
{ {
var permissions = GetPermissions(app, userId); var permissions = GetPermissions(app, userId, isFrontend);
var result = SimpleMapper.Map(app, new AppDto()); var result = SimpleMapper.Map(app, new AppDto());
result.Permissions = permissions.ToIds(); result.Permissions = permissions.ToIds();
if (resources.Includes(P.ForApp(P.AppApi, app.Name), permissions)) result.SetPlan(app, plans, resources, permissions);
result.SetImage(app, resources);
if (app.Contributors.TryGetValue(userId, out var roleName) && app.Roles.TryGet(app.Name, roleName, isFrontend, out var role))
{
result.RoleProperties = role.Properties;
}
else
{ {
result.CanAccessApi = true; result.RoleProperties = JsonValue.Object();
} }
if (resources.Includes(P.ForApp(P.AppContents, app.Name), permissions)) if (resources.Includes(P.ForApp(P.AppContents, app.Name), permissions))
@ -108,17 +124,14 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
result.CanAccessContent = true; result.CanAccessContent = true;
} }
result.SetPlan(app, plans, resources, permissions);
result.SetImage(app, resources);
return result.CreateLinks(resources, permissions); return result.CreateLinks(resources, permissions);
} }
private static PermissionSet GetPermissions(IAppEntity app, string userId) private static PermissionSet GetPermissions(IAppEntity app, string userId, bool isFrontend)
{ {
var permissions = new List<Permission>(); var permissions = new List<Permission>();
if (app.Contributors.TryGetValue(userId, out var roleName) && app.Roles.TryGet(app.Name, roleName, out var role)) if (app.Contributors.TryGetValue(userId, out var roleName) && app.Roles.TryGet(app.Name, roleName, isFrontend, out var role))
{ {
permissions.AddRange(role.Permissions); permissions.AddRange(role.Permissions);
} }
@ -187,12 +200,12 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
AddGetLink("contributors", resources.Url<AppContributorsController>(x => nameof(x.GetContributors), values)); AddGetLink("contributors", resources.Url<AppContributorsController>(x => nameof(x.GetContributors), values));
} }
if (resources.IsAllowed(P.AppCommon, Name, additional: permissions)) if (resources.IsAllowed(P.AppLanguagesRead, Name, additional: permissions))
{ {
AddGetLink("languages", resources.Url<AppLanguagesController>(x => nameof(x.GetLanguages), values)); AddGetLink("languages", resources.Url<AppLanguagesController>(x => nameof(x.GetLanguages), values));
} }
if (resources.IsAllowed(P.AppCommon, Name, additional: permissions)) if (resources.IsAllowed(P.AppPatternsRead, Name, additional: permissions))
{ {
AddGetLink("patterns", resources.Url<AppPatternsController>(x => nameof(x.GetPatterns), values)); AddGetLink("patterns", resources.Url<AppPatternsController>(x => nameof(x.GetPatterns), values));
} }
@ -212,7 +225,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
AddGetLink("rules", resources.Url<RulesController>(x => nameof(x.GetRules), values)); AddGetLink("rules", resources.Url<RulesController>(x => nameof(x.GetRules), values));
} }
if (resources.IsAllowed(P.AppCommon, Name, additional: permissions)) if (resources.IsAllowed(P.AppSchemasRead, Name, additional: permissions))
{ {
AddGetLink("schemas", resources.Url<SchemasController>(x => nameof(x.GetSchemas), values)); AddGetLink("schemas", resources.Url<SchemasController>(x => nameof(x.GetSchemas), values));
} }

15
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs

@ -5,10 +5,10 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
using Squidex.Web; using Squidex.Web;
@ -41,18 +41,23 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// Associated list of permissions. /// Associated list of permissions.
/// </summary> /// </summary>
[LocalizedRequired] [LocalizedRequired]
public IEnumerable<string> Permissions { get; set; } public string[] Permissions { get; set; }
/// <summary>
/// Associated list of UI properties.
/// </summary>
[LocalizedRequired]
public JsonObject Properties { get; set; }
public static RoleDto FromRole(Role role, IAppEntity app) public static RoleDto FromRole(Role role, IAppEntity app)
{ {
var permissions = role.Permissions;
var result = new RoleDto var result = new RoleDto
{ {
Name = role.Name, Name = role.Name,
NumClients = GetNumClients(role, app), NumClients = GetNumClients(role, app),
NumContributors = GetNumContributors(role, app), NumContributors = GetNumContributors(role, app),
Permissions = permissions.ToIds(), Permissions = role.Permissions.ToIds().ToArray(),
Properties = role.Properties,
IsDefaultRole = role.IsDefault IsDefaultRole = role.IsDefault
}; };

3
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateLanguageDto.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -27,7 +26,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// <summary> /// <summary>
/// Optional fallback languages. /// Optional fallback languages.
/// </summary> /// </summary>
public List<Language>? Fallback { get; set; } public Language[]? Fallback { get; set; }
public UpdateLanguage ToCommand(Language language) public UpdateLanguage ToCommand(Language language)
{ {

8
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateRoleDto.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Areas.Api.Controllers.Apps.Models namespace Squidex.Areas.Api.Controllers.Apps.Models
@ -18,9 +19,14 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
[LocalizedRequired] [LocalizedRequired]
public string[] Permissions { get; set; } public string[] Permissions { get; set; }
/// <summary>
/// Associated list of UI properties.
/// </summary>
public JsonObject? Properties { get; set; }
public UpdateRole ToCommand(string name) public UpdateRole ToCommand(string name)
{ {
return new UpdateRole { Name = name, Permissions = Permissions }; return new UpdateRole { Name = name, Permissions = Permissions, Properties = Properties };
} }
} }
} }

2
backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs

@ -121,6 +121,8 @@ namespace Squidex.Areas.Api.Controllers.Assets
if (asset.IsProtected && !Resources.CanReadAssets) if (asset.IsProtected && !Resources.CanReadAssets)
{ {
Response.Headers[HeaderNames.CacheControl] = $"public,max-age=0";
return StatusCode(403); return StatusCode(403);
} }

6
backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetContentQueryDto.cs

@ -87,6 +87,12 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models
[FromQuery(Name = "force")] [FromQuery(Name = "force")]
public bool ForceResize { get; set; } public bool ForceResize { get; set; }
/// <summary>
/// The target image format.
/// </summary>
[FromQuery(Name = "format")]
public ImageFormat Format { get; set; }
public ResizeOptions ToResizeOptions(IAssetEntity asset) public ResizeOptions ToResizeOptions(IAssetEntity asset)
{ {
Guard.NotNull(asset, nameof(asset)); Guard.NotNull(asset, nameof(asset));

8
backend/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs

@ -48,7 +48,7 @@ namespace Squidex.Areas.Api.Controllers.Comments
[HttpGet] [HttpGet]
[Route("apps/{app}/comments/{commentsId}")] [Route("apps/{app}/comments/{commentsId}")]
[ProducesResponseType(typeof(CommentsDto), 200)] [ProducesResponseType(typeof(CommentsDto), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppCommentsRead)]
[ApiCosts(0)] [ApiCosts(0)]
public async Task<IActionResult> GetComments(string app, string commentsId, [FromQuery] long version = EtagVersion.Any) public async Task<IActionResult> GetComments(string app, string commentsId, [FromQuery] long version = EtagVersion.Any)
{ {
@ -78,7 +78,7 @@ namespace Squidex.Areas.Api.Controllers.Comments
[HttpPost] [HttpPost]
[Route("apps/{app}/comments/{commentsId}")] [Route("apps/{app}/comments/{commentsId}")]
[ProducesResponseType(typeof(CommentDto), 201)] [ProducesResponseType(typeof(CommentDto), 201)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppCommentsCreate)]
[ApiCosts(0)] [ApiCosts(0)]
public async Task<IActionResult> PostComment(string app, string commentsId, [FromBody] UpsertCommentDto request) public async Task<IActionResult> PostComment(string app, string commentsId, [FromBody] UpsertCommentDto request)
{ {
@ -105,7 +105,7 @@ namespace Squidex.Areas.Api.Controllers.Comments
/// </returns> /// </returns>
[HttpPut] [HttpPut]
[Route("apps/{app}/comments/{commentsId}/{commentId}")] [Route("apps/{app}/comments/{commentsId}/{commentId}")]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppCommentsUpdate)]
[ApiCosts(0)] [ApiCosts(0)]
public async Task<IActionResult> PutComment(string app, string commentsId, string commentId, [FromBody] UpsertCommentDto request) public async Task<IActionResult> PutComment(string app, string commentsId, string commentId, [FromBody] UpsertCommentDto request)
{ {
@ -126,7 +126,7 @@ namespace Squidex.Areas.Api.Controllers.Comments
/// </returns> /// </returns>
[HttpDelete] [HttpDelete]
[Route("apps/{app}/comments/{commentsId}/{commentId}")] [Route("apps/{app}/comments/{commentsId}/{commentId}")]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppCommentsDelete)]
[ApiCosts(0)] [ApiCosts(0)]
public async Task<IActionResult> DeleteComment(string app, string commentsId, string commentId) public async Task<IActionResult> DeleteComment(string app, string commentsId, string commentId)
{ {

5
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/BulkUpdateDto.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -19,7 +18,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// The contents to update or insert. /// The contents to update or insert.
/// </summary> /// </summary>
[LocalizedRequired] [LocalizedRequired]
public List<BulkUpdateJobDto> Jobs { get; set; } public BulkUpdateJobDto[]? Jobs { get; set; }
/// <summary> /// <summary>
/// True to automatically publish the content. /// True to automatically publish the content.
@ -40,7 +39,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
{ {
var result = SimpleMapper.Map(this, new BulkUpdateContents()); var result = SimpleMapper.Map(this, new BulkUpdateContents());
result.Jobs = Jobs?.Select(x => x.ToJob())?.ToList(); result.Jobs = Jobs?.Select(x => x.ToJob())?.ToArray();
return result; return result;
} }

1
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/GraphQLGetDto.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using GraphQL;
using GraphQL.NewtonsoftJson; using GraphQL.NewtonsoftJson;
using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Domain.Apps.Entities.Contents.GraphQL;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;

1
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/GraphQLPostDto.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using GraphQL;
using GraphQL.NewtonsoftJson; using GraphQL.NewtonsoftJson;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Domain.Apps.Entities.Contents.GraphQL;

2
backend/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs

@ -42,7 +42,7 @@ namespace Squidex.Areas.Api.Controllers.History
[HttpGet] [HttpGet]
[Route("apps/{app}/history/")] [Route("apps/{app}/history/")]
[ProducesResponseType(typeof(HistoryEventDto), 200)] [ProducesResponseType(typeof(HistoryEventDto), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppHistory)]
[ApiCosts(0.1)] [ApiCosts(0.1)]
public async Task<IActionResult> GetHistory(string app, string channel) public async Task<IActionResult> GetHistory(string app, string channel)
{ {

2
backend/src/Squidex/Areas/Api/Controllers/Ping/PingController.cs

@ -68,7 +68,7 @@ namespace Squidex.Areas.Api.Controllers.Ping
/// </remarks> /// </remarks>
[HttpGet] [HttpGet]
[Route("ping/{app}/")] [Route("ping/{app}/")]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppPing)]
[ApiCosts(0)] [ApiCosts(0)]
public IActionResult GetAppPing(string app) public IActionResult GetAppPing(string app)
{ {

3
backend/src/Squidex/Areas/Api/Controllers/QueryDto.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
@ -18,7 +17,7 @@ namespace Squidex.Areas.Api.Controllers
/// <summary> /// <summary>
/// The optional list of ids to query. /// The optional list of ids to query.
/// </summary> /// </summary>
public List<DomainId>? Ids { get; set; } public DomainId[]? Ids { get; set; }
/// <summary> /// <summary>
/// The optional odata query. /// The optional odata query.

5
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureFieldRulesDto.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.Commands;
@ -16,13 +15,13 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
/// <summary> /// <summary>
/// The field rules to configure. /// The field rules to configure.
/// </summary> /// </summary>
public List<FieldRuleDto>? FieldRules { get; set; } public FieldRuleDto[]? FieldRules { get; set; }
public ConfigureFieldRules ToCommand() public ConfigureFieldRules ToCommand()
{ {
return new ConfigureFieldRules return new ConfigureFieldRules
{ {
FieldRules = FieldRules?.Select(x => x.ToCommand()).ToList() FieldRules = FieldRules?.Select(x => x.ToCommand()).ToArray()
}; };
} }
} }

3
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/ReorderFieldsDto.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
@ -17,7 +16,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
/// The field ids in the target order. /// The field ids in the target order.
/// </summary> /// </summary>
[LocalizedRequired] [LocalizedRequired]
public List<long> FieldIds { get; set; } public long[] FieldIds { get; set; }
public ReorderFields ToCommand(long? parentId = null) public ReorderFields ToCommand(long? parentId = null)
{ {

22
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaDto.cs

@ -27,17 +27,17 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
/// <summary> /// <summary>
/// The names of the fields that should be used in references. /// The names of the fields that should be used in references.
/// </summary> /// </summary>
public List<string>? FieldsInReferences { get; set; } public string[]? FieldsInReferences { get; set; }
/// <summary> /// <summary>
/// The names of the fields that should be shown in lists, including meta fields. /// The names of the fields that should be shown in lists, including meta fields.
/// </summary> /// </summary>
public List<string>? FieldsInLists { get; set; } public string[]? FieldsInLists { get; set; }
/// <summary> /// <summary>
/// Optional fields. /// Optional fields.
/// </summary> /// </summary>
public List<UpsertSchemaFieldDto?>? Fields { get; set; } public UpsertSchemaFieldDto[]? Fields { get; set; }
/// <summary> /// <summary>
/// The optional preview urls. /// The optional preview urls.
@ -83,9 +83,9 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
command.FieldsInReferences = new FieldNames(dto.FieldsInReferences); command.FieldsInReferences = new FieldNames(dto.FieldsInReferences);
} }
if (dto.Fields != null) if (dto.Fields?.Length > 0)
{ {
command.Fields = new List<UpsertSchemaField>(); var fields = new List<UpsertSchemaField>();
foreach (var rootFieldDto in dto.Fields) foreach (var rootFieldDto in dto.Fields)
{ {
@ -96,9 +96,9 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
{ {
SimpleMapper.Map(rootFieldDto, rootField); SimpleMapper.Map(rootFieldDto, rootField);
if (rootFieldDto?.Nested?.Count > 0) if (rootFieldDto?.Nested?.Length > 0)
{ {
rootField.Nested = new List<UpsertSchemaNestedField>(); var nestedFields = new List<UpsertSchemaNestedField>();
foreach (var nestedFieldDto in rootFieldDto.Nested) foreach (var nestedFieldDto in rootFieldDto.Nested)
{ {
@ -110,13 +110,17 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
SimpleMapper.Map(nestedFieldDto, nestedField); SimpleMapper.Map(nestedFieldDto, nestedField);
} }
rootField.Nested.Add(nestedField); nestedFields.Add(nestedField);
} }
rootField.Nested = nestedFields.ToArray();
} }
} }
command.Fields.Add(rootField); fields.Add(rootField);
} }
command.Fields = fields.ToArray();
} }
return command; return command;

3
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaFieldDto.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Areas.Api.Controllers.Schemas.Models namespace Squidex.Areas.Api.Controllers.Schemas.Models
@ -48,6 +47,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
/// <summary> /// <summary>
/// The nested fields. /// The nested fields.
/// </summary> /// </summary>
public List<UpsertSchemaNestedFieldDto>? Nested { get; set; } public UpsertSchemaNestedFieldDto[]? Nested { get; set; }
} }
} }

4
backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs

@ -44,7 +44,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpGet] [HttpGet]
[Route("apps/{app}/schemas/")] [Route("apps/{app}/schemas/")]
[ProducesResponseType(typeof(SchemasDto), 200)] [ProducesResponseType(typeof(SchemasDto), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppSchemasRead)]
[ApiCosts(0)] [ApiCosts(0)]
public async Task<IActionResult> GetSchemas(string app) public async Task<IActionResult> GetSchemas(string app)
{ {
@ -72,7 +72,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas
[HttpGet] [HttpGet]
[Route("apps/{app}/schemas/{name}/")] [Route("apps/{app}/schemas/{name}/")]
[ProducesResponseType(typeof(SchemaDetailsDto), 200)] [ProducesResponseType(typeof(SchemaDetailsDto), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppSchemasRead)]
[ApiCosts(0)] [ApiCosts(0)]
public async Task<IActionResult> GetSchema(string app, string name) public async Task<IActionResult> GetSchema(string app, string name)
{ {

4
backend/src/Squidex/Areas/Api/Controllers/Search/SearchController.cs

@ -42,9 +42,9 @@ namespace Squidex.Areas.Api.Controllers.Search
[HttpGet] [HttpGet]
[Route("apps/{app}/search/")] [Route("apps/{app}/search/")]
[ProducesResponseType(typeof(SearchResultDto[]), 200)] [ProducesResponseType(typeof(SearchResultDto[]), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppSearch)]
[ApiCosts(0)] [ApiCosts(0)]
public async Task<IActionResult> GetSchemas(string app, [FromQuery] string? query = null) public async Task<IActionResult> GetSearchResults(string app, [FromQuery] string? query = null)
{ {
var result = await searchManager.SearchAsync(query, Context); var result = await searchManager.SearchAsync(query, Context);

8
backend/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs

@ -66,7 +66,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics
[HttpGet] [HttpGet]
[Route("apps/{app}/usages/log/")] [Route("apps/{app}/usages/log/")]
[ProducesResponseType(typeof(LogDownloadDto), 200)] [ProducesResponseType(typeof(LogDownloadDto), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppUsage)]
[ApiCosts(0)] [ApiCosts(0)]
public IActionResult GetLog(string app) public IActionResult GetLog(string app)
{ {
@ -93,7 +93,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics
[HttpGet] [HttpGet]
[Route("apps/{app}/usages/calls/{fromDate}/{toDate}/")] [Route("apps/{app}/usages/calls/{fromDate}/{toDate}/")]
[ProducesResponseType(typeof(CallsUsageDtoDto), 200)] [ProducesResponseType(typeof(CallsUsageDtoDto), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppUsage)]
[ApiCosts(0)] [ApiCosts(0)]
public async Task<IActionResult> GetUsages(string app, DateTime fromDate, DateTime toDate) public async Task<IActionResult> GetUsages(string app, DateTime fromDate, DateTime toDate)
{ {
@ -122,7 +122,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics
[HttpGet] [HttpGet]
[Route("apps/{app}/usages/storage/today/")] [Route("apps/{app}/usages/storage/today/")]
[ProducesResponseType(typeof(CurrentStorageDto), 200)] [ProducesResponseType(typeof(CurrentStorageDto), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppUsage)]
[ApiCosts(0)] [ApiCosts(0)]
public async Task<IActionResult> GetCurrentStorageSize(string app) public async Task<IActionResult> GetCurrentStorageSize(string app)
{ {
@ -149,7 +149,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics
[HttpGet] [HttpGet]
[Route("apps/{app}/usages/storage/{fromDate}/{toDate}/")] [Route("apps/{app}/usages/storage/{fromDate}/{toDate}/")]
[ProducesResponseType(typeof(StorageUsagePerDateDto[]), 200)] [ProducesResponseType(typeof(StorageUsagePerDateDto[]), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppUsage)]
[ApiCosts(0)] [ApiCosts(0)]
public async Task<IActionResult> GetStorageSizes(string app, DateTime fromDate, DateTime toDate) public async Task<IActionResult> GetStorageSizes(string app, DateTime fromDate, DateTime toDate)
{ {

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

@ -40,9 +40,9 @@ namespace Squidex.Areas.Api.Controllers.Translations
[HttpPost] [HttpPost]
[Route("apps/{app}/translations/")] [Route("apps/{app}/translations/")]
[ProducesResponseType(typeof(TranslationDto), 200)] [ProducesResponseType(typeof(TranslationDto), 200)]
[ApiPermissionOrAnonymous(Permissions.AppCommon)] [ApiPermissionOrAnonymous(Permissions.AppTranslate)]
[ApiCosts(0)] [ApiCosts(0)]
public async Task<IActionResult> GetLanguages(string app, [FromBody] TranslateDto request) public async Task<IActionResult> PostTranslation(string app, [FromBody] TranslateDto request)
{ {
var result = await translator.Translate(request.Text, request.TargetLanguage, request.SourceLanguage, HttpContext.RequestAborted); var result = await translator.Translate(request.Text, request.TargetLanguage, request.SourceLanguage, HttpContext.RequestAborted);
var response = TranslationDto.FromTranslation(result); var response = TranslationDto.FromTranslation(result);

2
backend/src/Squidex/Areas/IdentityServer/Views/Account/Login.cshtml

@ -1,7 +1,7 @@
@model Squidex.Areas.IdentityServer.Controllers.Account.LoginVM @model Squidex.Areas.IdentityServer.Controllers.Account.LoginVM
@{ @{
var action = Model.IsLogin ? @T.Get("common.login") : @T.Get("common.signup"); var action = Model.IsLogin ? T.Get("common.login") : T.Get("common.signup");
ViewBag.Title = action; ViewBag.Title = action;
} }

2
backend/src/Squidex/Areas/IdentityServer/Views/Error/Error.cshtml

@ -3,7 +3,7 @@
@{ @{
ViewBag.Theme = "white"; ViewBag.Theme = "white";
ViewBag.Title = @T.Get("users.error.title"); ViewBag.Title = T.Get("users.error.title");
} }
<img class="splash-image" src="@Url.RootContentUrl("~/squid.svg?title=OH%20DAMN&text=I%20am%20sorry%2C%20that%20something%20went%20wrong")" /> <img class="splash-image" src="@Url.RootContentUrl("~/squid.svg?title=OH%20DAMN&text=I%20am%20sorry%2C%20that%20something%20went%20wrong")" />

2
backend/src/Squidex/Areas/IdentityServer/Views/Profile/Profile.cshtml

@ -3,7 +3,7 @@
@{ @{
ViewBag.Class = "profile-lg"; ViewBag.Class = "profile-lg";
ViewBag.Title = @T.Get("users.profile.title"); ViewBag.Title = T.Get("users.profile.title");
void RenderValidation(string field) void RenderValidation(string field)
{ {

1
backend/src/Squidex/Config/Domain/SerializationServices.cs

@ -49,6 +49,7 @@ namespace Squidex.Config.Domain
new NamedStringIdConverter(), new NamedStringIdConverter(),
new PropertyPathConverter(), new PropertyPathConverter(),
new RefTokenConverter(), new RefTokenConverter(),
new RoleConverter(),
new RolesConverter(), new RolesConverter(),
new RuleConverter(), new RuleConverter(),
new SchemaConverter(), new SchemaConverter(),

22
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RoleTests.cs

@ -16,7 +16,7 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
[Fact] [Fact]
public void Should_be_default_role() public void Should_be_default_role()
{ {
var role = new Role("Owner"); var role = Role.Create("Owner");
Assert.True(role.IsDefault); Assert.True(role.IsDefault);
} }
@ -24,25 +24,25 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
[Fact] [Fact]
public void Should_not_be_default_role() public void Should_not_be_default_role()
{ {
var role = new Role("Custom"); var role = Role.Create("Custom");
Assert.False(role.IsDefault); Assert.False(role.IsDefault);
} }
[Fact] [Fact]
public void Should_add_common_permission() public void Should_not_add_common_permission()
{ {
var role = new Role("Name"); var role = Role.Create("Name");
var result = role.ForApp("my-app").Permissions.ToIds(); var result = role.ForApp("my-app").Permissions.ToIds();
Assert.Equal(new[] { "squidex.apps.my-app.common" }, result); Assert.Empty(result);
} }
[Fact] [Fact]
public void Should_not_have_duplicate_permission() public void Should_not_have_duplicate_permission()
{ {
var role = new Role("Name", "common", "common", "common"); var role = Role.WithPermissions("Name", "common", "common", "common");
var result = role.ForApp("my-app").Permissions.ToIds(); var result = role.ForApp("my-app").Permissions.ToIds();
@ -50,19 +50,19 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
} }
[Fact] [Fact]
public void Should_ForApp_permission() public void Should_append_app_prefix_to_permission()
{ {
var role = new Role("Name", "clients.read"); var role = Role.WithPermissions("Name", "clients.read");
var result = role.ForApp("my-app").Permissions.ToIds(); var result = role.ForApp("my-app").Permissions.ToIds();
Assert.Equal("squidex.apps.my-app.clients.read", result.ElementAt(1)); Assert.Equal("squidex.apps.my-app.clients.read", result.ElementAt(0));
} }
[Fact] [Fact]
public void Should_check_for_name() public void Should_check_for_name()
{ {
var role = new Role("Custom"); var role = Role.WithPermissions("Custom");
Assert.True(role.Equals("Custom")); Assert.True(role.Equals("Custom"));
} }
@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
[Fact] [Fact]
public void Should_check_for_null_name() public void Should_check_for_null_name()
{ {
var role = new Role("Custom"); var role = Role.WithPermissions("Custom");
Assert.False(role.Equals((string)null!)); Assert.False(role.Equals((string)null!));
Assert.False(role.Equals("Other")); Assert.False(role.Equals("Other"));

39
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesJsonTests.cs

@ -5,19 +5,56 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using FluentAssertions; using FluentAssertions;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Security;
using Xunit; using Xunit;
namespace Squidex.Domain.Apps.Core.Model.Apps namespace Squidex.Domain.Apps.Core.Model.Apps
{ {
public class RolesJsonTests public class RolesJsonTests
{ {
[Fact]
public void Should_deserialize_from_old_role_format()
{
var source = new Dictionary<string, string[]>
{
["Custom"] = new[]
{
"Permission1",
"Permission2"
}
};
var expected =
Roles.Empty
.Add("Custom")
.Update("Custom",
new PermissionSet(
"Permission1",
"Permission2"));
var roles = source.SerializeAndDeserialize<Roles>();
roles.Should().BeEquivalentTo(expected);
}
[Fact] [Fact]
public void Should_serialize_and_deserialize() public void Should_serialize_and_deserialize()
{ {
var sut = Roles.Empty.Add("Custom").Update("Custom", "Permission1", "Permission2"); var sut =
Roles.Empty
.Add("Custom")
.Update("Custom",
new PermissionSet(
"Permission1",
"Permission2"),
JsonValue.Object()
.Add("Property1", true)
.Add("Property2", true));
var roles = sut.SerializeAndDeserialize(); var roles = sut.SerializeAndDeserialize();

51
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs

@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentAssertions; using FluentAssertions;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using Xunit; using Xunit;
@ -40,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
{ {
var roles_1 = roles_0.Add(role); var roles_1 = roles_0.Add(role);
roles_1[role].Should().BeEquivalentTo(new Role(role, PermissionSet.Empty)); roles_1[role].Should().BeEquivalentTo(Role.Create(role));
} }
[Fact] [Fact]
@ -61,15 +62,23 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
} }
[Fact] [Fact]
public void Should_update_role() public void Should_update_role_permissions()
{ {
var roles_1 = roles_0.Update(firstRole, "P1", "P2"); var roles_1 = roles_0.Update(firstRole, permissions: new PermissionSet("P1", "P2"));
roles_1[firstRole].Should().BeEquivalentTo(new Role(firstRole, new PermissionSet("P1", "P2"))); roles_1[firstRole].Should().BeEquivalentTo(Role.WithPermissions(firstRole, "P1", "P2"));
} }
[Fact] [Fact]
public void Should_return_same_roles_if_role_is_updated_with_same_values() public void Should_update_role_properties()
{
var roles_1 = roles_0.Update(firstRole, properties: JsonValue.Object().Add("P1", true));
roles_1[firstRole].Should().BeEquivalentTo(Role.WithProperties(firstRole, JsonValue.Object().Add("P1", true)));
}
[Fact]
public void Should_return_same_roles_if_role_is_updated_with_same_permissions()
{ {
var roles_1 = roles_0.Update(firstRole); var roles_1 = roles_0.Update(firstRole);
@ -79,7 +88,7 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
[Fact] [Fact]
public void Should_return_same_roles_if_role_not_found() public void Should_return_same_roles_if_role_not_found()
{ {
var roles_1 = roles_0.Update(role, "P1", "P2"); var roles_1 = roles_0.Update(role, permissions: new PermissionSet("P1", "P2"));
Assert.Same(roles_0, roles_1); Assert.Same(roles_0, roles_1);
} }
@ -142,14 +151,14 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
Assert.False(Roles.IsDefault(firstRole)); Assert.False(Roles.IsDefault(firstRole));
} }
[InlineData("Developer")] [InlineData("Developer", 7)]
[InlineData("Editor")] [InlineData("Editor", 4)]
[InlineData("Owner")] [InlineData("Reader", 2)]
[InlineData("Reader")] [InlineData("Owner", 1)]
[Theory] [Theory]
public void Should_get_default_roles(string name) public void Should_get_default_roles(string name, int permissionCount)
{ {
var found = roles_0.TryGet("app", name, out var result); var found = roles_0.TryGet("app", name, false, out var result);
Assert.True(found); Assert.True(found);
Assert.True(result!.IsDefault); Assert.True(result!.IsDefault);
@ -158,13 +167,29 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
foreach (var permission in result.Permissions) foreach (var permission in result.Permissions)
{ {
Assert.StartsWith("squidex.apps.app.", permission.Id); Assert.StartsWith("squidex.apps.app.", permission.Id);
Assert.DoesNotContain("{app}", permission.Id);
} }
Assert.Equal(permissionCount, result!.Permissions.Count);
}
[InlineData("Developer", 17)]
[InlineData("Editor", 14)]
[InlineData("Reader", 13)]
[InlineData("Owner", 1)]
[Theory]
public void Should_add_extra_permissions_for_frontend_client(string name, int permissionCount)
{
var found = roles_0.TryGet("app", name, true, out var result);
Assert.True(found);
Assert.Equal(permissionCount, result!.Permissions.Count);
} }
[Fact] [Fact]
public void Should_return_null_if_role_not_found() public void Should_return_null_if_role_not_found()
{ {
var found = roles_0.TryGet("app", "custom", out var result); var found = roles_0.TryGet("app", "custom", false, out var result);
Assert.False(found); Assert.False(found);
Assert.Null(result); Assert.Null(result);

10
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs

@ -575,7 +575,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization
var events = sourceSchema.Synchronize(targetSchema, idGenerator); var events = sourceSchema.Synchronize(targetSchema, idGenerator);
events.ShouldHaveSameEvents( events.ShouldHaveSameEvents(
new SchemaFieldsReordered { FieldIds = new List<long> { 11, 10 }, ParentFieldId = arrayId } new SchemaFieldsReordered { FieldIds = new[] { 11L, 10L }, ParentFieldId = arrayId }
); );
} }
@ -595,7 +595,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization
var events = sourceSchema.Synchronize(targetSchema, idGenerator); var events = sourceSchema.Synchronize(targetSchema, idGenerator);
events.ShouldHaveSameEvents( events.ShouldHaveSameEvents(
new SchemaFieldsReordered { FieldIds = new List<long> { 11, 10 } } new SchemaFieldsReordered { FieldIds = new[] { 11L, 10L } }
); );
} }
@ -617,7 +617,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization
events.ShouldHaveSameEvents( events.ShouldHaveSameEvents(
new FieldDeleted { FieldId = NamedId.Of(11L, "f2") }, new FieldDeleted { FieldId = NamedId.Of(11L, "f2") },
new FieldAdded { FieldId = NamedId.Of(50L, "f3"), Name = "f3", Partitioning = Partitioning.Invariant.Key, Properties = new StringFieldProperties() }, new FieldAdded { FieldId = NamedId.Of(50L, "f3"), Name = "f3", Partitioning = Partitioning.Invariant.Key, Properties = new StringFieldProperties() },
new SchemaFieldsReordered { FieldIds = new List<long> { 50, 10 } } new SchemaFieldsReordered { FieldIds = new[] { 50L, 10L } }
); );
} }
@ -639,7 +639,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization
events.ShouldHaveSameEvents( events.ShouldHaveSameEvents(
new FieldAdded { FieldId = NamedId.Of(50L, "f3"), Name = "f3", Partitioning = Partitioning.Invariant.Key, Properties = new StringFieldProperties() }, new FieldAdded { FieldId = NamedId.Of(50L, "f3"), Name = "f3", Partitioning = Partitioning.Invariant.Key, Properties = new StringFieldProperties() },
new SchemaFieldsReordered { FieldIds = new List<long> { 10, 50, 11 } } new SchemaFieldsReordered { FieldIds = new[] { 10L, 50L, 11L } }
); );
} }
@ -661,7 +661,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization
events.ShouldHaveSameEvents( events.ShouldHaveSameEvents(
new FieldDeleted { FieldId = NamedId.Of(10L, "f1") }, new FieldDeleted { FieldId = NamedId.Of(10L, "f1") },
new FieldAdded { FieldId = NamedId.Of(50L, "f3"), Name = "f3", Partitioning = Partitioning.Invariant.Key, Properties = new StringFieldProperties() }, new FieldAdded { FieldId = NamedId.Of(50L, "f3"), Name = "f3", Partitioning = Partitioning.Invariant.Key, Properties = new StringFieldProperties() },
new SchemaFieldsReordered { FieldIds = new List<long> { 50, 11 } } new SchemaFieldsReordered { FieldIds = new[] { 50L, 11L } }
); );
} }
} }

4
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/ExpressionsAttribute.cs

@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{ {
yield return new object[] yield return new object[]
{ {
string.Format("Script(`{0}`)", script) $"Script(`{script}`)"
}; };
} }
@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{ {
yield return new object[] yield return new object[]
{ {
string.Format("Liquid({0})", liquid) $"Liquid({liquid})"
}; };
} }
} }

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterCompareTests.cs

@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{ {
public (bool Match, ValueTask<string?>) Format(EnrichedEvent @event, object value, string[] path) public (bool Match, ValueTask<string?>) Format(EnrichedEvent @event, object value, string[] path)
{ {
if (path[0] == "data" && value is JsonArray _) if (path[0] == "data" && value is JsonArray)
{ {
return (true, GetValueAsync()); return (true, GetValueAsync());
} }

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs

@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{ {
public (bool Match, ValueTask<string?>) Format(EnrichedEvent @event, object value, string[] path) public (bool Match, ValueTask<string?>) Format(EnrichedEvent @event, object value, string[] path)
{ {
if (path[0] == "data" && value is JsonArray _) if (path[0] == "data" && value is JsonArray)
{ {
return (true, GetValueAsync()); return (true, GetValueAsync());
} }

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

@ -42,7 +42,8 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
sut = new JintScriptEngine(cache, extensions) sut = new JintScriptEngine(cache, extensions)
{ {
Timeout = TimeSpan.FromSeconds(1) TimeoutScript = TimeSpan.FromSeconds(10),
TimeoutExecution = TimeSpan.FromSeconds(2)
}; };
} }

3
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs

@ -58,7 +58,8 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting
sut = new JintScriptEngine(cache, extensions) sut = new JintScriptEngine(cache, extensions)
{ {
Timeout = TimeSpan.FromSeconds(1) TimeoutScript = TimeSpan.FromSeconds(10),
TimeoutExecution = TimeSpan.FromSeconds(2)
}; };
} }

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save