Browse Source

Fixes (#993)

* Exception handling for scheduler.

* Image hover

* Fix transfer.

* Fixes

* Flush write.

* OpenApi fixes
pull/995/head
Sebastian Stehle 3 years ago
committed by GitHub
parent
commit
c3c72c076b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      backend/extensions/Squidex.Extensions/APM/Stackdriver/StackdriverExceptionHandler.cs
  2. 2
      backend/extensions/Squidex.Extensions/APM/Stackdriver/StackdriverSeverityLogAppender.cs
  3. 4
      backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs
  4. 8
      backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs
  5. 2
      backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs
  6. 6
      backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueActionHandler.cs
  7. 4
      backend/extensions/Squidex.Extensions/Actions/ClientPool.cs
  8. 2
      backend/extensions/Squidex.Extensions/Actions/Comment/CommentAction.cs
  9. 2
      backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs
  10. 2
      backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs
  11. 2
      backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs
  12. 4
      backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs
  13. 8
      backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs
  14. 14
      backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs
  15. 10
      backend/extensions/Squidex.Extensions/Actions/Email/EmailActionHandler.cs
  16. 10
      backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaAction.cs
  17. 22
      backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaActionHandler.cs
  18. 28
      backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs
  19. 6
      backend/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs
  20. 8
      backend/extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs
  21. 4
      backend/extensions/Squidex.Extensions/Actions/Notification/NotificationAction.cs
  22. 2
      backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs
  23. 8
      backend/extensions/Squidex.Extensions/Actions/OpenSearch/OpenSearchAction.cs
  24. 14
      backend/extensions/Squidex.Extensions/Actions/OpenSearch/OpenSearchActionHandler.cs
  25. 6
      backend/extensions/Squidex.Extensions/Actions/RuleHelper.cs
  26. 6
      backend/extensions/Squidex.Extensions/Actions/SignalR/SignalRAction.cs
  27. 9
      backend/extensions/Squidex.Extensions/Actions/SignalR/SignalRActionHandler.cs
  28. 2
      backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs
  29. 4
      backend/extensions/Squidex.Extensions/Actions/Typesense/TypesenseAction.cs
  30. 4
      backend/extensions/Squidex.Extensions/Actions/Typesense/TypesenseActionHandler.cs
  31. 8
      backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs
  32. 12
      backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs
  33. 6
      backend/extensions/Squidex.Extensions/Samples/Middleware/DoubleLinkedContentMiddleware.cs
  34. 1
      backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj
  35. 4
      backend/extensions/Squidex.Extensions/Text/Azure/AzureIndexDefinition.cs
  36. 6
      backend/extensions/Squidex.Extensions/Text/Azure/AzureTextIndex.cs
  37. 4
      backend/extensions/Squidex.Extensions/Text/Azure/CommandFactory.cs
  38. 21
      backend/extensions/Squidex.Extensions/Text/ElasticSearch/CommandFactory.cs
  39. 6
      backend/extensions/Squidex.Extensions/Text/ElasticSearch/ElasticSearchTextIndex.cs
  40. 6
      backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs
  41. 2
      backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidatorFactory.cs
  42. 28
      backend/i18n/frontend_en.json
  43. 28
      backend/i18n/frontend_it.json
  44. 28
      backend/i18n/frontend_nl.json
  45. 28
      backend/i18n/frontend_pt.json
  46. 28
      backend/i18n/frontend_zh.json
  47. 28
      backend/i18n/source/frontend_en.json
  48. 10
      backend/i18n/source/frontend_it.json
  49. 10
      backend/i18n/source/frontend_nl.json
  50. 10
      backend/i18n/source/frontend_pt.json
  51. 10
      backend/i18n/source/frontend_zh.json
  52. 6
      backend/src/Migrations/OldEvents/SchemaCreated.cs
  53. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs
  54. 1
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/IConverter.cs
  55. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs
  56. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  57. 3
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs
  58. 9
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs
  59. 4
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs
  60. 4
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetHeaders.cs
  61. 8
      backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreProcessor.cs
  62. 69
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs
  63. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs
  64. 26
      backend/src/Squidex.Domain.Apps.Entities/ContextHeaders.cs
  65. 9
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs
  66. 5
      backend/src/Squidex.Infrastructure/Queries/QueryExtensions.cs
  67. 2
      backend/src/Squidex.Infrastructure/Queries/SortNode.cs
  68. 2
      backend/src/Squidex.Web/ApiExceptionConverter.cs
  69. 96
      backend/src/Squidex/Areas/Api/Config/OpenApi/AcceptHeaderAttribute.cs
  70. 36
      backend/src/Squidex/Areas/Api/Config/OpenApi/AcceptQueryAttribute.cs
  71. 2
      backend/src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs
  72. 34
      backend/src/Squidex/Areas/Api/Config/OpenApi/QueryParamsProcessor.cs
  73. 40
      backend/src/Squidex/Areas/Api/Config/OpenApi/RequiredSchemaProcessor.cs
  74. 1
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
  75. 1
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs
  76. 1
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs
  77. 1
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppSettingsDto.cs
  78. 1
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs
  79. 1
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/EditorDto.cs
  80. 1
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternDto.cs
  81. 1
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateAppSettingsDto.cs
  82. 2
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowDto.cs
  83. 1
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowStepDto.cs
  84. 6
      backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs
  85. 1
      backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs
  86. 1
      backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFolderDto.cs
  87. 1
      backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFoldersDto.cs
  88. 1
      backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs
  89. 1
      backend/src/Squidex/Areas/Api/Controllers/Assets/Models/BulkUpdateAssetsDto.cs
  90. 7
      backend/src/Squidex/Areas/Api/Controllers/Assets/Models/CreateAssetDto.cs
  91. 4
      backend/src/Squidex/Areas/Api/Controllers/Assets/Models/DeleteAssetDto.cs
  92. 5
      backend/src/Squidex/Areas/Api/Controllers/Assets/Models/UpsertAssetDto.cs
  93. 2
      backend/src/Squidex/Areas/Api/Controllers/Backups/Models/RestoreJobDto.cs
  94. 59
      backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  95. 12
      backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsSharedController.cs
  96. 8
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/AllContentsByGetDto.cs
  97. 1
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  98. 1
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  99. 6
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/CreateContentDto.cs
  100. 4
      backend/src/Squidex/Areas/Api/Controllers/Contents/Models/DeleteContentDto.cs

2
backend/extensions/Squidex.Extensions/APM/Stackdriver/StackdriverExceptionHandler.cs

@ -50,7 +50,7 @@ internal sealed class StackdriverExceptionHandler : ILogAppender
httpContextWrapper = new HttpContextWrapper(httpContextAccessor);
}
public void Append(IObjectWriter writer, SemanticLogLevel logLevel, Exception exception)
public void Append(IObjectWriter writer, SemanticLogLevel logLevel, Exception? exception)
{
try
{

2
backend/extensions/Squidex.Extensions/APM/Stackdriver/StackdriverSeverityLogAppender.cs

@ -11,7 +11,7 @@ namespace Squidex.Extensions.APM.Stackdriver;
public sealed class StackdriverSeverityLogAppender : ILogAppender
{
public void Append(IObjectWriter writer, SemanticLogLevel logLevel, Exception exception)
public void Append(IObjectWriter writer, SemanticLogLevel logLevel, Exception? exception)
{
var severity = GetSeverity(logLevel);

4
backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs

@ -42,9 +42,9 @@ public sealed record AlgoliaAction : RuleAction
[Display(Name = "Document", Description = "The optional custom document.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Document { get; set; }
public string? Document { get; set; }
[Display(Name = "Deletion", Description = "The condition when to delete the entry.")]
[Editor(RuleFieldEditor.Text)]
public string Delete { get; set; }
public string? Delete { get; set; }
}

8
backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs

@ -46,7 +46,7 @@ public sealed class AlgoliaActionHandler : RuleActionHandler<AlgoliaAction, Algo
var ruleDescription = string.Empty;
var contentId = entityEvent.Id.ToString();
var content = (AlgoliaContent)null;
var content = (AlgoliaContent?)null;
if (delete)
{
@ -58,7 +58,7 @@ public sealed class AlgoliaActionHandler : RuleActionHandler<AlgoliaAction, Algo
try
{
string jsonString;
string? jsonString;
if (!string.IsNullOrEmpty(action.Document))
{
@ -70,7 +70,7 @@ public sealed class AlgoliaActionHandler : RuleActionHandler<AlgoliaAction, Algo
jsonString = ToJson(@event);
}
content = serializer.Deserialize<AlgoliaContent>(jsonString);
content = serializer.Deserialize<AlgoliaContent>(jsonString!);
}
catch (Exception ex)
{
@ -92,7 +92,7 @@ public sealed class AlgoliaActionHandler : RuleActionHandler<AlgoliaAction, Algo
ApiKey = action.ApiKey,
Content = serializer.Serialize(content, true),
ContentId = contentId,
IndexName = await FormatAsync(action.IndexName, @event)
IndexName = (await FormatAsync(action.IndexName, @event))!
};
return (ruleDescription, ruleJob);

2
backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs

@ -37,7 +37,7 @@ public sealed record AzureQueueAction : RuleAction
[Display(Name = "Payload (Optional)", Description = "Leave it empty to use the full event as body.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Payload { get; set; }
public string? Payload { get; set; }
protected override IEnumerable<ValidationError> CustomValidate()
{

6
backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueActionHandler.cs

@ -36,7 +36,7 @@ public sealed class AzureQueueActionHandler : RuleActionHandler<AzureQueueAction
{
var queueName = await FormatAsync(action.Queue, @event);
string requestBody;
string? requestBody;
if (!string.IsNullOrEmpty(action.Payload))
{
@ -51,7 +51,7 @@ public sealed class AzureQueueActionHandler : RuleActionHandler<AzureQueueAction
var ruleJob = new AzureQueueJob
{
QueueConnectionString = action.ConnectionString,
QueueName = queueName,
QueueName = queueName!,
MessageBodyV2 = requestBody
};
@ -75,5 +75,5 @@ public sealed class AzureQueueJob
public string QueueName { get; set; }
public string MessageBodyV2 { get; set; }
public string? MessageBodyV2 { get; set; }
}

4
backend/extensions/Squidex.Extensions/Actions/ClientPool.cs

@ -12,7 +12,7 @@ using Microsoft.Extensions.Options;
namespace Squidex.Extensions.Actions;
internal sealed class ClientPool<TKey, TClient>
internal sealed class ClientPool<TKey, TClient> where TKey : notnull
{
private static readonly TimeSpan TimeToLive = TimeSpan.FromMinutes(30);
private readonly MemoryCache memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
@ -37,6 +37,6 @@ internal sealed class ClientPool<TKey, TClient>
memoryCache.Set(key, client, TimeToLive);
}
return client;
return client!;
}
}

2
backend/extensions/Squidex.Extensions/Actions/Comment/CommentAction.cs

@ -28,5 +28,5 @@ public sealed record CommentAction : RuleAction
[Display(Name = "Client", Description = "An optional client name.")]
[Editor(RuleFieldEditor.Text)]
public string Client { get; set; }
public string? Client { get; set; }
}

2
backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs

@ -33,7 +33,7 @@ public sealed class CommentActionHandler : RuleActionHandler<CommentAction, Crea
AppId = contentEvent.AppId
};
ruleJob.Text = await FormatAsync(action.Text, @event);
ruleJob.Text = (await FormatAsync(action.Text, @event))!;
if (!string.IsNullOrEmpty(action.Client))
{

2
backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs

@ -50,7 +50,7 @@ public sealed class CreateContentActionHandler : RuleActionHandler<CreateContent
var json = await FormatAsync(action.Data, @event);
ruleJob.Data = jsonSerializer.Deserialize<ContentData>(json);
ruleJob.Data = jsonSerializer.Deserialize<ContentData>(json!);
if (!string.IsNullOrEmpty(action.Client))
{

2
backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs

@ -46,7 +46,7 @@ public sealed record DiscourseAction : RuleAction
[Display(Name = "Title", Description = "The optional title when creating new topics.")]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string Title { get; set; }
public string? Title { get; set; }
[Display(Name = "Topic", Description = "The optional topic id.")]
[Editor(RuleFieldEditor.Text)]

4
backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs

@ -30,9 +30,9 @@ public sealed class DiscourseActionHandler : RuleActionHandler<DiscourseAction,
{
var url = $"{action.Url.ToString().TrimEnd('/')}/posts.json?api_key={action.ApiKey}&api_username={action.ApiUsername}";
var json = new Dictionary<string, object>
var json = new Dictionary<string, object?>
{
["title"] = await FormatAsync(action.Title, @event)
["title"] = await FormatAsync(action.Title!, @event)
};
if (action.Topic != null)

8
backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs

@ -35,18 +35,18 @@ public sealed record ElasticSearchAction : RuleAction
[Display(Name = "Username", Description = "The optional username.")]
[Editor(RuleFieldEditor.Text)]
public string Username { get; set; }
public string? Username { get; set; }
[Display(Name = "Password", Description = "The optional password.")]
[Editor(RuleFieldEditor.Text)]
public string Password { get; set; }
public string? Password { get; set; }
[Display(Name = "Document", Description = "The optional custom document.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Document { get; set; }
public string? Document { get; set; }
[Display(Name = "Deletion", Description = "The condition when to delete the document.")]
[Editor(RuleFieldEditor.Text)]
public string Delete { get; set; }
public string? Delete { get; set; }
}

14
backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs

@ -20,14 +20,14 @@ namespace Squidex.Extensions.Actions.ElasticSearch;
public sealed class ElasticSearchActionHandler : RuleActionHandler<ElasticSearchAction, ElasticSearchJob>
{
private readonly ClientPool<(Uri Host, string Username, string Password), ElasticLowLevelClient> clients;
private readonly ClientPool<(Uri Host, string? Username, string? Password), ElasticLowLevelClient> clients;
private readonly IScriptEngine scriptEngine;
private readonly IJsonSerializer serializer;
public ElasticSearchActionHandler(RuleEventFormatter formatter, IScriptEngine scriptEngine, IJsonSerializer serializer)
: base(formatter)
{
clients = new ClientPool<(Uri Host, string Username, string Password), ElasticLowLevelClient>(key =>
clients = new ClientPool<(Uri Host, string? Username, string? Password), ElasticLowLevelClient>(key =>
{
var config = new ConnectionConfiguration(key.Host);
@ -61,7 +61,7 @@ public sealed class ElasticSearchActionHandler : RuleActionHandler<ElasticSearch
var ruleText = string.Empty;
var ruleJob = new ElasticSearchJob
{
IndexName = await FormatAsync(action.IndexName, @event),
IndexName = (await FormatAsync(action.IndexName, @event))!,
ServerHost = action.Host.ToString(),
ServerUser = action.Username,
ServerPassword = action.Password,
@ -79,7 +79,7 @@ public sealed class ElasticSearchActionHandler : RuleActionHandler<ElasticSearch
ElasticSearchContent content;
try
{
string jsonString;
string? jsonString;
if (!string.IsNullOrEmpty(action.Document))
{
@ -91,7 +91,7 @@ public sealed class ElasticSearchActionHandler : RuleActionHandler<ElasticSearch
jsonString = ToJson(@event);
}
content = serializer.Deserialize<ElasticSearchContent>(jsonString);
content = serializer.Deserialize<ElasticSearchContent>(jsonString!);
}
catch (Exception ex)
{
@ -156,9 +156,9 @@ public sealed class ElasticSearchJob
{
public string ServerHost { get; set; }
public string ServerUser { get; set; }
public string? ServerUser { get; set; }
public string ServerPassword { get; set; }
public string? ServerPassword { get; set; }
public string ContentId { get; set; }

10
backend/extensions/Squidex.Extensions/Actions/Email/EmailActionHandler.cs

@ -29,11 +29,11 @@ public sealed class EmailActionHandler : RuleActionHandler<EmailAction, EmailJob
ServerHost = action.ServerHost,
ServerPassword = action.ServerPassword,
ServerPort = action.ServerPort,
ServerUsername = await FormatAsync(action.ServerUsername, @event),
MessageFrom = await FormatAsync(action.MessageFrom, @event),
MessageTo = await FormatAsync(action.MessageTo, @event),
MessageSubject = await FormatAsync(action.MessageSubject, @event),
MessageBody = await FormatAsync(action.MessageBody, @event)
ServerUsername = (await FormatAsync(action.ServerUsername, @event))!,
MessageFrom = (await FormatAsync(action.MessageFrom, @event))!,
MessageTo = (await FormatAsync(action.MessageTo, @event))!,
MessageSubject = (await FormatAsync(action.MessageSubject, @event))!,
MessageBody = (await FormatAsync(action.MessageBody, @event))!
};
var description = $"Send an email to {action.MessageTo}";

10
backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaAction.cs

@ -30,17 +30,17 @@ public sealed record KafkaAction : RuleAction
[Display(Name = "Payload (Optional)", Description = "Leave it empty to use the full event as body.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Payload { get; set; }
public string? Payload { get; set; }
[Display(Name = "Key", Description = "The message key, commonly used for partitioning.")]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string Key { get; set; }
public string? Key { get; set; }
[Display(Name = "Partition Key", Description = "The partition key, only used when we don't want to define partiontionig with key.")]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string PartitionKey { get; set; }
public string? PartitionKey { get; set; }
[Display(Name = "Partition Count", Description = "Define the number of partitions for specific topic.")]
[Editor(RuleFieldEditor.Text)]
@ -49,9 +49,9 @@ public sealed record KafkaAction : RuleAction
[Display(Name = "Headers (Optional)", Description = "The message headers in the format '[Key]=[Value]', one entry per line.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Headers { get; set; }
public string? Headers { get; set; }
[Display(Name = "Schema (Optional)", Description = "Define a specific AVRO schema in JSON format.")]
[Editor(RuleFieldEditor.TextArea)]
public string Schema { get; set; }
public string? Schema { get; set; }
}

22
backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaActionHandler.cs

@ -25,7 +25,7 @@ public sealed class KafkaActionHandler : RuleActionHandler<KafkaAction, KafkaJob
protected override async Task<(string Description, KafkaJob Data)> CreateJobAsync(EnrichedEvent @event, KafkaAction action)
{
string value, key;
string? value, key;
if (!string.IsNullOrEmpty(action.Payload))
{
@ -59,7 +59,7 @@ public sealed class KafkaActionHandler : RuleActionHandler<KafkaAction, KafkaJob
return (Description, ruleJob);
}
private async Task<Dictionary<string, string>> ParseHeadersAsync(string headers, EnrichedEvent @event)
private async Task<Dictionary<string, string>?> ParseHeadersAsync(string? headers, EnrichedEvent @event)
{
if (string.IsNullOrWhiteSpace(headers))
{
@ -76,12 +76,12 @@ public sealed class KafkaActionHandler : RuleActionHandler<KafkaAction, KafkaJob
if (indexEqual > 0 && indexEqual < line.Length - 1)
{
var key = line[..indexEqual];
var val = line[(indexEqual + 1)..];
var headerKey = line[..indexEqual];
var headerValue = line[(indexEqual + 1)..];
val = await FormatAsync(val, @event);
headerValue = await FormatAsync(headerValue, @event);
headersDictionary[key] = val;
headersDictionary[headerKey] = headerValue!;
}
}
@ -108,15 +108,15 @@ public sealed class KafkaJob
{
public string TopicName { get; set; }
public string MessageKey { get; set; }
public string? MessageKey { get; set; }
public string MessageValue { get; set; }
public string? MessageValue { get; set; }
public string Schema { get; set; }
public string? Schema { get; set; }
public string PartitionKey { get; set; }
public string? PartitionKey { get; set; }
public Dictionary<string, string> Headers { get; set; }
public Dictionary<string, string>? Headers { get; set; }
public int PartitionCount { get; set; }
}

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

@ -108,7 +108,7 @@ public sealed class KafkaProducer
{
if (!string.IsNullOrWhiteSpace(job.Schema))
{
var value = CreateAvroRecord(job.MessageValue, job.Schema);
var value = CreateAvroRecord(job.MessageValue!, job.Schema);
var message = new Message<string, GenericRecord> { Value = value };
@ -116,7 +116,7 @@ public sealed class KafkaProducer
}
else
{
var message = new Message<string, string> { Value = job.MessageValue };
var message = new Message<string, string> { Value = job.MessageValue! };
await ProduceAsync(textProducer, message, job, ct);
}
@ -125,7 +125,7 @@ public sealed class KafkaProducer
private static async Task ProduceAsync<T>(IProducer<string, T> producer, Message<string, T> message, KafkaJob job,
CancellationToken ct)
{
message.Key = job.MessageKey;
message.Key = job.MessageKey!;
if (job.Headers?.Count > 0)
{
@ -157,9 +157,7 @@ public sealed class KafkaProducer
var jsonObject = jsonSerializer.Deserialize<JsonObject>(json);
var result = (GenericRecord)GetValue(jsonObject, schema);
return result;
return (GenericRecord)GetValue(jsonObject, schema)!;
}
catch (JsonException ex)
{
@ -173,7 +171,7 @@ public sealed class KafkaProducer
avroProducer?.Dispose();
}
private static object GetValue(JsonValue value, Schema schema)
private static object? GetValue(JsonValue value, Schema? schema)
{
switch (value.Value)
{
@ -191,11 +189,11 @@ public sealed class KafkaProducer
return s;
case JsonObject o when IsTypeOrUnionWith(schema, Schema.Type.Map):
{
var mapResult = new Dictionary<string, object>();
var mapResult = new Dictionary<string, object?>();
if (schema is UnionSchema union)
{
var map = (MapSchema)union.Schemas.FirstOrDefault(x => x.Tag == Schema.Type.Map);
var map = (MapSchema?)union.Schemas.FirstOrDefault(x => x.Tag == Schema.Type.Map);
foreach (var (key, childValue) in o)
{
@ -215,11 +213,11 @@ public sealed class KafkaProducer
case JsonObject o when IsTypeOrUnionWith(schema, Schema.Type.Record):
{
GenericRecord result = null;
GenericRecord? result = null;
if (schema is UnionSchema union)
{
var record = (RecordSchema)union.Schemas.FirstOrDefault(x => x.Tag == Schema.Type.Record);
var record = (RecordSchema?)union.Schemas.FirstOrDefault(x => x.Tag == Schema.Type.Record);
result = new GenericRecord(record);
@ -249,11 +247,11 @@ public sealed class KafkaProducer
case JsonArray a when IsTypeOrUnionWith(schema, Schema.Type.Array):
{
var result = new List<object>();
var result = new List<object?>();
if (schema is UnionSchema union)
{
var arraySchema = (ArraySchema)union.Schemas.FirstOrDefault(x => x.Tag == Schema.Type.Array);
var arraySchema = (ArraySchema?)union.Schemas.FirstOrDefault(x => x.Tag == Schema.Type.Array);
foreach (var item in a)
{
@ -275,8 +273,8 @@ public sealed class KafkaProducer
return null;
}
private static bool IsTypeOrUnionWith(Schema schema, Schema.Type expected)
private static bool IsTypeOrUnionWith(Schema? schema, Schema.Type expected)
{
return schema.Tag == expected || (schema is UnionSchema union && union.Schemas.Any(x => x.Tag == expected));
return schema?.Tag == expected || (schema is UnionSchema union && union.Schemas.Any(x => x.Tag == expected));
}
}

6
backend/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs

@ -41,16 +41,16 @@ public sealed record MediumAction : RuleAction
[Display(Name = "Canonical Url", Description = "The original home of this content, if it was originally published elsewhere.")]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string CanonicalUrl { get; set; }
public string? CanonicalUrl { get; set; }
[Display(Name = "Tags", Description = "The optional comma separated list of tags.")]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string Tags { get; set; }
public string? Tags { get; set; }
[Display(Name = "Publication Id", Description = "Optional publication id.")]
[Editor(RuleFieldEditor.Text)]
public string PublicationId { get; set; }
public string? PublicationId { get; set; }
[Display(Name = "Is Html", Description = "Indicates whether the content is markdown or html.")]
[Editor(RuleFieldEditor.Text)]

8
backend/extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs

@ -58,7 +58,7 @@ public sealed class MediumActionHandler : RuleActionHandler<MediumAction, Medium
return (Description, ruleJob);
}
private async Task<string[]> ParseTagsAsync(EnrichedEvent @event, MediumAction action)
private async Task<string[]?> ParseTagsAsync(EnrichedEvent @event, MediumAction action)
{
if (string.IsNullOrWhiteSpace(action.Tags))
{
@ -69,7 +69,7 @@ public sealed class MediumActionHandler : RuleActionHandler<MediumAction, Medium
{
var jsonTags = await FormatAsync(action.Tags, @event);
return serializer.Deserialize<string[]>(jsonTags);
return serializer.Deserialize<string[]>(jsonTags!);
}
catch
{
@ -90,7 +90,7 @@ public sealed class MediumActionHandler : RuleActionHandler<MediumAction, Medium
}
else
{
HttpResponseMessage response = null;
HttpResponseMessage? response = null;
var meRequest = BuildGetRequest(job, "/v1/me");
try
@ -141,7 +141,7 @@ public sealed class MediumJob
{
public string RequestBody { get; set; }
public string PublicationId { get; set; }
public string? PublicationId { get; set; }
public string AccessToken { get; set; }
}

4
backend/extensions/Squidex.Extensions/Actions/Notification/NotificationAction.cs

@ -34,9 +34,9 @@ public sealed record NotificationAction : RuleAction
[Display(Name = "Url", Description = "The optional url to attach to the notification.")]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string Url { get; set; }
public string? Url { get; set; }
[Display(Name = "Client", Description = "An optional client name.")]
[Editor(RuleFieldEditor.Text)]
public string Client { get; set; }
public string? Client { get; set; }
}

2
backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs

@ -53,7 +53,7 @@ public sealed class NotificationActionHandler : RuleActionHandler<NotificationAc
CommentId = DomainId.NewGuid(),
CommentsId = DomainId.Create(user.Id),
FromRule = true,
Text = await FormatAsync(action.Text, @event)
Text = (await FormatAsync(action.Text, @event))!
};
if (!string.IsNullOrWhiteSpace(action.Url))

8
backend/extensions/Squidex.Extensions/Actions/OpenSearch/OpenSearchAction.cs

@ -35,18 +35,18 @@ public sealed record OpenSearchAction : RuleAction
[Display(Name = "Username", Description = "The optional username.")]
[Editor(RuleFieldEditor.Text)]
public string Username { get; set; }
public string? Username { get; set; }
[Display(Name = "Password", Description = "The optional password.")]
[Editor(RuleFieldEditor.Text)]
public string Password { get; set; }
public string? Password { get; set; }
[Display(Name = "Document", Description = "The optional custom document.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Document { get; set; }
public string? Document { get; set; }
[Display(Name = "Deletion", Description = "The condition when to delete the document.")]
[Editor(RuleFieldEditor.Text)]
public string Delete { get; set; }
public string? Delete { get; set; }
}

14
backend/extensions/Squidex.Extensions/Actions/OpenSearch/OpenSearchActionHandler.cs

@ -20,14 +20,14 @@ namespace Squidex.Extensions.Actions.OpenSearch;
public sealed class OpenSearchActionHandler : RuleActionHandler<OpenSearchAction, OpenSearchJob>
{
private readonly ClientPool<(Uri Host, string Username, string Password), OpenSearchLowLevelClient> clients;
private readonly ClientPool<(Uri Host, string? Username, string? Password), OpenSearchLowLevelClient> clients;
private readonly IScriptEngine scriptEngine;
private readonly IJsonSerializer serializer;
public OpenSearchActionHandler(RuleEventFormatter formatter, IScriptEngine scriptEngine, IJsonSerializer serializer)
: base(formatter)
{
clients = new ClientPool<(Uri Host, string Username, string Password), OpenSearchLowLevelClient>(key =>
clients = new ClientPool<(Uri Host, string? Username, string? Password), OpenSearchLowLevelClient>(key =>
{
var config = new ConnectionConfiguration(key.Host);
@ -61,7 +61,7 @@ public sealed class OpenSearchActionHandler : RuleActionHandler<OpenSearchAction
var ruleText = string.Empty;
var ruleJob = new OpenSearchJob
{
IndexName = await FormatAsync(action.IndexName, @event),
IndexName = (await FormatAsync(action.IndexName, @event))!,
ServerHost = action.Host.ToString(),
ServerUser = action.Username,
ServerPassword = action.Password,
@ -79,7 +79,7 @@ public sealed class OpenSearchActionHandler : RuleActionHandler<OpenSearchAction
OpenSearchContent content;
try
{
string jsonString;
string? jsonString;
if (!string.IsNullOrEmpty(action.Document))
{
@ -91,7 +91,7 @@ public sealed class OpenSearchActionHandler : RuleActionHandler<OpenSearchAction
jsonString = ToJson(@event);
}
content = serializer.Deserialize<OpenSearchContent>(jsonString);
content = serializer.Deserialize<OpenSearchContent>(jsonString!);
}
catch (Exception ex)
{
@ -156,9 +156,9 @@ public sealed class OpenSearchJob
{
public string ServerHost { get; set; }
public string ServerUser { get; set; }
public string? ServerUser { get; set; }
public string ServerPassword { get; set; }
public string? ServerPassword { get; set; }
public string ContentId { get; set; }

6
backend/extensions/Squidex.Extensions/Actions/RuleHelper.cs

@ -14,7 +14,7 @@ namespace Squidex.Extensions.Actions;
public static class RuleHelper
{
public static bool ShouldDelete(this EnrichedEvent @event, IScriptEngine scriptEngine, string expression)
public static bool ShouldDelete(this EnrichedEvent @event, IScriptEngine scriptEngine, string? expression)
{
if (!string.IsNullOrWhiteSpace(expression))
{
@ -40,10 +40,10 @@ public static class RuleHelper
return @event is EnrichedAssetEvent { Type: EnrichedAssetEventType.Deleted };
}
public static async Task<Result> OneWayRequestAsync(this HttpClient client, HttpRequestMessage request, string requestBody = null,
public static async Task<Result> OneWayRequestAsync(this HttpClient client, HttpRequestMessage request, string? requestBody = null,
CancellationToken ct = default)
{
HttpResponseMessage response = null;
HttpResponseMessage? response = null;
try
{
response = await client.SendAsync(request, ct);

6
backend/extensions/Squidex.Extensions/Actions/SignalR/SignalRAction.cs

@ -42,17 +42,17 @@ public sealed record SignalRAction : RuleAction
[Display(Name = "Methode Name", Description = "Set the Name of the hub method received by the customer.")]
[Editor(RuleFieldEditor.Text)]
public string MethodName { get; set; }
public string? MethodName { get; set; }
[Display(Name = "Target (Optional)", Description = "Define target users or groups by id or name. One item per line. Not needed for Broadcast action.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Target { get; set; }
public string? Target { get; set; }
[Display(Name = "Payload (Optional)", Description = "Leave it empty to use the full event as body.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Payload { get; set; }
public string? Payload { get; set; }
protected override IEnumerable<ValidationError> CustomValidate()
{

9
backend/extensions/Squidex.Extensions/Actions/SignalR/SignalRActionHandler.cs

@ -39,7 +39,7 @@ public sealed class SignalRActionHandler : RuleActionHandler<SignalRAction, Sign
{
var hubName = await FormatAsync(action.HubName, @event);
string requestBody;
string? requestBody;
if (!string.IsNullOrWhiteSpace(action.Payload))
{
@ -53,14 +53,13 @@ public sealed class SignalRActionHandler : RuleActionHandler<SignalRAction, Sign
var target = (await FormatAsync(action.Target, @event)) ?? string.Empty;
var ruleText = $"Send SignalRJob to signalR hub '{hubName}'";
var ruleJob = new SignalRJob
{
Action = action.Action,
ConnectionString = action.ConnectionString,
HubName = hubName,
HubName = hubName!,
MethodName = action.MethodName,
MethodPayload = requestBody,
MethodPayload = requestBody!,
Targets = target.Split("\n")
};
@ -102,7 +101,7 @@ public sealed class SignalRJob
public ActionTypeEnum Action { get; set; }
public string MethodName { get; set; }
public string? MethodName { get; set; }
public string MethodPayload { get; set; }

2
backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs

@ -30,7 +30,7 @@ public sealed class TweetActionHandler : RuleActionHandler<TweetAction, TweetJob
{
var ruleJob = new TweetJob
{
Text = await FormatAsync(action.Text, @event),
Text = (await FormatAsync(action.Text, @event))!,
AccessToken = action.AccessToken,
AccessSecret = action.AccessSecret
};

4
backend/extensions/Squidex.Extensions/Actions/Typesense/TypesenseAction.cs

@ -41,9 +41,9 @@ public sealed record TypesenseAction : RuleAction
[Display(Name = "Document", Description = "The optional custom document.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Document { get; set; }
public string? Document { get; set; }
[Display(Name = "Deletion", Description = "The condition when to delete the document.")]
[Editor(RuleFieldEditor.Text)]
public string Delete { get; set; }
public string? Delete { get; set; }
}

4
backend/extensions/Squidex.Extensions/Actions/Typesense/TypesenseActionHandler.cs

@ -68,7 +68,7 @@ public sealed class TypesenseActionHandler : RuleActionHandler<TypesenseAction,
TypesenseContent content;
try
{
string jsonString;
string? jsonString;
if (!string.IsNullOrEmpty(action.Document))
{
@ -80,7 +80,7 @@ public sealed class TypesenseActionHandler : RuleActionHandler<TypesenseAction,
jsonString = ToJson(@event);
}
content = serializer.Deserialize<TypesenseContent>(jsonString);
content = serializer.Deserialize<TypesenseContent>(jsonString!);
}
catch (Exception ex)
{

8
backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs

@ -36,20 +36,20 @@ public sealed record WebhookAction : RuleAction
[Display(Name = "Payload (Optional)", Description = "Leave it empty to use the full event as body.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Payload { get; set; }
public string? Payload { get; set; }
[Display(Name = "Payload Type", Description = "The mime type of the payload.")]
[Editor(RuleFieldEditor.Text)]
public string PayloadType { get; set; }
public string? PayloadType { get; set; }
[Display(Name = "Headers (Optional)", Description = "The message headers in the format '[Key]=[Value]', one entry per line.")]
[Editor(RuleFieldEditor.TextArea)]
[Formattable]
public string Headers { get; set; }
public string? Headers { get; set; }
[Display(Name = "Shared Secret", Description = "The shared secret that is used to calculate the payload signature.")]
[Editor(RuleFieldEditor.Text)]
public string SharedSecret { get; set; }
public string? SharedSecret { get; set; }
}
public enum WebhookMethod

12
backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs

@ -48,9 +48,9 @@ public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction, Webh
var ruleJob = new WebhookJob
{
Method = action.Method,
RequestUrl = await FormatAsync(action.Url.ToString(), @event),
RequestUrl = (await FormatAsync(action.Url.ToString(), @event))!,
RequestSignature = requestSignature,
RequestBody = requestBody,
RequestBody = requestBody!,
RequestBodyType = action.PayloadType,
Headers = await ParseHeadersAsync(action.Headers, @event)
};
@ -58,7 +58,7 @@ public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction, Webh
return (ruleText, ruleJob);
}
private async Task<Dictionary<string, string>> ParseHeadersAsync(string headers, EnrichedEvent @event)
private async Task<Dictionary<string, string>?> ParseHeadersAsync(string? headers, EnrichedEvent @event)
{
if (string.IsNullOrWhiteSpace(headers))
{
@ -80,7 +80,7 @@ public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction, Webh
headerValue = await FormatAsync(headerValue, @event);
headersDictionary[headerKey] = headerValue;
headersDictionary[headerKey] = headerValue!;
}
}
@ -146,7 +146,7 @@ public sealed class WebhookJob
public string RequestBody { get; set; }
public string RequestBodyType { get; set; }
public string? RequestBodyType { get; set; }
public Dictionary<string, string> Headers { get; set; }
public Dictionary<string, string>? Headers { get; set; }
}

6
backend/extensions/Squidex.Extensions/Samples/Middleware/DoubleLinkedContentMiddleware.cs

@ -101,11 +101,11 @@ public sealed class DoubleLinkedContentMiddleware : ICustomCommandMiddleware
}, ct);
}
private static string GetReference(ContentData data)
private static string? GetReference(ContentData? data)
{
if (data != null && data.TryGetValue("reference", out ContentFieldData fieldData))
if (data != null && data.TryGetValue("reference", out ContentFieldData? fieldData))
{
return fieldData.Values.OfType<JsonArray>().SelectMany(x => x).SingleOrDefault().ToString();
return fieldData?.Values.OfType<JsonArray>().SelectMany(x => x).SingleOrDefault().ToString();
}
return null;

1
backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj

@ -3,6 +3,7 @@
<TargetFramework>net7.0</TargetFramework>
<LangVersion>10.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Core.Operations\Squidex.Domain.Apps.Core.Operations.csproj" />

4
backend/extensions/Squidex.Extensions/Text/Azure/AzureIndexDefinition.cs

@ -23,8 +23,8 @@ public static class AzureIndexDefinition
var analyzers =
typeof(LexicalAnalyzerName)
.GetProperties(BindingFlags.Public | BindingFlags.Static)
.Select(x => x.GetValue(null))
.Select(x => x.ToString())
.Select(x => x.GetValue(null)!)
.Select(x => x.ToString()!)
.OrderBy(x => x)
.ToList();

6
backend/extensions/Squidex.Extensions/Text/Azure/AzureTextIndex.cs

@ -70,7 +70,7 @@ public sealed class AzureTextIndex : IInitializable, ITextIndex
await searchClient.IndexDocumentsAsync(batch, cancellationToken: ct);
}
public async Task<List<DomainId>> SearchAsync(IAppEntity app, GeoQuery query, SearchScope scope,
public async Task<List<DomainId>?> SearchAsync(IAppEntity app, GeoQuery query, SearchScope scope,
CancellationToken ct = default)
{
Guard.NotNull(app);
@ -83,7 +83,7 @@ public sealed class AzureTextIndex : IInitializable, ITextIndex
return result.OrderByDescending(x => x.Score).Select(x => x.Id).Distinct().ToList();
}
public async Task<List<DomainId>> SearchAsync(IAppEntity app, TextQuery query, SearchScope scope,
public async Task<List<DomainId>?> SearchAsync(IAppEntity app, TextQuery query, SearchScope scope,
CancellationToken ct = default)
{
Guard.NotNull(app);
@ -157,7 +157,7 @@ public sealed class AzureTextIndex : IInitializable, ITextIndex
{
if (item != null)
{
var id = DomainId.Create(item.Document["contentId"].ToString());
var id = DomainId.Create(item.Document["contentId"].ToString()!);
result.Add((id, factor * item.Score ?? 0));
}

4
backend/extensions/Squidex.Extensions/Text/Azure/CommandFactory.cs

@ -33,7 +33,7 @@ public static class CommandFactory
private static void UpsertTextEntry(UpsertIndexEntry upsert, IList<IndexDocumentsAction<SearchDocument>> batch)
{
var geoField = string.Empty;
var geoObject = (object)null;
var geoObject = (object?)null;
if (upsert.GeoObjects != null)
{
@ -56,7 +56,7 @@ public static class CommandFactory
}
}
if (upsert.Texts != null || geoObject != null)
if (upsert.Texts != null && geoObject != null)
{
var document = new SearchDocument
{

21
backend/extensions/Squidex.Extensions/Text/ElasticSearch/CommandFactory.cs

@ -31,7 +31,7 @@ public static class CommandFactory
private static void UpsertEntry(UpsertIndexEntry upsert, List<object> args, string indexName)
{
var geoField = string.Empty;
var geoObject = (object)null;
var geoObject = (object?)null;
if (upsert.GeoObjects != null)
{
@ -63,18 +63,21 @@ public static class CommandFactory
var texts = new Dictionary<string, string>();
foreach (var (key, value) in upsert.Texts)
if (upsert.Texts != null)
{
var text = value;
foreach (var (key, value) in upsert.Texts)
{
var text = value;
var languageCode = ElasticSearchIndexDefinition.GetFieldName(key);
var languageCode = ElasticSearchIndexDefinition.GetFieldName(key);
if (texts.TryGetValue(languageCode, out var existing))
{
text = $"{existing} {value}";
}
if (texts.TryGetValue(languageCode, out var existing))
{
text = $"{existing} {value}";
}
texts[languageCode] = text;
texts[languageCode] = text;
}
}
args.Add(new

6
backend/extensions/Squidex.Extensions/Text/ElasticSearch/ElasticSearchTextIndex.cs

@ -61,7 +61,7 @@ public sealed class ElasticSearchTextIndex : ITextIndex, IInitializable
return elasticClient.BulkAsync(args, ct);
}
public async Task<List<DomainId>> SearchAsync(IAppEntity app, GeoQuery query, SearchScope scope,
public async Task<List<DomainId>?> SearchAsync(IAppEntity app, GeoQuery query, SearchScope scope,
CancellationToken ct = default)
{
Guard.NotNull(app);
@ -123,7 +123,7 @@ public sealed class ElasticSearchTextIndex : ITextIndex, IInitializable
return await SearchAsync(elasticQuery, ct);
}
public async Task<List<DomainId>> SearchAsync(IAppEntity app, TextQuery query, SearchScope scope,
public async Task<List<DomainId>?> SearchAsync(IAppEntity app, TextQuery query, SearchScope scope,
CancellationToken ct = default)
{
Guard.NotNull(app);
@ -194,7 +194,7 @@ public sealed class ElasticSearchTextIndex : ITextIndex, IInitializable
{
var bySchema = new
{
terms = new Dictionary<string, object>
terms = new Dictionary<string, object?>
{
["schemaId.keyword"] = query.PreferredSchemaId.ToString()
}

6
backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs

@ -27,7 +27,7 @@ internal sealed class CompositeUniqueValidator : IValidator
this.contentRepository = contentRepository;
}
public void Validate(object value, ValidationContext context)
public void Validate(object? value, ValidationContext context)
{
if (value is ContentData data)
{
@ -64,11 +64,11 @@ internal sealed class CompositeUniqueValidator : IValidator
}
}
private static ClrValue TryGetValue(IRootField field, ContentData data)
private static ClrValue? TryGetValue(IRootField field, ContentData data)
{
var value = JsonValue.Null;
if (data.TryGetValue(field.Name, out var fieldValue))
if (data.TryGetValue(field.Name, out var fieldValue) && fieldValue != null)
{
if (fieldValue.TryGetValue(InvariantPartitioning.Key, out var temp) && temp != default)
{

2
backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidatorFactory.cs

@ -28,7 +28,7 @@ public sealed class CompositeUniqueValidatorFactory : IValidatorsFactory
}
}
private static IEnumerable<string> ValidatorTags(IEnumerable<string> tags)
private static IEnumerable<string> ValidatorTags(IEnumerable<string>? tags)
{
if (tags == null)
{

28
backend/i18n/frontend_en.json

@ -183,6 +183,22 @@
"clients.connectWizard.cliStep3": "Add your app name the CLI config",
"clients.connectWizard.cliStep3Hint": "You can manage configuration to multiple apps in the CLI and switch to an app.",
"clients.connectWizard.cliStep4": "Switch to your app in the CLI",
"clients.connectWizard.dotnetSdk": "Use the .NET SDK",
"clients.connectWizard.dotnetSdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.dotnetSdkHint": "Install the .NET SDK and establish a connection to this app.",
"clients.connectWizard.dotnetSdkStep1": "Install the .NET SDK",
"clients.connectWizard.dotnetSdkStep1Download": "The SDK is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.dotnetSdkStep2": "Version 14 and before: Create a client manager",
"clients.connectWizard.dotnetSdkStep2_15": "Version 15 and later: Create a client",
"clients.connectWizard.dotnetSdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.dotnetSdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.dotnetSdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.javascriptSdk": "Use the JavaScript SDK",
"clients.connectWizard.javascriptSdkDocumentation": "Documentations for the JavaScript SDK is available: ",
"clients.connectWizard.javascriptSdkHint": "Install the SDK and establish a connection to this app.",
"clients.connectWizard.javascriptSdkStep1": "Install the Javascript SDK",
"clients.connectWizard.javascriptSdkStep1Download": "The SDK is available on [npm](https://www.npmjs.com/package/@squidex/squidex",
"clients.connectWizard.javascriptSdkStep2": "Create a client",
"clients.connectWizard.manually": "Connect manually",
"clients.connectWizard.manuallyHint": "Get instructions how to establish a connection with Postman or curl.",
"clients.connectWizard.manuallyStep1": "Get a token using curl",
@ -190,18 +206,8 @@
"clients.connectWizard.manuallyStep3": "Add the token as HTTP header to all requests",
"clients.connectWizard.manuallyTokenHint": "Tokens usally expire after 30days, but you can request multiple tokens.",
"clients.connectWizard.postManDocs": "Start with the Postman tutorial in the [Documentation](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman).",
"clients.connectWizard.sdk": "Connect to your App with SDK",
"clients.connectWizard.sdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.sdkHelp": "You need another SDK?",
"clients.connectWizard.sdkHelpLink": "Contact us in the Support Forum",
"clients.connectWizard.sdkHint": "Download an SDK and establish a connection to this app.",
"clients.connectWizard.sdkStep1": "Install the .NET SDK",
"clients.connectWizard.sdkStep1Download": "The SDK is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "Version 14 and before: Create a client manager",
"clients.connectWizard.sdkStep2_15": "Version 15 and later: Create a client",
"clients.connectWizard.sdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.sdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.sdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.step0Title": "Setup client",
"clients.connectWizard.step1Title": "Choose connection method",
"clients.connectWizard.step2Title": "Connect",
@ -869,6 +875,8 @@
"schemas.fieldTypes.assets.folderId": "Folder",
"schemas.fieldTypes.assets.folderIdHint": "The asset folder where the new assets will be uploaded to.",
"schemas.fieldTypes.assets.previewFileName": "Only file name",
"schemas.fieldTypes.assets.previewFormat": "Preview Format",
"schemas.fieldTypes.assets.previewFormatHint": "Define custom formatting parameters for preview images. [Documentation](https://docs.squidex.io/02-documentation/developer-guides/api-overview/assets-api#how-to-resize-images)",
"schemas.fieldTypes.assets.previewImage": "Only thumbnail or file name if not an image",
"schemas.fieldTypes.assets.previewImageAndFileName": "Thumbnail and file name",
"schemas.fieldTypes.assets.previewMode": "PreviewMode",

28
backend/i18n/frontend_it.json

@ -183,6 +183,22 @@
"clients.connectWizard.cliStep3": "Inserisci il nome della tua app per la configurazione della CLI",
"clients.connectWizard.cliStep3Hint": "È possibile gestire le configurazione per le diverse appi all'interno della CLI e passare ad un'app.",
"clients.connectWizard.cliStep4": "Passa alla tua app usando CLI",
"clients.connectWizard.dotnetSdk": "Connetti la tua APP utilizzando SDK",
"clients.connectWizard.dotnetSdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.dotnetSdkHint": "Scarica l'SDK e connetti quest'app.",
"clients.connectWizard.dotnetSdkStep1": "Installa .NET SDK",
"clients.connectWizard.dotnetSdkStep1Download": "L'SDK è disponibile su [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.dotnetSdkStep2": "Crea un client manager",
"clients.connectWizard.dotnetSdkStep2_15": "Version 15 and later: Create a client",
"clients.connectWizard.dotnetSdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.dotnetSdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.dotnetSdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.javascriptSdk": "Use the JavaScript SDK",
"clients.connectWizard.javascriptSdkDocumentation": "Documentations for the JavaScript SDK is available: ",
"clients.connectWizard.javascriptSdkHint": "Install the SDK and establish a connection to this app.",
"clients.connectWizard.javascriptSdkStep1": "Install the Javascript SDK",
"clients.connectWizard.javascriptSdkStep1Download": "The SDK is available on [npm](https://www.npmjs.com/package/@squidex/squidex",
"clients.connectWizard.javascriptSdkStep2": "Create a client",
"clients.connectWizard.manually": "Connetti manualmente",
"clients.connectWizard.manuallyHint": "Leggi le istruzioni su come stabilire una connessione utilizzando Postman o curl.",
"clients.connectWizard.manuallyStep1": "Ottenere un token usando curl",
@ -190,18 +206,8 @@
"clients.connectWizard.manuallyStep3": "Aggiungi il token come header HTTP header a tutte le richieste",
"clients.connectWizard.manuallyTokenHint": "Solitamente i Token scadono dopo 30 giorni, ma puoi richiedere token multipli.",
"clients.connectWizard.postManDocs": "Per il tutorial Postman inizia da questo link [Documentazione](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman).",
"clients.connectWizard.sdk": "Connetti la tua APP utilizzando SDK",
"clients.connectWizard.sdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.sdkHelp": "Hai bisogno di un altro SDK?",
"clients.connectWizard.sdkHelpLink": "Contattaci nel Forum di assistenza",
"clients.connectWizard.sdkHint": "Scarica l'SDK e connetti quest'app.",
"clients.connectWizard.sdkStep1": "Installa .NET SDK",
"clients.connectWizard.sdkStep1Download": "L'SDK è disponibile su [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "Crea un client manager",
"clients.connectWizard.sdkStep2_15": "Version 15 and later: Create a client",
"clients.connectWizard.sdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.sdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.sdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.step0Title": "Setup client",
"clients.connectWizard.step1Title": "Scegli la tipologia di connessione",
"clients.connectWizard.step2Title": "Collega",
@ -869,6 +875,8 @@
"schemas.fieldTypes.assets.folderId": "Folder",
"schemas.fieldTypes.assets.folderIdHint": "The asset folder where the new assets will be uploaded to.",
"schemas.fieldTypes.assets.previewFileName": "Solamente il nome del file",
"schemas.fieldTypes.assets.previewFormat": "Preview Format",
"schemas.fieldTypes.assets.previewFormatHint": "Define custom formatting parameters for preview images. [Documentation](https://docs.squidex.io/02-documentation/developer-guides/api-overview/assets-api#how-to-resize-images)",
"schemas.fieldTypes.assets.previewImage": "Solamente l'anteprima o il nome del file se non è un immagine",
"schemas.fieldTypes.assets.previewImageAndFileName": "L'anteprima e il nome del file",
"schemas.fieldTypes.assets.previewMode": "Modalità anteprima",

28
backend/i18n/frontend_nl.json

@ -183,6 +183,22 @@
"clients.connectWizard.cliStep3": "Voeg uw app-naam toe aan de CLI-configuratie",
"clients.connectWizard.cliStep3Hint": "Je kunt de configuratie voor meerdere apps in de CLI beheren en overschakelen naar een app.",
"clients.connectWizard.cliStep4": "Schakel over naar uw app in de CLI",
"clients.connectWizard.dotnetSdk": "Maak verbinding met uw app met SDK",
"clients.connectWizard.dotnetSdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.dotnetSdkHint": "Download een SDK en maak verbinding met deze app.",
"clients.connectWizard.dotnetSdkStep1": "Installeer de .NET SDK",
"clients.connectWizard.dotnetSdkStep1Download": "De SDK is beschikbaar op [nuget] (https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.dotnetSdkStep2": "Maak een klantenbeheerder",
"clients.connectWizard.dotnetSdkStep2_15": "Version 15 and later: Create a client",
"clients.connectWizard.dotnetSdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.dotnetSdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.dotnetSdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.javascriptSdk": "Use the JavaScript SDK",
"clients.connectWizard.javascriptSdkDocumentation": "Documentations for the JavaScript SDK is available: ",
"clients.connectWizard.javascriptSdkHint": "Install the SDK and establish a connection to this app.",
"clients.connectWizard.javascriptSdkStep1": "Install the Javascript SDK",
"clients.connectWizard.javascriptSdkStep1Download": "The SDK is available on [npm](https://www.npmjs.com/package/@squidex/squidex",
"clients.connectWizard.javascriptSdkStep2": "Create a client",
"clients.connectWizard.manually": "Handmatig verbinden",
"clients.connectWizard.manuallyHint": "Krijg instructies om een ​​verbinding tot stand te brengen met Postman of curl.",
"clients.connectWizard.manuallyStep1": "Verkrijg een token met curl",
@ -190,18 +206,8 @@
"clients.connectWizard.manuallyStep3": "Voeg het token toe als HTTP-header aan alle verzoeken",
"clients.connectWizard.manuallyTokenHint": "Tokens vervallen gewoonlijk na 30 dagen, maar je kunt meerdere tokens aanvragen.",
"clients.connectWizard.postManDocs": "Begin met de Postman-tutorial in de [Documentatie] (https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman).",
"clients.connectWizard.sdk": "Maak verbinding met uw app met SDK",
"clients.connectWizard.sdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.sdkHelp": "Heb je een andere SDK nodig?",
"clients.connectWizard.sdkHelpLink": "Neem contact met ons op in het ondersteuningsforum",
"clients.connectWizard.sdkHint": "Download een SDK en maak verbinding met deze app.",
"clients.connectWizard.sdkStep1": "Installeer de .NET SDK",
"clients.connectWizard.sdkStep1Download": "De SDK is beschikbaar op [nuget] (https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "Maak een klantenbeheerder",
"clients.connectWizard.sdkStep2_15": "Version 15 and later: Create a client",
"clients.connectWizard.sdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.sdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.sdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.step0Title": "Client instellen",
"clients.connectWizard.step1Title": "Kies verbindingsmethode",
"clients.connectWizard.step2Title": "Verbinden",
@ -869,6 +875,8 @@
"schemas.fieldTypes.assets.folderId": "Map",
"schemas.fieldTypes.assets.folderIdHint": "De map waarnaar de nieuwe bestanden worden geüpload.",
"schemas.fieldTypes.assets.previewFileName": "Alleen bestandsnaam",
"schemas.fieldTypes.assets.previewFormat": "Preview Format",
"schemas.fieldTypes.assets.previewFormatHint": "Define custom formatting parameters for preview images. [Documentation](https://docs.squidex.io/02-documentation/developer-guides/api-overview/assets-api#how-to-resize-images)",
"schemas.fieldTypes.assets.previewImage": "Alleen miniatuur- of bestandsnaam indien geen afbeelding",
"schemas.fieldTypes.assets.previewImageAndFileName": "Miniatuur- en bestandsnaam",
"schemas.fieldTypes.assets.previewMode": "PreviewMode",

28
backend/i18n/frontend_pt.json

@ -183,6 +183,22 @@
"clients.connectWizard.cliStep3": "Adicione o nome da sua aplicação ao CLI config",
"clients.connectWizard.cliStep3Hint": "Pode gerir a configuração de várias aplicações no CLI e mudar para uma aplicação.",
"clients.connectWizard.cliStep4": "Mude para a sua aplicação no CLI",
"clients.connectWizard.dotnetSdk": "Conecte-se à sua App com a SDK",
"clients.connectWizard.dotnetSdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.dotnetSdkHint": "Descarregue um SDK e estabeleça uma ligação a esta aplicação.",
"clients.connectWizard.dotnetSdkStep1": "Instale o .NET SDK",
"clients.connectWizard.dotnetSdkStep1Download": "O SDK está disponível em [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.dotnetSdkStep2": "Criar um gestor de clientes",
"clients.connectWizard.dotnetSdkStep2_15": "Version 15 and later: Create a client",
"clients.connectWizard.dotnetSdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.dotnetSdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.dotnetSdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.javascriptSdk": "Use the JavaScript SDK",
"clients.connectWizard.javascriptSdkDocumentation": "Documentations for the JavaScript SDK is available: ",
"clients.connectWizard.javascriptSdkHint": "Install the SDK and establish a connection to this app.",
"clients.connectWizard.javascriptSdkStep1": "Install the Javascript SDK",
"clients.connectWizard.javascriptSdkStep1Download": "The SDK is available on [npm](https://www.npmjs.com/package/@squidex/squidex",
"clients.connectWizard.javascriptSdkStep2": "Create a client",
"clients.connectWizard.manually": "Conecte-se manualmente",
"clients.connectWizard.manuallyHint": "Obtenha instruções sobre como estabelecer uma ligação com o Carteiro ou o caracol.",
"clients.connectWizard.manuallyStep1": "Obter um símbolo usando caracóis",
@ -190,18 +206,8 @@
"clients.connectWizard.manuallyStep3": "Adicione o token como cabeçalho HTTP a todos os pedidos",
"clients.connectWizard.manuallyTokenHint": "Tokens normalmente expiram após 30 dias, mas você pode solicitar várias tokens.",
"clients.connectWizard.postManDocs": "Comece com o tutorial do Carteiro na [Documentação](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman).",
"clients.connectWizard.sdk": "Conecte-se à sua App com a SDK",
"clients.connectWizard.sdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.sdkHelp": "Precisa de outro SDK?",
"clients.connectWizard.sdkHelpLink": "Contacte-nos no Fórum de Apoio",
"clients.connectWizard.sdkHint": "Descarregue um SDK e estabeleça uma ligação a esta aplicação.",
"clients.connectWizard.sdkStep1": "Instale o .NET SDK",
"clients.connectWizard.sdkStep1Download": "O SDK está disponível em [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "Criar um gestor de clientes",
"clients.connectWizard.sdkStep2_15": "Version 15 and later: Create a client",
"clients.connectWizard.sdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.sdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.sdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.step0Title": "Cliente de configuração",
"clients.connectWizard.step1Title": "Escolha o método de ligação",
"clients.connectWizard.step2Title": "Ligar",
@ -869,6 +875,8 @@
"schemas.fieldTypes.assets.folderId": "Pasta",
"schemas.fieldTypes.assets.folderIdHint": "A pasta de ficheiros para onde os novos ficheiros serão carregados.",
"schemas.fieldTypes.assets.previewFileName": "Apenas nome de ficheiro",
"schemas.fieldTypes.assets.previewFormat": "Preview Format",
"schemas.fieldTypes.assets.previewFormatHint": "Define custom formatting parameters for preview images. [Documentation](https://docs.squidex.io/02-documentation/developer-guides/api-overview/assets-api#how-to-resize-images)",
"schemas.fieldTypes.assets.previewImage": "Apenas miniatura ou nome de ficheiro se não uma imagem",
"schemas.fieldTypes.assets.previewImageAndFileName": "Miniatura e nome do ficheiro",
"schemas.fieldTypes.assets.previewMode": "Pré-visualizaçãoMode",

28
backend/i18n/frontend_zh.json

@ -183,6 +183,22 @@
"clients.connectWizard.cliStep3": "在 CLI 配置中添加你的应用名称",
"clients.connectWizard.cliStep3Hint": "您可以在 CLI 中管理多个应用程序的配置并切换到一个应用程序。",
"clients.connectWizard.cliStep4": "在 CLI 中切换到您的应用程序",
"clients.connectWizard.dotnetSdk": "使用 SDK 连接到您的应用程序",
"clients.connectWizard.dotnetSdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.dotnetSdkHint": "下载 SDK 并建立与此应用程序的连接。",
"clients.connectWizard.dotnetSdkStep1": "安装.NET SDK",
"clients.connectWizard.dotnetSdkStep1Download": "SDK 可在 [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.dotnetSdkStep2": "创建客户端管理器",
"clients.connectWizard.dotnetSdkStep2_15": "Version 15 and later: Create a client",
"clients.connectWizard.dotnetSdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.dotnetSdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.dotnetSdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.javascriptSdk": "Use the JavaScript SDK",
"clients.connectWizard.javascriptSdkDocumentation": "Documentations for the JavaScript SDK is available: ",
"clients.connectWizard.javascriptSdkHint": "Install the SDK and establish a connection to this app.",
"clients.connectWizard.javascriptSdkStep1": "Install the Javascript SDK",
"clients.connectWizard.javascriptSdkStep1Download": "The SDK is available on [npm](https://www.npmjs.com/package/@squidex/squidex",
"clients.connectWizard.javascriptSdkStep2": "Create a client",
"clients.connectWizard.manually": "手动连接",
"clients.connectWizard.manuallyHint": "获取如何与 Postman 或 curl 建立连接的说明。",
"clients.connectWizard.manuallyStep1": "使用 curl 获取令牌",
@ -190,18 +206,8 @@
"clients.connectWizard.manuallyStep3": "将令牌作为 HTTP 标头添加到所有请求中",
"clients.connectWizard.manuallyTokenHint": "令牌通常会在 30 天后过期,但您可以请求多个令牌。",
"clients.connectWizard.postManDocs": "从 [文档](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman) 中的 Postman 教程开始。",
"clients.connectWizard.sdk": "使用 SDK 连接到您的应用程序",
"clients.connectWizard.sdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.sdkHelp": "你需要另一个 SDK?",
"clients.connectWizard.sdkHelpLink": "在支持论坛联系我们",
"clients.connectWizard.sdkHint": "下载 SDK 并建立与此应用程序的连接。",
"clients.connectWizard.sdkStep1": "安装.NET SDK",
"clients.connectWizard.sdkStep1Download": "SDK 可在 [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "创建客户端管理器",
"clients.connectWizard.sdkStep2_15": "Version 15 and later: Create a client",
"clients.connectWizard.sdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.sdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.sdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.step0Title": "设置客户端",
"clients.connectWizard.step1Title": "选择连接方式",
"clients.connectWizard.step2Title": "连接",
@ -869,6 +875,8 @@
"schemas.fieldTypes.assets.folderId": "文件夹",
"schemas.fieldTypes.assets.folderIdHint": "新资源将上传到的资源文件夹。",
"schemas.fieldTypes.assets.previewFileName": "仅文件名",
"schemas.fieldTypes.assets.previewFormat": "Preview Format",
"schemas.fieldTypes.assets.previewFormatHint": "Define custom formatting parameters for preview images. [Documentation](https://docs.squidex.io/02-documentation/developer-guides/api-overview/assets-api#how-to-resize-images)",
"schemas.fieldTypes.assets.previewImage": "如果不是图像,则只有缩略图或文件名",
"schemas.fieldTypes.assets.previewImageAndFileName": "缩略图和文件名",
"schemas.fieldTypes.assets.previewMode": "PreviewMode",

28
backend/i18n/source/frontend_en.json

@ -183,6 +183,22 @@
"clients.connectWizard.cliStep3": "Add your app name the CLI config",
"clients.connectWizard.cliStep3Hint": "You can manage configuration to multiple apps in the CLI and switch to an app.",
"clients.connectWizard.cliStep4": "Switch to your app in the CLI",
"clients.connectWizard.dotnetSdk": "Use the .NET SDK",
"clients.connectWizard.dotnetSdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.dotnetSdkHint": "Install the .NET SDK and establish a connection to this app.",
"clients.connectWizard.dotnetSdkStep1": "Install the .NET SDK",
"clients.connectWizard.dotnetSdkStep1Download": "The SDK is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.dotnetSdkStep2": "Version 14 and before: Create a client manager",
"clients.connectWizard.dotnetSdkStep2_15": "Version 15 and later: Create a client",
"clients.connectWizard.dotnetSdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.dotnetSdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.dotnetSdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.javascriptSdk": "Use the JavaScript SDK",
"clients.connectWizard.javascriptSdkDocumentation": "Documentations for the JavaScript SDK is available: ",
"clients.connectWizard.javascriptSdkHint": "Install the SDK and establish a connection to this app.",
"clients.connectWizard.javascriptSdkStep1": "Install the Javascript SDK",
"clients.connectWizard.javascriptSdkStep1Download": "The SDK is available on [npm](https://www.npmjs.com/package/@squidex/squidex)",
"clients.connectWizard.javascriptSdkStep2": "Create a client",
"clients.connectWizard.manually": "Connect manually",
"clients.connectWizard.manuallyHint": "Get instructions how to establish a connection with Postman or curl.",
"clients.connectWizard.manuallyStep1": "Get a token using curl",
@ -190,18 +206,8 @@
"clients.connectWizard.manuallyStep3": "Add the token as HTTP header to all requests",
"clients.connectWizard.manuallyTokenHint": "Tokens usally expire after 30days, but you can request multiple tokens.",
"clients.connectWizard.postManDocs": "Start with the Postman tutorial in the [Documentation](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman).",
"clients.connectWizard.sdk": "Connect to your App with SDK",
"clients.connectWizard.sdkDocumentation": "Documentations for the .NET SDK is available: ",
"clients.connectWizard.sdkHelp": "You need another SDK?",
"clients.connectWizard.sdkHelpLink": "Contact us in the Support Forum",
"clients.connectWizard.sdkHint": "Download an SDK and establish a connection to this app.",
"clients.connectWizard.sdkStep1": "Install the .NET SDK",
"clients.connectWizard.sdkStep1Download": "The SDK is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "Version 14 and before: Create a client manager",
"clients.connectWizard.sdkStep2_15": "Version 15 and later: Create a client",
"clients.connectWizard.sdkStep3": "Optionally: Install the Service Extensions for the SDK",
"clients.connectWizard.sdkStep3Download": "The SDK Extension is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary.ServiceExtensions/)",
"clients.connectWizard.sdkStep4": "Optionally: Register the client manager and all clients",
"clients.connectWizard.step0Title": "Setup client",
"clients.connectWizard.step1Title": "Choose connection method",
"clients.connectWizard.step2Title": "Connect",
@ -869,6 +875,8 @@
"schemas.fieldTypes.assets.folderId": "Folder",
"schemas.fieldTypes.assets.folderIdHint": "The asset folder where the new assets will be uploaded to.",
"schemas.fieldTypes.assets.previewFileName": "Only file name",
"schemas.fieldTypes.assets.previewFormat": "Preview Format",
"schemas.fieldTypes.assets.previewFormatHint": "Define custom formatting parameters for preview images. [Documentation](https://docs.squidex.io/02-documentation/developer-guides/api-overview/assets-api#how-to-resize-images)",
"schemas.fieldTypes.assets.previewImage": "Only thumbnail or file name if not an image",
"schemas.fieldTypes.assets.previewImageAndFileName": "Thumbnail and file name",
"schemas.fieldTypes.assets.previewMode": "PreviewMode",

10
backend/i18n/source/frontend_it.json

@ -147,6 +147,11 @@
"clients.connectWizard.cliStep3": "Inserisci il nome della tua app per la configurazione della CLI",
"clients.connectWizard.cliStep3Hint": "È possibile gestire le configurazione per le diverse appi all'interno della CLI e passare ad un'app.",
"clients.connectWizard.cliStep4": "Passa alla tua app usando CLI",
"clients.connectWizard.dotnetSdk": "Connetti la tua APP utilizzando SDK",
"clients.connectWizard.dotnetSdkHint": "Scarica l'SDK e connetti quest'app.",
"clients.connectWizard.dotnetSdkStep1": "Installa .NET SDK",
"clients.connectWizard.dotnetSdkStep1Download": "L'SDK è disponibile su [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.dotnetSdkStep2": "Crea un client manager",
"clients.connectWizard.manually": "Connetti manualmente",
"clients.connectWizard.manuallyHint": "Leggi le istruzioni su come stabilire una connessione utilizzando Postman o curl.",
"clients.connectWizard.manuallyStep1": "Ottenere un token usando curl",
@ -154,13 +159,8 @@
"clients.connectWizard.manuallyStep3": "Aggiungi il token come header HTTP header a tutte le richieste",
"clients.connectWizard.manuallyTokenHint": "Solitamente i Token scadono dopo 30 giorni, ma puoi richiedere token multipli.",
"clients.connectWizard.postManDocs": "Per il tutorial Postman inizia da questo link [Documentazione](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman).",
"clients.connectWizard.sdk": "Connetti la tua APP utilizzando SDK",
"clients.connectWizard.sdkHelp": "Hai bisogno di un altro SDK?",
"clients.connectWizard.sdkHelpLink": "Contattaci nel Forum di assistenza",
"clients.connectWizard.sdkHint": "Scarica l'SDK e connetti quest'app.",
"clients.connectWizard.sdkStep1": "Installa .NET SDK",
"clients.connectWizard.sdkStep1Download": "L'SDK è disponibile su [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "Crea un client manager",
"clients.connectWizard.step0Title": "Setup client",
"clients.connectWizard.step1Title": "Scegli la tipologia di connessione",
"clients.connectWizard.step2Title": "Collega",

10
backend/i18n/source/frontend_nl.json

@ -169,6 +169,11 @@
"clients.connectWizard.cliStep3": "Voeg uw app-naam toe aan de CLI-configuratie",
"clients.connectWizard.cliStep3Hint": "Je kunt de configuratie voor meerdere apps in de CLI beheren en overschakelen naar een app.",
"clients.connectWizard.cliStep4": "Schakel over naar uw app in de CLI",
"clients.connectWizard.dotnetSdk": "Maak verbinding met uw app met SDK",
"clients.connectWizard.dotnetSdkHint": "Download een SDK en maak verbinding met deze app.",
"clients.connectWizard.dotnetSdkStep1": "Installeer de .NET SDK",
"clients.connectWizard.dotnetSdkStep1Download": "De SDK is beschikbaar op [nuget] (https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.dotnetSdkStep2": "Maak een klantenbeheerder",
"clients.connectWizard.manually": "Handmatig verbinden",
"clients.connectWizard.manuallyHint": "Krijg instructies om een ​​verbinding tot stand te brengen met Postman of curl.",
"clients.connectWizard.manuallyStep1": "Verkrijg een token met curl",
@ -176,13 +181,8 @@
"clients.connectWizard.manuallyStep3": "Voeg het token toe als HTTP-header aan alle verzoeken",
"clients.connectWizard.manuallyTokenHint": "Tokens vervallen gewoonlijk na 30 dagen, maar je kunt meerdere tokens aanvragen.",
"clients.connectWizard.postManDocs": "Begin met de Postman-tutorial in de [Documentatie] (https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman).",
"clients.connectWizard.sdk": "Maak verbinding met uw app met SDK",
"clients.connectWizard.sdkHelp": "Heb je een andere SDK nodig?",
"clients.connectWizard.sdkHelpLink": "Neem contact met ons op in het ondersteuningsforum",
"clients.connectWizard.sdkHint": "Download een SDK en maak verbinding met deze app.",
"clients.connectWizard.sdkStep1": "Installeer de .NET SDK",
"clients.connectWizard.sdkStep1Download": "De SDK is beschikbaar op [nuget] (https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "Maak een klantenbeheerder",
"clients.connectWizard.step0Title": "Client instellen",
"clients.connectWizard.step1Title": "Kies verbindingsmethode",
"clients.connectWizard.step2Title": "Verbinden",

10
backend/i18n/source/frontend_pt.json

@ -180,6 +180,11 @@
"clients.connectWizard.cliStep3": "Adicione o nome da sua aplicação ao CLI config",
"clients.connectWizard.cliStep3Hint": "Pode gerir a configuração de várias aplicações no CLI e mudar para uma aplicação.",
"clients.connectWizard.cliStep4": "Mude para a sua aplicação no CLI",
"clients.connectWizard.dotnetSdk": "Conecte-se à sua App com a SDK",
"clients.connectWizard.dotnetSdkHint": "Descarregue um SDK e estabeleça uma ligação a esta aplicação.",
"clients.connectWizard.dotnetSdkStep1": "Instale o .NET SDK",
"clients.connectWizard.dotnetSdkStep1Download": "O SDK está disponível em [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.dotnetSdkStep2": "Criar um gestor de clientes",
"clients.connectWizard.manually": "Conecte-se manualmente",
"clients.connectWizard.manuallyHint": "Obtenha instruções sobre como estabelecer uma ligação com o Carteiro ou o caracol.",
"clients.connectWizard.manuallyStep1": "Obter um símbolo usando caracóis",
@ -187,13 +192,8 @@
"clients.connectWizard.manuallyStep3": "Adicione o token como cabeçalho HTTP a todos os pedidos",
"clients.connectWizard.manuallyTokenHint": "Tokens normalmente expiram após 30 dias, mas você pode solicitar várias tokens.",
"clients.connectWizard.postManDocs": "Comece com o tutorial do Carteiro na [Documentação](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman).",
"clients.connectWizard.sdk": "Conecte-se à sua App com a SDK",
"clients.connectWizard.sdkHelp": "Precisa de outro SDK?",
"clients.connectWizard.sdkHelpLink": "Contacte-nos no Fórum de Apoio",
"clients.connectWizard.sdkHint": "Descarregue um SDK e estabeleça uma ligação a esta aplicação.",
"clients.connectWizard.sdkStep1": "Instale o .NET SDK",
"clients.connectWizard.sdkStep1Download": "O SDK está disponível em [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "Criar um gestor de clientes",
"clients.connectWizard.step0Title": "Cliente de configuração",
"clients.connectWizard.step1Title": "Escolha o método de ligação",
"clients.connectWizard.step2Title": "Ligar",

10
backend/i18n/source/frontend_zh.json

@ -157,6 +157,11 @@
"clients.connectWizard.cliStep3": "在 CLI 配置中添加你的应用名称",
"clients.connectWizard.cliStep3Hint": "您可以在 CLI 中管理多个应用程序的配置并切换到一个应用程序。",
"clients.connectWizard.cliStep4": "在 CLI 中切换到您的应用程序",
"clients.connectWizard.dotnetSdk": "使用 SDK 连接到您的应用程序",
"clients.connectWizard.dotnetSdkHint": "下载 SDK 并建立与此应用程序的连接。",
"clients.connectWizard.dotnetSdkStep1": "安装.NET SDK",
"clients.connectWizard.dotnetSdkStep1Download": "SDK 可在 [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.dotnetSdkStep2": "创建客户端管理器",
"clients.connectWizard.manually": "手动连接",
"clients.connectWizard.manuallyHint": "获取如何与 Postman 或 curl 建立连接的说明。",
"clients.connectWizard.manuallyStep1": "使用 curl 获取令牌",
@ -164,13 +169,8 @@
"clients.connectWizard.manuallyStep3": "将令牌作为 HTTP 标头添加到所有请求中",
"clients.connectWizard.manuallyTokenHint": "令牌通常会在 30 天后过期,但您可以请求多个令牌。",
"clients.connectWizard.postManDocs": "从 [文档](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman) 中的 Postman 教程开始。",
"clients.connectWizard.sdk": "使用 SDK 连接到您的应用程序",
"clients.connectWizard.sdkHelp": "你需要另一个 SDK?",
"clients.connectWizard.sdkHelpLink": "在支持论坛联系我们",
"clients.connectWizard.sdkHint": "下载 SDK 并建立与此应用程序的连接。",
"clients.connectWizard.sdkStep1": "安装.NET SDK",
"clients.connectWizard.sdkStep1Download": "SDK 可在 [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "创建客户端管理器",
"clients.connectWizard.step0Title": "设置客户端",
"clients.connectWizard.step1Title": "选择连接方式",
"clients.connectWizard.step2Title": "连接",

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

@ -32,7 +32,11 @@ public sealed class SchemaCreated : SchemaEvent, IMigrated<IEvent>
public IEvent Migrate()
{
var schema = new Schema(Name, Properties, Singleton ? SchemaType.Singleton : SchemaType.Default);
var type = Singleton ?
SchemaType.Singleton :
SchemaType.Default;
var schema = new Schema(Name, Properties, type);
if (Publish)
{

2
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs

@ -20,6 +20,8 @@ public sealed record AssetsFieldProperties : FieldProperties
public string? FolderId { get; init; }
public string? PreviewFormat { get; set; }
public int? MinItems { get; init; }
public int? MaxItems { get; init; }

1
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/IConverter.cs

@ -8,7 +8,6 @@
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects;
using static Google.Rpc.Context.AttributeContext.Types;
#pragma warning disable MA0048 // File name must match type name

2
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs

@ -46,7 +46,7 @@ public abstract class RuleActionHandler<TAction, TData> : IRuleActionHandler whe
return formatter.FormatAsync(uri.ToString(), @event);
}
protected ValueTask<string?> FormatAsync(string text, EnrichedEvent @event)
protected ValueTask<string?> FormatAsync(string? text, EnrichedEvent @event)
{
return formatter.FormatAsync(text, @event);
}

2
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -96,7 +96,7 @@ public class RuleEventFormatter
return serializer.Serialize(new { type, payload, timestamp }, true);
}
public async ValueTask<string?> FormatAsync(string text, EnrichedEvent @event)
public async ValueTask<string?> FormatAsync(string? text, EnrichedEvent @event)
{
if (string.IsNullOrWhiteSpace(text))
{

3
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs

@ -47,10 +47,9 @@ public static class FindExtensions
var isDefault = false;
if (!query.HasFilterField("IsDeleted"))
if (query.Filter?.HasField("IsDeleted") != true)
{
filters.Add(Filter.Ne(x => x.IsDeleted, true));
isDefault = true;
}

9
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs

@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.MongoDb.Queries;
using Squidex.Infrastructure.Queries;
@ -136,7 +137,7 @@ internal sealed class QueryByQuery : OperationBase
Filter.Eq(x => x.IndexedSchemaId, schemaId)
};
if (filter?.HasField("dl") != true)
if (filter?.HasField(Field.Of<MongoContentEntity>(x => nameof(x.IsDeleted))) != true)
{
filters.Add(Filter.Ne(x => x.IsDeleted, true));
}
@ -162,31 +163,27 @@ internal sealed class QueryByQuery : OperationBase
var isDefault = false;
if (query?.HasFilterField("dl") != true)
if (query?.Filter?.HasField(Field.Of<MongoContentEntity>(x => nameof(x.IsDeleted))) != true)
{
filters.Add(Filter.Ne(x => x.IsDeleted, true));
isDefault = true;
}
if (query?.Filter != null)
{
filters.Add(query.Filter.BuildFilter<MongoContentEntity>());
isDefault = false;
}
if (reference != default)
{
filters.Add(Filter.AnyEq(x => x.ReferencedIds, reference));
isDefault = false;
}
if (createdBy != null)
{
filters.Add(Filter.Eq(x => x.CreatedBy, createdBy));
isDefault = false;
}

4
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs

@ -151,7 +151,7 @@ internal sealed class QueryInDedicatedCollection : MongoBase<MongoContentEntity>
Filter.Exists(x => x.Id)
};
if (filter?.HasField("dl") != true)
if (filter?.HasField(Field.Of<MongoContentEntity>(x => nameof(x.IsDeleted))) != true)
{
filters.Add(Filter.Ne(x => x.IsDeleted, true));
}
@ -173,7 +173,7 @@ internal sealed class QueryInDedicatedCollection : MongoBase<MongoContentEntity>
Filter.Gt(x => x.Id, default)
};
if (query?.HasFilterField("dl") != true)
if (query?.Filter?.HasField(Field.Of<MongoContentEntity>(x => nameof(x.IsDeleted))) != true)
{
filters.Add(Filter.Ne(x => x.IsDeleted, true));
}

4
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetExtensions.cs → backend/src/Squidex.Domain.Apps.Entities/Assets/AssetHeaders.cs

@ -7,9 +7,9 @@
namespace Squidex.Domain.Apps.Entities.Assets;
public static class AssetExtensions
public static class AssetHeaders
{
private const string HeaderNoEnrichment = "X-NoAssetEnrichment";
public const string HeaderNoEnrichment = "X-NoAssetEnrichment";
public static bool ShouldSkipAssetEnrichment(this Context context)
{

8
backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreProcessor.cs

@ -139,6 +139,7 @@ public sealed partial class RestoreProcessor
await LogAsync(run, " * Restore events and attachments.");
await LogAsync(run, " * Restore all objects like app, schemas and contents");
await LogAsync(run, " * Complete the restore operation for all objects");
await LogFlushAsync(run);
log.LogInformation("Backup with job id {backupId} with from URL '{url}' started.", run.Job.Id, run.Job.Url);
@ -430,7 +431,7 @@ public sealed partial class RestoreProcessor
run.Job.Log.Add($"{now}: {message}");
return state.WriteAsync(ct: default);
return state.WriteAsync(default);
}
private Task LogAsync(Run run, string message, bool replace = false)
@ -448,4 +449,9 @@ public sealed partial class RestoreProcessor
return state.WriteAsync(100, run.CancellationToken);
}
private Task LogFlushAsync(Run run)
{
return state.WriteAsync(run.CancellationToken);
}
}

69
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentExtensions.cs → backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs

@ -13,28 +13,29 @@ using Squidex.Infrastructure.Caching;
namespace Squidex.Domain.Apps.Entities.Contents;
public static class ContentExtensions
public static class ContentHeaders
{
private const string HeaderFlatten = "X-Flatten";
private const string HeaderLanguages = "X-Languages";
private const string HeaderNoCleanup = "X-NoCleanup";
private const string HeaderNoEnrichment = "X-NoEnrichment";
private const string HeaderNoResolveLanguages = "X-NoResolveLanguages";
private const string HeaderResolveFlow = "X-ResolveFlow";
private const string HeaderResolveUrls = "X-Resolve-Urls";
private const string HeaderUnpublished = "X-Unpublished";
private static readonly char[] Separators = { ',', ';' };
public const string Flatten = "X-Flatten";
public const string Languages = "X-Languages";
public const string NoCleanup = "X-NoCleanup";
public const string NoEnrichment = "X-NoEnrichment";
public const string NoResolveLanguages = "X-NoResolveLanguages";
public const string ResolveFlow = "X-ResolveFlow";
public const string ResolveUrls = "X-Resolve-Urls";
public const string Unpublished = "X-Unpublished";
public static void AddCacheHeaders(this Context context, IRequestCache cache)
{
cache.AddHeader(HeaderFlatten);
cache.AddHeader(HeaderLanguages);
cache.AddHeader(HeaderNoCleanup);
cache.AddHeader(HeaderNoEnrichment);
cache.AddHeader(HeaderNoResolveLanguages);
cache.AddHeader(HeaderResolveFlow);
cache.AddHeader(HeaderResolveUrls);
cache.AddHeader(HeaderUnpublished);
cache.AddHeader(Flatten);
cache.AddHeader(Languages);
cache.AddHeader(NoCleanup);
cache.AddHeader(NoEnrichment);
cache.AddHeader(NoResolveLanguages);
cache.AddHeader(ResolveFlow);
cache.AddHeader(ResolveUrls);
cache.AddHeader(Unpublished);
}
public static Status EditingStatus(this IContentEntity content)
@ -54,67 +55,67 @@ public static class ContentExtensions
public static bool ShouldSkipCleanup(this Context context)
{
return context.Headers.ContainsKey(HeaderNoCleanup);
return context.Headers.ContainsKey(NoCleanup);
}
public static ICloneBuilder WithoutCleanup(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(HeaderNoCleanup, value);
return builder.WithBoolean(NoCleanup, value);
}
public static bool ShouldSkipContentEnrichment(this Context context)
{
return context.Headers.ContainsKey(HeaderNoEnrichment);
return context.Headers.ContainsKey(NoEnrichment);
}
public static ICloneBuilder WithoutContentEnrichment(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(HeaderNoEnrichment, value);
return builder.WithBoolean(NoEnrichment, value);
}
public static bool ShouldProvideUnpublished(this Context context)
{
return context.Headers.ContainsKey(HeaderUnpublished);
return context.Headers.ContainsKey(Unpublished);
}
public static ICloneBuilder WithUnpublished(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(HeaderUnpublished, value);
return builder.WithBoolean(Unpublished, value);
}
public static bool ShouldFlatten(this Context context)
{
return context.Headers.ContainsKey(HeaderFlatten);
return context.Headers.ContainsKey(Flatten);
}
public static ICloneBuilder WithFlatten(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(HeaderFlatten, value);
return builder.WithBoolean(Flatten, value);
}
public static bool ShouldResolveFlow(this Context context)
{
return context.Headers.ContainsKey(HeaderResolveFlow);
return context.Headers.ContainsKey(ResolveFlow);
}
public static ICloneBuilder WithResolveFlow(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(HeaderResolveFlow, value);
return builder.WithBoolean(ResolveFlow, value);
}
public static bool ShouldResolveLanguages(this Context context)
{
return !context.Headers.ContainsKey(HeaderNoResolveLanguages);
return !context.Headers.ContainsKey(NoResolveLanguages);
}
public static ICloneBuilder WithoutResolveLanguages(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(HeaderNoResolveLanguages, value);
return builder.WithBoolean(NoResolveLanguages, value);
}
public static IEnumerable<string> AssetUrls(this Context context)
{
if (context.Headers.TryGetValue(HeaderResolveUrls, out var value))
if (context.Headers.TryGetValue(ResolveUrls, out var value))
{
return value.Split(Separators, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToHashSet();
}
@ -124,12 +125,12 @@ public static class ContentExtensions
public static ICloneBuilder WithAssetUrlsToResolve(this ICloneBuilder builder, IEnumerable<string>? fieldNames)
{
return builder.WithStrings(HeaderResolveUrls, fieldNames);
return builder.WithStrings(ResolveUrls, fieldNames);
}
public static IEnumerable<Language> Languages(this Context context)
public static IEnumerable<Language> LanguageList(this Context context)
{
if (context.Headers.TryGetValue(HeaderLanguages, out var value))
if (context.Headers.TryGetValue(Languages, out var value))
{
var languages = new HashSet<Language>();
@ -146,6 +147,6 @@ public static class ContentExtensions
public static ICloneBuilder WithLanguages(this ICloneBuilder builder, IEnumerable<string> fieldNames)
{
return builder.WithStrings(HeaderLanguages, fieldNames);
return builder.WithStrings(Languages, fieldNames);
}
}

2
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs

@ -136,7 +136,7 @@ public sealed class ConvertData : IContentEnricherStep
converter.Add(
new ResolveLanguages(
context.App.Languages,
context.Languages().ToArray())
context.LanguageList().ToArray())
{
ResolveFallback = !context.IsFrontendClient && context.ShouldResolveLanguages()
});

26
backend/src/Squidex.Domain.Apps.Entities/ContextExtensions.cs → backend/src/Squidex.Domain.Apps.Entities/ContextHeaders.cs

@ -7,51 +7,51 @@
namespace Squidex.Domain.Apps.Entities;
public static class ContextExtensions
public static class ContextHeaders
{
private const string HeaderNoTotal = "X-NoTotal";
private const string HeaderNoSlowTotal = "X-NoSlowTotal";
private const string HeaderNoCacheKeys = "X-NoCacheKeys";
private const string HeaderNoScripting = "X-NoScripting";
public const string NoTotal = "X-NoTotal";
public const string NoSlowTotal = "X-NoSlowTotal";
public const string NoCacheKeys = "X-NoCacheKeys";
public const string NoScripting = "X-NoScripting";
public static bool ShouldSkipCacheKeys(this Context context)
{
return context.Headers.ContainsKey(HeaderNoCacheKeys);
return context.Headers.ContainsKey(NoCacheKeys);
}
public static ICloneBuilder WithoutCacheKeys(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(HeaderNoCacheKeys, value);
return builder.WithBoolean(NoCacheKeys, value);
}
public static bool ShouldSkipScripting(this Context context)
{
return context.Headers.ContainsKey(HeaderNoScripting);
return context.Headers.ContainsKey(NoScripting);
}
public static ICloneBuilder WithoutScripting(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(HeaderNoScripting, value);
return builder.WithBoolean(NoScripting, value);
}
public static bool ShouldSkipTotal(this Context context)
{
return context.Headers.ContainsKey(HeaderNoTotal);
return context.Headers.ContainsKey(NoTotal);
}
public static ICloneBuilder WithoutTotal(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(HeaderNoTotal, value);
return builder.WithBoolean(NoTotal, value);
}
public static bool ShouldSkipSlowTotal(this Context context)
{
return context.Headers.ContainsKey(HeaderNoSlowTotal);
return context.Headers.ContainsKey(NoSlowTotal);
}
public static ICloneBuilder WithoutSlowTotal(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(HeaderNoSlowTotal, value);
return builder.WithBoolean(NoSlowTotal, value);
}
public static ICloneBuilder WithBoolean(this ICloneBuilder builder, string key, bool value)

9
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs

@ -7,28 +7,27 @@
using MongoDB.Driver;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation;
namespace Squidex.Infrastructure.MongoDb.Queries;
public static class FilterBuilder
{
public static (FilterDefinition<TDocument>? Filter, bool Last) BuildFilter<TDocument>(this ClrQuery query, bool supportsSearch = true)
public static (FilterDefinition<T>? Filter, bool Last) BuildFilter<T>(this ClrQuery query, bool supportsSearch = true)
{
if (query.FullText != null)
{
if (!supportsSearch)
{
throw new ValidationException(T.Get("common.fullTextNotSupported"));
throw new ValidationException(Translations.T.Get("common.fullTextNotSupported"));
}
return (Builders<TDocument>.Filter.Text(query.FullText), false);
return (Builders<T>.Filter.Text(query.FullText), false);
}
if (query.Filter != null)
{
return (query.Filter.BuildFilter<TDocument>(), true);
return (query.Filter.BuildFilter<T>(), true);
}
return (null, false);

5
backend/src/Squidex.Infrastructure/Queries/QueryExtensions.cs

@ -9,11 +9,6 @@ namespace Squidex.Infrastructure.Queries;
public static class QueryExtensions
{
public static bool HasFilterField<T>(this Query<T>? query, string field)
{
return HasField(query?.Filter, field);
}
public static bool HasField<T>(this FilterNode<T>? filter, string field)
{
if (filter == null)

2
backend/src/Squidex.Infrastructure/Queries/SortNode.cs

@ -7,6 +7,8 @@
namespace Squidex.Infrastructure.Queries;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
public sealed record SortNode(PropertyPath Path, SortOrder Order)
{
public override string ToString()

2
backend/src/Squidex.Web/ApiExceptionConverter.cs

@ -142,7 +142,7 @@ public static class ApiExceptionConverter
private static ErrorDto CreateError(int status, string? message = null, string? errorCode = null, IEnumerable<string>? details = null)
{
var error = new ErrorDto { StatusCode = status, Message = message };
var error = new ErrorDto { StatusCode = status, Message = message ?? string.Empty };
if (!string.IsNullOrWhiteSpace(errorCode))
{

96
backend/src/Squidex/Areas/Api/Config/OpenApi/AcceptHeaderAttribute.cs

@ -0,0 +1,96 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using NJsonSchema;
using NSwag;
using NSwag.Annotations;
using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Contents;
#pragma warning disable MA0048 // File name must match type name
namespace Squidex.Areas.Api.Config.OpenApi;
public sealed class AcceptHeader_Unpublished : AcceptHeaderAttribute
{
public AcceptHeader_Unpublished()
: base(ContentHeaders.Unpublished, "Return unpublished content items.", JsonObjectType.Boolean)
{
}
}
public sealed class AcceptHeader_Flatten : AcceptHeaderAttribute
{
public AcceptHeader_Flatten()
: base(ContentHeaders.Flatten, "Provide the data as flat object.", JsonObjectType.Boolean)
{
}
}
public sealed class AcceptHeader_Languages : AcceptHeaderAttribute
{
public AcceptHeader_Languages()
: base(ContentHeaders.Languages, "Only resolve these languages (comma-separated).")
{
}
}
public sealed class AcceptHeader_NoTotal : AcceptHeaderAttribute
{
public AcceptHeader_NoTotal()
: base(ContextHeaders.NoTotal, "Do not return the total amount.", JsonObjectType.Boolean)
{
}
}
public sealed class AcceptHeader_NoSlowTotal : AcceptHeaderAttribute
{
public AcceptHeader_NoSlowTotal()
: base(ContextHeaders.NoSlowTotal, "Do not return the total amount, if it would be slow.", JsonObjectType.Boolean)
{
}
}
public class AcceptHeaderAttribute : OpenApiOperationProcessorAttribute
{
public AcceptHeaderAttribute(string name, string description, JsonObjectType type = JsonObjectType.String)
: base(typeof(Processor), name, description, type)
{
}
public sealed class Processor : IOperationProcessor
{
private readonly string name;
private readonly string description;
private readonly JsonObjectType type;
public Processor(string name, string description, JsonObjectType type)
{
this.name = name;
this.description = description;
this.type = type;
}
public bool Process(OperationProcessorContext context)
{
context.OperationDescription.Operation.Parameters.Add(new OpenApiParameter
{
Name = name,
Kind = OpenApiParameterKind.Header,
Schema = new JsonSchema
{
Type = type
},
Description = description,
});
return true;
}
}
}

36
backend/src/Squidex/Areas/Api/Config/OpenApi/AcceptQueryAttribute.cs

@ -0,0 +1,36 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using NSwag.Annotations;
using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
namespace Squidex.Areas.Api.Config.OpenApi;
public sealed class AcceptQueryAttribute : OpenApiOperationProcessorAttribute
{
public AcceptQueryAttribute(bool supportsSearch)
: base(typeof(Processor), supportsSearch)
{
}
public sealed class Processor : IOperationProcessor
{
private readonly bool supportsSearch;
public Processor(bool supportsSearch)
{
this.supportsSearch = supportsSearch;
}
public bool Process(OperationProcessorContext context)
{
context.OperationDescription.Operation.AddQuery(supportsSearch);
return true;
}
}
}

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

@ -79,8 +79,6 @@ public static class OpenApiServices
services.AddOpenApiDocument((settings, services) =>
{
ConfigureSchemaSettings(settings, services.GetRequiredService<TypeRegistry>(), false);
settings.OperationProcessors.Add(new QueryParamsProcessor("/api/apps/{app}/assets"));
});
}

34
backend/src/Squidex/Areas/Api/Config/OpenApi/QueryParamsProcessor.cs

@ -1,34 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using NSwag;
using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
namespace Squidex.Areas.Api.Config.OpenApi;
public sealed class QueryParamsProcessor : IOperationProcessor
{
private readonly string path;
public QueryParamsProcessor(string path)
{
this.path = path;
}
public bool Process(OperationProcessorContext context)
{
if (context.OperationDescription.Path == path && context.OperationDescription.Method == OpenApiOperationMethod.Get)
{
var operation = context.OperationDescription.Operation;
operation.AddQuery(false);
}
return true;
}
}

40
backend/src/Squidex/Areas/Api/Config/OpenApi/RequiredSchemaProcessor.cs

@ -17,9 +17,47 @@ public sealed class RequiredSchemaProcessor : ISchemaProcessor
{
if (context.ContextualType.GetAttribute<OpenApiRequestAttribute>() != null)
{
return;
FixRequest(context);
}
else
{
FixResponse(context);
}
}
private static void FixRequest(SchemaProcessorContext context)
{
FixRequired(context.Schema);
foreach (var schema in context.Schema.AllOf)
{
FixRequired(schema);
}
foreach (var schema in context.Schema.OneOf)
{
FixRequired(schema);
}
static void FixRequired(JsonSchema schema)
{
foreach (var property in schema.Properties.Values)
{
if (IsValueType(property) || property.IsEnumeration)
{
property.IsRequired = false;
}
}
}
static bool IsValueType(JsonSchemaProperty property)
{
return property.Type is JsonObjectType.Boolean or JsonObjectType.Integer or JsonObjectType.Number;
}
}
private static void FixResponse(SchemaProcessorContext context)
{
FixRequired(context.Schema);
foreach (var schema in context.Schema.AllOf)

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

@ -21,7 +21,6 @@ using Squidex.Infrastructure.Validation;
using Squidex.Shared;
using Squidex.Shared.Identity;
using Squidex.Web;
using System.Text.Json;
#pragma warning disable RECS0033 // Convert 'if' to '||' expression

1
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs

@ -8,7 +8,6 @@
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models;

1
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs

@ -6,7 +6,6 @@
// ==========================================================================
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models;

1
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppSettingsDto.cs

@ -6,7 +6,6 @@
// ==========================================================================
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models;

1
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs

@ -6,7 +6,6 @@
// ==========================================================================
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models;

1
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/EditorDto.cs

@ -7,7 +7,6 @@
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;
namespace Squidex.Areas.Api.Controllers.Apps.Models;

1
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/PatternDto.cs

@ -7,7 +7,6 @@
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;
namespace Squidex.Areas.Api.Controllers.Apps.Models;

1
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateAppSettingsDto.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure.Collections;

2
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowDto.cs

@ -9,12 +9,10 @@ using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models;
[OpenApiRequest]
public sealed class WorkflowDto : Resource
{
/// <summary>

1
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowStepDto.cs

@ -8,7 +8,6 @@
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
using NoUpdateType = Squidex.Domain.Apps.Core.Contents.NoUpdate;

6
backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs

@ -8,6 +8,7 @@
using System.Globalization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using Squidex.Areas.Api.Config.OpenApi;
using Squidex.Areas.Api.Controllers.Assets.Models;
using Squidex.Assets;
using Squidex.Domain.Apps.Core.Scripting;
@ -113,6 +114,9 @@ public sealed class AssetsController : ApiController
[ProducesResponseType(typeof(AssetsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppAssetsRead)]
[ApiCosts(1)]
[AcceptQuery(false)]
[AcceptHeader_NoTotal]
[AcceptHeader_NoSlowTotal]
public async Task<IActionResult> GetAssets(string app, [FromQuery] DomainId? parentId, [FromQuery] string? ids = null, [FromQuery] string? q = null)
{
var assets = await assetQuery.QueryAsync(Context, parentId, CreateQuery(ids, q), HttpContext.RequestAborted);
@ -140,6 +144,8 @@ public sealed class AssetsController : ApiController
[ProducesResponseType(typeof(AssetsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppAssetsRead)]
[ApiCosts(1)]
[AcceptHeader_NoTotal]
[AcceptHeader_NoSlowTotal]
public async Task<IActionResult> GetAssetsPost(string app, [FromBody] QueryDto query)
{
var assets = await assetQuery.QueryAsync(Context, query?.ParentId, query?.ToQuery() ?? Q.Empty, HttpContext.RequestAborted);

1
backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs

@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Assets.Models;

1
backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFolderDto.cs

@ -8,7 +8,6 @@
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Assets.Models;

1
backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFoldersDto.cs

@ -7,7 +7,6 @@
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Assets.Models;

1
backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs

@ -7,7 +7,6 @@
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Assets.Models;

1
backend/src/Squidex/Areas/Api/Controllers/Assets/Models/BulkUpdateAssetsDto.cs

@ -7,7 +7,6 @@
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Assets.Models;

7
backend/src/Squidex/Areas/Api/Controllers/Assets/Models/CreateAssetDto.cs

@ -20,24 +20,25 @@ public sealed class CreateAssetDto
/// <summary>
/// The file to upload.
/// </summary>
[FromForm(Name = "file")]
public IFormFile File { get; set; }
/// <summary>
/// The optional parent folder id.
/// </summary>
[FromQuery]
[FromQuery(Name = "parentId")]
public DomainId ParentId { get; set; }
/// <summary>
/// The optional custom asset id.
/// </summary>
[FromQuery]
[FromQuery(Name = "id")]
public DomainId? Id { get; set; }
/// <summary>
/// True to duplicate the asset, event if the file has been uploaded.
/// </summary>
[FromQuery]
[FromQuery(Name = "duplicate")]
public bool Duplicate { get; set; }
public CreateAsset ToCommand(AssetFile file)

4
backend/src/Squidex/Areas/Api/Controllers/Assets/Models/DeleteAssetDto.cs

@ -19,13 +19,13 @@ public sealed class DeleteAssetDto
/// <summary>
/// True to check referrers of this asset.
/// </summary>
[FromQuery]
[FromQuery(Name = "checkReferrers")]
public bool CheckReferrers { get; set; }
/// <summary>
/// True to delete the asset permanently.
/// </summary>
[FromQuery]
[FromQuery(Name = "permanent")]
public bool Permanent { get; set; }
public DeleteAsset ToCommand(DomainId id)

5
backend/src/Squidex/Areas/Api/Controllers/Assets/Models/UpsertAssetDto.cs

@ -20,18 +20,19 @@ public sealed class UpsertAssetDto
/// <summary>
/// The file to upload.
/// </summary>
[FromForm(Name = "file")]
public IFormFile File { get; set; }
/// <summary>
/// The optional parent folder id.
/// </summary>
[FromQuery]
[FromQuery(Name = "parentId")]
public DomainId ParentId { get; set; }
/// <summary>
/// True to duplicate the asset, event if the file has been uploaded.
/// </summary>
[FromQuery]
[FromQuery(Name = "duplicate")]
public bool Duplicate { get; set; }
public static UpsertAsset ToCommand(AssetTusFile file)

2
backend/src/Squidex/Areas/Api/Controllers/Backups/Models/RestoreJobDto.cs

@ -8,8 +8,6 @@
using NodaTime;
using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Backups.Models;

59
backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -7,6 +7,7 @@
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
using Squidex.Areas.Api.Config.OpenApi;
using Squidex.Areas.Api.Controllers.Contents.Models;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities;
@ -77,6 +78,12 @@ public sealed class ContentsController : ApiController
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
[AcceptQuery(true)]
[AcceptHeader_Flatten]
[AcceptHeader_Languages]
[AcceptHeader_NoSlowTotal]
[AcceptHeader_NoTotal]
[AcceptHeader_Unpublished]
public async Task<IActionResult> GetContents(string app, string schema, [FromQuery] string? ids = null, [FromQuery] string? q = null)
{
var contents = await contentQuery.QueryAsync(Context, schema, CreateQuery(ids, q), HttpContext.RequestAborted);
@ -105,6 +112,11 @@ public sealed class ContentsController : ApiController
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
[AcceptHeader_Flatten]
[AcceptHeader_Languages]
[AcceptHeader_NoSlowTotal]
[AcceptHeader_NoTotal]
[AcceptHeader_Unpublished]
public async Task<IActionResult> GetContentsPost(string app, string schema, [FromBody] QueryDto query)
{
var contents = await contentQuery.QueryAsync(Context, schema, query?.ToQuery() ?? Q.Empty, HttpContext.RequestAborted);
@ -134,6 +146,9 @@ public sealed class ContentsController : ApiController
[ProducesResponseType(typeof(ContentDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
[AcceptHeader_Flatten]
[AcceptHeader_Languages]
[AcceptHeader_Unpublished]
public async Task<IActionResult> GetContent(string app, string schema, DomainId id, long version = EtagVersion.Any)
{
var content = await contentQuery.FindAsync(Context, schema, id, version, HttpContext.RequestAborted);
@ -194,6 +209,11 @@ public sealed class ContentsController : ApiController
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
[AcceptHeader_Flatten]
[AcceptHeader_Languages]
[AcceptHeader_Unpublished]
[AcceptHeader_NoSlowTotal]
[AcceptHeader_NoTotal]
public async Task<IActionResult> GetReferences(string app, string schema, DomainId id, [FromQuery] string? q = null)
{
var contents = await contentQuery.QueryAsync(Context, CreateQuery(null, q).WithReferencing(id), HttpContext.RequestAborted);
@ -223,6 +243,11 @@ public sealed class ContentsController : ApiController
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
[AcceptHeader_Flatten]
[AcceptHeader_Languages]
[AcceptHeader_Unpublished]
[AcceptHeader_NoSlowTotal]
[AcceptHeader_NoTotal]
public async Task<IActionResult> GetReferencing(string app, string schema, DomainId id, [FromQuery] string? q = null)
{
var contents = await contentQuery.QueryAsync(Context, CreateQuery(null, q).WithReference(id), HttpContext.RequestAborted);
@ -251,6 +276,8 @@ public sealed class ContentsController : ApiController
[Route("content/{app}/{schema}/{id}/{version}/")]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsReadOwn)]
[ApiCosts(1)]
[AcceptHeader_Unpublished]
[AcceptHeader_Languages]
[Obsolete("Use ID endpoint with version query.")]
public async Task<IActionResult> GetContentVersion(string app, string schema, DomainId id, int version)
{
@ -283,8 +310,10 @@ public sealed class ContentsController : ApiController
/// </remarks>
[HttpPost]
[Route("content/{app}/{schema}/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ContentDto), StatusCodes.Status201Created)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsCreate)]
[AcceptHeader_Unpublished]
[AcceptHeader_Languages]
[ApiCosts(1)]
public async Task<IActionResult> PostContent(string app, string schema, CreateContentDto request)
{
@ -369,8 +398,10 @@ public sealed class ContentsController : ApiController
/// </remarks>
[HttpPost]
[Route("content/{app}/{schema}/{id}/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ContentDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsUpsert)]
[AcceptHeader_Unpublished]
[AcceptHeader_Languages]
[ApiCosts(1)]
public async Task<IActionResult> PostUpsertContent(string app, string schema, DomainId id, UpsertContentDto request)
{
@ -396,8 +427,10 @@ public sealed class ContentsController : ApiController
/// </remarks>
[HttpPut]
[Route("content/{app}/{schema}/{id}/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ContentDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsUpdateOwn)]
[AcceptHeader_Unpublished]
[AcceptHeader_Languages]
[ApiCosts(1)]
public async Task<IActionResult> PutContent(string app, string schema, DomainId id, [FromBody] ContentData request)
{
@ -423,8 +456,10 @@ public sealed class ContentsController : ApiController
/// </remarks>
[HttpPatch]
[Route("content/{app}/{schema}/{id}/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ContentDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsUpdateOwn)]
[AcceptHeader_Unpublished]
[AcceptHeader_Languages]
[ApiCosts(1)]
public async Task<IActionResult> PatchContent(string app, string schema, DomainId id, [FromBody] ContentData request)
{
@ -450,8 +485,10 @@ public sealed class ContentsController : ApiController
/// </remarks>
[HttpPut]
[Route("content/{app}/{schema}/{id}/status/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ContentDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsChangeStatusOwn)]
[AcceptHeader_Unpublished]
[AcceptHeader_Languages]
[ApiCosts(1)]
public async Task<IActionResult> PutContentStatus(string app, string schema, DomainId id, [FromBody] ChangeStatusDto request)
{
@ -476,8 +513,10 @@ public sealed class ContentsController : ApiController
/// </remarks>
[HttpDelete]
[Route("content/{app}/{schema}/{id}/status/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ContentDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsChangeStatusOwn)]
[AcceptHeader_Unpublished]
[AcceptHeader_Languages]
[ApiCosts(1)]
public async Task<IActionResult> DeleteContentStatus(string app, string schema, DomainId id)
{
@ -501,8 +540,10 @@ public sealed class ContentsController : ApiController
/// </remarks>
[HttpPost]
[Route("content/{app}/{schema}/{id}/draft/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ContentDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsVersionCreateOwn)]
[AcceptHeader_Unpublished]
[AcceptHeader_Languages]
[ApiCosts(1)]
public async Task<IActionResult> CreateDraft(string app, string schema, DomainId id)
{
@ -526,8 +567,10 @@ public sealed class ContentsController : ApiController
/// </remarks>
[HttpDelete]
[Route("content/{app}/{schema}/{id}/draft/")]
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ContentDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsVersionDeleteOwn)]
[AcceptHeader_Unpublished]
[AcceptHeader_Languages]
[ApiCosts(1)]
public async Task<IActionResult> DeleteVersion(string app, string schema, DomainId id)
{

12
backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsSharedController.cs

@ -8,6 +8,7 @@
using GraphQL.Server.Transports.AspNetCore;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using Squidex.Areas.Api.Config.OpenApi;
using Squidex.Areas.Api.Controllers.Contents.Models;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Contents;
@ -47,6 +48,7 @@ public sealed class ContentsSharedController : ApiController
[Route("content/{app}/graphql/batch")]
[ApiPermissionOrAnonymous]
[ApiCosts(2)]
[AcceptHeader_Unpublished]
[IgnoreCacheFilter]
public IActionResult GetGraphQL(string app)
{
@ -73,6 +75,11 @@ public sealed class ContentsSharedController : ApiController
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
[AcceptHeader_Flatten]
[AcceptHeader_Languages]
[AcceptHeader_NoSlowTotal]
[AcceptHeader_NoTotal]
[AcceptHeader_Unpublished]
public async Task<IActionResult> GetAllContents(string app, AllContentsByGetDto query)
{
var contents = await contentQuery.QueryAsync(Context, (query ?? new AllContentsByGetDto()).ToQuery(Request), HttpContext.RequestAborted);
@ -100,6 +107,11 @@ public sealed class ContentsSharedController : ApiController
[ProducesResponseType(typeof(ContentsDto), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(1)]
[AcceptHeader_Flatten]
[AcceptHeader_Languages]
[AcceptHeader_NoSlowTotal]
[AcceptHeader_NoTotal]
[AcceptHeader_Unpublished]
public async Task<IActionResult> GetAllContentsPost(string app, [FromBody] AllContentsByPostDto query)
{
var contents = await contentQuery.QueryAsync(Context, query?.ToQuery() ?? Q.Empty, HttpContext.RequestAborted);

8
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/AllContentsByGetDto.cs

@ -27,25 +27,25 @@ public sealed class AllContentsByGetDto
/// <summary>
/// The start of the schedule.
/// </summary>
[FromQuery]
[FromQuery(Name = "scheduleFrom")]
public Instant? ScheduledFrom { get; set; }
/// <summary>
/// The end of the schedule.
/// </summary>
[FromQuery]
[FromQuery(Name = "scheduleTo")]
public Instant? ScheduledTo { get; set; }
/// <summary>
/// The ID of the referencing content item.
/// </summary>
[FromQuery]
[FromQuery(Name = "referencing")]
public DomainId? Referencing { get; set; }
/// <summary>
/// The ID of the reference content item.
/// </summary>
[FromQuery]
[FromQuery(Name = "references")]
public DomainId? References { get; set; }
/// <summary>

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

@ -12,7 +12,6 @@ using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Contents.Models;

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

@ -8,7 +8,6 @@
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Validation;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Contents.Models;

6
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/CreateContentDto.cs

@ -24,19 +24,19 @@ public class CreateContentDto
/// <summary>
/// The initial status.
/// </summary>
[FromQuery]
[FromQuery(Name = "status")]
public StatusType? Status { get; set; }
/// <summary>
/// The optional custom content id.
/// </summary>
[FromQuery]
[FromQuery(Name = "id")]
public DomainId? Id { get; set; }
/// <summary>
/// True to automatically publish the content.
/// </summary>
[FromQuery]
[FromQuery(Name = "publish")]
[Obsolete("Use 'status' query string now.")]
public bool Publish { get; set; }

4
backend/src/Squidex/Areas/Api/Controllers/Contents/Models/DeleteContentDto.cs

@ -17,13 +17,13 @@ public sealed class DeleteContentDto
/// <summary>
/// True to check referrers of this content.
/// </summary>
[FromQuery]
[FromQuery(Name = "checkReferrers")]
public bool CheckReferrers { get; set; }
/// <summary>
/// True to delete the content permanently.
/// </summary>
[FromQuery]
[FromQuery(Name = "permanent")]
public bool Permanent { get; set; }
public DeleteContent ToCommand(DomainId id)

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

Loading…
Cancel
Save