Browse Source

Backend localization. (#552)

* Backend localization.

* Fixes

* Grain context fixes.
pull/561/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
d8d49640f6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Comments/Comment.cs
  2. 9
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  3. 9
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOperations.cs
  4. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/Templates/TemplateParseException.cs
  5. 35
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs
  6. 15
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs
  7. 23
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs
  8. 13
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs
  9. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs
  10. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs
  11. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs
  12. 7
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs
  13. 21
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs
  14. 7
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs
  15. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs
  16. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs
  17. 11
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs
  18. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs
  19. 9
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs
  20. 5
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  21. 7
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs
  22. 10
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/StatusSerializer.cs
  23. 5
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs
  24. 5
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs
  25. 37
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs
  26. 19
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs
  27. 21
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs
  28. 23
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs
  29. 27
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs
  30. 33
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs
  31. 29
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs
  32. 25
      backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs
  33. 9
      backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs
  34. 6
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs
  35. 5
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs
  36. 5
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs
  37. 9
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetHistoryEventsCreator.cs
  38. 26
      backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs
  39. 17
      backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAssetFolder.cs
  40. 1
      backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs
  41. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs
  42. 7
      backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs
  43. 9
      backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs
  44. 7
      backend/src/Squidex.Domain.Apps.Entities/Backup/BackupRestoreException.cs
  45. 5
      backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs
  46. 2
      backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs
  47. 17
      backend/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs
  48. 9
      backend/src/Squidex.Domain.Apps.Entities/Contents/BulkUpdateCommandMiddleware.cs
  49. 21
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs
  50. 19
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHistoryEventsCreator.cs
  51. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs
  52. 7
      backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs
  53. 31
      backend/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs
  54. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs
  55. 5
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs
  56. 9
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs
  57. 5
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs
  58. 21
      backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs
  59. 9
      backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs
  60. 5
      backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs
  61. 5
      backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs
  62. 71
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs
  63. 11
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardHelper.cs
  64. 122
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs
  65. 29
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs
  66. 7
      backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs
  67. 5
      backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs
  68. 37
      backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs
  69. 9
      backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs
  70. 4
      backend/src/Squidex.Domain.Users.MongoDb/MongoUser.cs
  71. 11
      backend/src/Squidex.Domain.Users/PwnedPasswordValidator.cs
  72. 38
      backend/src/Squidex.Domain.Users/UserManagerExtensions.cs
  73. 10
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs
  74. 10
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/InstantSerializer.cs
  75. 11
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs
  76. 10
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs
  77. 7
      backend/src/Squidex.Infrastructure/Assets/AssetAlreadyExistsException.cs
  78. 7
      backend/src/Squidex.Infrastructure/Assets/AssetNotFoundException.cs
  79. 8
      backend/src/Squidex.Infrastructure/CollectionExtensions.cs
  80. 4
      backend/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs
  81. 6
      backend/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs
  82. 2
      backend/src/Squidex.Infrastructure/Commands/DomainObjectGrainFormatter.cs
  83. 3
      backend/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs
  84. 2
      backend/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs
  85. 5
      backend/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs
  86. 11
      backend/src/Squidex.Infrastructure/ConfigurationException.cs
  87. 7
      backend/src/Squidex.Infrastructure/DomainException.cs
  88. 7
      backend/src/Squidex.Infrastructure/DomainForbiddenException.cs
  89. 11
      backend/src/Squidex.Infrastructure/DomainObjectDeletedException.cs
  90. 13
      backend/src/Squidex.Infrastructure/DomainObjectException.cs
  91. 21
      backend/src/Squidex.Infrastructure/DomainObjectNotFoundException.cs
  92. 11
      backend/src/Squidex.Infrastructure/DomainObjectVersionException.cs
  93. 4
      backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs
  94. 9
      backend/src/Squidex.Infrastructure/Guard.cs
  95. 2
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs
  96. 5
      backend/src/Squidex.Infrastructure/Log/Internal/FileLogProcessor.cs
  97. 8
      backend/src/Squidex.Infrastructure/Migrations/MigrationFailedException.cs
  98. 40
      backend/src/Squidex.Infrastructure/Orleans/CultureFilter.cs
  99. 55
      backend/src/Squidex.Infrastructure/Orleans/GrainContext.cs
  100. 16
      backend/src/Squidex.Infrastructure/Queries/Json/QueryParser.cs

4
backend/src/Squidex.Domain.Apps.Core.Model/Comments/Comment.cs

@ -30,13 +30,9 @@ namespace Squidex.Domain.Apps.Core.Comments
Guard.NotNull(text, nameof(text)); Guard.NotNull(text, nameof(text));
Id = id; Id = id;
Text = text; Text = text;
Time = time; Time = time;
User = user; User = user;
Url = url; Url = url;
} }
} }

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

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -20,6 +20,7 @@ using Squidex.Domain.Apps.Core.Scripting.ContentWrapper;
using Squidex.Domain.Apps.Core.Scripting.Internal; using Squidex.Domain.Apps.Core.Scripting.Internal;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Core.Scripting namespace Squidex.Domain.Apps.Core.Scripting
@ -179,15 +180,15 @@ namespace Squidex.Domain.Apps.Core.Scripting
} }
catch (ArgumentException ex) catch (ArgumentException ex)
{ {
throw new ValidationException($"Failed to execute script with javascript syntax error: {ex.Message}", new ValidationError(ex.Message)); throw new ValidationException(T.Get("common.jsParseError", new { error = ex.Message }));
} }
catch (JavaScriptException ex) catch (JavaScriptException ex)
{ {
throw new ValidationException($"Failed to execute script with javascript error: {ex.Message}", new ValidationError(ex.Message)); throw new ValidationException(T.Get("common.jsError", new { error = ex.Message }));
} }
catch (ParserException ex) catch (ParserException ex)
{ {
throw new ValidationException($"Failed to execute script with javascript error: {ex.Message}", new ValidationError(ex.Message)); throw new ValidationException(T.Get("common.jsError", new { error = ex.Message }));
} }
} }
} }

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

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -7,6 +7,7 @@
using Jint; using Jint;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Core.Scripting namespace Squidex.Domain.Apps.Core.Scripting
@ -17,16 +18,16 @@ namespace Squidex.Domain.Apps.Core.Scripting
private static readonly MessageDelegate Disallow = new MessageDelegate(message => private static readonly MessageDelegate Disallow = new MessageDelegate(message =>
{ {
message = !string.IsNullOrWhiteSpace(message) ? message : "Not allowed"; message = !string.IsNullOrWhiteSpace(message) ? message : T.Get("common.jsNotAlloweed");
throw new DomainForbiddenException(message); throw new DomainForbiddenException(message);
}); });
private static readonly MessageDelegate Reject = new MessageDelegate(message => private static readonly MessageDelegate Reject = new MessageDelegate(message =>
{ {
var errors = !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null; message = !string.IsNullOrWhiteSpace(message) ? message : T.Get("common.jsRejected");
throw new ValidationException("Script rejected the operation.", errors); throw new ValidationException(message);
}); });
public static Engine AddDisallow(this Engine engine) public static Engine AddDisallow(this Engine engine)

4
backend/src/Squidex.Domain.Apps.Core.Operations/Templates/TemplateParseException.cs

@ -18,8 +18,8 @@ namespace Squidex.Domain.Apps.Core.Templates
{ {
public IReadOnlyList<string> Errors { get; } public IReadOnlyList<string> Errors { get; }
public TemplateParseException(string template, IEnumerable<string> errors) public TemplateParseException(string template, IEnumerable<string> errors, Exception? inner = null)
: base(BuildErrorMessage(errors, template)) : base(BuildErrorMessage(errors, template), inner)
{ {
Errors = errors.ToList(); Errors = errors.ToList();
} }

35
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -10,6 +10,7 @@ using System.Collections.Generic;
using NodaTime.Text; using NodaTime.Text;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Core.ValidateContent namespace Squidex.Domain.Apps.Core.ValidateContent
@ -55,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (b.Value, null); return (b.Value, null);
} }
return (null, new JsonError("Invalid json type, expected boolean.")); return (null, new JsonError(T.Get("contents.invalidBoolean")));
} }
public (object? Result, JsonError? Error) Visit(IField<NumberFieldProperties> field) public (object? Result, JsonError? Error) Visit(IField<NumberFieldProperties> field)
@ -65,7 +66,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (n.Value, null); return (n.Value, null);
} }
return (null, new JsonError("Invalid json type, expected number.")); return (null, new JsonError(T.Get("contents.invalidNumber")));
} }
public (object? Result, JsonError? Error) Visit(IField<StringFieldProperties> field) public (object? Result, JsonError? Error) Visit(IField<StringFieldProperties> field)
@ -75,7 +76,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (s.Value, null); return (s.Value, null);
} }
return (null, new JsonError("Invalid json type, expected string.")); return (null, new JsonError(T.Get("contents.invalidString")));
} }
public (object? Result, JsonError? Error) Visit(IField<UIFieldProperties> field) public (object? Result, JsonError? Error) Visit(IField<UIFieldProperties> field)
@ -97,7 +98,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (parseResult.Value, null); return (parseResult.Value, null);
} }
return (null, new JsonError("Invalid json type, expected string.")); return (null, new JsonError(T.Get("contents.invalidString")));
} }
public (object? Result, JsonError? Error) Visit(IField<GeolocationFieldProperties> field) public (object? Result, JsonError? Error) Visit(IField<GeolocationFieldProperties> field)
@ -109,7 +110,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
if (!string.Equals(propertyName, "latitude", StringComparison.OrdinalIgnoreCase) && if (!string.Equals(propertyName, "latitude", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(propertyName, "longitude", StringComparison.OrdinalIgnoreCase)) !string.Equals(propertyName, "longitude", StringComparison.OrdinalIgnoreCase))
{ {
return (null, new JsonError("Geolocation can only have latitude and longitude property.")); return (null, new JsonError(T.Get("contents.invalidGeolocationMoreProperties")));
} }
} }
@ -119,12 +120,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
if (!lat.IsBetween(-90, 90)) if (!lat.IsBetween(-90, 90))
{ {
return (null, new JsonError("Latitude must be between -90 and 90.")); return (null, new JsonError(T.Get("contents.invalidGeolocationLatitude")));
} }
} }
else else
{ {
return (null, new JsonError("Invalid json type, expected latitude/longitude object.")); return (null, new JsonError(T.Get("contents.invalidGeolocation")));
} }
if (geolocation.TryGetValue("longitude", out var lonValue) && lonValue is JsonNumber lonNumber) if (geolocation.TryGetValue("longitude", out var lonValue) && lonValue is JsonNumber lonNumber)
@ -133,18 +134,18 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
if (!lon.IsBetween(-180, 180)) if (!lon.IsBetween(-180, 180))
{ {
return (null, new JsonError("Longitude must be between -180 and 180.")); return (null, new JsonError(T.Get("contents.invalidGeolocationLongitude")));
} }
} }
else else
{ {
return (null, new JsonError("Invalid json type, expected latitude/longitude object.")); return (null, new JsonError(T.Get("contents.invalidGeolocation")));
} }
return (value, null); return (value, null);
} }
return (null, new JsonError("Invalid json type, expected latitude/longitude object.")); return (null, new JsonError(T.Get("contents.invalidGeolocation")));
} }
public (object? Result, JsonError? Error) Visit(IField<JsonFieldProperties> field) public (object? Result, JsonError? Error) Visit(IField<JsonFieldProperties> field)
@ -166,14 +167,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
} }
else else
{ {
return (null, new JsonError("Invalid json type, expected array of guid strings.")); return (null, new JsonError(T.Get("contents.invalidArrayOfIds")));
} }
} }
return (result, null); return (result, null);
} }
return (null, new JsonError("Invalid json type, expected array of guid strings.")); return (null, new JsonError(T.Get("contents.invalidArrayOfIds")));
} }
private (object? Result, JsonError? Error) ConvertToStringList() private (object? Result, JsonError? Error) ConvertToStringList()
@ -194,14 +195,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
} }
else else
{ {
return (null, new JsonError("Invalid json type, expected array of strings.")); return (null, new JsonError(T.Get("contents.invalidArrayOfStrings")));
} }
} }
return (result, null); return (result, null);
} }
return (null, new JsonError("Invalid json type, expected array of strings.")); return (null, new JsonError(T.Get("contents.invalidArrayOfStrings")));
} }
private (object? Result, JsonError? Error) ConvertToObjectList() private (object? Result, JsonError? Error) ConvertToObjectList()
@ -218,14 +219,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
} }
else else
{ {
return (null, new JsonError("Invalid json type, expected array of objects.")); return (null, new JsonError(T.Get("contents.invalidArrayOfObjects")));
} }
} }
return (result, null); return (result, null);
} }
return (null, new JsonError("Invalid json type, expected array of objects.")); return (null, new JsonError(T.Get("contents.invalidArrayOfObjects")));
} }
} }
} }

15
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs

@ -9,19 +9,20 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
public sealed class AllowedValuesValidator<T> : IValidator public sealed class AllowedValuesValidator<TValue> : IValidator
{ {
private readonly IEnumerable<T> allowedValues; private readonly IEnumerable<TValue> allowedValues;
public AllowedValuesValidator(params T[] allowedValues) public AllowedValuesValidator(params TValue[] allowedValues)
: this((IEnumerable<T>)allowedValues) : this((IEnumerable<TValue>)allowedValues)
{ {
} }
public AllowedValuesValidator(IEnumerable<T> allowedValues) public AllowedValuesValidator(IEnumerable<TValue> allowedValues)
{ {
Guard.NotNull(allowedValues, nameof(allowedValues)); Guard.NotNull(allowedValues, nameof(allowedValues));
@ -30,9 +31,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
public Task ValidateAsync(object? value, ValidationContext context, AddError addError) public Task ValidateAsync(object? value, ValidationContext context, AddError addError)
{ {
if (value != null && value is T typedValue && !allowedValues.Contains(typedValue)) if (value != null && value is TValue typedValue && !allowedValues.Contains(typedValue))
{ {
addError(context.Path, "Not an allowed value."); addError(context.Path, T.Get("contents.validation.notAllowed"));
} }
return Task.CompletedTask; return Task.CompletedTask;

23
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -12,6 +12,7 @@ using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -54,32 +55,32 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (asset == null) if (asset == null)
{ {
addError(path, $"Id '{assetId}' not found."); addError(path, T.Get("contents.validation.assetNotFound", new { id = assetId }));
continue; continue;
} }
if (properties.MinSize.HasValue && asset.FileSize < properties.MinSize) if (properties.MinSize.HasValue && asset.FileSize < properties.MinSize)
{ {
addError(path, $"'{asset.FileSize.ToReadableSize()}' less than minimum of '{properties.MinSize.Value.ToReadableSize()}'."); addError(path, T.Get("contents.validation.minimumSize", new { size = asset.FileSize.ToReadableSize(), min = properties.MinSize.Value.ToReadableSize() }));
} }
if (properties.MaxSize.HasValue && asset.FileSize > properties.MaxSize) if (properties.MaxSize.HasValue && asset.FileSize > properties.MaxSize)
{ {
addError(path, $"'{asset.FileSize.ToReadableSize()}' greater than maximum of '{properties.MaxSize.Value.ToReadableSize()}'."); addError(path, T.Get("contents.validation.maximumSize", new { size = asset.FileSize.ToReadableSize(), max = properties.MaxSize.Value.ToReadableSize() }));
} }
if (properties.AllowedExtensions != null && if (properties.AllowedExtensions != null &&
properties.AllowedExtensions.Count > 0 && properties.AllowedExtensions.Count > 0 &&
!properties.AllowedExtensions.Any(x => asset.FileName.EndsWith("." + x, StringComparison.OrdinalIgnoreCase))) !properties.AllowedExtensions.Any(x => asset.FileName.EndsWith("." + x, StringComparison.OrdinalIgnoreCase)))
{ {
addError(path, "Invalid file extension."); addError(path, T.Get("contents.validation.extension"));
} }
if (asset.Type != AssetType.Image) if (asset.Type != AssetType.Image)
{ {
if (properties.MustBeImage) if (properties.MustBeImage)
{ {
addError(path, "Not an image."); addError(path, T.Get("contents.validation.image"));
} }
continue; continue;
@ -97,22 +98,22 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (properties.MinWidth.HasValue && w < properties.MinWidth) if (properties.MinWidth.HasValue && w < properties.MinWidth)
{ {
addError(path, $"Width '{w}px' less than minimum of '{properties.MinWidth}px'."); addError(path, T.Get("contents.validation.minimumWidth", new { width = w, min = properties.MinWidth }));
} }
if (properties.MaxWidth.HasValue && w > properties.MaxWidth) if (properties.MaxWidth.HasValue && w > properties.MaxWidth)
{ {
addError(path, $"Width '{w}px' greater than maximum of '{properties.MaxWidth}px'."); addError(path, T.Get("contents.validation.maximumWidth", new { width = w, max = properties.MaxWidth }));
} }
if (properties.MinHeight.HasValue && h < properties.MinHeight) if (properties.MinHeight.HasValue && h < properties.MinHeight)
{ {
addError(path, $"Height '{h}px' less than minimum of '{properties.MinHeight}px'."); addError(path, T.Get("contents.validation.minimumHeight", new { height = h, min = properties.MinHeight }));
} }
if (properties.MaxHeight.HasValue && h > properties.MaxHeight) if (properties.MaxHeight.HasValue && h > properties.MaxHeight)
{ {
addError(path, $"Height '{h}px' greater than maximum of '{properties.MaxHeight}px'."); addError(path, T.Get("contents.validation.maximumHeight", new { height = h, max = properties.MaxHeight }));
} }
if (properties.AspectHeight.HasValue && properties.AspectWidth.HasValue) if (properties.AspectHeight.HasValue && properties.AspectWidth.HasValue)
@ -121,7 +122,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (Math.Abs(expectedRatio - actualRatio) > double.Epsilon) if (Math.Abs(expectedRatio - actualRatio) > double.Epsilon)
{ {
addError(path, $"Aspect ratio not '{properties.AspectWidth}:{properties.AspectHeight}'."); addError(path, T.Get("contents.validation.aspectRatio", new { width = properties.AspectWidth, height = properties.AspectHeight }));
} }
} }
} }

13
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -8,6 +8,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -35,7 +36,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (isRequired && !context.IsOptional) if (isRequired && !context.IsOptional)
{ {
addError(context.Path, "Field is required."); addError(context.Path, T.Get("contents.validation.required"));
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -45,23 +46,23 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (minItems == maxItems && minItems != items.Count) if (minItems == maxItems && minItems != items.Count)
{ {
addError(context.Path, $"Must have exactly {maxItems} item(s)."); addError(context.Path, T.Get("contents.validation.itemCount", new { count = minItems }));
} }
else if (items.Count < minItems || items.Count > maxItems) else if (items.Count < minItems || items.Count > maxItems)
{ {
addError(context.Path, $"Must have between {minItems} and {maxItems} item(s)."); addError(context.Path, T.Get("contents.validation.itemCountBetween", new { min = minItems, max = maxItems }));
} }
} }
else else
{ {
if (minItems.HasValue && items.Count < minItems.Value) if (minItems.HasValue && items.Count < minItems.Value)
{ {
addError(context.Path, $"Must have at least {minItems} item(s)."); addError(context.Path, T.Get("contents.validation.minItems", new { min = minItems }));
} }
if (maxItems.HasValue && items.Count > maxItems.Value) if (maxItems.HasValue && items.Count > maxItems.Value)
{ {
addError(context.Path, $"Must not have more than {maxItems} item(s)."); addError(context.Path, T.Get("contents.validation.maxItems", new { max = maxItems }));
} }
} }

5
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -11,6 +11,7 @@ using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -69,7 +70,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
} }
catch catch
{ {
addError(context.Path, "Not a valid value."); addError(context.Path, T.Get("contents.validation.invalid"));
} }
} }
} }

5
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -21,7 +22,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (!value.IsUndefined()) if (!value.IsUndefined())
{ {
addError(context.Path, "Value must not be defined."); addError(context.Path, T.Get("contents.validation.mustBeEmpty"));
} }
return Task.CompletedTask; return Task.CompletedTask;

5
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -7,6 +7,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -39,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (!schema.ContainsKey(name)) if (!schema.ContainsKey(name))
{ {
addError(context.Path.Enqueue(name), $"Not a known {fieldType}."); addError(context.Path.Enqueue(name), T.Get("contents.validation.unknownField", new { fieldType }));
} }
} }

7
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -9,6 +9,7 @@ using System;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -39,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (string.IsNullOrWhiteSpace(errorMessage)) if (string.IsNullOrWhiteSpace(errorMessage))
{ {
addError(context.Path, "Does not match to the pattern."); addError(context.Path, T.Get("contents.validation.pattern"));
} }
else else
{ {
@ -49,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
} }
catch catch
{ {
addError(context.Path, "Regex is too slow."); addError(context.Path, T.Get("contents.validation.regexTooSlow"));
} }
} }
} }

21
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -7,15 +7,16 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
public sealed class RangeValidator<T> : IValidator where T : struct, IComparable<T> public sealed class RangeValidator<TValue> : IValidator where TValue : struct, IComparable<TValue>
{ {
private readonly T? min; private readonly TValue? min;
private readonly T? max; private readonly TValue? max;
public RangeValidator(T? min, T? max) public RangeValidator(TValue? min, TValue? max)
{ {
if (min.HasValue && max.HasValue && min.Value.CompareTo(max.Value) > 0) if (min.HasValue && max.HasValue && min.Value.CompareTo(max.Value) > 0)
{ {
@ -28,29 +29,29 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
public Task ValidateAsync(object? value, ValidationContext context, AddError addError) public Task ValidateAsync(object? value, ValidationContext context, AddError addError)
{ {
if (value != null && value is T typedValue) if (value != null && value is TValue typedValue)
{ {
if (min.HasValue && max.HasValue) if (min.HasValue && max.HasValue)
{ {
if (Equals(min, max) && Equals(min.Value, max.Value)) if (Equals(min, max) && Equals(min.Value, max.Value))
{ {
addError(context.Path, $"Must be exactly '{max}'."); addError(context.Path, T.Get("contents.validation.exactValue", new { value = max.Value }));
} }
else if (typedValue.CompareTo(min.Value) < 0 || typedValue.CompareTo(max.Value) > 0) else if (typedValue.CompareTo(min.Value) < 0 || typedValue.CompareTo(max.Value) > 0)
{ {
addError(context.Path, $"Must be between '{min}' and '{max}'."); addError(context.Path, T.Get("contents.validation.between", new { min, max }));
} }
} }
else else
{ {
if (min.HasValue && typedValue.CompareTo(min.Value) < 0) if (min.HasValue && typedValue.CompareTo(min.Value) < 0)
{ {
addError(context.Path, $"Must be greater or equal to '{min}'."); addError(context.Path, T.Get("contents.validation.min", new { min }));
} }
if (max.HasValue && typedValue.CompareTo(max.Value) > 0) if (max.HasValue && typedValue.CompareTo(max.Value) > 0)
{ {
addError(context.Path, $"Must be less or equal to '{max}'."); addError(context.Path, T.Get("contents.validation.max", new { max }));
} }
} }
} }

7
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -46,11 +47,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (schemaId == Guid.Empty) if (schemaId == Guid.Empty)
{ {
addError(context.Path, $"Contains invalid reference '{id}'."); addError(context.Path, T.Get("common.referenceNotFound", new { id }));
} }
else if (schemaIds?.Any() == true && !schemaIds.Contains(schemaId)) else if (schemaIds?.Any() == true && !schemaIds.Contains(schemaId))
{ {
addError(context.Path, $"Contains reference '{id}' to invalid schema."); addError(context.Path, T.Get("common.referenceToInvalidSchema", new { id }));
} }
} }
} }

5
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -27,7 +28,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (value.IsNullOrUndefined() || IsEmptyString(value)) if (value.IsNullOrUndefined() || IsEmptyString(value))
{ {
addError(context.Path, "Field is required."); addError(context.Path, T.Get("contents.validation.required"));
} }
return Task.CompletedTask; return Task.CompletedTask;

5
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -15,7 +16,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (value.IsNullOrUndefined() && !context.IsOptional) if (value.IsNullOrUndefined() && !context.IsOptional)
{ {
addError(context.Path, "Field is required."); addError(context.Path, T.Get("contents.validation.required"));
} }
return Task.CompletedTask; return Task.CompletedTask;

11
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -7,6 +7,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -34,23 +35,23 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (minLength == maxLength && minLength != stringValue.Length) if (minLength == maxLength && minLength != stringValue.Length)
{ {
addError(context.Path, $"Must have exactly {maxLength} character(s)."); addError(context.Path, T.Get("contents.validation.characterCount", new { count = minLength }));
} }
else if (stringValue.Length < minLength || stringValue.Length > maxLength) else if (stringValue.Length < minLength || stringValue.Length > maxLength)
{ {
addError(context.Path, $"Must have between {minLength} and {maxLength} character(s)."); addError(context.Path, T.Get("contents.validation.charactersBetween", new { min = minLength, max = maxLength }));
} }
} }
else else
{ {
if (minLength.HasValue && stringValue.Length < minLength.Value) if (minLength.HasValue && stringValue.Length < minLength.Value)
{ {
addError(context.Path, $"Must have at least {minLength} character(s)."); addError(context.Path, T.Get("contents.validation.minLength", new { min = minLength }));
} }
if (maxLength.HasValue && stringValue.Length > maxLength.Value) if (maxLength.HasValue && stringValue.Length > maxLength.Value)
{ {
addError(context.Path, $"Must not have more than {maxLength} character(s)."); addError(context.Path, T.Get("contents.validation.maxLength", new { max = maxLength }));
} }
} }
} }

5
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -52,7 +53,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (found.Any(x => x.Id != context.ContentId)) if (found.Any(x => x.Id != context.ContentId))
{ {
addError(context.Path, "Another content with the same value exists."); addError(context.Path, T.Get("contents.validation.unique"));
} }
} }
} }

9
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -8,20 +8,21 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
public sealed class UniqueValuesValidator<T> : IValidator public sealed class UniqueValuesValidator<TValue> : IValidator
{ {
public Task ValidateAsync(object? value, ValidationContext context, AddError addError) public Task ValidateAsync(object? value, ValidationContext context, AddError addError)
{ {
if (value is IEnumerable<T> items && items.Any()) if (value is IEnumerable<TValue> items && items.Any())
{ {
var itemsArray = items.ToArray(); var itemsArray = items.ToArray();
if (itemsArray.Length != itemsArray.Distinct().Count()) if (itemsArray.Length != itemsArray.Distinct().Count())
{ {
addError(context.Path, "Must not contain duplicate values."); addError(context.Path, T.Get("contents.validation.duplicates"));
} }
} }

5
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -21,6 +21,7 @@ using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.MongoDb.Queries;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{ {
@ -91,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
} }
catch (MongoQueryException ex) when (ex.Message.Contains("17406")) catch (MongoQueryException ex) when (ex.Message.Contains("17406"))
{ {
throw new DomainException("Result set is too large to be retrieved. Use $take parameter to reduce the number of items."); throw new DomainException(T.Get("common.resultTooLarge"));
} }
} }
} }

7
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -21,6 +21,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.MongoDb.Queries;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{ {
@ -99,11 +100,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
} }
catch (MongoCommandException ex) when (ex.Code == 96) catch (MongoCommandException ex) when (ex.Code == 96)
{ {
throw new DomainException("Result set is too large to be retrieved. Use $take parameter to reduce the number of items."); throw new DomainException(T.Get("common.resultTooLarge"));
} }
catch (MongoQueryException ex) when (ex.Message.Contains("17406")) catch (MongoQueryException ex) when (ex.Message.Contains("17406"))
{ {
throw new DomainException("Result set is too large to be retrieved. Use $take parameter to reduce the number of items."); throw new DomainException(T.Get("common.resultTooLarge"));
} }
} }

10
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/StatusSerializer.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Threading; using MongoDB.Bson;
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers; using MongoDB.Bson.Serialization.Serializers;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -14,14 +14,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
public sealed class StatusSerializer : SerializerBase<Status> public sealed class StatusSerializer : SerializerBase<Status>
{ {
private static volatile int isRegistered;
public static void Register() public static void Register()
{ {
if (Interlocked.Increment(ref isRegistered) == 1) try
{ {
BsonSerializer.RegisterSerializer(new StatusSerializer()); BsonSerializer.RegisterSerializer(new StatusSerializer());
} }
catch (BsonSerializationException)
{
return;
}
} }
public override Status Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) public override Status Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)

5
backend/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Apps namespace Squidex.Domain.Apps.Entities.Apps
@ -64,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
if (image == null) if (image == null)
{ {
throw new ValidationException("File is not an image."); throw new ValidationException(T.Get("apps.notImage"));
} }
} }

5
backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -21,6 +21,7 @@ using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Translations;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps namespace Squidex.Domain.Apps.Entities.Apps
@ -462,7 +463,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{ {
if (Snapshot.IsArchived) if (Snapshot.IsArchived)
{ {
throw new DomainException("App has already been archived."); throw new DomainException(T.Get("apps.alreadyArchieved"));
} }
} }

37
backend/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
@ -21,55 +22,55 @@ namespace Squidex.Domain.Apps.Entities.Apps
: base(typeNameRegistry) : base(typeNameRegistry)
{ {
AddEventMessage<AppContributorAssigned>( AddEventMessage<AppContributorAssigned>(
"assigned {user:[Contributor]} as {[Role]}"); T.Get("history.apps.contributoreAssigned"));
AddEventMessage<AppContributorRemoved>( AddEventMessage<AppContributorRemoved>(
"removed {user:[Contributor]} from app"); T.Get("history.apps.contributoreRemoved"));
AddEventMessage<AppClientAttached>( AddEventMessage<AppClientAttached>(
"added client {[Id]} to app"); T.Get("history.apps.clientAdded"));
AddEventMessage<AppClientRevoked>( AddEventMessage<AppClientRevoked>(
"revoked client {[Id]}"); T.Get("history.apps.clientRevoked"));
AddEventMessage<AppClientUpdated>( AddEventMessage<AppClientUpdated>(
"updated client {[Id]}"); T.Get("history.apps.clientUpdated"));
AddEventMessage<AppPlanChanged>( AddEventMessage<AppPlanChanged>(
"changed plan to {[Plan]}"); T.Get("history.apps.planChanged"));
AddEventMessage<AppPlanReset>( AddEventMessage<AppPlanReset>(
"resetted plan"); T.Get("history.apps.planReset"));
AddEventMessage<AppLanguageAdded>( AddEventMessage<AppLanguageAdded>(
"added language {[Language]}"); T.Get("history.apps.languagedAdded"));
AddEventMessage<AppLanguageRemoved>( AddEventMessage<AppLanguageRemoved>(
"removed language {[Language]}"); T.Get("history.apps.languagedRemoved"));
AddEventMessage<AppLanguageUpdated>( AddEventMessage<AppLanguageUpdated>(
"updated language {[Language]}"); T.Get("history.apps.languagedUpdated"));
AddEventMessage<AppMasterLanguageSet>( AddEventMessage<AppMasterLanguageSet>(
"changed master language to {[Language]}"); T.Get("history.apps.languagedSetToMaster"));
AddEventMessage<AppPatternAdded>( AddEventMessage<AppPatternAdded>(
"added pattern {[Name]}"); T.Get("history.apps.patternAdded"));
AddEventMessage<AppPatternDeleted>( AddEventMessage<AppPatternDeleted>(
"deleted pattern {[PatternId]}"); T.Get("history.apps.patternDeleted"));
AddEventMessage<AppPatternUpdated>( AddEventMessage<AppPatternUpdated>(
"updated pattern {[Name]}"); T.Get("history.apps.patternUpdated"));
AddEventMessage<AppRoleAdded>( AddEventMessage<AppRoleAdded>(
"added role {[Name]}"); T.Get("history.apps.roleAdded"));
AddEventMessage<AppRoleDeleted>( AddEventMessage<AppRoleDeleted>(
"deleted role {[Name]}"); T.Get("history.apps.roleDeleted"));
AddEventMessage<AppRoleUpdated>( AddEventMessage<AppRoleUpdated>(
"updated role {[Name]}"); T.Get("history.apps.roleUpdated"));
} }
private HistoryEvent? CreateEvent(IEvent @event) private HistoryEvent? CreateEvent(IEvent @event)

19
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -9,6 +9,7 @@ using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Plans; using Squidex.Domain.Apps.Entities.Apps.Plans;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Apps.Guards namespace Squidex.Domain.Apps.Entities.Apps.Guards
@ -19,11 +20,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot create app.", e => Validate.It(e =>
{ {
if (!command.Name.IsSlug()) if (!command.Name.IsSlug())
{ {
e(Not.ValidSlug("Name"), nameof(command.Name)); e(Not.ValidSlug(nameof(command.Name)), nameof(command.Name));
} }
}); });
} }
@ -32,11 +33,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot upload image.", e => Validate.It(e =>
{ {
if (command.File == null) if (command.File == null)
{ {
e(Not.Defined("File"), nameof(command.File)); e(Not.Defined(nameof(command.File)), nameof(command.File));
} }
}); });
} }
@ -55,22 +56,22 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot change plan.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.PlanId)) if (string.IsNullOrWhiteSpace(command.PlanId))
{ {
e(Not.Defined("Plan id"), nameof(command.PlanId)); e(Not.Defined(nameof(command.PlanId)), nameof(command.PlanId));
return; return;
} }
if (appPlans.GetPlan(command.PlanId) == null) if (appPlans.GetPlan(command.PlanId) == null)
{ {
e("A plan with this id does not exist.", nameof(command.PlanId)); e(T.Get("apps.plans.notFound"), nameof(command.PlanId));
} }
if (!string.IsNullOrWhiteSpace(command.PlanId) && plan != null && !plan.Owner.Equals(command.Actor)) if (!string.IsNullOrWhiteSpace(command.PlanId) && plan != null && !plan.Owner.Equals(command.Actor))
{ {
e("Plan can only changed from the user who configured the plan initially."); e(T.Get("apps.plans.notPlanOwner"));
} }
}); });
} }

21
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -8,6 +8,7 @@
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Apps.Guards namespace Squidex.Domain.Apps.Entities.Apps.Guards
@ -18,15 +19,15 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot attach client.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.Id)) if (string.IsNullOrWhiteSpace(command.Id))
{ {
e(Not.Defined("Client id"), nameof(command.Id)); e(Not.Defined("ClientId"), nameof(command.Id));
} }
else if (clients.ContainsKey(command.Id)) else if (clients.ContainsKey(command.Id))
{ {
e("A client with the same id already exists."); e(T.Get("apps.clients.idAlreadyExists"));
} }
}); });
} }
@ -37,11 +38,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
GetClientOrThrow(clients, command.Id); GetClientOrThrow(clients, command.Id);
Validate.It(() => "Cannot revoke client.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.Id)) if (string.IsNullOrWhiteSpace(command.Id))
{ {
e(Not.Defined("Client id"), nameof(command.Id)); e(Not.Defined("ClientId"), nameof(command.Id));
} }
}); });
} }
@ -52,16 +53,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
GetClientOrThrow(clients, command.Id); GetClientOrThrow(clients, command.Id);
Validate.It(() => "Cannot update client.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.Id)) if (string.IsNullOrWhiteSpace(command.Id))
{ {
e(Not.Defined("Client id"), nameof(command.Id)); e(Not.Defined("Clientd"), nameof(command.Id));
} }
if (command.Role != null && !roles.Contains(command.Role)) if (command.Role != null && !roles.Contains(command.Role))
{ {
e(Not.Valid("role"), nameof(command.Role)); e(Not.Valid("Role"), nameof(command.Role));
} }
}); });
} }
@ -75,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
if (!clients.TryGetValue(id, out var client)) if (!clients.TryGetValue(id, out var client))
{ {
throw new DomainObjectNotFoundException(id, "Clients", typeof(IAppEntity)); throw new DomainObjectNotFoundException(id);
} }
return client; return client;

23
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Plans; using Squidex.Domain.Apps.Entities.Apps.Plans;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
using Squidex.Shared.Users; using Squidex.Shared.Users;
@ -23,16 +24,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
return Validate.It(() => "Cannot assign contributor.", async e => return Validate.It(async e =>
{ {
if (!roles.Contains(command.Role)) if (!roles.Contains(command.Role))
{ {
e(Not.Valid("role"), nameof(command.Role)); e(Not.Valid(nameof(command.Role)), nameof(command.Role));
} }
if (string.IsNullOrWhiteSpace(command.ContributorId)) if (string.IsNullOrWhiteSpace(command.ContributorId))
{ {
e(Not.Defined("Contributor id"), nameof(command.ContributorId)); e(Not.Defined(nameof(command.ContributorId)), nameof(command.ContributorId));
} }
else else
{ {
@ -40,21 +41,21 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
if (user == null) if (user == null)
{ {
throw new DomainObjectNotFoundException(command.ContributorId, "Contributors", typeof(IAppEntity)); throw new DomainObjectNotFoundException(command.ContributorId);
} }
if (!command.Restoring) if (!command.Restoring)
{ {
if (string.Equals(command.ContributorId, command.Actor?.Identifier, StringComparison.OrdinalIgnoreCase)) if (string.Equals(command.ContributorId, command.Actor?.Identifier, StringComparison.OrdinalIgnoreCase))
{ {
throw new DomainForbiddenException("You cannot change your own role."); throw new DomainForbiddenException(T.Get("apps.contributors.cannotChangeYourself"));
} }
if (!contributors.TryGetValue(command.ContributorId, out var role)) if (!contributors.TryGetValue(command.ContributorId, out var role))
{ {
if (plan != null && plan.MaxContributors > 0 && contributors.Count >= plan.MaxContributors) if (plan != null && plan.MaxContributors > 0 && contributors.Count >= plan.MaxContributors)
{ {
e("You have reached the maximum number of contributors for your plan."); e(T.Get("apps.contributors.maxReached"));
} }
} }
} }
@ -66,24 +67,24 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot remove contributor.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.ContributorId)) if (string.IsNullOrWhiteSpace(command.ContributorId))
{ {
e(Not.Defined("Contributor id"), nameof(command.ContributorId)); e(Not.Defined(nameof(command.ContributorId)), nameof(command.ContributorId));
} }
var ownerIds = contributors.Where(x => x.Value == Role.Owner).Select(x => x.Key).ToList(); var ownerIds = contributors.Where(x => x.Value == Role.Owner).Select(x => x.Key).ToList();
if (ownerIds.Count == 1 && ownerIds.Contains(command.ContributorId)) if (ownerIds.Count == 1 && ownerIds.Contains(command.ContributorId))
{ {
e("Cannot remove the only owner."); e(T.Get("apps.contributors.onlyOneOwner"));
} }
}); });
if (!contributors.ContainsKey(command.ContributorId)) if (!contributors.ContainsKey(command.ContributorId))
{ {
throw new DomainObjectNotFoundException(command.ContributorId, "Contributors", typeof(IAppEntity)); throw new DomainObjectNotFoundException(command.ContributorId);
} }
} }
} }

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

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -8,6 +8,7 @@
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Apps.Guards namespace Squidex.Domain.Apps.Entities.Apps.Guards
@ -18,17 +19,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot add language.", e => Validate.It(e =>
{ {
var language = command.Language; var language = command.Language;
if (language == null) if (language == null)
{ {
e(Not.Defined("Language code"), nameof(command.Language)); e(Not.Defined(nameof(command.Language)), nameof(command.Language));
} }
else if (languages.Contains(language)) else if (languages.Contains(language))
{ {
e("Language has already been added."); e(T.Get("apps.languages.languageAlreadyAdded"));
} }
}); });
} }
@ -37,13 +38,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot remove language.", e => Validate.It(e =>
{ {
var language = command.Language; var language = command.Language;
if (language == null) if (language == null)
{ {
e(Not.Defined("Language code"), nameof(command.Language)); e(Not.Defined(nameof(command.Language)), nameof(command.Language));
} }
else else
{ {
@ -51,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
if (languages.IsMaster(language)) if (languages.IsMaster(language))
{ {
e("Master language cannot be removed."); e(T.Get("apps.languages.masterLanguageNotRemovable"));
} }
} }
}); });
@ -61,13 +62,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot update language.", e => Validate.It(e =>
{ {
var language = command.Language; var language = command.Language;
if (language == null) if (language == null)
{ {
e(Not.Defined("Language code"), nameof(command.Language)); e(Not.Defined(nameof(command.Language)), nameof(command.Language));
} }
else else
{ {
@ -77,12 +78,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
if (command.IsOptional) if (command.IsOptional)
{ {
e("Master language cannot be made optional.", nameof(command.IsMaster)); e(T.Get("apps.languages.masterLanguageNotOptional"), nameof(command.IsMaster));
} }
if (command.Fallback?.Count > 0) if (command.Fallback?.Count > 0)
{ {
e("Master language cannot have fallback languages.", nameof(command.Fallback)); e(T.Get("apps.languages.masterLanguageNoFallbacks"), nameof(command.Fallback));
} }
} }
@ -95,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
if (!languages.Contains(fallback)) if (!languages.Contains(fallback))
{ {
e($"App does not have fallback language '{fallback}'.", nameof(command.Fallback)); e(T.Get("apps.languages.fallbackNotFound", new { fallback }), nameof(command.Fallback));
} }
} }
} }
@ -106,7 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
if (!languages.Contains(language)) if (!languages.Contains(language))
{ {
throw new DomainObjectNotFoundException(language, "Languages", typeof(IAppEntity)); throw new DomainObjectNotFoundException(language);
} }
} }
} }

33
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -9,6 +9,7 @@ using System.Linq;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Apps.Guards namespace Squidex.Domain.Apps.Entities.Apps.Guards
@ -19,35 +20,35 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot add pattern.", e => Validate.It(e =>
{ {
if (command.PatternId == Guid.Empty) if (command.PatternId == Guid.Empty)
{ {
e(Not.Defined("Id"), nameof(command.PatternId)); e(Not.Defined(nameof(command.PatternId)), nameof(command.PatternId));
} }
if (string.IsNullOrWhiteSpace(command.Name)) if (string.IsNullOrWhiteSpace(command.Name))
{ {
e(Not.Defined("Name"), nameof(command.Name)); e(Not.Defined(nameof(command.Name)), nameof(command.Name));
} }
if (patterns.Values.Any(x => x.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) if (patterns.Values.Any(x => x.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase)))
{ {
e("A pattern with the same name already exists."); e(T.Get("apps.patterns.nameAlreadyExists"));
} }
if (string.IsNullOrWhiteSpace(command.Pattern)) if (string.IsNullOrWhiteSpace(command.Pattern))
{ {
e(Not.Defined("Pattern"), nameof(command.Pattern)); e(Not.Defined(nameof(command.Pattern)), nameof(command.Pattern));
} }
else if (!command.Pattern.IsValidRegex()) else if (!command.Pattern.IsValidRegex())
{ {
e(Not.Valid("Pattern"), nameof(command.Pattern)); e(Not.Valid(nameof(command.Pattern)), nameof(command.Pattern));
} }
if (patterns.Values.Any(x => x.Pattern == command.Pattern)) if (patterns.Values.Any(x => x.Pattern == command.Pattern))
{ {
e("This pattern already exists but with another name."); e(T.Get("apps.patterns.patternAlreadyExists"));
} }
}); });
} }
@ -58,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
if (!patterns.ContainsKey(command.PatternId)) if (!patterns.ContainsKey(command.PatternId))
{ {
throw new DomainObjectNotFoundException(command.PatternId.ToString(), typeof(AppPattern)); throw new DomainObjectNotFoundException(command.PatternId.ToString());
} }
} }
@ -68,33 +69,33 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
if (!patterns.ContainsKey(command.PatternId)) if (!patterns.ContainsKey(command.PatternId))
{ {
throw new DomainObjectNotFoundException(command.PatternId.ToString(), typeof(AppPattern)); throw new DomainObjectNotFoundException(command.PatternId.ToString());
} }
Validate.It(() => "Cannot update pattern.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.Name)) if (string.IsNullOrWhiteSpace(command.Name))
{ {
e(Not.Defined("Name"), nameof(command.Name)); e(Not.Defined(nameof(command.Name)), nameof(command.Name));
} }
if (patterns.Any(x => x.Key != command.PatternId && x.Value.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) if (patterns.Any(x => x.Key != command.PatternId && x.Value.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase)))
{ {
e("A pattern with the same name already exists."); e(T.Get("apps.patterns.nameAlreadyExists"));
} }
if (string.IsNullOrWhiteSpace(command.Pattern)) if (string.IsNullOrWhiteSpace(command.Pattern))
{ {
e(Not.Defined("Pattern"), nameof(command.Pattern)); e(Not.Defined(nameof(command.Pattern)), nameof(command.Pattern));
} }
else if (!command.Pattern.IsValidRegex()) else if (!command.Pattern.IsValidRegex())
{ {
e(Not.Valid("Pattern"), nameof(command.Pattern)); e(Not.Valid(nameof(command.Pattern)), nameof(command.Pattern));
} }
if (patterns.Any(x => x.Key != command.PatternId && x.Value.Pattern == command.Pattern)) if (patterns.Any(x => x.Key != command.PatternId && x.Value.Pattern == command.Pattern))
{ {
e("This pattern already exists but with another name."); e(T.Get("apps.patterns.patternAlreadyExists"));
} }
}); });
} }

29
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -10,6 +10,7 @@ using System.Linq;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Apps.Guards namespace Squidex.Domain.Apps.Entities.Apps.Guards
@ -20,15 +21,15 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot add role.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.Name)) if (string.IsNullOrWhiteSpace(command.Name))
{ {
e(Not.Defined("Name"), nameof(command.Name)); e(Not.Defined(nameof(command.Name)), nameof(command.Name));
} }
else if (roles.Contains(command.Name)) else if (roles.Contains(command.Name))
{ {
e("A role with the same name already exists."); e(T.Get("apps.roles.nameAlreadyExists"));
} }
}); });
} }
@ -39,25 +40,25 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
CheckRoleExists(roles, command.Name); CheckRoleExists(roles, command.Name);
Validate.It(() => "Cannot delete role.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.Name)) if (string.IsNullOrWhiteSpace(command.Name))
{ {
e(Not.Defined("Name"), nameof(command.Name)); e(Not.Defined(nameof(command.Name)), nameof(command.Name));
} }
else if (Roles.IsDefault(command.Name)) else if (Roles.IsDefault(command.Name))
{ {
e("Cannot delete a default role."); e(T.Get("apps.roles.defaultRoleNotRemovable"));
} }
if (clients.Values.Any(x => string.Equals(x.Role, command.Name, StringComparison.OrdinalIgnoreCase))) if (clients.Values.Any(x => string.Equals(x.Role, command.Name, StringComparison.OrdinalIgnoreCase)))
{ {
e("Cannot remove a role when a client is assigned."); e(T.Get("apps.roles.usedRoleByClientsNotRemovable"));
} }
if (contributors.Values.Any(x => string.Equals(x, command.Name, StringComparison.OrdinalIgnoreCase))) if (contributors.Values.Any(x => string.Equals(x, command.Name, StringComparison.OrdinalIgnoreCase)))
{ {
e("Cannot remove a role when a contributor is assigned."); e(T.Get("apps.roles.usedRoleByContributorsNotRemovable"));
} }
}); });
} }
@ -68,20 +69,20 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
CheckRoleExists(roles, command.Name); CheckRoleExists(roles, command.Name);
Validate.It(() => "Cannot delete role.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.Name)) if (string.IsNullOrWhiteSpace(command.Name))
{ {
e(Not.Defined("Name"), nameof(command.Name)); e(Not.Defined(nameof(command.Name)), nameof(command.Name));
} }
else if (Roles.IsDefault(command.Name)) else if (Roles.IsDefault(command.Name))
{ {
e("Cannot update a default role."); e(T.Get("apps.roles.defaultRoleNotUpdateable"));
} }
if (command.Permissions == null) if (command.Permissions == null)
{ {
e(Not.Defined("Permissions"), nameof(command.Permissions)); e(Not.Defined(nameof(command.Permissions)), nameof(command.Permissions));
} }
}); });
} }
@ -95,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
if (!roles.ContainsCustom(name)) if (!roles.ContainsCustom(name))
{ {
throw new DomainObjectNotFoundException(name, "Roles", typeof(IAppEntity)); throw new DomainObjectNotFoundException(name);
} }
} }
} }

25
backend/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -9,6 +9,7 @@ using System;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Apps.Guards namespace Squidex.Domain.Apps.Entities.Apps.Guards
@ -19,11 +20,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot add workflow.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.Name)) if (string.IsNullOrWhiteSpace(command.Name))
{ {
e(Not.Defined("Name"), nameof(command.Name)); e(Not.Defined(nameof(command.Name)), nameof(command.Name));
} }
}); });
} }
@ -34,11 +35,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
CheckWorkflowExists(workflows, command.WorkflowId); CheckWorkflowExists(workflows, command.WorkflowId);
Validate.It(() => "Cannot update workflow.", e => Validate.It(e =>
{ {
if (command.Workflow == null) if (command.Workflow == null)
{ {
e(Not.Defined("Workflow"), nameof(command.Workflow)); e(Not.Defined(nameof(command.Workflow)), nameof(command.Workflow));
return; return;
} }
@ -46,19 +47,19 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
if (!workflow.Steps.ContainsKey(workflow.Initial)) if (!workflow.Steps.ContainsKey(workflow.Initial))
{ {
e(Not.Defined("Initial step"), $"{nameof(command.Workflow)}.{nameof(workflow.Initial)}"); e(Not.Defined("InitialStep"), $"{nameof(command.Workflow)}.{nameof(workflow.Initial)}");
} }
if (workflow.Initial == Status.Published) if (workflow.Initial == Status.Published)
{ {
e("Initial step cannot be published step.", $"{nameof(command.Workflow)}.{nameof(workflow.Initial)}"); e(T.Get("workflows.initialNotPublished"), $"{nameof(command.Workflow)}.{nameof(workflow.Initial)}");
} }
var stepsPrefix = $"{nameof(command.Workflow)}.{nameof(workflow.Steps)}"; var stepsPrefix = $"{nameof(command.Workflow)}.{nameof(workflow.Steps)}";
if (!workflow.Steps.ContainsKey(Status.Published)) if (!workflow.Steps.ContainsKey(Status.Published))
{ {
e("Workflow must have a published step.", stepsPrefix); e(T.Get("apps.workflows.initialNotPublished"), stepsPrefix);
} }
foreach (var step in workflow.Steps) foreach (var step in workflow.Steps)
@ -67,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
if (step.Value == null) if (step.Value == null)
{ {
e(Not.Defined("Step"), stepPrefix); e(Not.Defined("WorkflowStep"), stepPrefix);
} }
else else
{ {
@ -77,12 +78,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
if (!workflow.Steps.ContainsKey(status)) if (!workflow.Steps.ContainsKey(status))
{ {
e("Transition has an invalid target.", transitionPrefix); e(T.Get("apps.workflows.publishedStepNotFound"), transitionPrefix);
} }
if (transition == null) if (transition == null)
{ {
e(Not.Defined("Transition"), transitionPrefix); e(Not.Defined("WorkflowTransition"), transitionPrefix);
} }
} }
} }
@ -101,7 +102,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
if (!workflows.ContainsKey(id)) if (!workflows.ContainsKey(id))
{ {
throw new DomainObjectNotFoundException(id.ToString(), "Workflows", typeof(IAppEntity)); throw new DomainObjectNotFoundException(id.ToString());
} }
} }
} }

9
backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -17,6 +17,7 @@ using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
using Squidex.Shared; using Squidex.Shared;
@ -237,7 +238,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
await RemoveContributorAsync(removeContributor); await RemoveContributorAsync(removeContributor);
break; break;
case ArchiveApp archiveApp: case ArchiveApp _:
await ArchiveAppAsync(app); await ArchiveAppAsync(app);
break; break;
} }
@ -256,9 +257,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
if (token == null) if (token == null)
{ {
var error = new ValidationError("An app with this already exists."); throw new ValidationException(T.Get("apps.nameAlreadyExists"));
throw new ValidationException("Cannot create app.", error);
} }
return token; return token;

6
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs

@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
return string.Equals(createApp.Template, TemplateName, StringComparison.OrdinalIgnoreCase); return string.Equals(createApp.Template, TemplateName, StringComparison.OrdinalIgnoreCase);
} }
private static Task CreateAuthenticationSchemeSchemaAsync(Func<ICommand, Task> publish) private static async Task<NamedId<Guid>> CreateAuthenticationSchemeSchemaAsync(Func<ICommand, Task> publish)
{ {
var schema = var schema =
SchemaBuilder.Create("Authentication Schemes") SchemaBuilder.Create("Authentication Schemes")
@ -71,7 +71,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
.Hints("Additional scopes you want from the provider.")) .Hints("Additional scopes you want from the provider."))
.Build(); .Build();
return publish(schema); await publish(schema);
return NamedId.Of(schema.SchemaId, schema.Name);
} }
private static Task CreateClientsSchemaAsync(Func<ICommand, Task> publish) private static Task CreateClientsSchemaAsync(Func<ICommand, Task> publish)

5
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -20,6 +20,7 @@ using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
@ -172,7 +173,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
if (Snapshot.IsDeleted) if (Snapshot.IsDeleted)
{ {
throw new DomainException("Asset has already been deleted"); throw new DomainException(T.Get("assets.assetAlreadyDeleted"));
} }
} }
} }

5
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetFolderDomainObject.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -18,6 +18,7 @@ using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
@ -112,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
if (Snapshot.IsDeleted) if (Snapshot.IsDeleted)
{ {
throw new DomainException("Asset folder has already been deleted"); throw new DomainException(T.Get("assets.assetFolderAlreadyDeleted"));
} }
} }
} }

9
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetHistoryEventsCreator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -10,6 +10,7 @@ using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
@ -19,13 +20,13 @@ namespace Squidex.Domain.Apps.Entities.Assets
: base(typeNameRegistry) : base(typeNameRegistry)
{ {
AddEventMessage<AssetCreated>( AddEventMessage<AssetCreated>(
"uploaded asset."); T.Get("history.assets.uploaded"));
AddEventMessage<AssetUpdated>( AddEventMessage<AssetUpdated>(
"replaced asset."); T.Get("history.assets.replaced"));
AddEventMessage<AssetAnnotated>( AddEventMessage<AssetAnnotated>(
"updated asset."); T.Get("history.assets.updated"));
} }
protected override Task<HistoryEvent?> CreateEventCoreAsync(Envelope<IEvent> @event) protected override Task<HistoryEvent?> CreateEventCoreAsync(Envelope<IEvent> @event)

26
backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -9,6 +9,7 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Assets.Guards namespace Squidex.Domain.Apps.Entities.Assets.Guards
@ -18,30 +19,13 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
public static void CanAnnotate(AnnotateAsset command) public static void CanAnnotate(AnnotateAsset command)
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot annotate asset.", e =>
{
if (string.IsNullOrWhiteSpace(command.FileName) &&
string.IsNullOrWhiteSpace(command.Slug) &&
command.IsProtected == null &&
command.Metadata == null &&
command.Tags == null)
{
e("At least one property must be defined.",
nameof(command.FileName),
nameof(command.IsProtected),
nameof(command.Metadata),
nameof(command.Slug),
nameof(command.Tags));
}
});
} }
public static Task CanCreate(CreateAsset command, IAssetQueryService assetQuery) public static Task CanCreate(CreateAsset command, IAssetQueryService assetQuery)
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
return Validate.It(() => "Cannot upload asset.", async e => return Validate.It(async e =>
{ {
await CheckPathAsync(command.ParentId, assetQuery, e); await CheckPathAsync(command.ParentId, assetQuery, e);
}); });
@ -51,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
return Validate.It(() => "Cannot move asset.", async e => return Validate.It(async e =>
{ {
if (command.ParentId != oldParentId) if (command.ParentId != oldParentId)
{ {
@ -78,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
if (path.Count == 0) if (path.Count == 0)
{ {
e("Asset folder does not exist.", nameof(MoveAsset.ParentId)); e(T.Get("assets.folderNotFound"), nameof(MoveAsset.ParentId));
} }
} }
} }

17
backend/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAssetFolder.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -9,6 +9,7 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Assets.Guards namespace Squidex.Domain.Apps.Entities.Assets.Guards
@ -19,11 +20,11 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
return Validate.It(() => "Cannot upload asset.", async e => return Validate.It(async e =>
{ {
if (string.IsNullOrWhiteSpace(command.FolderName)) if (string.IsNullOrWhiteSpace(command.FolderName))
{ {
e(Not.Defined("Folder name"), nameof(command.FolderName)); e(Not.Defined(nameof(command.FolderName)), nameof(command.FolderName));
} }
await CheckPathAsync(command.ParentId, assetQuery, Guid.Empty, e); await CheckPathAsync(command.ParentId, assetQuery, Guid.Empty, e);
@ -34,11 +35,11 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot rename asset.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.FolderName)) if (string.IsNullOrWhiteSpace(command.FolderName))
{ {
e(Not.Defined("Folder name"), nameof(command.FolderName)); e(Not.Defined(nameof(command.FolderName)), nameof(command.FolderName));
} }
}); });
} }
@ -47,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
return Validate.It(() => "Cannot move asset.", async e => return Validate.It(async e =>
{ {
if (command.ParentId != oldParentId) if (command.ParentId != oldParentId)
{ {
@ -69,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
if (path.Count == 0) if (path.Count == 0)
{ {
e("Asset folder does not exist.", nameof(MoveAssetFolder.ParentId)); e(T.Get("assets.folderNotFound"), nameof(MoveAssetFolder.ParentId));
} }
else if (id != default) else if (id != default)
{ {
@ -78,7 +79,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards
if (indexOfThis >= 0 && indexOfParent > indexOfThis) if (indexOfThis >= 0 && indexOfParent > indexOfThis)
{ {
e("Cannot add folder to its own child.", nameof(MoveAssetFolder.ParentId)); e(T.Get("assets.folderRecursion"), nameof(MoveAssetFolder.ParentId));
} }
} }
} }

1
backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs

@ -9,7 +9,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;

2
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs

@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
if (asset == null || asset.Version <= EtagVersion.Empty || (version > EtagVersion.Any && asset.Version != version)) if (asset == null || asset.Version <= EtagVersion.Empty || (version > EtagVersion.Any && asset.Version != version))
{ {
throw new DomainObjectNotFoundException(id.ToString(), typeof(IAssetEntity)); throw new DomainObjectNotFoundException(id.ToString());
} }
return asset; return asset;

7
backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryParser.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -19,6 +19,7 @@ using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Queries.Json; using Squidex.Infrastructure.Queries.Json;
using Squidex.Infrastructure.Queries.OData; using Squidex.Infrastructure.Queries.OData;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Assets.Queries namespace Squidex.Domain.Apps.Entities.Assets.Queries
@ -106,11 +107,11 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
} }
catch (NotSupportedException) catch (NotSupportedException)
{ {
throw new ValidationException("OData operation is not supported."); throw new ValidationException(T.Get("common.odataNotSupported"));
} }
catch (ODataException ex) catch (ODataException ex)
{ {
throw new ValidationException($"Failed to parse query: {ex.Message}", ex); throw new ValidationException(T.Get("common.odataFailure", new { message = ex.Message }), ex);
} }
} }

9
backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -20,6 +20,7 @@ using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
using Squidex.Infrastructure.Translations;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Backup namespace Squidex.Domain.Apps.Entities.Backup
@ -92,12 +93,12 @@ namespace Squidex.Domain.Apps.Entities.Backup
{ {
if (currentJobToken != null) if (currentJobToken != null)
{ {
throw new DomainException("Another backup process is already running."); throw new DomainException(T.Get("backups.alreadyRunning"));
} }
if (state.Value.Jobs.Count >= MaxBackups) if (state.Value.Jobs.Count >= MaxBackups)
{ {
throw new DomainException($"You cannot have more than {MaxBackups} backups."); throw new DomainException(T.Get("backups.maxReached", new { max = MaxBackups }));
} }
var job = new BackupJob var job = new BackupJob
@ -231,7 +232,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
if (job == null) if (job == null)
{ {
throw new DomainObjectNotFoundException(id.ToString(), typeof(IBackupJob)); throw new DomainObjectNotFoundException(id.ToString());
} }
if (currentJob == job) if (currentJob == job)

7
backend/src/Squidex.Domain.Apps.Entities/Backup/BackupRestoreException.cs

@ -13,12 +13,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
[Serializable] [Serializable]
public class BackupRestoreException : Exception public class BackupRestoreException : Exception
{ {
public BackupRestoreException(string message) public BackupRestoreException(string message, Exception? inner = null)
: base(message)
{
}
public BackupRestoreException(string message, Exception inner)
: base(message, inner) : base(message, inner)
{ {
} }

5
backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -23,6 +23,7 @@ using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
using Squidex.Infrastructure.Translations;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Backup namespace Squidex.Domain.Apps.Entities.Backup
@ -112,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
if (CurrentJob?.Status == JobStatus.Started) if (CurrentJob?.Status == JobStatus.Started)
{ {
throw new DomainException("A restore operation is already running."); throw new DomainException(T.Get("backups.restoreRunning"));
} }
state.Value.Job = new RestoreJob state.Value.Job = new RestoreJob

2
backend/src/Squidex.Domain.Apps.Entities/Comments/CommentsGrain.cs

@ -112,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Comments
if (command.ExpectedVersion > EtagVersion.Any && command.ExpectedVersion != Version) if (command.ExpectedVersion > EtagVersion.Any && command.ExpectedVersion != Version)
{ {
throw new DomainObjectVersionException(Key, GetType(), Version, command.ExpectedVersion); throw new DomainObjectVersionException(Key, Version, command.ExpectedVersion);
} }
var prevVersion = version; var prevVersion = version;

17
backend/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Entities.Comments.Commands;
using Squidex.Domain.Apps.Events.Comments; using Squidex.Domain.Apps.Events.Comments;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Comments.Guards namespace Squidex.Domain.Apps.Entities.Comments.Guards
@ -21,11 +22,11 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot create comment.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.Text)) if (string.IsNullOrWhiteSpace(command.Text))
{ {
e(Not.Defined("Text"), nameof(command.Text)); e(Not.Defined("Text"), nameof(command.Text));
} }
}); });
} }
@ -38,14 +39,14 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
if (!string.Equals(commentsId, command.Actor.Identifier) && !comment.Payload.Actor.Equals(command.Actor)) if (!string.Equals(commentsId, command.Actor.Identifier) && !comment.Payload.Actor.Equals(command.Actor))
{ {
throw new DomainException("Comment is created by another user."); throw new DomainException(T.Get("comments.notUserComment"));
} }
Validate.It(() => "Cannot update comment.", e => Validate.It(e =>
{ {
if (string.IsNullOrWhiteSpace(command.Text)) if (string.IsNullOrWhiteSpace(command.Text))
{ {
e(Not.Defined("Text"), nameof(command.Text)); e(Not.Defined("Text"), nameof(command.Text));
} }
}); });
} }
@ -58,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
if (!string.Equals(commentsId, command.Actor.Identifier) && !comment.Payload.Actor.Equals(command.Actor)) if (!string.Equals(commentsId, command.Actor.Identifier) && !comment.Payload.Actor.Equals(command.Actor))
{ {
throw new DomainException("Comment is created by another user."); throw new DomainException(T.Get("comments.notUserComment"));
} }
} }
@ -80,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards
if (result == null) if (result == null)
{ {
throw new DomainObjectNotFoundException(commentId.ToString(), "Comments", typeof(CommentsGrain)); throw new DomainObjectNotFoundException(commentId.ToString());
} }
return result; return result;

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

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
@ -89,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
if (id == null || id == default) if (id == null || id == default)
{ {
throw new DomainObjectNotFoundException("NOT DEFINED", typeof(IContentEntity)); throw new DomainObjectNotFoundException("undefined");
} }
var command = SimpleMapper.Map(bulkUpdates, new ChangeContentStatus { ContentId = id.Value }); var command = SimpleMapper.Map(bulkUpdates, new ChangeContentStatus { ContentId = id.Value });
@ -107,7 +108,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
if (id == null || id == default) if (id == null || id == default)
{ {
throw new DomainObjectNotFoundException("NOT DEFINED", typeof(IContentEntity)); throw new DomainObjectNotFoundException("undefined");
} }
var command = SimpleMapper.Map(bulkUpdates, new DeleteContent { ContentId = id.Value }); var command = SimpleMapper.Map(bulkUpdates, new DeleteContent { ContentId = id.Value });
@ -162,7 +163,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (existing.Total > 1) if (existing.Total > 1)
{ {
throw new DomainException("More than one content matches to the query."); throw new DomainException(T.Get("contents.bulkInsertQueryNotUnique"));
} }
id = existing.FirstOrDefault()?.Id; id = existing.FirstOrDefault()?.Id;

21
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -21,6 +21,7 @@ using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
@ -48,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
case CreateContent createContent: case CreateContent createContent:
return CreateReturnAsync(createContent, async c => return CreateReturnAsync(createContent, async c =>
{ {
await LoadContext(c.AppId, c.SchemaId, c, () => "Failed to create content.", c.OptimizeValidation); await LoadContext(c.AppId, c.SchemaId, c, c.OptimizeValidation);
await GuardContent.CanCreate(context.Schema, contentWorkflow, c); await GuardContent.CanCreate(context.Schema, contentWorkflow, c);
@ -98,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
case CreateContentDraft createContentDraft: case CreateContentDraft createContentDraft:
return UpdateReturnAsync(createContentDraft, async c => return UpdateReturnAsync(createContentDraft, async c =>
{ {
await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c, () => "Failed to create draft."); await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c);
GuardContent.CanCreateDraft(c, Snapshot); GuardContent.CanCreateDraft(c, Snapshot);
@ -112,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
case DeleteContentDraft deleteContentDraft: case DeleteContentDraft deleteContentDraft:
return UpdateReturnAsync(deleteContentDraft, async c => return UpdateReturnAsync(deleteContentDraft, async c =>
{ {
await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c, () => "Failed to delete draft."); await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c);
GuardContent.CanDeleteDraft(c, Snapshot); GuardContent.CanDeleteDraft(c, Snapshot);
@ -142,7 +143,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
try try
{ {
await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c, () => "Failed to change content."); await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c);
await GuardContent.CanChangeStatus(context.Schema, Snapshot, contentWorkflow, c); await GuardContent.CanChangeStatus(context.Schema, Snapshot, contentWorkflow, c);
@ -187,7 +188,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
case DeleteContent deleteContent: case DeleteContent deleteContent:
return UpdateAsync(deleteContent, async c => return UpdateAsync(deleteContent, async c =>
{ {
await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c, () => "Failed to delete content."); await LoadContext(Snapshot.AppId, Snapshot.SchemaId, c);
GuardContent.CanDelete(context.Schema, c); GuardContent.CanDelete(context.Schema, c);
@ -219,7 +220,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (!currentData!.Equals(newData)) if (!currentData!.Equals(newData))
{ {
await LoadContext(Snapshot.AppId, Snapshot.SchemaId, command, () => "Failed to update content.", command.OptimizeValidation); await LoadContext(Snapshot.AppId, Snapshot.SchemaId, command, command.OptimizeValidation);
if (!command.DoNotValidate) if (!command.DoNotValidate)
{ {
@ -337,13 +338,13 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
if (Snapshot.IsDeleted) if (Snapshot.IsDeleted)
{ {
throw new DomainException("Content has already been deleted."); throw new DomainException(T.Get("contents.alreadyDeleted"));
} }
} }
private Task LoadContext(NamedId<Guid> appId, NamedId<Guid> schemaId, ContentCommand command, Func<string> message, bool optimized = false) private Task LoadContext(NamedId<Guid> appId, NamedId<Guid> schemaId, ContentCommand command, bool optimized = false)
{ {
return context.LoadAsync(appId, schemaId, command, message, optimized); return context.LoadAsync(appId, schemaId, command, optimized);
} }
} }
} }

19
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHistoryEventsCreator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents; using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
@ -20,28 +21,28 @@ namespace Squidex.Domain.Apps.Entities.Contents
: base(typeNameRegistry) : base(typeNameRegistry)
{ {
AddEventMessage<ContentCreated>( AddEventMessage<ContentCreated>(
"created {[Schema]} content."); T.Get("history.contents.created"));
AddEventMessage<ContentUpdated>( AddEventMessage<ContentUpdated>(
"updated {[Schema]} content."); T.Get("history.contents.updated"));
AddEventMessage<ContentDeleted>( AddEventMessage<ContentDeleted>(
"deleted {[Schema]} content."); T.Get("history.contents.deleted"));
AddEventMessage<ContentDraftCreated>( AddEventMessage<ContentDraftCreated>(
"created new draft."); T.Get("history.contents.draftCreated"));
AddEventMessage<ContentDraftDeleted>( AddEventMessage<ContentDraftDeleted>(
"deleted draft."); T.Get("history.contents.draftDeleted"));
AddEventMessage<ContentSchedulingCancelled>( AddEventMessage<ContentSchedulingCancelled>(
"failed to schedule status change for {[Schema]} content."); T.Get("history.contents.scheduleFailed"));
AddEventMessage<ContentStatusChanged>( AddEventMessage<ContentStatusChanged>(
"changed status of {[Schema]} content to {[Status]}."); T.Get("history.statusChanged"));
AddEventMessage<ContentStatusScheduled>( AddEventMessage<ContentStatusScheduled>(
"scheduled to change status of {[Schemra]} content to {[Status]}."); T.Get("history.contents.scheduleCompleted"));
} }
protected override Task<HistoryEvent?> CreateEventCoreAsync(Envelope<IEvent> @event) protected override Task<HistoryEvent?> CreateEventCoreAsync(Envelope<IEvent> @event)

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

@ -40,7 +40,6 @@ namespace Squidex.Domain.Apps.Entities.Contents
private IAppEntity app; private IAppEntity app;
private ContentCommand command; private ContentCommand command;
private ValidationContext validationContext; private ValidationContext validationContext;
private Func<string> message;
public ContentOperationContext(IAppProvider appProvider, IEnumerable<IValidatorsFactory> factories, IScriptEngine scriptEngine) public ContentOperationContext(IAppProvider appProvider, IEnumerable<IValidatorsFactory> factories, IScriptEngine scriptEngine)
{ {
@ -54,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
get { return schema; } get { return schema; }
} }
public async Task LoadAsync(NamedId<Guid> appId, NamedId<Guid> schemaId, ContentCommand command, Func<string> message, bool optimized) public async Task LoadAsync(NamedId<Guid> appId, NamedId<Guid> schemaId, ContentCommand command, bool optimized)
{ {
var (app, schema) = await appProvider.GetAppWithSchemaAsync(appId.Id, schemaId.Id); var (app, schema) = await appProvider.GetAppWithSchemaAsync(appId.Id, schemaId.Id);
@ -71,7 +70,6 @@ namespace Squidex.Domain.Apps.Entities.Contents
this.app = app; this.app = app;
this.schema = schema; this.schema = schema;
this.command = command; this.command = command;
this.message = message;
validationContext = new ValidationContext(appId, schemaId, schema.SchemaDef, command.ContentId).Optimized(optimized); validationContext = new ValidationContext(appId, schemaId, schema.SchemaDef, command.ContentId).Optimized(optimized);
} }
@ -114,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
if (validator.Errors.Count > 0) if (validator.Errors.Count > 0)
{ {
throw new ValidationException(message(), validator.Errors.ToList()); throw new ValidationException(validator.Errors.ToList());
} }
} }

7
backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
@ -33,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (workflows.Values.Count(x => x.SchemaIds.Count == 0) > 1) if (workflows.Values.Count(x => x.SchemaIds.Count == 0) > 1)
{ {
errors.Add("Multiple workflows cover all schemas."); errors.Add(T.Get("workflows.overlap"));
} }
var uniqueSchemaIds = workflows.Values.SelectMany(x => x.SchemaIds).Distinct().ToList(); var uniqueSchemaIds = workflows.Values.SelectMany(x => x.SchemaIds).Distinct().ToList();
@ -46,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (schema != null) if (schema != null)
{ {
errors.Add($"The schema `{schema.SchemaDef.Name}` is covered by multiple workflows."); errors.Add(T.Get("workflows.schemaOverlap", new { schema = schema.SchemaDef.Name }));
} }
} }
} }

31
backend/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -13,6 +13,7 @@ using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Contents.Guards namespace Squidex.Domain.Apps.Entities.Contents.Guards
@ -25,15 +26,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
if (schema.SchemaDef.IsSingleton && command.ContentId != schema.Id) if (schema.SchemaDef.IsSingleton && command.ContentId != schema.Id)
{ {
throw new DomainException("Singleton content cannot be created."); throw new DomainException(T.Get("contents.singletonNotCreatable"));
} }
if (command.Publish && !await contentWorkflow.CanPublishOnCreateAsync(schema, command.Data, command.User)) if (command.Publish && !await contentWorkflow.CanPublishOnCreateAsync(schema, command.Data, command.User))
{ {
throw new DomainException("Content workflow prevents publishing."); throw new DomainException(T.Get("contents.workflowErorPublishing"));
} }
Validate.It(() => "Cannot created content.", e => Validate.It(e =>
{ {
ValidateData(command, e); ValidateData(command, e);
}); });
@ -43,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot update content.", e => Validate.It(e =>
{ {
ValidateData(command, e); ValidateData(command, e);
}); });
@ -55,7 +56,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot patch content.", e => Validate.It(e =>
{ {
ValidateData(command, e); ValidateData(command, e);
}); });
@ -69,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
if (content.NewStatus == null) if (content.NewStatus == null)
{ {
throw new DomainException("There is nothing to delete."); throw new DomainException(T.Get("contents.draftToDeleteNotFound"));
} }
} }
@ -79,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
if (content.Status != Status.Published) if (content.Status != Status.Published)
{ {
throw new DomainException("You can only create a new version when the content is published."); throw new DomainException(T.Get("contents.draftNotCreateForUnpublished"));
} }
} }
@ -91,22 +92,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{ {
if (content.NewVersion == null || command.Status != Status.Published) if (content.NewVersion == null || command.Status != Status.Published)
{ {
throw new DomainException("Singleton content cannot be updated."); throw new DomainException(T.Get("contents.singletonNotChangeable"));
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
return Validate.It(() => "Cannot change status.", async e => return Validate.It(async e =>
{ {
if (!await contentWorkflow.CanMoveToAsync(content, content.EditingStatus, command.Status, command.User)) if (!await contentWorkflow.CanMoveToAsync(content, content.EditingStatus, command.Status, command.User))
{ {
e($"Cannot change status from {content.Status} to {command.Status}.", nameof(command.Status)); e(T.Get("contents.statusTransitionNotAllowed", new { oldStatus = content.EditingStatus, newStatus = command.Status }), nameof(command.Status));
} }
if (command.DueTime.HasValue && command.DueTime.Value < SystemClock.Instance.GetCurrentInstant()) if (command.DueTime.HasValue && command.DueTime.Value < SystemClock.Instance.GetCurrentInstant())
{ {
e("Due time must be in the future.", nameof(command.DueTime)); e(T.Get("contents.statusSchedulingNotInFuture"), nameof(command.DueTime));
} }
}); });
} }
@ -117,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
if (schema.SchemaDef.IsSingleton) if (schema.SchemaDef.IsSingleton)
{ {
throw new DomainException("Singleton content cannot be deleted."); throw new DomainException(T.Get("contents.singletonNotDeletable"));
} }
} }
@ -125,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{ {
if (command.Data == null) if (command.Data == null)
{ {
e(Not.Defined("Data"), nameof(command.Data)); e(Not.Defined(nameof(command.Data)), nameof(command.Data));
} }
} }
@ -133,7 +134,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{ {
if (!await contentWorkflow.CanUpdateAsync(content, content.EditingStatus, user)) if (!await contentWorkflow.CanUpdateAsync(content, content.EditingStatus, user))
{ {
throw new DomainException($"The workflow does not allow updates at status {content.Status}"); throw new DomainException(T.Get("contents.workflowErrorUpdate", new { status = content.EditingStatus }));
} }
} }
} }

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

@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
if (content == null || content.Version <= EtagVersion.Empty || (version > EtagVersion.Any && content.Version != version)) if (content == null || content.Version <= EtagVersion.Empty || (version > EtagVersion.Any && content.Version != version))
{ {
throw new DomainObjectNotFoundException(id.ToString(), typeof(IContentEntity)); throw new DomainObjectNotFoundException(id.ToString());
} }
return content; return content;

5
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs

@ -27,6 +27,7 @@ using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Queries.Json; using Squidex.Infrastructure.Queries.Json;
using Squidex.Infrastructure.Queries.OData; using Squidex.Infrastructure.Queries.OData;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Contents.Queries namespace Squidex.Domain.Apps.Entities.Contents.Queries
@ -119,11 +120,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
} }
catch (NotSupportedException) catch (NotSupportedException)
{ {
throw new ValidationException("OData operation is not supported."); throw new ValidationException(T.Get("common.odataNotSupported"));
} }
catch (ODataException ex) catch (ODataException ex)
{ {
throw new ValidationException($"Failed to parse query: {ex.Message}", ex); throw new ValidationException(T.Get("common.odataFailure", new { message = ex.Message }), ex);
} }
} }

9
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Translations;
using Squidex.Shared; using Squidex.Shared;
#pragma warning disable RECS0147 #pragma warning disable RECS0147
@ -73,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
if (content == null || content.SchemaId.Id != schema.Id) if (content == null || content.SchemaId.Id != schema.Id)
{ {
throw new DomainObjectNotFoundException(id.ToString(), typeof(IContentEntity)); throw new DomainObjectNotFoundException(id.ToString());
} }
return await TransformAsync(context, content); return await TransformAsync(context, content);
@ -170,7 +171,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
if (schema == null) if (schema == null)
{ {
throw new DomainObjectNotFoundException(schemaIdOrName, typeof(ISchemaEntity)); throw new DomainObjectNotFoundException(schemaIdOrName);
} }
return schema; return schema;
@ -182,7 +183,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{ {
if (!HasPermission(context, schema)) if (!HasPermission(context, schema))
{ {
throw new DomainForbiddenException("You do not have permission for this schema."); throw new DomainForbiddenException(T.Get("schemas.noPermission"));
} }
} }
} }

5
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -16,6 +16,7 @@ using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{ {
@ -128,7 +129,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
private static JsonObject CreateFallback(Context context, List<IEnrichedContentEntity> referencedContents) private static JsonObject CreateFallback(Context context, List<IEnrichedContentEntity> referencedContents)
{ {
var text = $"{referencedContents.Count} Reference(s)"; var text = T.Get("contents.listReferences", new { count = referencedContents.Count });
var value = JsonValue.Object(); var value = JsonValue.Object();

21
backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs

@ -19,28 +19,28 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
return Validate.It(() => "Cannot create rule.", async e => return Validate.It(async e =>
{ {
if (command.Trigger == null) if (command.Trigger == null)
{ {
e(Not.Defined("Trigger"), nameof(command.Trigger)); e(Not.Defined(nameof(command.Trigger)), nameof(command.Trigger));
} }
else else
{ {
var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Id, command.Trigger, appProvider); var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Id, command.Trigger, appProvider);
errors.Foreach(x => x.AddTo(e)); errors.Foreach((x, _) => x.AddTo(e));
} }
if (command.Action == null) if (command.Action == null)
{ {
e(Not.Defined("Action"), nameof(command.Action)); e(Not.Defined(nameof(command.Action)), nameof(command.Action));
} }
else else
{ {
var errors = command.Action.Validate(); var errors = command.Action.Validate();
errors.Foreach(x => x.AddTo(e)); errors.Foreach((x, _) => x.AddTo(e));
} }
}); });
} }
@ -49,25 +49,20 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
return Validate.It(() => "Cannot update rule.", async e => return Validate.It(async e =>
{ {
if (command.Trigger == null && command.Action == null && command.Name == null)
{
e(Not.Defined("Either trigger, action or name"), nameof(command.Trigger), nameof(command.Action));
}
if (command.Trigger != null) if (command.Trigger != null)
{ {
var errors = await RuleTriggerValidator.ValidateAsync(appId, command.Trigger, appProvider); var errors = await RuleTriggerValidator.ValidateAsync(appId, command.Trigger, appProvider);
errors.Foreach(x => x.AddTo(e)); errors.Foreach((x, _) => x.AddTo(e));
} }
if (command.Action != null) if (command.Action != null)
{ {
var errors = command.Action.Validate(); var errors = command.Action.Validate();
errors.Foreach(x => x.AddTo(e)); errors.Foreach((x, _) => x.AddTo(e));
} }
}); });
} }

9
backend/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -13,6 +13,7 @@ using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Rules.Guards namespace Squidex.Domain.Apps.Entities.Rules.Guards
@ -62,7 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
if (trigger.NumDays.HasValue && (trigger.NumDays < 1 || trigger.NumDays > 30)) if (trigger.NumDays.HasValue && (trigger.NumDays < 1 || trigger.NumDays > 30))
{ {
errors.Add(new ValidationError(Not.Between("Num days", 1, 30), nameof(trigger.NumDays))); errors.Add(new ValidationError(Not.Between(nameof(trigger.NumDays), 1, 30), nameof(trigger.NumDays)));
} }
return Task.FromResult<IEnumerable<ValidationError>>(errors); return Task.FromResult<IEnumerable<ValidationError>>(errors);
@ -80,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
{ {
if (schema.SchemaId == Guid.Empty) if (schema.SchemaId == Guid.Empty)
{ {
errors.Add(new ValidationError(Not.Defined("Schema id"), nameof(trigger.Schemas))); errors.Add(new ValidationError(Not.Defined("SchemaId"), nameof(trigger.Schemas)));
} }
else else
{ {
@ -100,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
{ {
if (await SchemaProvider(schema.SchemaId) == null) if (await SchemaProvider(schema.SchemaId) == null)
{ {
return new ValidationError($"Schema {schema.SchemaId} does not exist.", nameof(ContentChangedTriggerV2.Schemas)); return new ValidationError(T.Get("schemas.notFoundId", new { id = schema.SchemaId }), nameof(ContentChangedTriggerV2.Schemas));
} }
return null; return null;

5
backend/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -18,6 +18,7 @@ using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Rules namespace Squidex.Domain.Apps.Entities.Rules
{ {
@ -143,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
{ {
if (Snapshot.IsDeleted) if (Snapshot.IsDeleted)
{ {
throw new DomainException("Rule has already been deleted."); throw new DomainException(T.Get("rules.alreadyDeleted"));
} }
} }
} }

5
backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerGrain.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -20,6 +20,7 @@ using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Rules.Runner namespace Squidex.Domain.Apps.Entities.Rules.Runner
{ {
@ -109,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Runner
{ {
if (currentJobToken != null) if (currentJobToken != null)
{ {
throw new DomainException("Another rule is already running."); throw new DomainException(T.Get("rules.ruleAlreadyRunning"));
} }
state.Value = new State state.Value = new State

71
backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -7,6 +7,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Schemas.Guards namespace Squidex.Domain.Apps.Entities.Schemas.Guards
@ -34,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value) if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value)
{ {
yield return new ValidationError(Not.GreaterEquals("Max items", "min items"), yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxItems), nameof(properties.MinItems)),
nameof(properties.MinItems), nameof(properties.MinItems),
nameof(properties.MaxItems)); nameof(properties.MaxItems));
} }
@ -44,35 +45,35 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value) if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value)
{ {
yield return new ValidationError(Not.GreaterEquals("Max items", "min items"), yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxItems), nameof(properties.MinItems)),
nameof(properties.MinItems), nameof(properties.MinItems),
nameof(properties.MaxItems)); nameof(properties.MaxItems));
} }
if (properties.MaxHeight.HasValue && properties.MinHeight.HasValue && properties.MinHeight.Value > properties.MaxHeight.Value) if (properties.MaxHeight.HasValue && properties.MinHeight.HasValue && properties.MinHeight.Value > properties.MaxHeight.Value)
{ {
yield return new ValidationError(Not.GreaterEquals("Max height", "min height"), yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxHeight), nameof(properties.MinHeight)),
nameof(properties.MaxHeight), nameof(properties.MaxHeight),
nameof(properties.MinHeight)); nameof(properties.MinHeight));
} }
if (properties.MaxWidth.HasValue && properties.MinWidth.HasValue && properties.MinWidth.Value > properties.MaxWidth.Value) if (properties.MaxWidth.HasValue && properties.MinWidth.HasValue && properties.MinWidth.Value > properties.MaxWidth.Value)
{ {
yield return new ValidationError(Not.GreaterEquals("Max width", "min width"), yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxWidth), nameof(properties.MinWidth)),
nameof(properties.MaxWidth), nameof(properties.MaxWidth),
nameof(properties.MinWidth)); nameof(properties.MinWidth));
} }
if (properties.MaxSize.HasValue && properties.MinSize.HasValue && properties.MinSize.Value >= properties.MaxSize.Value) if (properties.MaxSize.HasValue && properties.MinSize.HasValue && properties.MinSize.Value >= properties.MaxSize.Value)
{ {
yield return new ValidationError(Not.GreaterThan("Max size", "min size"), yield return new ValidationError(Not.GreaterThan(nameof(properties.MaxSize), nameof(properties.MinSize)),
nameof(properties.MaxSize), nameof(properties.MaxSize),
nameof(properties.MinSize)); nameof(properties.MinSize));
} }
if (properties.AspectWidth.HasValue != properties.AspectHeight.HasValue) if (properties.AspectWidth.HasValue != properties.AspectHeight.HasValue)
{ {
yield return new ValidationError(Not.Defined2("Aspect width", "aspect height"), yield return new ValidationError(Not.BothDefined(nameof(properties.AspectWidth), nameof(properties.AspectHeight)),
nameof(properties.AspectWidth), nameof(properties.AspectWidth),
nameof(properties.AspectHeight)); nameof(properties.AspectHeight));
} }
@ -82,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (!properties.Editor.IsEnumValue()) if (!properties.Editor.IsEnumValue())
{ {
yield return new ValidationError(Not.Valid("Editor"), yield return new ValidationError(Not.Valid(nameof(properties.Editor)),
nameof(properties.Editor)); nameof(properties.Editor));
} }
} }
@ -91,25 +92,25 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (!properties.Editor.IsEnumValue()) if (!properties.Editor.IsEnumValue())
{ {
yield return new ValidationError(Not.Valid("Editor"), yield return new ValidationError(Not.Valid(nameof(properties.Editor)),
nameof(properties.Editor)); nameof(properties.Editor));
} }
if (properties.DefaultValue.HasValue && properties.MinValue.HasValue && properties.DefaultValue.Value < properties.MinValue.Value) if (properties.DefaultValue.HasValue && properties.MinValue.HasValue && properties.DefaultValue.Value < properties.MinValue.Value)
{ {
yield return new ValidationError(Not.GreaterEquals("Default value", "min value"), yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.DefaultValue), nameof(properties.MinValue)),
nameof(properties.DefaultValue)); nameof(properties.DefaultValue));
} }
if (properties.DefaultValue.HasValue && properties.MaxValue.HasValue && properties.DefaultValue.Value > properties.MaxValue.Value) if (properties.DefaultValue.HasValue && properties.MaxValue.HasValue && properties.DefaultValue.Value > properties.MaxValue.Value)
{ {
yield return new ValidationError(Not.LessEquals("Default value", "max value"), yield return new ValidationError(Not.LessEqualsThan(nameof(properties.DefaultValue), nameof(properties.MaxValue)),
nameof(properties.DefaultValue)); nameof(properties.DefaultValue));
} }
if (properties.MaxValue.HasValue && properties.MinValue.HasValue && properties.MinValue.Value >= properties.MaxValue.Value) if (properties.MaxValue.HasValue && properties.MinValue.HasValue && properties.MinValue.Value >= properties.MaxValue.Value)
{ {
yield return new ValidationError(Not.GreaterThan("Max value", "min value"), yield return new ValidationError(Not.GreaterThan(nameof(properties.MaxValue), nameof(properties.MinValue)),
nameof(properties.MinValue), nameof(properties.MinValue),
nameof(properties.MaxValue)); nameof(properties.MaxValue));
} }
@ -118,13 +119,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (!properties.CalculatedDefaultValue.Value.IsEnumValue()) if (!properties.CalculatedDefaultValue.Value.IsEnumValue())
{ {
yield return new ValidationError(Not.Valid("Calculated default value"), yield return new ValidationError(Not.Valid(nameof(properties.CalculatedDefaultValue)),
nameof(properties.CalculatedDefaultValue)); nameof(properties.CalculatedDefaultValue));
} }
if (properties.DefaultValue.HasValue) if (properties.DefaultValue.HasValue)
{ {
yield return new ValidationError("Calculated default value and default value cannot be used together.", yield return new ValidationError(T.Get("schemas.dateTimeCalculatedDefaultAndDefaultError"),
nameof(properties.CalculatedDefaultValue), nameof(properties.CalculatedDefaultValue),
nameof(properties.DefaultValue)); nameof(properties.DefaultValue));
} }
@ -135,7 +136,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (!properties.Editor.IsEnumValue()) if (!properties.Editor.IsEnumValue())
{ {
yield return new ValidationError(Not.Valid("Editor"), yield return new ValidationError(Not.Valid(nameof(properties.Editor)),
nameof(properties.Editor)); nameof(properties.Editor));
} }
} }
@ -149,38 +150,38 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (!properties.Editor.IsEnumValue()) if (!properties.Editor.IsEnumValue())
{ {
yield return new ValidationError(Not.Valid("Editor"), yield return new ValidationError(Not.Valid(nameof(properties.Editor)),
nameof(properties.Editor)); nameof(properties.Editor));
} }
if ((properties.Editor == NumberFieldEditor.Radio || properties.Editor == NumberFieldEditor.Dropdown) && (properties.AllowedValues == null || properties.AllowedValues.Count == 0)) if ((properties.Editor == NumberFieldEditor.Radio || properties.Editor == NumberFieldEditor.Dropdown) && (properties.AllowedValues == null || properties.AllowedValues.Count == 0))
{ {
yield return new ValidationError("Radio buttons or dropdown list need allowed values.", yield return new ValidationError(T.Get("schemas.stringEditorsNeedAllowedValuesError"),
nameof(properties.AllowedValues)); nameof(properties.AllowedValues));
} }
if (properties.DefaultValue.HasValue && properties.MinValue.HasValue && properties.DefaultValue.Value < properties.MinValue.Value) if (properties.DefaultValue.HasValue && properties.MinValue.HasValue && properties.DefaultValue.Value < properties.MinValue.Value)
{ {
yield return new ValidationError(Not.GreaterEquals("Default value", "min value"), yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.DefaultValue), nameof(properties.MinValue)),
nameof(properties.DefaultValue)); nameof(properties.DefaultValue));
} }
if (properties.DefaultValue.HasValue && properties.MaxValue.HasValue && properties.DefaultValue.Value > properties.MaxValue.Value) if (properties.DefaultValue.HasValue && properties.MaxValue.HasValue && properties.DefaultValue.Value > properties.MaxValue.Value)
{ {
yield return new ValidationError(Not.LessEquals("Default value", "max value"), yield return new ValidationError(Not.LessEqualsThan(nameof(properties.DefaultValue), nameof(properties.MaxValue)),
nameof(properties.DefaultValue)); nameof(properties.DefaultValue));
} }
if (properties.MaxValue.HasValue && properties.MinValue.HasValue && properties.MinValue.Value >= properties.MaxValue.Value) if (properties.MaxValue.HasValue && properties.MinValue.HasValue && properties.MinValue.Value >= properties.MaxValue.Value)
{ {
yield return new ValidationError(Not.GreaterThan("Max value", "min value"), yield return new ValidationError(Not.GreaterThan(nameof(properties.MaxValue), nameof(properties.MinValue)),
nameof(properties.MinValue), nameof(properties.MinValue),
nameof(properties.MaxValue)); nameof(properties.MaxValue));
} }
if (properties.AllowedValues != null && properties.AllowedValues.Count > 0 && (properties.MinValue.HasValue || properties.MaxValue.HasValue)) if (properties.AllowedValues != null && properties.AllowedValues.Count > 0 && (properties.MinValue.HasValue || properties.MaxValue.HasValue))
{ {
yield return new ValidationError("Either allowed values or min and max value can be defined.", yield return new ValidationError(T.Get("schemas.string.eitherMinMaxOrAllowedValuesError"),
nameof(properties.AllowedValues), nameof(properties.AllowedValues),
nameof(properties.MinValue), nameof(properties.MinValue),
nameof(properties.MaxValue)); nameof(properties.MaxValue));
@ -188,7 +189,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (properties.InlineEditable && properties.Editor == NumberFieldEditor.Radio) if (properties.InlineEditable && properties.Editor == NumberFieldEditor.Radio)
{ {
yield return new ValidationError("Inline editing is not allowed for Radio editor.", yield return new ValidationError(T.Get("schemas.number.inlineEditorError"),
nameof(properties.InlineEditable), nameof(properties.InlineEditable),
nameof(properties.Editor)); nameof(properties.Editor));
} }
@ -198,20 +199,20 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (!properties.Editor.IsEnumValue()) if (!properties.Editor.IsEnumValue())
{ {
yield return new ValidationError(Not.Valid("Editor"), yield return new ValidationError(Not.Valid(nameof(properties.Editor)),
nameof(properties.Editor)); nameof(properties.Editor));
} }
if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value) if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value)
{ {
yield return new ValidationError(Not.GreaterEquals("Max items", "min items"), yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxItems), nameof(properties.MinItems)),
nameof(properties.MinItems), nameof(properties.MinItems),
nameof(properties.MaxItems)); nameof(properties.MaxItems));
} }
if (properties.ResolveReference && properties.MaxItems != 1) if (properties.ResolveReference && properties.MaxItems != 1)
{ {
yield return new ValidationError("Can only resolve references when MaxItems is 1.", yield return new ValidationError(T.Get("schemas.references.resolveError"),
nameof(properties.ResolveReference), nameof(properties.ResolveReference),
nameof(properties.MaxItems)); nameof(properties.MaxItems));
} }
@ -221,32 +222,32 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (!properties.Editor.IsEnumValue()) if (!properties.Editor.IsEnumValue())
{ {
yield return new ValidationError(Not.Valid("Editor"), yield return new ValidationError(Not.Valid(nameof(properties.Editor)),
nameof(properties.Editor)); nameof(properties.Editor));
} }
if ((properties.Editor == StringFieldEditor.Radio || properties.Editor == StringFieldEditor.Dropdown) && (properties.AllowedValues == null || properties.AllowedValues.Count == 0)) if ((properties.Editor == StringFieldEditor.Radio || properties.Editor == StringFieldEditor.Dropdown) && (properties.AllowedValues == null || properties.AllowedValues.Count == 0))
{ {
yield return new ValidationError("Radio buttons or dropdown list need allowed values.", yield return new ValidationError(T.Get("schemas.stringEditorsNeedAllowedValuesError"),
nameof(properties.AllowedValues)); nameof(properties.AllowedValues));
} }
if (properties.Pattern != null && !properties.Pattern.IsValidRegex()) if (properties.Pattern != null && !properties.Pattern.IsValidRegex())
{ {
yield return new ValidationError(Not.Valid("Pattern"), yield return new ValidationError(Not.Valid(nameof(properties.Pattern)),
nameof(properties.Pattern)); nameof(properties.Pattern));
} }
if (properties.MaxLength.HasValue && properties.MinLength.HasValue && properties.MinLength.Value > properties.MaxLength.Value) if (properties.MaxLength.HasValue && properties.MinLength.HasValue && properties.MinLength.Value > properties.MaxLength.Value)
{ {
yield return new ValidationError(Not.GreaterEquals("Max length", "min length"), yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxLength), nameof(properties.MinLength)),
nameof(properties.MinLength), nameof(properties.MinLength),
nameof(properties.MaxLength)); nameof(properties.MaxLength));
} }
if (properties.AllowedValues != null && properties.AllowedValues.Count > 0 && (properties.MinLength.HasValue || properties.MaxLength.HasValue)) if (properties.AllowedValues != null && properties.AllowedValues.Count > 0 && (properties.MinLength.HasValue || properties.MaxLength.HasValue))
{ {
yield return new ValidationError("Either allowed values or min and max length can be defined.", yield return new ValidationError(T.Get("schemas.number.eitherMinMaxOrAllowedValuesError"),
nameof(properties.AllowedValues), nameof(properties.AllowedValues),
nameof(properties.MinLength), nameof(properties.MinLength),
nameof(properties.MaxLength)); nameof(properties.MaxLength));
@ -254,7 +255,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (properties.InlineEditable && properties.Editor != StringFieldEditor.Dropdown && properties.Editor != StringFieldEditor.Input && properties.Editor != StringFieldEditor.Slug) if (properties.InlineEditable && properties.Editor != StringFieldEditor.Dropdown && properties.Editor != StringFieldEditor.Input && properties.Editor != StringFieldEditor.Slug)
{ {
yield return new ValidationError("Inline editing is only allowed for dropdowns, slugs and input fields.", yield return new ValidationError(T.Get("schemas.string.inlineEditorError"),
nameof(properties.InlineEditable), nameof(properties.InlineEditable),
nameof(properties.Editor)); nameof(properties.Editor));
} }
@ -264,19 +265,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (!properties.Editor.IsEnumValue()) if (!properties.Editor.IsEnumValue())
{ {
yield return new ValidationError(Not.Valid("Editor"), yield return new ValidationError(Not.Valid(nameof(properties.Editor)),
nameof(properties.Editor)); nameof(properties.Editor));
} }
if ((properties.Editor == TagsFieldEditor.Checkboxes || properties.Editor == TagsFieldEditor.Dropdown) && (properties.AllowedValues == null || properties.AllowedValues.Count == 0)) if ((properties.Editor == TagsFieldEditor.Checkboxes || properties.Editor == TagsFieldEditor.Dropdown) && (properties.AllowedValues == null || properties.AllowedValues.Count == 0))
{ {
yield return new ValidationError("Checkboxes or dropdown list need allowed values.", yield return new ValidationError(T.Get("schemas.tags.editorNeedsAllowedValues"),
nameof(properties.AllowedValues)); nameof(properties.AllowedValues));
} }
if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value) if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value)
{ {
yield return new ValidationError(Not.GreaterEquals("Max items", "min items"), yield return new ValidationError(Not.GreaterEqualsThan(nameof(properties.MaxItems), nameof(properties.MinItems)),
nameof(properties.MinItems), nameof(properties.MinItems),
nameof(properties.MaxItems)); nameof(properties.MaxItems));
} }
@ -286,7 +287,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (!properties.Editor.IsEnumValue()) if (!properties.Editor.IsEnumValue())
{ {
yield return new ValidationError(Not.Valid("Editor"), yield return new ValidationError(Not.Valid(nameof(properties.Editor)),
nameof(properties.Editor)); nameof(properties.Editor));
} }
} }

11
backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardHelper.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -7,6 +7,7 @@
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Schemas.Guards namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
@ -16,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (!schema.FieldsById.TryGetValue(parentId, out var rootField) || !(rootField is IArrayField arrayField)) if (!schema.FieldsById.TryGetValue(parentId, out var rootField) || !(rootField is IArrayField arrayField))
{ {
throw new DomainObjectNotFoundException(parentId.ToString(), "Fields", typeof(Schema)); throw new DomainObjectNotFoundException(parentId.ToString());
} }
if (!allowLocked) if (!allowLocked)
@ -35,7 +36,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (!arrayField.FieldsById.TryGetValue(fieldId, out var nestedField)) if (!arrayField.FieldsById.TryGetValue(fieldId, out var nestedField))
{ {
throw new DomainObjectNotFoundException(fieldId.ToString(), $"Fields[{parentId}].Fields", typeof(Schema)); throw new DomainObjectNotFoundException(fieldId.ToString());
} }
return nestedField; return nestedField;
@ -43,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (!schema.FieldsById.TryGetValue(fieldId, out var field)) if (!schema.FieldsById.TryGetValue(fieldId, out var field))
{ {
throw new DomainObjectNotFoundException(fieldId.ToString(), "Fields", typeof(Schema)); throw new DomainObjectNotFoundException(fieldId.ToString());
} }
if (!allowLocked) if (!allowLocked)
@ -58,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (field.IsLocked) if (field.IsLocked)
{ {
throw new DomainException("Schema field is locked."); throw new DomainException(T.Get("schemas.fieldIsLocked"));
} }
} }
} }

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

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
#pragma warning disable IDE0060 // Remove unused parameter #pragma warning disable IDE0060 // Remove unused parameter
@ -25,11 +26,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot create schema.", e => Validate.It(e =>
{ {
if (!command.Name.IsSlug()) if (!command.Name.IsSlug())
{ {
e(Not.ValidSlug("Name"), nameof(command.Name)); e(Not.ValidSlug(nameof(command.Name)), nameof(command.Name));
} }
ValidateUpsert(command, e); ValidateUpsert(command, e);
@ -40,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot synchronize schema.", e => Validate.It(e =>
{ {
ValidateUpsert(command, e); ValidateUpsert(command, e);
}); });
@ -57,11 +58,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
arrayField = GuardHelper.GetArrayFieldOrThrow(schema, command.ParentFieldId.Value, false); arrayField = GuardHelper.GetArrayFieldOrThrow(schema, command.ParentFieldId.Value, false);
} }
Validate.It(() => "Cannot reorder schema fields.", e => Validate.It(e =>
{ {
if (command.FieldIds == null) if (command.FieldIds == null)
{ {
e(Not.Defined("Field ids"), nameof(command.FieldIds)); e(Not.Defined(nameof(command.FieldIds)), nameof(command.FieldIds));
} }
if (arrayField == null) if (arrayField == null)
@ -79,11 +80,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot configure preview urls.", e => Validate.It(e =>
{ {
if (command.PreviewUrls == null) if (command.PreviewUrls == null)
{ {
e(Not.Defined("Preview Urls"), nameof(command.PreviewUrls)); e(Not.Defined(nameof(command.PreviewUrls)), nameof(command.PreviewUrls));
} }
}); });
} }
@ -92,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot configure UI fields.", e => Validate.It(e =>
{ {
ValidateFieldNames(schema, command.FieldsInLists, nameof(command.FieldsInLists), e, IsMetaField); ValidateFieldNames(schema, command.FieldsInLists, nameof(command.FieldsInLists), e, IsMetaField);
ValidateFieldNames(schema, command.FieldsInReferences, nameof(command.FieldsInReferences), e, IsNotAllowed); ValidateFieldNames(schema, command.FieldsInReferences, nameof(command.FieldsInReferences), e, IsNotAllowed);
@ -103,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot configure field rules.", e => Validate.It(e =>
{ {
ValidateFieldRules(command.FieldRules, nameof(command.FieldRules), e); ValidateFieldRules(command.FieldRules, nameof(command.FieldRules), e);
}); });
@ -143,22 +144,18 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (command.Fields?.Count > 0) if (command.Fields?.Count > 0)
{ {
var fieldIndex = 0; command.Fields.Foreach((field, fieldIndex) =>
var fieldPrefix = string.Empty;
foreach (var field in command.Fields)
{ {
fieldIndex++; var fieldPrefix = $"Fields[{fieldIndex}]";
fieldPrefix = $"Fields[{fieldIndex}]";
ValidateRootField(field, fieldPrefix, e); ValidateRootField(field, fieldPrefix, e);
} });
foreach (var fieldName in command.Fields.Duplicates(x => x.Name)) foreach (var fieldName in command.Fields.Duplicates(x => x.Name))
{ {
if (fieldName.IsPropertyName()) if (fieldName.IsPropertyName())
{ {
e($"Field '{fieldName}' has been added twice.", nameof(command.Fields)); e(T.Get("schemas.duplicateFieldName", new { field = fieldName }), nameof(command.Fields));
} }
} }
} }
@ -179,7 +176,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (!field.Partitioning.IsValidPartitioning()) if (!field.Partitioning.IsValidPartitioning())
{ {
e(Not.Valid("Partitioning"), $"{prefix}.{nameof(field.Partitioning)}"); e(Not.Valid(nameof(field.Partitioning)), $"{prefix}.{nameof(field.Partitioning)}");
} }
ValidateField(field, prefix, e); ValidateField(field, prefix, e);
@ -188,27 +185,23 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (field.Properties is ArrayFieldProperties) if (field.Properties is ArrayFieldProperties)
{ {
var nestedIndex = 0; field.Nested.Foreach((nestedField, nestedIndex) =>
var nestedPrefix = string.Empty;
foreach (var nestedField in field.Nested)
{ {
nestedIndex++; var nestedPrefix = $"{prefix}.Nested[{nestedIndex}]";
nestedPrefix = $"{prefix}.Nested[{nestedIndex}]";
ValidateNestedField(nestedField, nestedPrefix, e); ValidateNestedField(nestedField, nestedPrefix, e);
} });
} }
else if (field.Nested.Count > 0) else if (field.Nested.Count > 0)
{ {
e("Only array fields can have nested fields.", $"{prefix}.{nameof(field.Partitioning)}"); e(T.Get("schemas.onlyArraysHaveNested"), $"{prefix}.{nameof(field.Partitioning)}");
} }
foreach (var fieldName in field.Nested.Duplicates(x => x.Name)) foreach (var fieldName in field.Nested.Duplicates(x => x.Name))
{ {
if (fieldName.IsPropertyName()) if (fieldName.IsPropertyName())
{ {
e($"Field '{fieldName}' has been added twice.", $"{prefix}.Nested"); e(T.Get("schemas.duplicateFieldName", new { field = fieldName }), $"{prefix}.Nested");
} }
} }
} }
@ -225,7 +218,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (nestedField.Properties is ArrayFieldProperties) if (nestedField.Properties is ArrayFieldProperties)
{ {
e("Nested field cannot be array fields.", $"{prefix}.{nameof(nestedField.Properties)}"); e(T.Get("schemas.onylArraysInRoot"), $"{prefix}.{nameof(nestedField.Properties)}");
} }
ValidateField(nestedField, prefix, e); ValidateField(nestedField, prefix, e);
@ -236,12 +229,12 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (!field.Name.IsPropertyName()) if (!field.Name.IsPropertyName())
{ {
e(Not.ValidPropertyName("Field name"), $"{prefix}.{nameof(field.Name)}"); e(Not.ValidJavascriptName(nameof(field.Name)), $"{prefix}.{nameof(field.Name)}");
} }
if (field.Properties == null) if (field.Properties == null)
{ {
e(Not.Defined("Field properties"), $"{prefix}.{nameof(field.Properties)}"); e(Not.Defined(nameof(field.Properties)), $"{prefix}.{nameof(field.Properties)}");
} }
else else
{ {
@ -249,18 +242,18 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (field.IsHidden) if (field.IsHidden)
{ {
e("UI field cannot be hidden.", $"{prefix}.{nameof(field.IsHidden)}"); e(T.Get("schemas.uiFieldCannotBeHidden"), $"{prefix}.{nameof(field.IsHidden)}");
} }
if (field.IsDisabled) if (field.IsDisabled)
{ {
e("UI field cannot be disabled.", $"{prefix}.{nameof(field.IsDisabled)}"); e(T.Get("schemas.uiFieldCannotBeDisabled"), $"{prefix}.{nameof(field.IsDisabled)}");
} }
} }
var errors = FieldPropertiesValidator.Validate(field.Properties); var errors = FieldPropertiesValidator.Validate(field.Properties);
errors.Foreach(x => x.WithPrefix($"{prefix}.{nameof(field.Properties)}").AddTo(e)); errors.Foreach((x, _) => x.WithPrefix($"{prefix}.{nameof(field.Properties)}").AddTo(e));
} }
} }
@ -268,13 +261,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
if (fields != null) if (fields != null)
{ {
var fieldIndex = 0; fields.Foreach((fieldName, fieldIndex) =>
var fieldPrefix = string.Empty;
foreach (var fieldName in fields)
{ {
fieldIndex++; var fieldPrefix = $"{path}[{fieldIndex}]";
fieldPrefix = $"{path}[{fieldIndex}]";
var field = schema.FieldsByName.GetOrDefault(fieldName ?? string.Empty); var field = schema.FieldsByName.GetOrDefault(fieldName ?? string.Empty);
@ -284,19 +273,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
} }
else if (field == null && !isAllowed(fieldName)) else if (field == null && !isAllowed(fieldName))
{ {
e("Field is not part of the schema.", fieldPrefix); e(T.Get("schemas.fieldNotInSchema"), fieldPrefix);
} }
else if (field?.IsUI() == true) else if (field?.IsUI() == true)
{ {
e("Field cannot be an UI field.", fieldPrefix); e(T.Get("schemas.fieldCannotBeUIField"), fieldPrefix);
} }
} });
foreach (var duplicate in fields.Duplicates()) foreach (var duplicate in fields.Duplicates())
{ {
if (!string.IsNullOrWhiteSpace(duplicate)) if (!string.IsNullOrWhiteSpace(duplicate))
{ {
e($"Field '{duplicate}' has been added twice.", path); e(T.Get("schemas.duplicateFieldName", new { field = duplicate }), path);
} }
} }
} }
@ -304,40 +293,29 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
private static void ValidateFieldRules(List<FieldRuleCommand>? fieldRules, string path, AddValidation e) private static void ValidateFieldRules(List<FieldRuleCommand>? fieldRules, string path, AddValidation e)
{ {
if (fieldRules != null) fieldRules?.Foreach((rule, ruleIndex) =>
{ {
var ruleIndex = 0; var rulePrefix = $"{path}[{ruleIndex}]";
var rulePrefix = string.Empty;
foreach (var fieldRule in fieldRules) if (string.IsNullOrWhiteSpace(rule.Field))
{ {
ruleIndex++; e(Not.Defined(nameof(rule.Field)), $"{rulePrefix}.{nameof(rule.Field)}");
rulePrefix = $"{path}[{ruleIndex}]"; }
if (string.IsNullOrWhiteSpace(fieldRule.Field))
{
e(Not.Defined(nameof(fieldRule.Field)), $"{rulePrefix}.{nameof(fieldRule.Field)}");
}
if (!fieldRule.Action.IsEnumValue()) if (!rule.Action.IsEnumValue())
{ {
e(Not.Valid(nameof(fieldRule.Action)), $"{rulePrefix}.{nameof(fieldRule.Action)}"); e(Not.Valid(nameof(rule.Action)), $"{rulePrefix}.{nameof(rule.Action)}");
}
} }
} });
} }
private static void ValidateFieldNames(UpsertCommand command, FieldNames? fields, string path, AddValidation e, Func<string, bool> isAllowed) private static void ValidateFieldNames(UpsertCommand command, FieldNames? fields, string path, AddValidation e, Func<string, bool> isAllowed)
{ {
if (fields != null) if (fields != null)
{ {
var fieldIndex = 0; fields.Foreach((fieldName, fieldIndex) =>
var fieldPrefix = string.Empty;
foreach (var fieldName in fields)
{ {
fieldIndex++; var fieldPrefix = $"{path}[{fieldIndex}]";
fieldPrefix = $"{path}[{fieldIndex}]";
var field = command?.Fields?.Find(x => x.Name == fieldName); var field = command?.Fields?.Find(x => x.Name == fieldName);
@ -347,19 +325,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
} }
else if (field == null && !isAllowed(fieldName)) else if (field == null && !isAllowed(fieldName))
{ {
e("Field is not part of the schema.", fieldPrefix); e(T.Get("schemas.fieldNotInSchema"), fieldPrefix);
} }
else if (field?.Properties?.IsUIProperty() == true) else if (field?.Properties?.IsUIProperty() == true)
{ {
e("Field cannot be an UI field.", fieldPrefix); e(T.Get("schemas.fieldCannotBeUIField"), fieldPrefix);
} }
} });
foreach (var duplicate in fields.Duplicates()) foreach (var duplicate in fields.Duplicates())
{ {
if (!string.IsNullOrWhiteSpace(duplicate)) if (!string.IsNullOrWhiteSpace(duplicate))
{ {
e($"Field '{duplicate}' has been added twice.", path); e(T.Get("schemas.duplicateFieldName", new { field = duplicate }), path);
} }
} }
} }
@ -375,11 +353,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
return false; return false;
} }
private static void ValidateFieldIds<T>(ReorderFields c, IReadOnlyDictionary<long, T> fields, AddValidation e) private static void ValidateFieldIds<TField>(ReorderFields c, IReadOnlyDictionary<long, TField> fields, AddValidation e)
{ {
if (c.FieldIds != null && (c.FieldIds.Count != fields.Count || c.FieldIds.Any(x => !fields.ContainsKey(x)))) if (c.FieldIds != null && (c.FieldIds.Count != fields.Count || c.FieldIds.Any(x => !fields.ContainsKey(x))))
{ {
e("Field ids do not cover all fields.", nameof(c.FieldIds)); e(T.Get("schemas.fieldsNotCovered"), nameof(c.FieldIds));
} }
} }
} }

29
backend/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -9,6 +9,7 @@ using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Schemas.Guards namespace Squidex.Domain.Apps.Entities.Schemas.Guards
@ -19,22 +20,22 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot add a new field.", e => Validate.It(e =>
{ {
if (!command.Name.IsPropertyName()) if (!command.Name.IsPropertyName())
{ {
e(Not.ValidPropertyName("Name"), nameof(command.Name)); e(Not.ValidJavascriptName(nameof(command.Name)), nameof(command.Name));
} }
if (command.Properties == null) if (command.Properties == null)
{ {
e(Not.Defined("Properties"), nameof(command.Properties)); e(Not.Defined(nameof(command.Properties)), nameof(command.Properties));
} }
else else
{ {
var errors = FieldPropertiesValidator.Validate(command.Properties); var errors = FieldPropertiesValidator.Validate(command.Properties);
errors.Foreach(x => x.WithPrefix(nameof(command.Properties)).AddTo(e)); errors.Foreach((x, _) => x.WithPrefix(nameof(command.Properties)).AddTo(e));
} }
if (command.ParentFieldId.HasValue) if (command.ParentFieldId.HasValue)
@ -43,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (arrayField.FieldsByName.ContainsKey(command.Name)) if (arrayField.FieldsByName.ContainsKey(command.Name))
{ {
e("A field with the same name already exists."); e(T.Get("schemas.fieldNameAlreadyExists"));
} }
} }
else else
@ -55,7 +56,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (schema.FieldsByName.ContainsKey(command.Name)) if (schema.FieldsByName.ContainsKey(command.Name))
{ {
e("A field with the same name already exists."); e(T.Get("schemas.fieldNameAlreadyExists"));
} }
} }
}); });
@ -67,17 +68,17 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId, false); GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId, false);
Validate.It(() => "Cannot update field.", e => Validate.It(e =>
{ {
if (command.Properties == null) if (command.Properties == null)
{ {
e(Not.Defined("Properties"), nameof(command.Properties)); e(Not.Defined("Properties"), nameof(command.Properties));
} }
else else
{ {
var errors = FieldPropertiesValidator.Validate(command.Properties); var errors = FieldPropertiesValidator.Validate(command.Properties);
errors.Foreach(x => x.WithPrefix(nameof(command.Properties)).AddTo(e)); errors.Foreach((x, _) => x.WithPrefix(nameof(command.Properties)).AddTo(e));
} }
}); });
} }
@ -90,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (!field.IsForApi(true)) if (!field.IsForApi(true))
{ {
throw new DomainException("UI field cannot be hidden."); throw new DomainException(T.Get("schemas.uiFieldCannotBeHidden"));
} }
} }
@ -102,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (!field.IsForApi(true)) if (!field.IsForApi(true))
{ {
throw new DomainException("UI field cannot be diabled."); throw new DomainException(T.Get("schemas.uiFieldCannotBeDisabled"));
} }
} }
@ -114,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (!field.IsForApi(true)) if (!field.IsForApi(true))
{ {
throw new DomainException("UI field cannot be shown."); throw new DomainException(T.Get("schemas.uiFieldCannotBeShown"));
} }
} }
@ -126,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
if (!field.IsForApi(true)) if (!field.IsForApi(true))
{ {
throw new DomainException("UI field cannot be enabled."); throw new DomainException(T.Get("schemas.uiFieldCannotBeEnabled"));
} }
} }

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

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -15,6 +15,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Schemas.Indexes namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
@ -178,9 +179,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
if (token == null) if (token == null)
{ {
var error = new ValidationError("A schema with this name already exists."); throw new ValidationException(T.Get("schemas.nameAlreadyExists"));
throw new ValidationException("Cannot create schema.", error);
} }
return token; return token;

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

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -21,6 +21,7 @@ using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Schemas namespace Squidex.Domain.Apps.Entities.Schemas
{ {
@ -429,7 +430,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{ {
if (Snapshot.IsDeleted) if (Snapshot.IsDeleted)
{ {
throw new DomainException("Schema has already been deleted."); throw new DomainException(T.Get("schemas.alreadyDeleted"));
} }
} }

37
backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Schemas; using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Entities.Schemas namespace Squidex.Domain.Apps.Entities.Schemas
{ {
@ -20,55 +21,55 @@ namespace Squidex.Domain.Apps.Entities.Schemas
: base(typeNameRegistry) : base(typeNameRegistry)
{ {
AddEventMessage<SchemaFieldsReordered>( AddEventMessage<SchemaFieldsReordered>(
"reordered fields of schema {[Name]}."); T.Get("history.schemas.fieldsReordered"));
AddEventMessage<SchemaCreated>( AddEventMessage<SchemaCreated>(
"created schema {[Name]}."); T.Get("history.schemas.created"));
AddEventMessage<SchemaUpdated>( AddEventMessage<SchemaUpdated>(
"updated schema {[Name]}."); T.Get("history.schemas.updated"));
AddEventMessage<SchemaDeleted>( AddEventMessage<SchemaDeleted>(
"deleted schema {[Name]}."); T.Get("history.schemas.deleted"));
AddEventMessage<SchemaPublished>( AddEventMessage<SchemaPublished>(
"published schema {[Name]}."); T.Get("history.schemas.published"));
AddEventMessage<SchemaUnpublished>( AddEventMessage<SchemaUnpublished>(
"unpublished schema {[Name]}."); T.Get("history.schemas.unpublished"));
AddEventMessage<SchemaFieldsReordered>( AddEventMessage<SchemaFieldsReordered>(
"reordered fields of schema {[Name]}."); T.Get("history.schemas.fieldsReordered"));
AddEventMessage<SchemaScriptsConfigured>( AddEventMessage<SchemaScriptsConfigured>(
"configured script of schema {[Name]}."); T.Get("history.schemas.scriptsConfigured"));
AddEventMessage<FieldAdded>( AddEventMessage<FieldAdded>(
"added field {[Field]} to schema {[Name]}."); T.Get("history.schemas.fieldAdded"));
AddEventMessage<FieldDeleted>( AddEventMessage<FieldDeleted>(
"deleted field {[Field]} from schema {[Name]}."); T.Get("history.schemas.fieldDeleted"));
AddEventMessage<FieldLocked>( AddEventMessage<FieldLocked>(
"has locked field {[Field]} of schema {[Name]}."); T.Get("history.schemas.fieldLocked"));
AddEventMessage<FieldHidden>( AddEventMessage<FieldHidden>(
"has hidden field {[Field]} of schema {[Name]}."); T.Get("history.schemas.fieldHidden"));
AddEventMessage<FieldShown>( AddEventMessage<FieldShown>(
"has shown field {[Field]} of schema {[Name]}."); T.Get("history.schemas.fieldShown"));
AddEventMessage<FieldDisabled>( AddEventMessage<FieldDisabled>(
"disabled field {[Field]} of schema {[Name]}."); T.Get("history.schemas.fieldDisabled"));
AddEventMessage<FieldEnabled>( AddEventMessage<FieldEnabled>(
"disabled field {[Field]} of schema {[Name]}."); T.Get("history.schemas.fieldDisabled"));
AddEventMessage<FieldUpdated>( AddEventMessage<FieldUpdated>(
"has updated field {[Field]} of schema {[Name]}."); T.Get("history.schemas.fieldUpdated"));
AddEventMessage<FieldDeleted>( AddEventMessage<FieldDeleted>(
"deleted field {[Field]} of schema {[Name]}."); T.Get("history.schemas.fieldDeleted"));
} }
protected override Task<HistoryEvent?> CreateEventCoreAsync(Envelope<IEvent> @event) protected override Task<HistoryEvent?> CreateEventCoreAsync(Envelope<IEvent> @event)

9
backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Search; using Squidex.Domain.Apps.Entities.Search;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations;
using Squidex.Shared; using Squidex.Shared;
namespace Squidex.Domain.Apps.Entities.Schemas namespace Squidex.Domain.Apps.Entities.Schemas
@ -66,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{ {
var schemaUrl = urlGenerator.SchemaUI(appId, schemaId); var schemaUrl = urlGenerator.SchemaUI(appId, schemaId);
result.Add($"{name} Schema", SearchResultType.Schema, schemaUrl); result.Add(T.Get("search.schemaResult", new { name }), SearchResultType.Schema, schemaUrl);
} }
private void AddContentsUrl(SearchResults result, NamedId<Guid> appId, ISchemaEntity schema, NamedId<Guid> schemaId, string name) private void AddContentsUrl(SearchResults result, NamedId<Guid> appId, ISchemaEntity schema, NamedId<Guid> schemaId, string name)
@ -75,13 +76,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{ {
var contentUrl = urlGenerator.ContentUI(appId, schemaId, schemaId.Id); var contentUrl = urlGenerator.ContentUI(appId, schemaId, schemaId.Id);
result.Add($"{name} Content", SearchResultType.Content, contentUrl, name); result.Add(T.Get("search.contentResult", new { name }), SearchResultType.Content, contentUrl, name);
} }
else else
{ {
var contentUrl = urlGenerator.ContentsUI(appId, schemaId); var contentUrl = urlGenerator.ContentsUI(appId, schemaId);
result.Add($"{name} Contents", SearchResultType.Content, contentUrl, name); result.Add(T.Get("search.contentsResult", new { name }), SearchResultType.Content, contentUrl, name);
} }
} }

4
backend/src/Squidex.Domain.Users.MongoDb/MongoUser.cs

@ -50,7 +50,7 @@ namespace Squidex.Domain.Users.MongoDb
internal void AddClaims(IEnumerable<Claim> claims) internal void AddClaims(IEnumerable<Claim> claims)
{ {
claims.Foreach(AddClaim); claims.Foreach((x, _) => AddClaim(x));
} }
internal void RemoveClaim(Claim claim) internal void RemoveClaim(Claim claim)
@ -60,7 +60,7 @@ namespace Squidex.Domain.Users.MongoDb
internal void RemoveClaims(IEnumerable<Claim> claims) internal void RemoveClaims(IEnumerable<Claim> claims)
{ {
claims.Foreach(RemoveClaim); claims.Foreach((x, _) => RemoveClaim(x));
} }
internal string? GetToken(string loginProvider, string name) internal string? GetToken(string loginProvider, string name)

11
backend/src/Squidex.Domain.Users/PwnedPasswordValidator.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -11,15 +11,12 @@ using Microsoft.AspNetCore.Identity;
using SharpPwned.NET; using SharpPwned.NET;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Users namespace Squidex.Domain.Users
{ {
public sealed class PwnedPasswordValidator : IPasswordValidator<IdentityUser> public sealed class PwnedPasswordValidator : IPasswordValidator<IdentityUser>
{ {
private const string ErrorCode = "PwnedError";
private const string ErrorText = "This password has previously appeared in a data breach and should never be used. If you've ever used it anywhere before, change it!";
private static readonly IdentityResult Error = IdentityResult.Failed(new IdentityError { Code = ErrorCode, Description = ErrorText });
private readonly HaveIBeenPwnedRestClient client = new HaveIBeenPwnedRestClient(); private readonly HaveIBeenPwnedRestClient client = new HaveIBeenPwnedRestClient();
private readonly ISemanticLog log; private readonly ISemanticLog log;
@ -38,7 +35,9 @@ namespace Squidex.Domain.Users
if (isBreached) if (isBreached)
{ {
return Error; var errorText = T.Get("security.passwordStolen");
return IdentityResult.Failed(new IdentityError { Code = "PwnedError", Description = errorText });
} }
} }
catch (Exception ex) catch (Exception ex)

38
backend/src/Squidex.Domain.Users/UserManagerExtensions.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -131,12 +131,12 @@ namespace Squidex.Domain.Users
try try
{ {
await DoChecked(() => userManager.CreateAsync(user), "Cannot create user."); await DoChecked(() => userManager.CreateAsync(user));
await DoChecked(() => values.SyncClaims(userManager, user), "Cannot add user."); await DoChecked(() => values.SyncClaims(userManager, user));
if (!string.IsNullOrWhiteSpace(values.Password)) if (!string.IsNullOrWhiteSpace(values.Password))
{ {
await DoChecked(() => userManager.AddPasswordAsync(user, values.Password), "Cannot create user."); await DoChecked(() => userManager.AddPasswordAsync(user, values.Password));
} }
} }
catch catch
@ -155,7 +155,7 @@ namespace Squidex.Domain.Users
if (user == null) if (user == null)
{ {
throw new DomainObjectNotFoundException(id, typeof(IdentityUser)); throw new DomainObjectNotFoundException(id);
} }
await UpdateAsync(userManager, user, values); await UpdateAsync(userManager, user, values);
@ -189,23 +189,21 @@ namespace Squidex.Domain.Users
public static async Task UpdateAsync(this UserManager<IdentityUser> userManager, IdentityUser user, UserValues values) public static async Task UpdateAsync(this UserManager<IdentityUser> userManager, IdentityUser user, UserValues values)
{ {
if (user == null) Guard.NotNull(user, nameof(user));
{ Guard.NotNull(values, nameof(values));
throw new DomainObjectNotFoundException("Id", typeof(IdentityUser));
}
if (!string.IsNullOrWhiteSpace(values.Email) && values.Email != user.Email) if (!string.IsNullOrWhiteSpace(values.Email) && values.Email != user.Email)
{ {
await DoChecked(() => userManager.SetEmailAsync(user, values.Email), "Cannot update email."); await DoChecked(() => userManager.SetEmailAsync(user, values.Email));
await DoChecked(() => userManager.SetUserNameAsync(user, values.Email), "Cannot update email."); await DoChecked(() => userManager.SetUserNameAsync(user, values.Email));
} }
await DoChecked(() => values.SyncClaims(userManager, user), "Cannot update user."); await DoChecked(() => values.SyncClaims(userManager, user));
if (!string.IsNullOrWhiteSpace(values.Password)) if (!string.IsNullOrWhiteSpace(values.Password))
{ {
await DoChecked(() => userManager.RemovePasswordAsync(user), "Cannot replace password."); await DoChecked(() => userManager.RemovePasswordAsync(user));
await DoChecked(() => userManager.AddPasswordAsync(user, values.Password), "Cannot replace password."); await DoChecked(() => userManager.AddPasswordAsync(user, values.Password));
} }
} }
@ -215,10 +213,10 @@ namespace Squidex.Domain.Users
if (user == null) if (user == null)
{ {
throw new DomainObjectNotFoundException(id, typeof(IdentityUser)); throw new DomainObjectNotFoundException(id);
} }
await DoChecked(() => userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddYears(100)), "Cannot lock user."); await DoChecked(() => userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddYears(100)));
return (await userManager.ResolveUserAsync(user))!; return (await userManager.ResolveUserAsync(user))!;
} }
@ -229,21 +227,21 @@ namespace Squidex.Domain.Users
if (user == null) if (user == null)
{ {
throw new DomainObjectNotFoundException(id, typeof(IdentityUser)); throw new DomainObjectNotFoundException(id);
} }
await DoChecked(() => userManager.SetLockoutEndDateAsync(user, null), "Cannot unlock user."); await DoChecked(() => userManager.SetLockoutEndDateAsync(user, null));
return (await userManager.ResolveUserAsync(user))!; return (await userManager.ResolveUserAsync(user))!;
} }
private static async Task DoChecked(Func<Task<IdentityResult>> action, string message) private static async Task DoChecked(Func<Task<IdentityResult>> action)
{ {
var result = await action(); var result = await action();
if (!result.Succeeded) if (!result.Succeeded)
{ {
throw new ValidationException(message, result.Errors.Select(x => new ValidationError(x.Description)).ToArray()); throw new ValidationException(result.Errors.Select(x => new ValidationError(x.Description)).ToList());
} }
} }

10
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs

@ -8,7 +8,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; using MongoDB.Bson;
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Conventions; using MongoDB.Bson.Serialization.Conventions;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -18,11 +18,9 @@ namespace Squidex.Infrastructure.MongoDb
{ {
public static class BsonJsonConvention public static class BsonJsonConvention
{ {
private static volatile int isRegistered;
public static void Register(JsonSerializer serializer) public static void Register(JsonSerializer serializer)
{ {
if (Interlocked.Increment(ref isRegistered) == 1) try
{ {
var pack = new ConventionPack(); var pack = new ConventionPack();
@ -53,6 +51,10 @@ namespace Squidex.Infrastructure.MongoDb
ConventionRegistry.Register("json", pack, t => true); ConventionRegistry.Register("json", pack, t => true);
} }
catch (BsonSerializationException)
{
return;
}
} }
} }
} }

10
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/InstantSerializer.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Threading; using MongoDB.Bson;
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers; using MongoDB.Bson.Serialization.Serializers;
using NodaTime; using NodaTime;
@ -14,14 +14,16 @@ namespace Squidex.Infrastructure.MongoDb
{ {
public sealed class InstantSerializer : SerializerBase<Instant>, IBsonPolymorphicSerializer public sealed class InstantSerializer : SerializerBase<Instant>, IBsonPolymorphicSerializer
{ {
private static volatile int isRegistered;
public static void Register() public static void Register()
{ {
if (Interlocked.Increment(ref isRegistered) == 1) try
{ {
BsonSerializer.RegisterSerializer(new InstantSerializer()); BsonSerializer.RegisterSerializer(new InstantSerializer());
} }
catch (BsonSerializationException)
{
return;
}
} }
public bool IsDiscriminatorCompatibleWithObjectSerializer public bool IsDiscriminatorCompatibleWithObjectSerializer

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

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -7,27 +7,28 @@
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Infrastructure.MongoDb.Queries namespace Squidex.Infrastructure.MongoDb.Queries
{ {
public static class FilterBuilder public static class FilterBuilder
{ {
public static (FilterDefinition<T>? Filter, bool Last) BuildFilter<T>(this ClrQuery query, bool supportsSearch = true) public static (FilterDefinition<TDocument>? Filter, bool Last) BuildFilter<TDocument>(this ClrQuery query, bool supportsSearch = true)
{ {
if (query.FullText != null) if (query.FullText != null)
{ {
if (!supportsSearch) if (!supportsSearch)
{ {
throw new ValidationException("Query $search clause not supported."); throw new ValidationException(T.Get("common.fullTextNotSupported"));
} }
return (Builders<T>.Filter.Text(query.FullText), false); return (Builders<TDocument>.Filter.Text(query.FullText), false);
} }
if (query.Filter != null) if (query.Filter != null)
{ {
return (query.Filter.BuildFilter<T>(), true); return (query.Filter.BuildFilter<TDocument>(), true);
} }
return (null, false); return (null, false);

10
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Threading; using MongoDB.Bson;
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers; using MongoDB.Bson.Serialization.Serializers;
@ -13,14 +13,16 @@ namespace Squidex.Infrastructure.MongoDb
{ {
public class RefTokenSerializer : ClassSerializerBase<RefToken> public class RefTokenSerializer : ClassSerializerBase<RefToken>
{ {
private static volatile int isRegistered;
public static void Register() public static void Register()
{ {
if (Interlocked.Increment(ref isRegistered) == 1) try
{ {
BsonSerializer.RegisterSerializer(new RefTokenSerializer()); BsonSerializer.RegisterSerializer(new RefTokenSerializer());
} }
catch (BsonSerializationException)
{
return;
}
} }
protected override RefToken DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args) protected override RefToken DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args)

7
backend/src/Squidex.Infrastructure/Assets/AssetAlreadyExistsException.cs

@ -13,12 +13,7 @@ namespace Squidex.Infrastructure.Assets
[Serializable] [Serializable]
public class AssetAlreadyExistsException : Exception public class AssetAlreadyExistsException : Exception
{ {
public AssetAlreadyExistsException(string fileName) public AssetAlreadyExistsException(string fileName, Exception? inner = null)
: base(FormatMessage(fileName))
{
}
public AssetAlreadyExistsException(string fileName, Exception inner)
: base(FormatMessage(fileName), inner) : base(FormatMessage(fileName), inner)
{ {
} }

7
backend/src/Squidex.Infrastructure/Assets/AssetNotFoundException.cs

@ -13,12 +13,7 @@ namespace Squidex.Infrastructure.Assets
[Serializable] [Serializable]
public class AssetNotFoundException : Exception public class AssetNotFoundException : Exception
{ {
public AssetNotFoundException(string fileName) public AssetNotFoundException(string fileName, Exception? inner = null)
: base(FormatMessage(fileName))
{
}
public AssetNotFoundException(string fileName, Exception inner)
: base(FormatMessage(fileName), inner) : base(FormatMessage(fileName), inner)
{ {
} }

8
backend/src/Squidex.Infrastructure/CollectionExtensions.cs

@ -258,11 +258,15 @@ namespace Squidex.Infrastructure
return result; return result;
} }
public static void Foreach<T>(this IEnumerable<T> collection, Action<T> action) public static void Foreach<T>(this IEnumerable<T> collection, Action<T, int> action)
{ {
var index = 0;
foreach (var item in collection) foreach (var item in collection)
{ {
action(item); action(item, index);
index++;
} }
} }

4
backend/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs

@ -153,12 +153,12 @@ namespace Squidex.Infrastructure.Commands
if (command.ExpectedVersion > EtagVersion.Any && command.ExpectedVersion != Version) if (command.ExpectedVersion > EtagVersion.Any && command.ExpectedVersion != Version)
{ {
throw new DomainObjectVersionException(id.ToString(), GetType(), Version, command.ExpectedVersion); throw new DomainObjectVersionException(id.ToString(), Version, command.ExpectedVersion);
} }
if (isUpdate && Version < 0) if (isUpdate && Version < 0)
{ {
throw new DomainObjectNotFoundException(id.ToString(), GetType()); throw new DomainObjectNotFoundException(id.ToString());
} }
var previousSnapshot = Snapshot; var previousSnapshot = Snapshot;

6
backend/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs

@ -12,7 +12,7 @@ using Squidex.Infrastructure.Orleans;
namespace Squidex.Infrastructure.Commands namespace Squidex.Infrastructure.Commands
{ {
public abstract class DomainObjectGrain<T, TState> : GrainOfGuid where T : DomainObjectBase<TState> where TState : class, IDomainState<TState>, new() public abstract class DomainObjectGrain<T, TState> : GrainOfGuid, IDomainObjectGrain where T : DomainObjectBase<TState> where TState : class, IDomainState<TState>, new()
{ {
private readonly T domainObject; private readonly T domainObject;
@ -40,8 +40,10 @@ namespace Squidex.Infrastructure.Commands
return base.OnActivateAsync(key); return base.OnActivateAsync(key);
} }
public async Task<J<object?>> ExecuteAsync(J<IAggregateCommand> command) public async Task<J<object?>> ExecuteAsync(J<IAggregateCommand> command, GrainContext context)
{ {
context?.Use();
var result = await domainObject.ExecuteAsync(command.Value); var result = await domainObject.ExecuteAsync(command.Value);
return result; return result;

2
backend/src/Squidex.Infrastructure/Commands/DomainObjectGrainFormatter.cs

@ -20,7 +20,7 @@ namespace Squidex.Infrastructure.Commands
} }
if (string.Equals(context.InterfaceMethod.Name, nameof(IDomainObjectGrain.ExecuteAsync), StringComparison.CurrentCultureIgnoreCase) && if (string.Equals(context.InterfaceMethod.Name, nameof(IDomainObjectGrain.ExecuteAsync), StringComparison.CurrentCultureIgnoreCase) &&
context.Arguments?.Length == 1 && context.Arguments?.Length > 0 &&
context.Arguments[0] != null) context.Arguments[0] != null)
{ {
var argumentFullName = context.Arguments[0].ToString(); var argumentFullName = context.Arguments[0].ToString();

3
backend/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs

@ -7,6 +7,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Orleans; using Orleans;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Infrastructure.Commands namespace Squidex.Infrastructure.Commands
{ {
@ -42,7 +43,7 @@ namespace Squidex.Infrastructure.Commands
{ {
var grain = grainFactory.GetGrain<TGrain>(typedCommand.AggregateId); var grain = grainFactory.GetGrain<TGrain>(typedCommand.AggregateId);
var result = await grain.ExecuteAsync(typedCommand); var result = await grain.ExecuteAsync(typedCommand, GrainContext.Create());
return result.Value; return result.Value;
} }

2
backend/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs

@ -13,6 +13,6 @@ namespace Squidex.Infrastructure.Commands
{ {
public interface IDomainObjectGrain : IGrainWithGuidKey public interface IDomainObjectGrain : IGrainWithGuidKey
{ {
Task<J<object?>> ExecuteAsync(J<IAggregateCommand> command); Task<J<object?>> ExecuteAsync(J<IAggregateCommand> command, GrainContext context);
} }
} }

5
backend/src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -7,6 +7,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Infrastructure.Translations;
namespace Squidex.Infrastructure.Commands namespace Squidex.Infrastructure.Commands
{ {
@ -25,7 +26,7 @@ namespace Squidex.Infrastructure.Commands
{ {
if (options.IsReadonly) if (options.IsReadonly)
{ {
throw new DomainException("Application is in readonly mode at the moment."); throw new DomainException(T.Get("common.readonlyMode"));
} }
return next(context); return next(context);

11
backend/src/Squidex.Infrastructure/ConfigurationException.cs

@ -13,16 +13,7 @@ namespace Squidex.Infrastructure
[Serializable] [Serializable]
public class ConfigurationException : Exception public class ConfigurationException : Exception
{ {
public ConfigurationException() public ConfigurationException(string message, Exception? inner = null)
{
}
public ConfigurationException(string message)
: base(message)
{
}
public ConfigurationException(string message, Exception inner)
: base(message, inner) : base(message, inner)
{ {
} }

7
backend/src/Squidex.Infrastructure/DomainException.cs

@ -13,12 +13,7 @@ namespace Squidex.Infrastructure
[Serializable] [Serializable]
public class DomainException : Exception public class DomainException : Exception
{ {
public DomainException(string message) public DomainException(string message, Exception? inner = null)
: base(message)
{
}
public DomainException(string message, Exception? inner)
: base(message, inner) : base(message, inner)
{ {
} }

7
backend/src/Squidex.Infrastructure/DomainForbiddenException.cs

@ -13,12 +13,7 @@ namespace Squidex.Infrastructure
[Serializable] [Serializable]
public class DomainForbiddenException : DomainException public class DomainForbiddenException : DomainException
{ {
public DomainForbiddenException(string message) public DomainForbiddenException(string message, Exception? inner = null)
: base(message)
{
}
public DomainForbiddenException(string message, Exception inner)
: base(message, inner) : base(message, inner)
{ {
} }

11
backend/src/Squidex.Infrastructure/DomainObjectDeletedException.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -7,14 +7,15 @@
using System; using System;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using Squidex.Infrastructure.Translations;
namespace Squidex.Infrastructure namespace Squidex.Infrastructure
{ {
[Serializable] [Serializable]
public class DomainObjectDeletedException : DomainObjectException public class DomainObjectDeletedException : DomainObjectException
{ {
public DomainObjectDeletedException(string id, Type type) public DomainObjectDeletedException(string id, Exception? inner = null)
: base(FormatMessage(id, type), id, type) : base(FormatMessage(id), id, inner)
{ {
} }
@ -23,9 +24,9 @@ namespace Squidex.Infrastructure
{ {
} }
private static string FormatMessage(string id, Type type) private static string FormatMessage(string id)
{ {
return $"Domain object \'{id}\' (type {type}) already deleted."; return T.Get("exceptions.domainObjectDeleted", new { id });
} }
} }
} }

13
backend/src/Squidex.Infrastructure/DomainObjectException.cs

@ -13,30 +13,25 @@ namespace Squidex.Infrastructure
[Serializable] [Serializable]
public class DomainObjectException : Exception public class DomainObjectException : Exception
{ {
public string? TypeName { get; }
public string Id { get; } public string Id { get; }
protected DomainObjectException(string message, string id, Type type, Exception? inner = null) public DomainObjectException(string message, string id, Exception? inner = null)
: base(message, inner) : base(message, inner)
{ {
Id = id; Guard.NotNullOrEmpty(id, nameof(id));
TypeName = type?.Name; Id = id;
} }
protected DomainObjectException(SerializationInfo info, StreamingContext context) public DomainObjectException(SerializationInfo info, StreamingContext context)
: base(info, context) : base(info, context)
{ {
Id = info.GetString(nameof(Id))!; Id = info.GetString(nameof(Id))!;
TypeName = info.GetString(nameof(TypeName))!;
} }
public override void GetObjectData(SerializationInfo info, StreamingContext context) public override void GetObjectData(SerializationInfo info, StreamingContext context)
{ {
info.AddValue(nameof(Id), Id); info.AddValue(nameof(Id), Id);
info.AddValue(nameof(TypeName), TypeName);
base.GetObjectData(info, context); base.GetObjectData(info, context);
} }

21
backend/src/Squidex.Infrastructure/DomainObjectNotFoundException.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -7,19 +7,15 @@
using System; using System;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using Squidex.Infrastructure.Translations;
namespace Squidex.Infrastructure namespace Squidex.Infrastructure
{ {
[Serializable] [Serializable]
public class DomainObjectNotFoundException : DomainObjectException public class DomainObjectNotFoundException : DomainObjectException
{ {
public DomainObjectNotFoundException(string id, Type type) public DomainObjectNotFoundException(string id, Exception? inner = null)
: base(FormatMessage(id, type), id, type) : base(FormatMessage(id), id, inner)
{
}
public DomainObjectNotFoundException(string id, string collection, Type type)
: base(FormatMessage(id, collection, type), id, type)
{ {
} }
@ -28,14 +24,9 @@ namespace Squidex.Infrastructure
{ {
} }
private static string FormatMessage(string id, Type type) private static string FormatMessage(string id)
{
return $"Domain object \'{id}\' (type {type}) is not found.";
}
private static string FormatMessage(string id, string collection, Type type)
{ {
return $"Domain object \'{id}\' not found on {type}.{collection}"; return T.Get("exceptions.domainObjectNotFound", new { id });
} }
} }
} }

11
backend/src/Squidex.Infrastructure/DomainObjectVersionException.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -7,6 +7,7 @@
using System; using System;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using Squidex.Infrastructure.Translations;
namespace Squidex.Infrastructure namespace Squidex.Infrastructure
{ {
@ -17,8 +18,8 @@ namespace Squidex.Infrastructure
public long ExpectedVersion { get; } public long ExpectedVersion { get; }
public DomainObjectVersionException(string id, Type type, long currentVersion, long expectedVersion) public DomainObjectVersionException(string id, long currentVersion, long expectedVersion, Exception? inner = null)
: base(FormatMessage(id, type, currentVersion, expectedVersion), id, type) : base(FormatMessage(id, currentVersion, expectedVersion), id, inner)
{ {
CurrentVersion = currentVersion; CurrentVersion = currentVersion;
@ -41,9 +42,9 @@ namespace Squidex.Infrastructure
base.GetObjectData(info, context); base.GetObjectData(info, context);
} }
private static string FormatMessage(string id, Type type, long currentVersion, long expectedVersion) private static string FormatMessage(string id, long currentVersion, long expectedVersion)
{ {
return $"Requested version {expectedVersion} for object '{id}' (type {type}), but found {currentVersion}."; return T.Get("exceptions.domainObjectVersion", new { id, currentVersion, expectedVersion });
} }
} }
} }

4
backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs

@ -17,8 +17,8 @@ namespace Squidex.Infrastructure.EventSourcing
public long ExpectedVersion { get; } public long ExpectedVersion { get; }
public WrongEventVersionException(long currentVersion, long expectedVersion) public WrongEventVersionException(long currentVersion, long expectedVersion, Exception? inner = null)
: base(FormatMessage(currentVersion, expectedVersion)) : base(FormatMessage(currentVersion, expectedVersion), inner)
{ {
CurrentVersion = currentVersion; CurrentVersion = currentVersion;

9
backend/src/Squidex.Infrastructure/Guard.cs

@ -205,14 +205,5 @@ namespace Squidex.Infrastructure
throw new ArgumentException("Value contains an invalid character.", parameterName); throw new ArgumentException("Value contains an invalid character.", parameterName);
} }
} }
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Valid(IValidatable? target, [CallerArgumentExpression("target")] string parameterName, Func<string> message)
{
NotNull(target, parameterName);
target?.Validate(message);
}
} }
} }

2
backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)

5
backend/src/Squidex.Infrastructure/Log/Internal/FileLogProcessor.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -32,7 +32,8 @@ namespace Squidex.Infrastructure.Log.Internal
outputThread = new Thread(ProcessLogQueue) outputThread = new Thread(ProcessLogQueue)
{ {
IsBackground = true, Name = "Logging" IsBackground = true,
Name = "Logging"
}; };
} }

8
backend/src/Squidex.Infrastructure/Migrations/MigrationFailedException.cs

@ -15,13 +15,7 @@ namespace Squidex.Infrastructure.Migrations
{ {
public string Name { get; } public string Name { get; }
public MigrationFailedException(string name) public MigrationFailedException(string name, Exception? inner = null)
: base(FormatException(name))
{
Name = name;
}
public MigrationFailedException(string name, Exception inner)
: base(FormatException(name), inner) : base(FormatException(name), inner)
{ {
Name = name; Name = name;

40
backend/src/Squidex.Infrastructure/Orleans/CultureFilter.cs

@ -0,0 +1,40 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Globalization;
using System.Threading.Tasks;
using Orleans;
using Orleans.Runtime;
namespace Squidex.Infrastructure.Orleans
{
public sealed class CultureFilter : IIncomingGrainCallFilter, IOutgoingGrainCallFilter
{
public Task Invoke(IOutgoingGrainCallContext context)
{
RequestContext.Set("Culture", CultureInfo.CurrentCulture.Name);
RequestContext.Set("CultureUI", CultureInfo.CurrentUICulture.Name);
return context.Invoke();
}
public Task Invoke(IIncomingGrainCallContext context)
{
if (RequestContext.Get("Culture") is string culture)
{
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(culture);
}
if (RequestContext.Get("CultureUI") is string cultureUI)
{
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo(cultureUI);
}
return context.Invoke();
}
}
}

55
backend/src/Squidex.Infrastructure/Orleans/GrainContext.cs

@ -0,0 +1,55 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Globalization;
using System.Runtime.Serialization;
namespace Squidex.Infrastructure.Orleans
{
[Serializable]
public class GrainContext : ISerializable
{
public CultureInfo Culture { get; private set; }
public CultureInfo CultureUI { get; private set; }
private GrainContext()
{
}
protected GrainContext(SerializationInfo info, StreamingContext context)
{
Culture = CultureInfo.GetCultureInfo(info.GetString(nameof(Culture))!);
CultureUI = CultureInfo.GetCultureInfo(info.GetString(nameof(CultureUI))!);
}
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Culture), Culture.Name);
info.AddValue(nameof(CultureUI), CultureUI.Name);
}
public static GrainContext Create()
{
return new GrainContext
{
Culture = CultureInfo.CurrentCulture,
CultureUI = CultureInfo.CurrentUICulture
};
}
public void Use()
{
if (Culture != null)
{
CultureInfo.CurrentCulture = Culture;
CultureInfo.CurrentUICulture = CultureUI;
}
}
}
}

16
backend/src/Squidex.Infrastructure/Queries/Json/QueryParser.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt) // Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -12,6 +12,7 @@ using NJsonSchema;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Infrastructure.Queries.Json namespace Squidex.Infrastructure.Queries.Json
@ -46,12 +47,19 @@ namespace Squidex.Infrastructure.Queries.Json
if (errors.Count > 0) if (errors.Count > 0)
{ {
throw new ValidationException("Failed to parse json query", errors.Select(x => new ValidationError(x)).ToArray()); throw new ValidationException(errors.Select(BuildError).ToList());
} }
return result; return result;
} }
private static ValidationError BuildError(string message)
{
var error = T.Get("exception.invalidJsonQuery", new { message });
return new ValidationError(error);
}
private static void ConvertFilters(JsonSchema schema, ClrQuery result, List<string> errors, Query<IJsonValue> query) private static void ConvertFilters(JsonSchema schema, ClrQuery result, List<string> errors, Query<IJsonValue> query)
{ {
if (query.Filter != null) if (query.Filter != null)
@ -84,7 +92,9 @@ namespace Squidex.Infrastructure.Queries.Json
} }
catch (JsonException ex) catch (JsonException ex)
{ {
throw new ValidationException("Failed to parse json query.", new ValidationError(ex.Message)); var error = T.Get("exception.invalidJsonQueryJson", new { message = ex.Message });
throw new ValidationException(error);
} }
} }
} }

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

Loading…
Cancel
Save