Browse Source

Feature/status (#663)

Bulk endpoint improvements.
pull/664/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
58362be283
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs
  2. 6
      backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs
  3. 4
      backend/i18n/source/backend_en.json
  4. 2
      backend/i18n/source/backend_it.json
  5. 2
      backend/i18n/source/backend_nl.json
  6. 2
      backend/src/Migrations/OldEvents/AssetCreated.cs
  7. 2
      backend/src/Migrations/OldEvents/AssetUpdated.cs
  8. 17
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPlan.cs
  9. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs
  10. 6
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs
  11. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs
  12. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs
  13. 26
      backend/src/Squidex.Domain.Apps.Core.Model/Comments/Comment.cs
  14. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs
  15. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs
  16. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Freezable.cs
  17. 2
      backend/src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs
  18. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs
  19. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs
  20. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs
  21. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedManualEvent.cs
  22. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs
  23. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUsageExceededEvent.cs
  24. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs
  25. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs
  26. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs
  27. 10
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField.cs
  28. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs
  29. 12
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField.cs
  30. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs
  31. 28
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs
  32. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs
  33. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventJsonSchemaGenerator.cs
  34. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs
  35. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs
  36. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs
  37. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs
  38. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs
  39. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs
  40. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs
  41. 18
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs
  42. 1
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs
  43. 18
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs
  44. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs
  45. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs
  46. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs
  47. 6
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs
  48. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringTextValidator.cs
  49. 4
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs
  50. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderEntity.cs
  51. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs
  52. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository_SnapshotStore.cs
  53. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs
  54. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs
  55. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  56. 20
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  57. 7
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndex.cs
  58. 4
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs
  59. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs
  60. 8
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs
  61. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AppUpdateCommand.cs
  62. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs
  63. 9
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppCommandMiddleware.cs
  64. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.State.cs
  65. 60
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs
  66. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEventConsumer.cs
  67. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/NoopAppPlanBillingManager.cs
  68. 4
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs
  69. 10
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs
  70. 14
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDuplicate.cs
  71. 4
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs
  72. 8
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker_EventHandling.cs
  73. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommand.cs
  74. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetFolderCommand.cs
  75. 8
      backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/BulkUpdateAssetType.cs
  76. 13
      backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/BulkUpdateAssets.cs
  77. 38
      backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/BulkUpdateJob.cs
  78. 9
      backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs
  79. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs
  80. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/MoveAsset.cs
  81. 3
      backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/UploadAssetCommand.cs
  82. 39
      backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpsertAsset.cs
  83. 127
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs
  84. 20
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.State.cs
  85. 128
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs
  86. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObjectGrain.cs
  87. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.State.cs
  88. 36
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs
  89. 117
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderResolver.cs
  90. 210
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetsBulkUpdateCommandMiddleware.cs
  91. 7
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/GuardAsset.cs
  92. 18
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/IAssetFolderResolver.cs
  93. 19
      backend/src/Squidex.Domain.Apps.Entities/Assets/FileTagAssetMetadataSource.cs
  94. 6
      backend/src/Squidex.Domain.Apps.Entities/Assets/FileTypeAssetMetadataSource.cs
  95. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetMetadataSource.cs
  96. 12
      backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs
  97. 4
      backend/src/Squidex.Domain.Apps.Entities/Assets/RecursiveDeleter.cs
  98. 2
      backend/src/Squidex.Domain.Apps.Entities/Backup/BackupContextBase.cs
  99. 2
      backend/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs
  100. 4
      backend/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs

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

@ -68,7 +68,10 @@ namespace Squidex.Extensions.Actions.CreateContent
ruleJob.Actor = userEvent.Actor; ruleJob.Actor = userEvent.Actor;
} }
ruleJob.Publish = action.Publish; if (action.Publish)
{
ruleJob.Status = Status.Published;
}
return (Description, ruleJob); return (Description, ruleJob);
} }

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

@ -37,12 +37,12 @@ namespace Squidex.Extensions.Actions.Discourse
["title"] = await FormatAsync(action.Title, @event) ["title"] = await FormatAsync(action.Title, @event)
}; };
if (action.Topic.HasValue) if (action.Topic != null)
{ {
json.Add("topic_id", action.Topic.Value); json.Add("topic_id", action.Topic.Value);
} }
if (action.Category.HasValue) if (action.Category != null)
{ {
json.Add("category", action.Category.Value); json.Add("category", action.Category.Value);
} }
@ -58,7 +58,7 @@ namespace Squidex.Extensions.Actions.Discourse
}; };
var description = var description =
action.Topic.HasValue ? action.Topic != null ?
DescriptionCreateTopic : DescriptionCreateTopic :
DescriptionCreatePost; DescriptionCreatePost;

4
backend/i18n/source/backend_en.json

@ -137,7 +137,7 @@
"contents.singletonNotChangeable": "Singleton content cannot be updated.", "contents.singletonNotChangeable": "Singleton content cannot be updated.",
"contents.singletonNotCreatable": "Singleton content cannot be created.", "contents.singletonNotCreatable": "Singleton content cannot be created.",
"contents.singletonNotDeletable": "Singleton content cannot be deleted.", "contents.singletonNotDeletable": "Singleton content cannot be deleted.",
"contents.statusSchedulingNotInFuture": "Due time must be in the future.", "contents.statusNotValid": "Status is not defined in the workflow.",
"contents.statusTransitionNotAllowed": "Cannot change status from {oldStatus} to {newStatus}.", "contents.statusTransitionNotAllowed": "Cannot change status from {oldStatus} to {newStatus}.",
"contents.validation.aspectRatio": "Must have aspect ratio {width}:{height}.", "contents.validation.aspectRatio": "Must have aspect ratio {width}:{height}.",
"contents.validation.assetNotFound": "Id {id} not found.", "contents.validation.assetNotFound": "Id {id} not found.",
@ -173,7 +173,6 @@
"contents.validation.normalCharactersBetween": "Must have between {min} and {max} text character(s).", "contents.validation.normalCharactersBetween": "Must have between {min} and {max} text character(s).",
"contents.validation.notAllowed": "Not an allowed value.", "contents.validation.notAllowed": "Not an allowed value.",
"contents.validation.pattern": "Must follow the pattern.", "contents.validation.pattern": "Must follow the pattern.",
"contents.validation.reference": "Geolocation can only have latitude and longitude property.",
"contents.validation.referenceNotFound": "Reference '{id}' not found.", "contents.validation.referenceNotFound": "Reference '{id}' not found.",
"contents.validation.referenceToInvalidSchema": "Reference '{id}' has invalid schema.", "contents.validation.referenceToInvalidSchema": "Reference '{id}' has invalid schema.",
"contents.validation.regexTooSlow": "Regex is too slow.", "contents.validation.regexTooSlow": "Regex is too slow.",
@ -182,7 +181,6 @@
"contents.validation.unknownField": "Not a known {fieldType}.", "contents.validation.unknownField": "Not a known {fieldType}.",
"contents.validation.wordCount": "Must have exactly {count} word(s).", "contents.validation.wordCount": "Must have exactly {count} word(s).",
"contents.validation.wordsBetween": "Must have between {min} and {max} word(s).", "contents.validation.wordsBetween": "Must have between {min} and {max} word(s).",
"contents.workflowErorPublishing": "Content workflow prevents publishing.",
"contents.workflowErrorUpdate": "The workflow does not allow updates at status {status}", "contents.workflowErrorUpdate": "The workflow does not allow updates at status {status}",
"dotnet_identity_DefaultEror": "An unknown failure has occurred.", "dotnet_identity_DefaultEror": "An unknown failure has occurred.",
"dotnet_identity_DuplicateEmail": "Email is already taken.", "dotnet_identity_DuplicateEmail": "Email is already taken.",

2
backend/i18n/source/backend_it.json

@ -137,7 +137,6 @@
"contents.singletonNotChangeable": "Il contenuto singleton non può essere aggiornato", "contents.singletonNotChangeable": "Il contenuto singleton non può essere aggiornato",
"contents.singletonNotCreatable": "Il contenuto singleton non può essere creato.", "contents.singletonNotCreatable": "Il contenuto singleton non può essere creato.",
"contents.singletonNotDeletable": "Il contenuto singleton non può essere eliminato.", "contents.singletonNotDeletable": "Il contenuto singleton non può essere eliminato.",
"contents.statusSchedulingNotInFuture": "L'ora deve essere futura.",
"contents.statusTransitionNotAllowed": "Non è possibile cambiare stato da {oldStatus} a {newStatus}.", "contents.statusTransitionNotAllowed": "Non è possibile cambiare stato da {oldStatus} a {newStatus}.",
"contents.validation.aspectRatio": "Deve essere le proporzioni {width}:{height}.", "contents.validation.aspectRatio": "Deve essere le proporzioni {width}:{height}.",
"contents.validation.assetNotFound": "Id {id} non trovato.", "contents.validation.assetNotFound": "Id {id} non trovato.",
@ -182,7 +181,6 @@
"contents.validation.unknownField": "Non è noto {fieldType}.", "contents.validation.unknownField": "Non è noto {fieldType}.",
"contents.validation.wordCount": "Deve avere esattamente {count} parola(e).", "contents.validation.wordCount": "Deve avere esattamente {count} parola(e).",
"contents.validation.wordsBetween": "Deve essere tra {min} e {max} parola(e).", "contents.validation.wordsBetween": "Deve essere tra {min} e {max} parola(e).",
"contents.workflowErorPublishing": "Il workflow del contenuto impedisce la pubblicazione.",
"contents.workflowErrorUpdate": "Il workflow non consente le modifiche per lo stato {status}", "contents.workflowErrorUpdate": "Il workflow non consente le modifiche per lo stato {status}",
"dotnet_identity_DefaultEror": "Si è verificato un errore sconosciuto.", "dotnet_identity_DefaultEror": "Si è verificato un errore sconosciuto.",
"dotnet_identity_DuplicateEmail": "Email già in uso.", "dotnet_identity_DuplicateEmail": "Email già in uso.",

2
backend/i18n/source/backend_nl.json

@ -133,7 +133,6 @@
"contents.singletonNotChangeable": "Singleton-inhoud kan niet worden bijgewerkt.", "contents.singletonNotChangeable": "Singleton-inhoud kan niet worden bijgewerkt.",
"contents.singletonNotCreatable": "Singleton-inhoud kan niet worden gemaakt.", "contents.singletonNotCreatable": "Singleton-inhoud kan niet worden gemaakt.",
"contents.singletonNotDeletable": "Singleton-inhoud kan niet worden verwijderd.", "contents.singletonNotDeletable": "Singleton-inhoud kan niet worden verwijderd.",
"contents.statusSchedulingNotInFuture": "De tijd moet in de toekomst liggen.",
"contents.statusTransitionNotAllowed": "Kan status niet wijzigen van {oldStatus} in {newStatus}.", "contents.statusTransitionNotAllowed": "Kan status niet wijzigen van {oldStatus} in {newStatus}.",
"contents.validation.aspectRatio": "Moet aspectverhouding {breedte}: {hoogte} hebben.", "contents.validation.aspectRatio": "Moet aspectverhouding {breedte}: {hoogte} hebben.",
"contents.validation.assetNotFound": "Id {id} niet gevonden.", "contents.validation.assetNotFound": "Id {id} niet gevonden.",
@ -177,7 +176,6 @@
"contents.validation.unknownField": "Onbekend {fieldType}.", "contents.validation.unknownField": "Onbekend {fieldType}.",
"contents.validation.wordCount": "Moet exact {count} woord (en) bevatten.", "contents.validation.wordCount": "Moet exact {count} woord (en) bevatten.",
"contents.validation.wordsBetween": "Moet tussen {min} en {max} woord (en) bevatten.", "contents.validation.wordsBetween": "Moet tussen {min} en {max} woord (en) bevatten.",
"contents.workflowErorPublishing": "Contentworkflow verhindert publiceren.",
"contents.workflowErrorUpdate": "De werkstroom staat geen updates toe met status {status}", "contents.workflowErrorUpdate": "De werkstroom staat geen updates toe met status {status}",
"dotnet_identity_DefaultEror": "Er is een onbekende fout opgetreden.", "dotnet_identity_DefaultEror": "Er is een onbekende fout opgetreden.",
"dotnet_identity_DuplicateEmail": "E-mail is al in gebruik.", "dotnet_identity_DuplicateEmail": "E-mail is al in gebruik.",

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

@ -48,7 +48,7 @@ namespace Migrations.OldEvents
result.Metadata = new AssetMetadata(); result.Metadata = new AssetMetadata();
if (IsImage && PixelWidth.HasValue && PixelHeight.HasValue) if (IsImage && PixelWidth != null && PixelHeight != null)
{ {
result.Type = AssetType.Image; result.Type = AssetType.Image;

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

@ -37,7 +37,7 @@ namespace Migrations.OldEvents
result.Metadata = new AssetMetadata(); result.Metadata = new AssetMetadata();
if (IsImage && PixelWidth.HasValue && PixelHeight.HasValue) if (IsImage && PixelWidth != null && PixelHeight != null)
{ {
result.Type = AssetType.Image; result.Type = AssetType.Image;

17
backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPlan.cs

@ -7,22 +7,11 @@
using Squidex.Infrastructure; using Squidex.Infrastructure;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
{ {
public sealed record AppPlan public sealed record AppPlan(RefToken Owner, string PlanId)
{ {
public RefToken Owner { get; }
public string PlanId { get; }
public AppPlan(RefToken owner, string planId)
{
Guard.NotNull(owner, nameof(owner));
Guard.NotNullOrEmpty(planId, nameof(planId));
Owner = owner;
PlanId = planId;
}
} }
} }

2
backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs

@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Core.Apps
public IEnumerable<Language> Fallbacks public IEnumerable<Language> Fallbacks
{ {
get { return fallbacks; } get => fallbacks;
} }
public LanguageConfig(bool isOptional = false, params Language[]? fallbacks) public LanguageConfig(bool isOptional = false, params Language[]? fallbacks)

6
backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs

@ -26,17 +26,17 @@ namespace Squidex.Domain.Apps.Core.Apps
public string Master public string Master
{ {
get { return master; } get => master;
} }
public IEnumerable<string> AllKeys public IEnumerable<string> AllKeys
{ {
get { return languages.Keys; } get => languages.Keys;
} }
public IReadOnlyDictionary<string, LanguageConfig> Languages public IReadOnlyDictionary<string, LanguageConfig> Languages
{ {
get { return languages; } get => languages;
} }
public LanguagesConfig(Dictionary<string, LanguageConfig> languages, string master) public LanguagesConfig(Dictionary<string, LanguageConfig> languages, string master)

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

@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Apps
[IgnoreDuringEquals] [IgnoreDuringEquals]
public bool IsDefault public bool IsDefault
{ {
get { return Roles.IsDefault(this); } get => Roles.IsDefault(this);
} }
public Role(string name, PermissionSet permissions, JsonObject properties) public Role(string name, PermissionSet permissions, JsonObject properties)

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

@ -62,22 +62,22 @@ namespace Squidex.Domain.Apps.Core.Apps
public int CustomCount public int CustomCount
{ {
get { return inner.Count; } get => inner.Count;
} }
public Role this[string name] public Role this[string name]
{ {
get { return inner[name]; } get => inner[name];
} }
public IEnumerable<Role> Custom public IEnumerable<Role> Custom
{ {
get { return inner.Values; } get => inner.Values;
} }
public IEnumerable<Role> All public IEnumerable<Role> All
{ {
get { return inner.Values.Union(Defaults.Values); } get => inner.Values.Union(Defaults.Values);
} }
private Roles(ImmutableDictionary<string, Role> roles) private Roles(ImmutableDictionary<string, Role> roles)

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

@ -9,31 +9,11 @@ using System;
using NodaTime; using NodaTime;
using Squidex.Infrastructure; using Squidex.Infrastructure;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.Comments namespace Squidex.Domain.Apps.Core.Comments
{ {
public sealed class Comment public sealed record Comment(DomainId Id, Instant Time, RefToken User, string Text, Uri? Url = null)
{ {
public DomainId Id { get; }
public Instant Time { get; }
public RefToken User { get; }
public string Text { get; }
public Uri? Url { get; }
public Comment(DomainId id, Instant time, RefToken user, string text, Uri? url = null)
{
Guard.NotEmpty(id, nameof(id));
Guard.NotNull(user, nameof(user));
Guard.NotNull(text, nameof(text));
Id = id;
Text = text;
Time = time;
User = user;
Url = url;
}
} }
} }

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

@ -16,7 +16,7 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
public IEnumerable<KeyValuePair<string, ContentFieldData?>> ValidValues public IEnumerable<KeyValuePair<string, ContentFieldData?>> ValidValues
{ {
get { return this.Where(x => x.Value != null); } get => this.Where(x => x.Value != null);
} }
public ContentData() public ContentData()

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

@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Contents
public string Name public string Name
{ {
get { return name ?? "Unknown"; } get => name ?? "Unknown";
} }
public Status(string? name) public Status(string? name)

2
backend/src/Squidex.Domain.Apps.Core.Model/Freezable.cs

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Core
[IgnoreDuringEquals] [IgnoreDuringEquals]
public bool IsFrozen public bool IsFrozen
{ {
get { return isFrozen; } get => isFrozen;
} }
protected void CheckIfFrozen() protected void CheckIfFrozen()

2
backend/src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs

@ -16,7 +16,7 @@ namespace Squidex.Domain.Apps.Core
public string Master public string Master
{ {
get { return Key; } get => Key;
} }
public IEnumerable<string> AllKeys public IEnumerable<string> AllKeys

4
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs

@ -41,12 +41,12 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
public bool IsImage public bool IsImage
{ {
get { return AssetType == AssetType.Image; } get => AssetType == AssetType.Image;
} }
public override long Partition public override long Partition
{ {
get { return Id.GetHashCode(); } get => Id.GetHashCode();
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs

@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
[IgnoreDataMember] [IgnoreDataMember]
public override long Partition public override long Partition
{ {
get { return MentionedUser.Id.GetHashCode(); } get => MentionedUser.Id.GetHashCode();
} }
public bool ShouldSerializeMentionedUser() public bool ShouldSerializeMentionedUser()

2
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs

@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
public override long Partition public override long Partition
{ {
get { return Id.GetHashCode(); } get => Id.GetHashCode();
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedManualEvent.cs

@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public override long Partition public override long Partition
{ {
get { return 0; } get => 0;
} }
} }
} }

4
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs

@ -15,12 +15,12 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
public DomainId Id public DomainId Id
{ {
get { return SchemaId.Id; } get => SchemaId.Id;
} }
public override long Partition public override long Partition
{ {
get { return SchemaId.GetHashCode(); } get => SchemaId.GetHashCode();
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUsageExceededEvent.cs

@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
public override long Partition public override long Partition
{ {
get { return AppId.GetHashCode(); } get => AppId.GetHashCode();
} }
} }
} }

8
backend/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs

@ -20,22 +20,22 @@ namespace Squidex.Domain.Apps.Core.Rules
public string Name public string Name
{ {
get { return name; } get => name;
} }
public RuleTrigger Trigger public RuleTrigger Trigger
{ {
get { return trigger; } get => trigger;
} }
public RuleAction Action public RuleAction Action
{ {
get { return action; } get => action;
} }
public bool IsEnabled public bool IsEnabled
{ {
get { return isEnabled; } get => isEnabled;
} }
public Rule(RuleTrigger trigger, RuleAction action) public Rule(RuleTrigger trigger, RuleAction action)

8
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs

@ -18,22 +18,22 @@ namespace Squidex.Domain.Apps.Core.Schemas
public IReadOnlyList<NestedField> Fields public IReadOnlyList<NestedField> Fields
{ {
get { return fields.Ordered; } get => fields.Ordered;
} }
public IReadOnlyDictionary<long, NestedField> FieldsById public IReadOnlyDictionary<long, NestedField> FieldsById
{ {
get { return fields.ById; } get => fields.ById;
} }
public IReadOnlyDictionary<string, NestedField> FieldsByName public IReadOnlyDictionary<string, NestedField> FieldsByName
{ {
get { return fields.ByName; } get => fields.ByName;
} }
public FieldCollection<NestedField> FieldCollection public FieldCollection<NestedField> FieldCollection
{ {
get { return fields; } get => fields;
} }
public ArrayField(long id, string name, Partitioning partitioning, ArrayFieldProperties? properties = null, IFieldSettings? settings = null) public ArrayField(long id, string name, Partitioning partitioning, ArrayFieldProperties? properties = null, IFieldSettings? settings = null)

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

@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
public IReadOnlyList<T> Ordered public IReadOnlyList<T> Ordered
{ {
get { return fieldsOrdered; } get => fieldsOrdered;
} }
public IReadOnlyDictionary<long, T> ById public IReadOnlyDictionary<long, T> ById

10
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField.cs

@ -20,27 +20,27 @@ namespace Squidex.Domain.Apps.Core.Schemas
public long Id public long Id
{ {
get { return fieldId; } get => fieldId;
} }
public string Name public string Name
{ {
get { return fieldName; } get => fieldName;
} }
public bool IsLocked public bool IsLocked
{ {
get { return isLocked; } get => isLocked;
} }
public bool IsHidden public bool IsHidden
{ {
get { return isHidden; } get => isHidden;
} }
public bool IsDisabled public bool IsDisabled
{ {
get { return isDisabled; } get => isDisabled;
} }
public abstract FieldProperties RawProperties { get; } public abstract FieldProperties RawProperties { get; }

4
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs

@ -17,12 +17,12 @@ namespace Squidex.Domain.Apps.Core.Schemas
public T Properties public T Properties
{ {
get { return properties; } get => properties;
} }
public override FieldProperties RawProperties public override FieldProperties RawProperties
{ {
get { return properties; } get => properties;
} }
public NestedField(long id, string name, T? properties = null, IFieldSettings? settings = null) public NestedField(long id, string name, T? properties = null, IFieldSettings? settings = null)

12
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField.cs

@ -21,32 +21,32 @@ namespace Squidex.Domain.Apps.Core.Schemas
public long Id public long Id
{ {
get { return fieldId; } get => fieldId;
} }
public string Name public string Name
{ {
get { return fieldName; } get => fieldName;
} }
public bool IsLocked public bool IsLocked
{ {
get { return isLocked; } get => isLocked;
} }
public bool IsHidden public bool IsHidden
{ {
get { return isHidden; } get => isHidden;
} }
public bool IsDisabled public bool IsDisabled
{ {
get { return isDisabled; } get => isDisabled;
} }
public Partitioning Partitioning public Partitioning Partitioning
{ {
get { return partitioning; } get => partitioning;
} }
public abstract FieldProperties RawProperties { get; } public abstract FieldProperties RawProperties { get; }

4
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs

@ -17,12 +17,12 @@ namespace Squidex.Domain.Apps.Core.Schemas
public T Properties public T Properties
{ {
get { return properties; } get => properties;
} }
public override FieldProperties RawProperties public override FieldProperties RawProperties
{ {
get { return properties; } get => properties;
} }
public RootField(long id, string name, Partitioning partitioning, T? properties = null, IFieldSettings? settings = null) public RootField(long id, string name, Partitioning partitioning, T? properties = null, IFieldSettings? settings = null)

28
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs

@ -30,72 +30,72 @@ namespace Squidex.Domain.Apps.Core.Schemas
public string Name public string Name
{ {
get { return name; } get => name;
} }
public string Category public string Category
{ {
get { return category; } get => category;
} }
public bool IsPublished public bool IsPublished
{ {
get { return isPublished; } get => isPublished;
} }
public bool IsSingleton public bool IsSingleton
{ {
get { return isSingleton; } get => isSingleton;
} }
public IReadOnlyList<RootField> Fields public IReadOnlyList<RootField> Fields
{ {
get { return fields.Ordered; } get => fields.Ordered;
} }
public IReadOnlyDictionary<long, RootField> FieldsById public IReadOnlyDictionary<long, RootField> FieldsById
{ {
get { return fields.ById; } get => fields.ById;
} }
public IReadOnlyDictionary<string, RootField> FieldsByName public IReadOnlyDictionary<string, RootField> FieldsByName
{ {
get { return fields.ByName; } get => fields.ByName;
} }
public IReadOnlyDictionary<string, string> PreviewUrls public IReadOnlyDictionary<string, string> PreviewUrls
{ {
get { return previewUrls; } get => previewUrls;
} }
public FieldCollection<RootField> FieldCollection public FieldCollection<RootField> FieldCollection
{ {
get { return fields; } get => fields;
} }
public FieldRules FieldRules public FieldRules FieldRules
{ {
get { return fieldRules; } get => fieldRules;
} }
public FieldNames FieldsInLists public FieldNames FieldsInLists
{ {
get { return fieldsInLists; } get => fieldsInLists;
} }
public FieldNames FieldsInReferences public FieldNames FieldsInReferences
{ {
get { return fieldsInReferences; } get => fieldsInReferences;
} }
public SchemaScripts Scripts public SchemaScripts Scripts
{ {
get { return scripts; } get => scripts;
} }
public SchemaProperties Properties public SchemaProperties Properties
{ {
get { return properties; } get => properties;
} }
public Schema(string name, SchemaProperties? properties = null, bool isSingleton = false) public Schema(string name, SchemaProperties? properties = null, bool isSingleton = false)

4
backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs

@ -116,12 +116,12 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
{ {
var property = SchemaBuilder.NumberProperty(); var property = SchemaBuilder.NumberProperty();
if (field.Properties.MinValue.HasValue) if (field.Properties.MinValue != null)
{ {
property.Minimum = (decimal)field.Properties.MinValue.Value; property.Minimum = (decimal)field.Properties.MinValue.Value;
} }
if (field.Properties.MaxValue.HasValue) if (field.Properties.MaxValue != null)
{ {
property.Maximum = (decimal)field.Properties.MaxValue.Value; property.Maximum = (decimal)field.Properties.MaxValue.Value;
} }

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

@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public IReadOnlyCollection<string> AllTypes public IReadOnlyCollection<string> AllTypes
{ {
get { return schemas.Value.Keys; } get => schemas.Value.Keys;
} }
public EventJsonSchemaGenerator(JsonSchemaGenerator schemaGenerator) public EventJsonSchemaGenerator(JsonSchemaGenerator schemaGenerator)

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

@ -22,12 +22,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules
Type IRuleActionHandler.ActionType Type IRuleActionHandler.ActionType
{ {
get { return typeof(TAction); } get => typeof(TAction);
} }
Type IRuleActionHandler.DataType Type IRuleActionHandler.DataType
{ {
get { return typeof(TData); } get => typeof(TData);
} }
protected RuleActionHandler(RuleEventFormatter formatter) protected RuleActionHandler(RuleEventFormatter formatter)

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

@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public IReadOnlyDictionary<string, RuleActionDefinition> Actions public IReadOnlyDictionary<string, RuleActionDefinition> Actions
{ {
get { return actionTypes; } get => actionTypes;
} }
public RuleRegistry(IEnumerable<RuleActionRegistration>? registrations = null) public RuleRegistry(IEnumerable<RuleActionRegistration>? registrations = null)

4
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs

@ -25,12 +25,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public Type TriggerType public Type TriggerType
{ {
get { return typeof(TTrigger); } get => typeof(TTrigger);
} }
public virtual bool CanCreateSnapshotEvents public virtual bool CanCreateSnapshotEvents
{ {
get { return false; } get => false;
} }
public virtual async IAsyncEnumerable<EnrichedEvent> CreateSnapshotEvents(TTrigger trigger, DomainId appId) public virtual async IAsyncEnumerable<EnrichedEvent> CreateSnapshotEvents(TTrigger trigger, DomainId appId)

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

@ -48,7 +48,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
public ContentFieldObject? ContentField public ContentFieldObject? ContentField
{ {
get { return contentField; } get => contentField;
} }
public ContentDataProperty(ContentDataObject contentData, ContentFieldObject? contentField = null) public ContentDataProperty(ContentDataObject contentData, ContentFieldObject? contentField = null)

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

@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
public ContentFieldData? FieldData public ContentFieldData? FieldData
{ {
get { return fieldData; } get => fieldData;
} }
public override bool Extensible => true; public override bool Extensible => true;

4
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs

@ -47,12 +47,12 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
public IJsonValue ContentValue public IJsonValue ContentValue
{ {
get { return contentValue ??= JsonMapper.Map(value); } get => contentValue ??= JsonMapper.Map(value);
} }
public bool IsChanged public bool IsChanged
{ {
get { return isChanged; } get => isChanged;
} }
public ContentFieldProperty(ContentFieldObject contentField, IJsonValue? contentValue = null) public ContentFieldProperty(ContentFieldObject contentField, IJsonValue? contentValue = null)

2
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs

@ -29,7 +29,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public IReadOnlyCollection<ValidationError> Errors public IReadOnlyCollection<ValidationError> Errors
{ {
get { return errors; } get => errors;
} }
public ContentValidator(PartitionResolver partitionResolver, ValidationContext context, IEnumerable<IValidatorsFactory> factories, ISemanticLog log) public ContentValidator(PartitionResolver partitionResolver, ValidationContext context, IEnumerable<IValidatorsFactory> factories, ISemanticLog log)

18
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs

@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
var isRequired = IsRequired(properties, args.Context); var isRequired = IsRequired(properties, args.Context);
if (isRequired || properties.MinItems.HasValue || properties.MaxItems.HasValue) if (isRequired || properties.MinItems != null || properties.MaxItems != null)
{ {
yield return new CollectionValidator(isRequired, properties.MinItems, properties.MaxItems); yield return new CollectionValidator(isRequired, properties.MinItems, properties.MaxItems);
} }
@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
yield return new RequiredValidator(); yield return new RequiredValidator();
} }
if (properties.MinValue.HasValue || properties.MaxValue.HasValue) if (properties.MinValue != null || properties.MaxValue != null)
{ {
yield return new RangeValidator<Instant>(properties.MinValue, properties.MaxValue); yield return new RangeValidator<Instant>(properties.MinValue, properties.MaxValue);
} }
@ -133,7 +133,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
yield return new RequiredValidator(); yield return new RequiredValidator();
} }
if (properties.MinValue.HasValue || properties.MaxValue.HasValue) if (properties.MinValue != null || properties.MaxValue != null)
{ {
yield return new RangeValidator<double>(properties.MinValue, properties.MaxValue); yield return new RangeValidator<double>(properties.MinValue, properties.MaxValue);
} }
@ -160,15 +160,15 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
yield return new RequiredStringValidator(true); yield return new RequiredStringValidator(true);
} }
if (properties.MinLength.HasValue || properties.MaxLength.HasValue) if (properties.MinLength != null || properties.MaxLength != null)
{ {
yield return new StringLengthValidator(properties.MinLength, properties.MaxLength); yield return new StringLengthValidator(properties.MinLength, properties.MaxLength);
} }
if (properties.MinCharacters.HasValue || if (properties.MinCharacters != null ||
properties.MaxCharacters.HasValue || properties.MaxCharacters != null ||
properties.MinWords.HasValue || properties.MinWords != null ||
properties.MaxWords.HasValue) properties.MaxWords != null)
{ {
Func<string, string>? transform = null; Func<string, string>? transform = null;
@ -206,7 +206,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
var isRequired = IsRequired(properties, args.Context); var isRequired = IsRequired(properties, args.Context);
if (isRequired || properties.MinItems.HasValue || properties.MaxItems.HasValue) if (isRequired || properties.MinItems != null || properties.MaxItems != null)
{ {
yield return new CollectionValidator(isRequired, properties.MinItems, properties.MaxItems); yield return new CollectionValidator(isRequired, properties.MinItems, properties.MaxItems);
} }

1
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs

@ -32,6 +32,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
: base(appId, schemaId, schema) : base(appId, schemaId, schema)
{ {
JsonSerializer = jsonSerializer; JsonSerializer = jsonSerializer;
ContentId = contentId; ContentId = contentId;
} }

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

@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
this.properties = properties; this.properties = properties;
if (isRequired || properties.MinItems.HasValue || properties.MaxItems.HasValue) if (isRequired || properties.MinItems != null || properties.MaxItems != null)
{ {
collectionValidator = new CollectionValidator(isRequired, properties.MinItems, properties.MaxItems); collectionValidator = new CollectionValidator(isRequired, properties.MinItems, properties.MaxItems);
} }
@ -101,14 +101,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
private void ValidateCommon(IAssetInfo asset, ImmutableQueue<string> path, AddError addError) private void ValidateCommon(IAssetInfo asset, ImmutableQueue<string> path, AddError addError)
{ {
if (properties.MinSize.HasValue && asset.FileSize < properties.MinSize) if (properties.MinSize != null && asset.FileSize < properties.MinSize)
{ {
var min = properties.MinSize.Value.ToReadableSize(); var min = properties.MinSize.Value.ToReadableSize();
addError(path, T.Get("contents.validation.minimumSize", new { size = asset.FileSize.ToReadableSize(), min })); addError(path, T.Get("contents.validation.minimumSize", new { size = asset.FileSize.ToReadableSize(), min }));
} }
if (properties.MaxSize.HasValue && asset.FileSize > properties.MaxSize) if (properties.MaxSize != null && asset.FileSize > properties.MaxSize)
{ {
var max = properties.MaxSize.Value.ToReadableSize(); var max = properties.MaxSize.Value.ToReadableSize();
@ -136,34 +136,34 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
var pixelWidth = asset.Metadata.GetPixelWidth(); var pixelWidth = asset.Metadata.GetPixelWidth();
var pixelHeight = asset.Metadata.GetPixelHeight(); var pixelHeight = asset.Metadata.GetPixelHeight();
if (pixelWidth.HasValue && pixelHeight.HasValue) if (pixelWidth != null && pixelHeight != null)
{ {
var w = pixelWidth.Value; var w = pixelWidth.Value;
var h = pixelHeight.Value; var h = pixelHeight.Value;
var actualRatio = (double)w / h; var actualRatio = (double)w / h;
if (properties.MinWidth.HasValue && w < properties.MinWidth) if (properties.MinWidth != null && w < properties.MinWidth)
{ {
addError(path, T.Get("contents.validation.minimumWidth", new { width = w, min = properties.MinWidth })); addError(path, T.Get("contents.validation.minimumWidth", new { width = w, min = properties.MinWidth }));
} }
if (properties.MaxWidth.HasValue && w > properties.MaxWidth) if (properties.MaxWidth != null && w > properties.MaxWidth)
{ {
addError(path, T.Get("contents.validation.maximumWidth", new { width = w, max = properties.MaxWidth })); addError(path, T.Get("contents.validation.maximumWidth", new { width = w, max = properties.MaxWidth }));
} }
if (properties.MinHeight.HasValue && h < properties.MinHeight) if (properties.MinHeight != null && h < properties.MinHeight)
{ {
addError(path, T.Get("contents.validation.minimumHeight", new { height = h, min = properties.MinHeight })); addError(path, T.Get("contents.validation.minimumHeight", new { height = h, min = properties.MinHeight }));
} }
if (properties.MaxHeight.HasValue && h > properties.MaxHeight) if (properties.MaxHeight != null && h > properties.MaxHeight)
{ {
addError(path, T.Get("contents.validation.maximumHeight", new { height = h, max = properties.MaxHeight })); addError(path, T.Get("contents.validation.maximumHeight", new { height = h, max = properties.MaxHeight }));
} }
if (properties.AspectHeight.HasValue && properties.AspectWidth.HasValue) if (properties.AspectHeight != null && properties.AspectWidth != null)
{ {
var expectedRatio = (double)properties.AspectWidth.Value / properties.AspectHeight.Value; var expectedRatio = (double)properties.AspectWidth.Value / properties.AspectHeight.Value;

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

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
public CollectionValidator(bool isRequired, int? minItems = null, int? maxItems = null) public CollectionValidator(bool isRequired, int? minItems = null, int? maxItems = null)
{ {
if (minItems.HasValue && minItems > maxItems) if (minItems != null && minItems > maxItems)
{ {
throw new ArgumentException("Min length must be greater than max length.", nameof(minItems)); throw new ArgumentException("Min length must be greater than max length.", nameof(minItems));
} }
@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
return Task.CompletedTask; return Task.CompletedTask;
} }
if (minItems.HasValue && maxItems.HasValue) if (minItems != null && maxItems != null)
{ {
if (minItems == maxItems && minItems != items.Count) if (minItems == maxItems && minItems != items.Count)
{ {
@ -55,12 +55,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
} }
else else
{ {
if (minItems.HasValue && items.Count < minItems) if (minItems != null && items.Count < minItems)
{ {
addError(context.Path, T.Get("contents.validation.minItems", new { min = minItems })); addError(context.Path, T.Get("contents.validation.minItems", new { min = minItems }));
} }
if (maxItems.HasValue && items.Count > maxItems) if (maxItems != null && items.Count > maxItems)
{ {
addError(context.Path, T.Get("contents.validation.maxItems", new { max = maxItems })); addError(context.Path, T.Get("contents.validation.maxItems", new { max = maxItems }));
} }

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

@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
public RangeValidator(TValue? min, TValue? max) public RangeValidator(TValue? min, TValue? max)
{ {
if (min.HasValue && max.HasValue && min.Value.CompareTo(max.Value) > 0) if (min != null && max != null && min.Value.CompareTo(max.Value) > 0)
{ {
throw new ArgumentException("Min value must be greater than max value.", nameof(min)); throw new ArgumentException("Min value must be greater than max value.", nameof(min));
} }
@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (value is TValue typedValue) if (value is TValue typedValue)
{ {
if (min.HasValue && max.HasValue) if (min != null && max != null)
{ {
if (Equals(min, max) && Equals(min.Value, max.Value)) if (Equals(min, max) && Equals(min.Value, max.Value))
{ {
@ -44,12 +44,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
} }
else else
{ {
if (min.HasValue && typedValue.CompareTo(min.Value) < 0) if (min != null && typedValue.CompareTo(min.Value) < 0)
{ {
addError(context.Path, T.Get("contents.validation.min", new { min })); addError(context.Path, T.Get("contents.validation.min", new { min }));
} }
if (max.HasValue && typedValue.CompareTo(max.Value) > 0) if (max != null && typedValue.CompareTo(max.Value) > 0)
{ {
addError(context.Path, T.Get("contents.validation.max", new { max })); addError(context.Path, T.Get("contents.validation.max", new { max }));
} }

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

@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
this.properties = properties; this.properties = properties;
if (isRequired || properties.MinItems.HasValue || properties.MaxItems.HasValue) if (isRequired || properties.MinItems != null || properties.MaxItems != null)
{ {
collectionValidator = new CollectionValidator(isRequired, properties.MinItems, properties.MaxItems); collectionValidator = new CollectionValidator(isRequired, properties.MinItems, properties.MaxItems);
} }

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

@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (value is string stringValue && !string.IsNullOrEmpty(stringValue)) if (value is string stringValue && !string.IsNullOrEmpty(stringValue))
{ {
if (minLength.HasValue && maxLength.HasValue) if (minLength != null && maxLength != null)
{ {
if (minLength == maxLength && minLength != stringValue.Length) if (minLength == maxLength && minLength != stringValue.Length)
{ {
@ -44,12 +44,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
} }
else else
{ {
if (minLength.HasValue && stringValue.Length < minLength) if (minLength != null && stringValue.Length < minLength)
{ {
addError(context.Path, T.Get("contents.validation.minLength", new { min = minLength })); addError(context.Path, T.Get("contents.validation.minLength", new { min = minLength }));
} }
if (maxLength.HasValue && stringValue.Length > maxLength) if (maxLength != null && stringValue.Length > maxLength)
{ {
addError(context.Path, T.Get("contents.validation.maxLength", new { max = maxLength })); addError(context.Path, T.Get("contents.validation.maxLength", new { max = maxLength }));
} }

8
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringTextValidator.cs

@ -52,11 +52,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
stringValue = transform(stringValue); stringValue = transform(stringValue);
} }
if (minWords.HasValue || maxWords.HasValue) if (minWords != null || maxWords != null)
{ {
var words = stringValue.WordCount(); var words = stringValue.WordCount();
if (minWords.HasValue && maxWords.HasValue) if (minWords != null && maxWords != null)
{ {
if (minWords == maxWords && minWords != words) if (minWords == maxWords && minWords != words)
{ {
@ -81,11 +81,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
} }
} }
if (minCharacters.HasValue || maxCharacters.HasValue) if (minCharacters != null || maxCharacters != null)
{ {
var characters = stringValue.CharacterCount(); var characters = stringValue.CharacterCount();
if (minCharacters.HasValue && maxCharacters.HasValue) if (minCharacters != null && maxCharacters != null)
{ {
if (minCharacters == maxCharacters && minCharacters != characters) if (minCharacters == maxCharacters && minCharacters != characters)
{ {

4
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs

@ -104,12 +104,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
public DomainId AssetId public DomainId AssetId
{ {
get { return Id; } get => Id;
} }
public DomainId UniqueId public DomainId UniqueId
{ {
get { return DocumentId; } get => DocumentId;
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderEntity.cs

@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
public DomainId UniqueId public DomainId UniqueId
{ {
get { return DocumentId; } get => DocumentId;
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs

@ -94,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
Filter.Eq(x => x.IsDeleted, false) Filter.Eq(x => x.IsDeleted, false)
}; };
if (parentId.HasValue) if (parentId != null)
{ {
if (parentId == DomainId.Empty) if (parentId == DomainId.Empty)
{ {

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository_SnapshotStore.cs

@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
return (Map(existing), existing.Version); return (Map(existing), existing.Version);
} }
return (null!, EtagVersion.NotFound); return (null!, EtagVersion.Empty);
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs

@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
return (Map(existing), existing.Version); return (Map(existing), existing.Version);
} }
return (null!, EtagVersion.NotFound); return (null!, EtagVersion.Empty);
} }
} }

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

@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
Filter.Eq(x => x.IsDeleted, false) Filter.Eq(x => x.IsDeleted, false)
}; };
if (parentId.HasValue) if (parentId != null)
{ {
if (parentId == DomainId.Empty) if (parentId == DomainId.Empty)
{ {

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs

@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public DomainId UniqueId public DomainId UniqueId
{ {
get { return DocumentId; } get => DocumentId;
} }
} }
} }

20
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs

@ -25,6 +25,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
throw new NotSupportedException(); throw new NotSupportedException();
} }
Task<(ContentDomainObject.State Value, long Version)> ISnapshotStore<ContentDomainObject.State, DomainId>.ReadAsync(DomainId key)
{
return Task.FromResult<(ContentDomainObject.State, long Version)>((null!, EtagVersion.Empty));
}
async Task ISnapshotStore<ContentDomainObject.State, DomainId>.ClearAsync() async Task ISnapshotStore<ContentDomainObject.State, DomainId>.ClearAsync()
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
@ -43,21 +48,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
async Task<(ContentDomainObject.State Value, long Version)> ISnapshotStore<ContentDomainObject.State, DomainId>.ReadAsync(DomainId key)
{
using (Profiler.TraceMethod<MongoContentRepository>())
{
var contentEntity = await collectionAll.FindAsync(key);
if (contentEntity != null)
{
return (SimpleMapper.Map(contentEntity, new ContentDomainObject.State()), contentEntity.Version);
}
return (null!, EtagVersion.NotFound);
}
}
async Task ISnapshotStore<ContentDomainObject.State, DomainId>.WriteAsync(DomainId key, ContentDomainObject.State value, long oldVersion, long newVersion) async Task ISnapshotStore<ContentDomainObject.State, DomainId>.WriteAsync(DomainId key, ContentDomainObject.State value, long oldVersion, long newVersion)
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())

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

@ -114,10 +114,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText
} }
else else
{ {
var (bySchema, byApp) = var (bySchema, byApp) = await AsyncHelper.WhenAll(
await AsyncHelper.WhenAll( SearchBySchemaAsync(queryText, app, filter, scope, LimitHalf),
SearchBySchemaAsync(queryText, app, filter, scope, LimitHalf), SearchByAppAsync(queryText, app, scope, LimitHalf));
SearchByAppAsync(queryText, app, scope, LimitHalf));
return bySchema.Union(byApp).Distinct().ToList(); return bySchema.Union(byApp).Distinct().ToList();
} }

4
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs

@ -72,12 +72,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
DomainId IEntity.Id DomainId IEntity.Id
{ {
get { return DocumentId; } get => DocumentId;
} }
DomainId IEntity.UniqueId DomainId IEntity.UniqueId
{ {
get { return DocumentId; } get => DocumentId;
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs

@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{ {
var filter = Filter.Eq(x => x.AppId, appId); var filter = Filter.Eq(x => x.AppId, appId);
if (ruleId.HasValue && ruleId.Value != DomainId.Empty) if (ruleId != null && ruleId.Value != DomainId.Empty)
{ {
filter = Filter.And(filter, Filter.Eq(x => x.RuleId, ruleId.Value)); filter = Filter.And(filter, Filter.Eq(x => x.RuleId, ruleId.Value));
} }

8
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs

@ -24,22 +24,22 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
{ {
public int BatchSize public int BatchSize
{ {
get { return 1000; } get => 1000;
} }
public int BatchDelay public int BatchDelay
{ {
get { return 500; } get => 500;
} }
public string Name public string Name
{ {
get { return GetType().Name; } get => GetType().Name;
} }
public string EventsFilter public string EventsFilter
{ {
get { return "^(app-|schema-)"; } get => "^(app-|schema-)";
} }
public MongoSchemasHash(IMongoDatabase database, bool setup = false) public MongoSchemasHash(IMongoDatabase database, bool setup = false)

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AppUpdateCommand.cs

@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands
[IgnoreDataMember] [IgnoreDataMember]
public override DomainId AggregateId public override DomainId AggregateId
{ {
get { return AppId.Id; } get => AppId.Id;
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs

@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands
[IgnoreDataMember] [IgnoreDataMember]
public override DomainId AggregateId public override DomainId AggregateId
{ {
get { return AppId; } get => AppId;
} }
public CreateApp() public CreateApp()

9
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppCommandMiddleware.cs

@ -45,14 +45,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
await UploadAsync(uploadImage); await UploadAsync(uploadImage);
} }
await ExecuteCommandAsync(context); await base.HandleAsync(context, next);
}
if (context.PlainResult is IAppEntity app) protected override Task<object> EnrichResultAsync(CommandContext context, CommandResult result)
{
if (result.Payload is IAppEntity app)
{ {
contextProvider.Context.App = app; contextProvider.Context.App = app;
} }
await next(context); return base.EnrichResultAsync(context, result);
} }
private async Task UploadAsync(UploadAppImage uploadImage) private async Task UploadAsync(UploadAppImage uploadImage)

2
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.State.cs

@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
[IgnoreDataMember] [IgnoreDataMember]
public DomainId UniqueId public DomainId UniqueId
{ {
get { return Id; } get => Id;
} }
public override bool ApplyEvent(IEvent @event) public override bool ApplyEvent(IEvent @event)

60
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs

@ -64,12 +64,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
return command is AppUpdateCommand update && Equals(update?.AppId?.Id, Snapshot.Id); return command is AppUpdateCommand update && Equals(update?.AppId?.Id, Snapshot.Id);
} }
public override Task<object?> ExecuteAsync(IAggregateCommand command) public override Task<CommandResult> ExecuteAsync(IAggregateCommand command)
{ {
switch (command) switch (command)
{ {
case CreateApp createApp: case CreateApp create:
return CreateReturn(createApp, c => return CreateReturn(create, c =>
{ {
GuardApp.CanCreate(c); GuardApp.CanCreate(c);
@ -78,8 +78,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
return Snapshot; return Snapshot;
}); });
case UpdateApp updateApp: case UpdateApp update:
return UpdateReturn(updateApp, c => return UpdateReturn(update, c =>
{ {
GuardApp.CanUpdate(c); GuardApp.CanUpdate(c);
@ -304,8 +304,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
} }
}); });
case ArchiveApp archiveApp: case ArchiveApp archive:
return UpdateAsync(archiveApp, async c => return UpdateAsync(archive, async c =>
{ {
await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), null, null); await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), null, null);
@ -322,7 +322,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
return appPlansProvider.GetPlanForApp(Snapshot).Plan; return appPlansProvider.GetPlanForApp(Snapshot).Plan;
} }
public void Create(CreateApp command) private void Create(CreateApp command)
{ {
var appId = NamedId.Of(command.AppId, command.Name); var appId = NamedId.Of(command.AppId, command.Name);
@ -350,7 +350,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
} }
} }
public void ChangePlan(ChangePlan command) private void ChangePlan(ChangePlan command)
{ {
if (string.Equals(appPlansProvider.GetFreePlan()?.Id, command.PlanId)) if (string.Equals(appPlansProvider.GetFreePlan()?.Id, command.PlanId))
{ {
@ -362,107 +362,107 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
} }
} }
public void Update(UpdateApp command) private void Update(UpdateApp command)
{ {
Raise(command, new AppUpdated()); Raise(command, new AppUpdated());
} }
public void UpdateClient(UpdateClient command) private void UpdateClient(UpdateClient command)
{ {
Raise(command, new AppClientUpdated()); Raise(command, new AppClientUpdated());
} }
public void UploadImage(UploadAppImage command) private void UploadImage(UploadAppImage command)
{ {
Raise(command, new AppImageUploaded { Image = new AppImage(command.File.MimeType) }); Raise(command, new AppImageUploaded { Image = new AppImage(command.File.MimeType) });
} }
public void RemoveImage(RemoveAppImage command) private void RemoveImage(RemoveAppImage command)
{ {
Raise(command, new AppImageRemoved()); Raise(command, new AppImageRemoved());
} }
public void UpdateLanguage(UpdateLanguage command) private void UpdateLanguage(UpdateLanguage command)
{ {
Raise(command, new AppLanguageUpdated()); Raise(command, new AppLanguageUpdated());
} }
public void AssignContributor(AssignContributor command, bool isAdded) private void AssignContributor(AssignContributor command, bool isAdded)
{ {
Raise(command, new AppContributorAssigned { IsAdded = isAdded }); Raise(command, new AppContributorAssigned { IsAdded = isAdded });
} }
public void RemoveContributor(RemoveContributor command) private void RemoveContributor(RemoveContributor command)
{ {
Raise(command, new AppContributorRemoved()); Raise(command, new AppContributorRemoved());
} }
public void AttachClient(AttachClient command) private void AttachClient(AttachClient command)
{ {
Raise(command, new AppClientAttached()); Raise(command, new AppClientAttached());
} }
public void RevokeClient(RevokeClient command) private void RevokeClient(RevokeClient command)
{ {
Raise(command, new AppClientRevoked()); Raise(command, new AppClientRevoked());
} }
public void AddWorkflow(AddWorkflow command) private void AddWorkflow(AddWorkflow command)
{ {
Raise(command, new AppWorkflowAdded()); Raise(command, new AppWorkflowAdded());
} }
public void UpdateWorkflow(UpdateWorkflow command) private void UpdateWorkflow(UpdateWorkflow command)
{ {
Raise(command, new AppWorkflowUpdated()); Raise(command, new AppWorkflowUpdated());
} }
public void DeleteWorkflow(DeleteWorkflow command) private void DeleteWorkflow(DeleteWorkflow command)
{ {
Raise(command, new AppWorkflowDeleted()); Raise(command, new AppWorkflowDeleted());
} }
public void AddLanguage(AddLanguage command) private void AddLanguage(AddLanguage command)
{ {
Raise(command, new AppLanguageAdded()); Raise(command, new AppLanguageAdded());
} }
public void RemoveLanguage(RemoveLanguage command) private void RemoveLanguage(RemoveLanguage command)
{ {
Raise(command, new AppLanguageRemoved()); Raise(command, new AppLanguageRemoved());
} }
public void AddPattern(AddPattern command) private void AddPattern(AddPattern command)
{ {
Raise(command, new AppPatternAdded()); Raise(command, new AppPatternAdded());
} }
public void DeletePattern(DeletePattern command) private void DeletePattern(DeletePattern command)
{ {
Raise(command, new AppPatternDeleted()); Raise(command, new AppPatternDeleted());
} }
public void UpdatePattern(UpdatePattern command) private void UpdatePattern(UpdatePattern command)
{ {
Raise(command, new AppPatternUpdated()); Raise(command, new AppPatternUpdated());
} }
public void AddRole(AddRole command) private void AddRole(AddRole command)
{ {
Raise(command, new AppRoleAdded()); Raise(command, new AppRoleAdded());
} }
public void DeleteRole(DeleteRole command) private void DeleteRole(DeleteRole command)
{ {
Raise(command, new AppRoleDeleted()); Raise(command, new AppRoleDeleted());
} }
public void UpdateRole(UpdateRole command) private void UpdateRole(UpdateRole command)
{ {
Raise(command, new AppRoleUpdated()); Raise(command, new AppRoleUpdated());
} }
public void ArchiveApp(ArchiveApp command) private void ArchiveApp(ArchiveApp command)
{ {
Raise(command, new AppArchived()); Raise(command, new AppArchived());
} }

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEventConsumer.cs

@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
public string Name public string Name
{ {
get { return "NotificationEmailSender"; } get => "NotificationEmailSender";
} }
public string EventsFilter public string EventsFilter

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/NoopAppPlanBillingManager.cs

@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans
{ {
public bool HasPortal public bool HasPortal
{ {
get { return false; } get => false;
} }
public Task<IChangePlanResult> ChangePlanAsync(string userId, NamedId<DomainId> appId, string? planId, string? referer) public Task<IChangePlanResult> ChangePlanAsync(string userId, NamedId<DomainId> appId, string? planId, string? referer)

4
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs

@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
.AddField("text", .AddField("text",
new ContentFieldData() new ContentFieldData()
.AddInvariant("Just created a blog with Squidex. I love it!")), .AddInvariant("Just created a blog with Squidex. I love it!")),
Publish = true Status = Status.Published
}); });
} }
@ -83,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
.AddField("text", .AddField("text",
new ContentFieldData() new ContentFieldData()
.AddInvariant("I love Squidex and SciFi!")), .AddInvariant("I love Squidex and SciFi!")),
Publish = true Status = Status.Published
}); });
} }

10
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs

@ -73,14 +73,12 @@ namespace Squidex.Domain.Apps.Entities.Assets
@event.Payload.AssetId, @event.Payload.AssetId,
@event.Headers.EventStreamNumber()); @event.Headers.EventStreamNumber());
if (asset == null) if (asset != null)
{ {
throw new DomainObjectNotFoundException(@event.Payload.AssetId.ToString()); SimpleMapper.Map(asset, result);
}
SimpleMapper.Map(asset, result);
result.AssetType = asset.Type; result.AssetType = asset.Type;
}
switch (@event.Payload) switch (@event.Payload)
{ {

14
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs → backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDuplicate.cs

@ -5,19 +5,11 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
public sealed class AssetCreatedResult public sealed record AssetDuplicate(IEnrichedAssetEntity Asset)
{ {
public IEnrichedAssetEntity Asset { get; }
public bool IsDuplicate { get; }
public AssetCreatedResult(IEnrichedAssetEntity asset, bool isDuplicate)
{
Asset = asset;
IsDuplicate = isDuplicate;
}
} }
} }

4
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs

@ -58,12 +58,12 @@ namespace Squidex.Domain.Apps.Entities.Assets
public DomainId AssetId public DomainId AssetId
{ {
get { return Id; } get => Id;
} }
public DomainId UniqueId public DomainId UniqueId
{ {
get { return DomainId.Combine(AppId, Id); } get => DomainId.Combine(AppId, Id);
} }
} }
} }

8
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker_EventHandling.cs

@ -18,22 +18,22 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
public int BatchSize public int BatchSize
{ {
get { return 1000; } get => 1000;
} }
public int BatchDelay public int BatchDelay
{ {
get { return 1000; } get => 1000;
} }
public string Name public string Name
{ {
get { return GetType().Name; } get => GetType().Name;
} }
public string EventsFilter public string EventsFilter
{ {
get { return "^asset-"; } get => "^asset-";
} }
public Task On(Envelope<IEvent> @event) public Task On(Envelope<IEvent> @event)

2
backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommand.cs

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Commands
[IgnoreDataMember] [IgnoreDataMember]
public DomainId AggregateId public DomainId AggregateId
{ {
get { return DomainId.Combine(AppId, AssetId); } get => DomainId.Combine(AppId, AssetId);
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetFolderCommand.cs

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Commands
[IgnoreDataMember] [IgnoreDataMember]
public DomainId AggregateId public DomainId AggregateId
{ {
get { return DomainId.Combine(AppId, AssetFolderId); } get => DomainId.Combine(AppId, AssetFolderId);
} }
} }
} }

8
backend/src/Squidex.Infrastructure/EventSourcing/IEventEnricher.cs → backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/BulkUpdateAssetType.cs

@ -5,10 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public interface IEventEnricher<in T> public enum BulkUpdateAssetType
{ {
void Enrich(Envelope<IEvent> @event, T key); Annotate,
Move,
Delete
} }
} }

13
backend/src/Squidex.Infrastructure/Commands/EntitySavedResult.cs → backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/BulkUpdateAssets.cs

@ -5,15 +5,14 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
namespace Squidex.Infrastructure.Commands using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public class EntitySavedResult public sealed class BulkUpdateAssets : SquidexCommand, IAppCommand
{ {
public long Version { get; } public NamedId<DomainId> AppId { get; set; }
public EntitySavedResult(long version) public BulkUpdateJob[]? Jobs { get; set; }
{
Version = version;
}
} }
} }

38
backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/BulkUpdateJob.cs

@ -0,0 +1,38 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets.Commands
{
public sealed class BulkUpdateJob
{
public BulkUpdateAssetType Type { get; set; }
public DomainId Id { get; set; }
public DomainId ParentId { get; set; }
public string? ParentPath { get; set; }
public string? FileName { get; set; }
public string? Slug { get; set; }
public bool? IsProtected { get; set; }
public bool Permanent { get; set; }
public HashSet<string> Tags { get; set; }
public AssetMetadata? Metadata { get; set; }
public long ExpectedVersion { get; set; } = EtagVersion.Any;
}
}

9
backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs

@ -5,8 +5,8 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Assets.Commands namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public DomainId ParentId { get; set; } public DomainId ParentId { get; set; }
public HashSet<string> Tags { get; set; } = new HashSet<string>(); public string? ParentPath { get; set; }
public bool Duplicate { get; set; } public bool Duplicate { get; set; }
@ -22,5 +22,10 @@ namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
AssetId = DomainId.NewGuid(); AssetId = DomainId.NewGuid();
} }
public MoveAsset AsMove()
{
return SimpleMapper.Map(this, new MoveAsset());
}
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs

@ -10,5 +10,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Commands
public sealed class DeleteAsset : AssetCommand public sealed class DeleteAsset : AssetCommand
{ {
public bool CheckReferrers { get; set; } public bool CheckReferrers { get; set; }
public bool Permanent { get; set; }
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/MoveAsset.cs

@ -12,5 +12,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Commands
public sealed class MoveAsset : AssetCommand public sealed class MoveAsset : AssetCommand
{ {
public DomainId ParentId { get; set; } public DomainId ParentId { get; set; }
public string? ParentPath { get; set; }
} }
} }

3
backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/UploadAssetCommand.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Squidex.Assets; using Squidex.Assets;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
@ -12,6 +13,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public abstract class UploadAssetCommand : AssetCommand public abstract class UploadAssetCommand : AssetCommand
{ {
public HashSet<string> Tags { get; set; } = new HashSet<string>();
public AssetFile File { get; set; } public AssetFile File { get; set; }
public AssetMetadata Metadata { get; } = new AssetMetadata(); public AssetMetadata Metadata { get; } = new AssetMetadata();

39
backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpsertAsset.cs

@ -0,0 +1,39 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Assets.Commands
{
public sealed class UpsertAsset : UploadAssetCommand
{
public DomainId? ParentId { get; set; }
public string? ParentPath { get; set; }
public UpsertAsset()
{
AssetId = DomainId.NewGuid();
}
public CreateAsset AsCreate()
{
return SimpleMapper.Map(this, new CreateAsset());
}
public UpdateAsset AsUpdate()
{
return SimpleMapper.Map(this, new UpdateAsset());
}
public MoveAsset AsMove(DomainId parentId)
{
return SimpleMapper.Map(this, new MoveAsset { ParentId = parentId });
}
}
}

127
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs

@ -19,6 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
public sealed class AssetCommandMiddleware : GrainCommandMiddleware<AssetCommand, IAssetGrain> public sealed class AssetCommandMiddleware : GrainCommandMiddleware<AssetCommand, IAssetGrain>
{ {
private readonly IAssetFileStore assetFileStore; private readonly IAssetFileStore assetFileStore;
private readonly IAssetFolderResolver assetFolderResolver;
private readonly IAssetEnricher assetEnricher; private readonly IAssetEnricher assetEnricher;
private readonly IAssetQueryService assetQuery; private readonly IAssetQueryService assetQuery;
private readonly IContextProvider contextProvider; private readonly IContextProvider contextProvider;
@ -28,6 +29,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
IGrainFactory grainFactory, IGrainFactory grainFactory,
IAssetEnricher assetEnricher, IAssetEnricher assetEnricher,
IAssetFileStore assetFileStore, IAssetFileStore assetFileStore,
IAssetFolderResolver assetFolderResolver,
IAssetQueryService assetQuery, IAssetQueryService assetQuery,
IContextProvider contextProvider, IContextProvider contextProvider,
IEnumerable<IAssetMetadataSource> assetMetadataSources) IEnumerable<IAssetMetadataSource> assetMetadataSources)
@ -35,25 +37,27 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{ {
Guard.NotNull(assetEnricher, nameof(assetEnricher)); Guard.NotNull(assetEnricher, nameof(assetEnricher));
Guard.NotNull(assetFileStore, nameof(assetFileStore)); Guard.NotNull(assetFileStore, nameof(assetFileStore));
Guard.NotNull(assetQuery, nameof(assetQuery)); Guard.NotNull(assetFolderResolver, nameof(assetFolderResolver));
Guard.NotNull(assetMetadataSources, nameof(assetMetadataSources)); Guard.NotNull(assetMetadataSources, nameof(assetMetadataSources));
Guard.NotNull(assetQuery, nameof(assetQuery));
Guard.NotNull(contextProvider, nameof(contextProvider)); Guard.NotNull(contextProvider, nameof(contextProvider));
this.assetFileStore = assetFileStore;
this.assetEnricher = assetEnricher; this.assetEnricher = assetEnricher;
this.assetQuery = assetQuery; this.assetFileStore = assetFileStore;
this.assetFolderResolver = assetFolderResolver;
this.assetMetadataSources = assetMetadataSources; this.assetMetadataSources = assetMetadataSources;
this.assetQuery = assetQuery;
this.contextProvider = contextProvider; this.contextProvider = contextProvider;
} }
public override async Task HandleAsync(CommandContext context, NextDelegate next) public override async Task HandleAsync(CommandContext context, NextDelegate next)
{ {
var tempFile = context.ContextId.ToString();
switch (context.Command) switch (context.Command)
{ {
case CreateAsset createAsset: case CreateAsset createAsset:
{ {
var tempFile = context.ContextId.ToString();
try try
{ {
await EnrichWithHashAndUploadAsync(createAsset, tempFile); await EnrichWithHashAndUploadAsync(createAsset, tempFile);
@ -68,16 +72,25 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
if (existing != null) if (existing != null)
{ {
var result = new AssetCreatedResult(existing, true); context.Complete(new AssetDuplicate(existing));
context.Complete(result);
await next(context); await next(context);
return; return;
} }
} }
await UploadAsync(context, tempFile, createAsset, createAsset.Tags, true, next); if (!string.IsNullOrWhiteSpace(createAsset.ParentPath))
{
createAsset.ParentId =
await assetFolderResolver.ResolveOrCreateAsync(
contextProvider.Context,
context.CommandBus,
createAsset.ParentPath);
}
await EnrichWithMetadataAsync(createAsset);
await base.HandleAsync(context, next);
} }
finally finally
{ {
@ -89,63 +102,97 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
break; break;
} }
case UpdateAsset updateAsset: case MoveAsset move:
{ {
try if (!string.IsNullOrWhiteSpace(move.ParentPath))
{ {
await EnrichWithHashAndUploadAsync(updateAsset, tempFile); move.ParentId =
await assetFolderResolver.ResolveOrCreateAsync(
await UploadAsync(context, tempFile, updateAsset, null, false, next); contextProvider.Context,
context.CommandBus,
move.ParentPath);
} }
finally
{
await assetFileStore.DeleteAsync(tempFile);
updateAsset.File.Dispose(); await base.HandleAsync(context, next);
break;
}
case UpsertAsset upsert:
{
if (!string.IsNullOrWhiteSpace(upsert.ParentPath))
{
upsert.ParentId =
await assetFolderResolver.ResolveOrCreateAsync(
contextProvider.Context,
context.CommandBus,
upsert.ParentPath);
} }
await UploadAndHandleAsync(context, next, upsert);
break;
}
case UpdateAsset upload:
{
await UploadAndHandleAsync(context, next, upload);
break; break;
} }
default: default:
await HandleCoreAsync(context, false, next); await base.HandleAsync(context, next);
break; break;
} }
} }
private async Task UploadAsync(CommandContext context, string tempFile, UploadAssetCommand command, HashSet<string>? tags, bool created, NextDelegate next) private async Task UploadAndHandleAsync(CommandContext context, NextDelegate next, UploadAssetCommand upload)
{ {
await EnrichWithMetadataAsync(command, tags); var tempFile = context.ContextId.ToString();
var asset = await HandleCoreAsync(context, created, next); try
{
await EnrichWithHashAndUploadAsync(upload, tempFile);
await EnrichWithMetadataAsync(upload);
if (asset != null) await base.HandleAsync(context, next);
}
finally
{ {
await assetFileStore.CopyAsync(tempFile, command.AppId.Id, command.AssetId, asset.FileVersion); await assetFileStore.DeleteAsync(tempFile);
upload.File.Dispose();
} }
} }
private async Task<IEnrichedAssetEntity?> HandleCoreAsync(CommandContext context, bool created, NextDelegate next) protected override async Task<object> EnrichResultAsync(CommandContext context, CommandResult result)
{ {
await base.HandleAsync(context, next); var payload = await base.EnrichResultAsync(context, result);
if (context.PlainResult is IAssetEntity asset && !(context.PlainResult is IEnrichedAssetEntity)) if (payload is IAssetEntity asset)
{ {
var enriched = await assetEnricher.EnrichAsync(asset, contextProvider.Context); if (result.IsChanged && context.Command is UploadAssetCommand)
if (created)
{ {
context.Complete(new AssetCreatedResult(enriched, false)); var tempFile = context.ContextId.ToString();
try
{
await assetFileStore.CopyAsync(tempFile, asset.AppId.Id, asset.AssetId, asset.FileVersion);
}
catch (AssetAlreadyExistsException) when (context.Command is not UpsertAsset)
{
throw;
}
} }
else
if (payload is not IEnrichedAssetEntity)
{ {
context.Complete(enriched); payload = await assetEnricher.EnrichAsync(asset, contextProvider.Context);
} }
return enriched;
} }
return null; return payload;
} }
private async Task EnrichWithHashAndUploadAsync(UploadAssetCommand command, string tempFile) private async Task EnrichWithHashAndUploadAsync(UploadAssetCommand command, string tempFile)
@ -156,16 +203,18 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{ {
await assetFileStore.UploadAsync(tempFile, hashStream); await assetFileStore.UploadAsync(tempFile, hashStream);
command.FileHash = $"{hashStream.GetHashStringAndReset()}{command.File.FileName}{command.File.FileSize}".Sha256Base64(); var hash = $"{hashStream.GetHashStringAndReset()}{command.File.FileName}{command.File.FileSize}".Sha256Base64();
command.FileHash = hash;
} }
} }
} }
private async Task EnrichWithMetadataAsync(UploadAssetCommand command, HashSet<string>? tags) private async Task EnrichWithMetadataAsync(UploadAssetCommand command)
{ {
foreach (var metadataSource in assetMetadataSources) foreach (var metadataSource in assetMetadataSources)
{ {
await metadataSource.EnhanceAsync(command, tags); await metadataSource.EnhanceAsync(command);
} }
} }
} }

20
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.State.cs

@ -49,13 +49,13 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
[IgnoreDataMember] [IgnoreDataMember]
public DomainId AssetId public DomainId AssetId
{ {
get { return Id; } get => Id;
} }
[IgnoreDataMember] [IgnoreDataMember]
public DomainId UniqueId public DomainId UniqueId
{ {
get { return DomainId.Combine(AppId, Id); } get => DomainId.Combine(AppId, Id);
} }
public override bool ApplyEvent(IEvent @event) public override bool ApplyEvent(IEvent @event)
@ -68,15 +68,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
SimpleMapper.Map(e, this); SimpleMapper.Map(e, this);
FileName = e.FileName; if (string.IsNullOrWhiteSpace(Slug))
if (string.IsNullOrWhiteSpace(e.Slug))
{
Slug = e.FileName.ToAssetSlug();
}
else
{ {
Slug = e.Slug; Slug = FileName.ToAssetSlug();
} }
TotalSize += e.FileSize; TotalSize += e.FileSize;
@ -86,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
return true; return true;
} }
case AssetUpdated e: case AssetUpdated e when Is.Change(e.FileHash, FileHash):
{ {
SimpleMapper.Map(e, this); SimpleMapper.Map(e, this);
@ -122,14 +116,14 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
hasChanged = true; hasChanged = true;
} }
if (Is.OptionalChange(Tags, e.Tags)) if (Is.OptionalSetChange(Tags, e.Tags))
{ {
Tags = e.Tags; Tags = e.Tags;
hasChanged = true; hasChanged = true;
} }
if (Is.OptionalChange(Metadata, e.Metadata)) if (Is.OptionalMapChange(Metadata, e.Metadata))
{ {
Metadata = e.Metadata; Metadata = e.Metadata;

128
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs

@ -24,7 +24,7 @@ using IAssetTagService = Squidex.Domain.Apps.Core.Tags.ITagService;
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{ {
public sealed partial class AssetDomainObject : LogSnapshotDomainObject<AssetDomainObject.State> public sealed partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
{ {
private readonly IContentRepository contentRepository; private readonly IContentRepository contentRepository;
private readonly IAssetTagService assetTags; private readonly IAssetTagService assetTags;
@ -43,6 +43,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
this.assetTags = assetTags; this.assetTags = assetTags;
this.assetQuery = assetQuery; this.assetQuery = assetQuery;
this.contentRepository = contentRepository; this.contentRepository = contentRepository;
Capacity = int.MaxValue;
} }
protected override bool IsDeleted() protected override bool IsDeleted()
@ -50,6 +52,11 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
return Snapshot.IsDeleted; return Snapshot.IsDeleted;
} }
protected override bool CanRecreate()
{
return true;
}
protected override bool CanAcceptCreation(ICommand command) protected override bool CanAcceptCreation(ICommand command)
{ {
return command is AssetCommand; return command is AssetCommand;
@ -62,27 +69,45 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
Equals(assetCommand.AssetId, Snapshot.Id); Equals(assetCommand.AssetId, Snapshot.Id);
} }
public override Task<object?> ExecuteAsync(IAggregateCommand command) public override Task<CommandResult> ExecuteAsync(IAggregateCommand command)
{ {
switch (command) switch (command)
{ {
case CreateAsset createAsset: case UpsertAsset upsert:
return CreateReturnAsync(createAsset, async c => return UpsertReturnAsync(upsert, async c =>
{ {
await GuardAsset.CanCreate(c, assetQuery); if (Version > EtagVersion.Empty && !IsDeleted())
{
UpdateCore(c.AsUpdate());
}
else
{
await CreateCore(c.AsCreate());
}
if (c.Tags != null) if (Is.OptionalChange(Snapshot.ParentId, c.ParentId))
{ {
c.Tags = await NormalizeTagsAsync(c.AppId.Id, c.Tags); await MoveCore(c.AsMove(c.ParentId.Value));
} }
Create(c); return Snapshot;
});
case CreateAsset c:
return CreateReturnAsync(c, async create =>
{
await CreateCore(create);
if (Is.Change(Snapshot.ParentId, c.ParentId))
{
await MoveCore(c.AsMove());
}
return Snapshot; return Snapshot;
}); });
case AnnotateAsset annotateAsset: case AnnotateAsset c:
return UpdateReturnAsync(annotateAsset, async c => return UpdateReturnAsync(c, async c =>
{ {
GuardAsset.CanAnnotate(c); GuardAsset.CanAnnotate(c);
@ -96,48 +121,81 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
return Snapshot; return Snapshot;
}); });
case UpdateAsset updateAsset: case UpdateAsset update:
return UpdateReturn(updateAsset, c => return UpdateReturn(update, update =>
{ {
GuardAsset.CanUpdate(c); Update(update);
Update(c);
return Snapshot; return Snapshot;
}); });
case MoveAsset moveAsset: case MoveAsset move:
return UpdateReturnAsync(moveAsset, async c => return UpdateReturnAsync(move, async c =>
{ {
await GuardAsset.CanMove(c, Snapshot, assetQuery); await MoveCore(c);
Move(c);
return Snapshot; return Snapshot;
}); });
case DeleteAsset deleteAsset: case DeleteAsset delete when (delete.Permanent):
return UpdateAsync(deleteAsset, async c => return DeletePermanentAsync(delete, async c =>
{ {
await GuardAsset.CanDelete(c, Snapshot, contentRepository); await DeleteCore(c);
});
await assetTags.NormalizeTagsAsync(Snapshot.AppId.Id, TagGroups.Assets, null, Snapshot.Tags);
Delete(c); case DeleteAsset delete:
return UpdateAsync(delete, async c =>
{
await DeleteCore(c);
}); });
default: default:
throw new NotSupportedException(); throw new NotSupportedException();
} }
} }
private async Task<HashSet<string>> NormalizeTagsAsync(DomainId appId, HashSet<string> tags) private async Task CreateCore(CreateAsset create)
{
GuardAsset.CanCreate(create);
if (create.Tags != null)
{
create.Tags = await NormalizeTagsAsync(create.AppId.Id, create.Tags);
}
Create(create);
}
private async Task MoveCore(MoveAsset move)
{
await GuardAsset.CanMove(move, Snapshot, assetQuery);
Move(move);
}
private void UpdateCore(UpdateAsset update)
{
GuardAsset.CanUpdate(update);
Update(update);
}
private async Task DeleteCore(DeleteAsset delete)
{
await GuardAsset.CanDelete(delete, Snapshot, contentRepository);
await NormalizeTagsAsync(Snapshot.AppId.Id, null);
Delete(delete);
}
private async Task<HashSet<string>> NormalizeTagsAsync(DomainId appId, HashSet<string>? tags)
{ {
var normalized = await assetTags.NormalizeTagsAsync(appId, TagGroups.Assets, tags, Snapshot.Tags); var normalized = await assetTags.NormalizeTagsAsync(appId, TagGroups.Assets, tags, Snapshot.Tags);
return new HashSet<string>(normalized.Values); return new HashSet<string>(normalized.Values);
} }
public void Create(CreateAsset command) private void Create(CreateAsset command)
{ {
Raise(command, new AssetCreated Raise(command, new AssetCreated
{ {
@ -149,7 +207,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
}); });
} }
public void Update(UpdateAsset command) private void Update(UpdateAsset command)
{ {
Raise(command, new AssetUpdated Raise(command, new AssetUpdated
{ {
@ -159,28 +217,24 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
}); });
} }
public void Annotate(AnnotateAsset command) private void Annotate(AnnotateAsset command)
{ {
Raise(command, new AssetAnnotated()); Raise(command, new AssetAnnotated());
} }
public void Move(MoveAsset command) private void Move(MoveAsset command)
{ {
Raise(command, new AssetMoved()); Raise(command, new AssetMoved());
} }
public void Delete(DeleteAsset command) private void Delete(DeleteAsset command)
{ {
Raise(command, new AssetDeleted { DeletedSize = Snapshot.TotalSize }); Raise(command, new AssetDeleted { DeletedSize = Snapshot.TotalSize });
} }
private void Raise<T, TEvent>(T command, TEvent @event) where T : class where TEvent : AppEvent private void Raise<T, TEvent>(T command, TEvent @event) where T : class where TEvent : AppEvent
{ {
SimpleMapper.Map(command, @event); RaiseEvent(Envelope.Create(SimpleMapper.Map(command, @event)));
@event.AppId ??= Snapshot.AppId;
RaiseEvent(Envelope.Create(@event));
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObjectGrain.cs

@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{ {
await DomainObject.EnsureLoadedAsync(); await DomainObject.EnsureLoadedAsync();
return DomainObject.GetSnapshot(version); return await DomainObject.GetSnapshotAsync(version);
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.State.cs

@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
[IgnoreDataMember] [IgnoreDataMember]
public DomainId UniqueId public DomainId UniqueId
{ {
get { return DomainId.Combine(AppId, Id); } get => DomainId.Combine(AppId, Id);
} }
public override bool ApplyEvent(IEvent @event) public override bool ApplyEvent(IEvent @event)

36
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs

@ -50,22 +50,22 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
Equals(assetFolderCommand.AssetFolderId, Snapshot.Id); Equals(assetFolderCommand.AssetFolderId, Snapshot.Id);
} }
public override Task<object?> ExecuteAsync(IAggregateCommand command) public override Task<CommandResult> ExecuteAsync(IAggregateCommand command)
{ {
switch (command) switch (command)
{ {
case CreateAssetFolder createAssetFolder: case CreateAssetFolder c:
return CreateReturnAsync(createAssetFolder, async c => return CreateReturnAsync(c, async create =>
{ {
await GuardAssetFolder.CanCreate(c, assetQuery); await GuardAssetFolder.CanCreate(create, assetQuery);
Create(c); Create(create);
return Snapshot; return Snapshot;
}); });
case MoveAssetFolder moveAssetFolder: case MoveAssetFolder move:
return UpdateReturnAsync(moveAssetFolder, async c => return UpdateReturnAsync(move, async c =>
{ {
await GuardAssetFolder.CanMove(c, Snapshot, assetQuery); await GuardAssetFolder.CanMove(c, Snapshot, assetQuery);
@ -74,8 +74,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
return Snapshot; return Snapshot;
}); });
case RenameAssetFolder renameAssetFolder: case RenameAssetFolder rename:
return UpdateReturn(renameAssetFolder, c => return UpdateReturn(rename, c =>
{ {
GuardAssetFolder.CanRename(c); GuardAssetFolder.CanRename(c);
@ -84,8 +84,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
return Snapshot; return Snapshot;
}); });
case DeleteAssetFolder deleteAssetFolder: case DeleteAssetFolder delete:
return Update(deleteAssetFolder, c => return Update(delete, c =>
{ {
GuardAssetFolder.CanDelete(c); GuardAssetFolder.CanDelete(c);
@ -97,33 +97,29 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
} }
} }
public void Create(CreateAssetFolder command) private void Create(CreateAssetFolder command)
{ {
Raise(command, new AssetFolderCreated()); Raise(command, new AssetFolderCreated());
} }
public void Move(MoveAssetFolder command) private void Move(MoveAssetFolder command)
{ {
Raise(command, new AssetFolderMoved()); Raise(command, new AssetFolderMoved());
} }
public void Rename(RenameAssetFolder command) private void Rename(RenameAssetFolder command)
{ {
Raise(command, new AssetFolderRenamed()); Raise(command, new AssetFolderRenamed());
} }
public void Delete(DeleteAssetFolder command) private void Delete(DeleteAssetFolder command)
{ {
Raise(command, new AssetFolderDeleted()); Raise(command, new AssetFolderDeleted());
} }
private void Raise<T, TEvent>(T command, TEvent @event) where T : class where TEvent : AppEvent private void Raise<T, TEvent>(T command, TEvent @event) where T : class where TEvent : AppEvent
{ {
SimpleMapper.Map(command, @event); RaiseEvent(Envelope.Create(SimpleMapper.Map(command, @event)));
@event.AppId ??= Snapshot.AppId;
RaiseEvent(Envelope.Create(@event));
} }
} }
} }

117
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderResolver.cs

@ -0,0 +1,117 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Caching;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{
public sealed class AssetFolderResolver : IAssetFolderResolver
{
private static readonly char[] TrimChars = { '/', '\\' };
private static readonly char[] SplitChars = { ' ', '/', '\\' };
private readonly ILocalCache localCache;
private readonly IAssetQueryService assetQuery;
public AssetFolderResolver(ILocalCache localCache, IAssetQueryService assetQuery)
{
Guard.NotNull(localCache, nameof(localCache));
Guard.NotNull(assetQuery, nameof(assetQuery));
this.localCache = localCache;
this.assetQuery = assetQuery;
}
public async Task<DomainId> ResolveOrCreateAsync(Context context, ICommandBus commandBus, string path)
{
Guard.NotNull(commandBus, nameof(commandBus));
Guard.NotNull(path, nameof(path));
path = path.Trim(TrimChars);
var elements = path.Split(SplitChars, StringSplitOptions.RemoveEmptyEntries);
if (elements.Length == 0)
{
return DomainId.Empty;
}
var currentId = DomainId.Empty;
var i = elements.Length;
for (; i > 0; i--)
{
var subPath = string.Join('/', elements.Take(i));
if (localCache.TryGetValue(GetCacheKey(subPath), out var cached) && cached is DomainId id)
{
currentId = id;
break;
}
}
var creating = false;
for (; i < elements.Length; i++)
{
var name = elements[i];
var isResolved = false;
if (!creating)
{
var children = await assetQuery.QueryAssetFoldersAsync(context, currentId);
foreach (var child in children)
{
var childPath = string.Join('/', elements.Take(i).Union(Enumerable.Repeat(child.FolderName, 1)));
localCache.Add(GetCacheKey(childPath), child.Id);
}
foreach (var child in children)
{
if (child.FolderName == name)
{
currentId = child.Id;
isResolved = true;
break;
}
}
}
if (!isResolved)
{
var command = new CreateAssetFolder { ParentId = currentId, FolderName = name };
await commandBus.PublishAsync(command);
currentId = command.AssetFolderId;
creating = true;
}
var newPath = string.Join('/', elements.Take(i).Union(Enumerable.Repeat(name, 1)));
localCache.Add(GetCacheKey(newPath), currentId);
}
return currentId;
}
private static object GetCacheKey(string path)
{
return $"ASSET_FOLDERS_{path}";
}
}
}

210
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetsBulkUpdateCommandMiddleware.cs

@ -0,0 +1,210 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Reflection;
using Squidex.Shared;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
#pragma warning disable RECS0082 // Parameter has the same name as a member and hides it
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{
public sealed class AssetsBulkUpdateCommandMiddleware : ICommandMiddleware
{
private readonly IContextProvider contextProvider;
private sealed record BulkTaskCommand(BulkTask Task, DomainId Id, ICommand Command)
{
}
private sealed record BulkTask(
ICommandBus Bus,
int JobIndex,
BulkUpdateJob Job,
BulkUpdateAssets Command,
ConcurrentBag<BulkUpdateResultItem> Results
)
{
}
public AssetsBulkUpdateCommandMiddleware(IContextProvider contextProvider)
{
Guard.NotNull(contextProvider, nameof(contextProvider));
this.contextProvider = contextProvider;
}
public async Task HandleAsync(CommandContext context, NextDelegate next)
{
if (context.Command is BulkUpdateAssets bulkUpdates)
{
if (bulkUpdates.Jobs?.Length > 0)
{
var executionOptions = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2)
};
var createCommandsBlock = new TransformBlock<BulkTask, BulkTaskCommand?>(task =>
{
return CreateCommand(task);
}, executionOptions);
var executeCommandBlock = new ActionBlock<BulkTaskCommand?>(async command =>
{
if (command != null)
{
await ExecuteCommandAsync(command);
}
}, executionOptions);
createCommandsBlock.LinkTo(executeCommandBlock, new DataflowLinkOptions
{
PropagateCompletion = true
});
contextProvider.Context.Change(b => b
.WithoutAssetEnrichment()
.WithoutCleanup()
.WithUnpublished(true)
.WithoutTotal());
var results = new ConcurrentBag<BulkUpdateResultItem>();
for (var i = 0; i < bulkUpdates.Jobs.Length; i++)
{
var task = new BulkTask(
context.CommandBus,
i,
bulkUpdates.Jobs[i],
bulkUpdates,
results);
await createCommandsBlock.SendAsync(task);
}
createCommandsBlock.Complete();
await executeCommandBlock.Completion;
context.Complete(new BulkUpdateResult(results));
}
else
{
context.Complete(new BulkUpdateResult());
}
}
else
{
await next(context);
}
}
private static async Task ExecuteCommandAsync(BulkTaskCommand bulkCommand)
{
var (task, id, command) = bulkCommand;
Exception? exception = null;
try
{
await task.Bus.PublishAsync(command);
}
catch (Exception ex)
{
exception = ex;
}
task.Results.Add(new BulkUpdateResultItem
{
Id = id,
JobIndex = task.JobIndex,
Exception = exception
});
}
private BulkTaskCommand? CreateCommand(BulkTask task)
{
var id = task.Job.Id;
try
{
var command = CreateCommandCore(task);
command.AssetId = id;
return new BulkTaskCommand(task, id, command);
}
catch (Exception ex)
{
task.Results.Add(new BulkUpdateResultItem
{
Id = id,
JobIndex = task.JobIndex,
Exception = ex
});
return null;
}
}
private AssetCommand CreateCommandCore(BulkTask task)
{
var job = task.Job;
switch (job.Type)
{
case BulkUpdateAssetType.Annotate:
{
var command = new AnnotateAsset();
Enrich(task, command, Permissions.AppAssetsUpdate);
return command;
}
case BulkUpdateAssetType.Move:
{
var command = new MoveAsset();
Enrich(task, command, Permissions.AppAssetsUpdate);
return command;
}
case BulkUpdateAssetType.Delete:
{
var command = new DeleteAsset();
Enrich(task, command, Permissions.AppAssetsDelete);
return command;
}
default:
throw new NotSupportedException();
}
}
private void Enrich<T>(BulkTask task, T command, string permissionId) where T : AssetCommand
{
SimpleMapper.Map(task.Command, command);
SimpleMapper.Map(task.Job, command);
if (!contextProvider.Context.Allows(permissionId))
{
throw new DomainForbiddenException("Forbidden");
}
command.ExpectedVersion = task.Command.ExpectedVersion;
}
}
}

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

@ -22,14 +22,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
} }
public static Task CanCreate(CreateAsset command, IAssetQueryService assetQuery) public static void CanCreate(CreateAsset command)
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
return Validate.It(async e =>
{
await CheckPathAsync(command.AppId.Id, command.ParentId, assetQuery, e);
});
} }
public static Task CanMove(MoveAsset command, IAssetEntity asset, IAssetQueryService assetQuery) public static Task CanMove(MoveAsset command, IAssetEntity asset, IAssetQueryService assetQuery)

18
backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventEnricher.cs → backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/IAssetFolderResolver.cs

@ -5,16 +5,14 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
namespace Squidex.Infrastructure.EventSourcing using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{ {
public class DefaultEventEnricher<TKey> : IEventEnricher<TKey> public interface IAssetFolderResolver
{ {
public virtual void Enrich(Envelope<IEvent> @event, TKey key) Task<DomainId> ResolveOrCreateAsync(Context context, ICommandBus commandBus, string path);
{
if (key is DomainId domainId)
{
@event.SetAggregateId(domainId);
}
}
} }
} }

19
backend/src/Squidex.Domain.Apps.Entities/Assets/FileTagAssetMetadataSource.cs

@ -28,12 +28,12 @@ namespace Squidex.Domain.Apps.Entities.Assets
public string Name public string Name
{ {
get { return file.FileName; } get => file.FileName;
} }
public Stream ReadStream public Stream ReadStream
{ {
get { return file.OpenRead(); } get => file.OpenRead();
} }
public Stream WriteStream public Stream WriteStream
@ -52,14 +52,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
} }
} }
public Task EnhanceAsync(UploadAssetCommand command, HashSet<string>? tags) public Task EnhanceAsync(UploadAssetCommand command)
{
Enhance(command);
return Task.CompletedTask;
}
private static void Enhance(UploadAssetCommand command)
{ {
try try
{ {
@ -67,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
if (file.Properties == null) if (file.Properties == null)
{ {
return; return Task.CompletedTask;
} }
var type = file.Properties.MediaTypes; var type = file.Properties.MediaTypes;
@ -146,10 +139,12 @@ namespace Squidex.Domain.Apps.Entities.Assets
TryAddString("description", file.Properties.Description); TryAddString("description", file.Properties.Description);
} }
return Task.CompletedTask;
} }
catch catch
{ {
return; return Task.CompletedTask;
} }
} }

6
backend/src/Squidex.Domain.Apps.Entities/Assets/FileTypeAssetMetadataSource.cs

@ -14,15 +14,15 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
public sealed class FileTypeAssetMetadataSource : IAssetMetadataSource public sealed class FileTypeAssetMetadataSource : IAssetMetadataSource
{ {
public Task EnhanceAsync(UploadAssetCommand command, HashSet<string>? tags) public Task EnhanceAsync(UploadAssetCommand command)
{ {
if (tags != null) if (command.Tags != null)
{ {
var extension = command.File?.FileName?.FileType(); var extension = command.File?.FileName?.FileType();
if (!string.IsNullOrWhiteSpace(extension)) if (!string.IsNullOrWhiteSpace(extension))
{ {
tags.Add($"type/{extension.ToLowerInvariant()}"); command.Tags.Add($"type/{extension.ToLowerInvariant()}");
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetMetadataSource.cs

@ -13,7 +13,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
public interface IAssetMetadataSource public interface IAssetMetadataSource
{ {
Task EnhanceAsync(UploadAssetCommand command, HashSet<string>? tags); Task EnhanceAsync(UploadAssetCommand command);
IEnumerable<string> Format(IAssetEntity asset); IEnumerable<string> Format(IAssetEntity asset);
} }

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

@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
} }
} }
public async Task EnhanceAsync(UploadAssetCommand command, HashSet<string>? tags) public async Task EnhanceAsync(UploadAssetCommand command)
{ {
if (command.Type == AssetType.Unknown || command.Type == AssetType.Image) if (command.Type == AssetType.Unknown || command.Type == AssetType.Image)
{ {
@ -96,23 +96,23 @@ namespace Squidex.Domain.Apps.Entities.Assets
} }
} }
if (command.Type == AssetType.Image && tags != null) if (command.Type == AssetType.Image && command.Tags != null)
{ {
tags.Add("image"); command.Tags.Add("image");
var wh = command.Metadata.GetPixelWidth() + command.Metadata.GetPixelWidth(); var wh = command.Metadata.GetPixelWidth() + command.Metadata.GetPixelWidth();
if (wh > 2000) if (wh > 2000)
{ {
tags.Add("image/large"); command.Tags.Add("image/large");
} }
else if (wh > 1000) else if (wh > 1000)
{ {
tags.Add("image/medium"); command.Tags.Add("image/medium");
} }
else else
{ {
tags.Add("image/small"); command.Tags.Add("image/small");
} }
} }
} }

4
backend/src/Squidex.Domain.Apps.Entities/Assets/RecursiveDeleter.cs

@ -28,12 +28,12 @@ namespace Squidex.Domain.Apps.Entities.Assets
public string Name public string Name
{ {
get { return GetType().Name; } get => GetType().Name;
} }
public string EventsFilter public string EventsFilter
{ {
get { return "^assetFolder-"; } get => "^assetFolder-";
} }
public RecursiveDeleter( public RecursiveDeleter(

2
backend/src/Squidex.Domain.Apps.Entities/Backup/BackupContextBase.cs

@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
public RefToken Initiator public RefToken Initiator
{ {
get { return UserMapping.Initiator; } get => UserMapping.Initiator;
} }
protected BackupContextBase(DomainId appId, IUserMapping userMapping) protected BackupContextBase(DomainId appId, IUserMapping userMapping)

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

@ -85,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private async Task RecoverAfterRestartAsync() private async Task RecoverAfterRestartAsync()
{ {
state.Value.Jobs.RemoveAll(x => !x.Stopped.HasValue); state.Value.Jobs.RemoveAll(x => x.Stopped == null);
await state.WriteAsync(); await state.WriteAsync();
} }

4
backend/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs

@ -27,12 +27,12 @@ namespace Squidex.Domain.Apps.Entities.Backup
public int ReadEvents public int ReadEvents
{ {
get { return readEvents; } get => readEvents;
} }
public int ReadAttachments public int ReadAttachments
{ {
get { return readAttachments; } get => readAttachments;
} }
public BackupReader(IJsonSerializer serializer, Stream stream) public BackupReader(IJsonSerializer serializer, Stream stream)

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

Loading…
Cancel
Save