Browse Source

Simplify field names. (#1051)

* Simplify field names.

* Progress

* Update.

* A lot of fixes.

* Change test order.

* Fix tests

* Fix status property.

* Fix field names.

* Update dependencies.
pull/1055/head
Sebastian Stehle 2 years ago
committed by GitHub
parent
commit
3560fbaab2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 44
      .github/workflows/dev.yml
  2. 44
      .github/workflows/release.yml
  3. 2
      backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs
  4. 2
      backend/extensions/Squidex.Extensions/Actions/DeepDetect/DeepDetectActionHandler.cs
  5. 2
      backend/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs
  6. 2
      backend/extensions/Squidex.Extensions/Assets/Azure/AzureMetadataSource.cs
  7. 4
      backend/extensions/Squidex.Extensions/Samples/Middleware/DoubleLinkedContentMiddleware.cs
  8. 8
      backend/extensions/Squidex.Extensions/Text/Azure/AzureTextIndex.cs
  9. 6
      backend/extensions/Squidex.Extensions/Text/ElasticSearch/ElasticSearchTextIndex.cs
  10. 2
      backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs
  11. 6
      backend/src/Migrations/Migrations/ClearRules.cs
  12. 6
      backend/src/Migrations/Migrations/ClearSchemas.cs
  13. 8
      backend/src/Migrations/Migrations/CreateAssetSlugs.cs
  14. 5
      backend/src/Migrations/OldEvents/AppPatternAdded.cs
  15. 6
      backend/src/Migrations/OldEvents/AppPatternDeleted.cs
  16. 5
      backend/src/Migrations/OldEvents/AppPatternUpdated.cs
  17. 58
      backend/src/Migrations/OldEvents/SchemaCreated.cs
  18. 17
      backend/src/Migrations/RebuilderExtensions.cs
  19. 19
      backend/src/Squidex.Domain.Apps.Core.Model/AppEntity.cs
  20. 183
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/App.cs
  21. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs
  22. 18
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppExtensions.cs
  23. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguageConfigSurrogate.cs
  24. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesSurrogate.cs
  25. 81
      backend/src/Squidex.Domain.Apps.Core.Model/Assets/Asset.cs
  26. 40
      backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetFolder.cs
  27. 14
      backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetItem.cs
  28. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetMetadata.cs
  29. 24
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Content.cs
  30. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs
  31. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentVersion.cs
  32. 17
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowStepSurrogate.cs
  33. 21
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowTransitionSurrogate.cs
  34. 23
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/ScheduleJob.cs
  35. 56
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/WriteContent.cs
  36. 3
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/IEnrichedEntityEvent.cs
  37. 50
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleSorrgate.cs
  38. 55
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs
  39. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs
  40. 19
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs
  41. 6
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs
  42. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs
  43. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs
  44. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ComponentFieldProperties.cs
  45. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ComponentsFieldProperties.cs
  46. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs
  47. 12
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs
  48. 113
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs
  49. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs
  50. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRules.cs
  51. 202
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs
  52. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs
  53. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IField.cs
  54. 17
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldSettings.cs
  55. 56
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/FieldSurrogate.cs
  56. 23
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/FieldsSurrogate.cs
  57. 128
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaSurrogate.cs
  58. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs
  59. 57
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField.cs
  60. 15
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs
  61. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs
  62. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs
  63. 62
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField.cs
  64. 15
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs
  65. 165
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs
  66. 10
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs
  67. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs
  68. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs
  69. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldProperties.cs
  70. 58
      backend/src/Squidex.Domain.Apps.Core.Model/Teams/Team.cs
  71. 14
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/AddDefaultValues.cs
  72. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs
  73. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveAssetUrls.cs
  74. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveLanguages.cs
  75. 1
      backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs
  76. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/JobResult.cs
  77. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleContext.cs
  78. 98
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  79. 1
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetCommandScriptVars.cs
  80. 1
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs
  81. 17
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/RootContext.cs
  82. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs
  83. 10
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs
  84. 4
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs
  85. 24
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs
  86. 26
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/AssetItemClassMap.cs
  87. 180
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs
  88. 96
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderEntity.cs
  89. 13
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs
  90. 27
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository_SnapshotStore.cs
  91. 23
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  92. 26
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs
  93. 34
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  94. 254
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  95. 29
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  96. 39
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  97. 16
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Extensions.cs
  98. 4
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs
  99. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryById.cs
  100. 17
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByIds.cs

44
.github/workflows/dev.yml

@ -45,28 +45,6 @@ jobs:
run: docker-compose up -d
working-directory: tools/TestSuite
- name: Test - Install Playwright Dependencies
run: npm ci
working-directory: './tools/e2e'
- name: Test - Install Playwright Browsers
run: npx playwright install --with-deps
working-directory: './tools/e2e'
- name: Test - Run Playwright Tests
run: npx playwright test
working-directory: './tools/e2e'
env:
BASE__URL: http://localhost:8080
- name: Test - Upload Playwright Artifacts
if: always()
uses: actions/upload-artifact@v3.1.3
with:
name: playwright-report
path: tools/e2e/playwright-report/
retention-days: 30
- name: Test - RUN
uses: kohlerdominik/docker-run-action@v1.1.0
with:
@ -108,6 +86,28 @@ jobs:
options: --name test3
volumes: ${{ github.workspace }}:/src
run: dotnet test /src/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj --filter Category!=NotAutomated
- name: Test - Install Playwright Dependencies
run: npm ci
working-directory: './tools/e2e'
- name: Test - Install Playwright Browsers
run: npx playwright install --with-deps
working-directory: './tools/e2e'
- name: Test - Run Playwright Tests
run: npx playwright test
working-directory: './tools/e2e'
env:
BASE__URL: http://localhost:8080
- name: Test - Upload Playwright Artifacts
if: always()
uses: actions/upload-artifact@v3.1.3
with:
name: playwright-report
path: tools/e2e/playwright-report/
retention-days: 30
- name: Test - Dump docker logs on failure
if: failure()

44
.github/workflows/release.yml

@ -40,28 +40,6 @@ jobs:
run: docker-compose up -d
working-directory: tools/TestSuite
- name: Test - Install Playwright Dependencies
run: npm ci
working-directory: './tools/e2e'
- name: Test - Install Playwright Browsers
run: npx playwright install --with-deps
working-directory: './tools/e2e'
- name: Test - Run Playwright Tests
run: npx playwright test
working-directory: './tools/e2e'
env:
BASE__URL: http://localhost:8080
- name: Test - Upload Playwright Artifacts
if: always()
uses: actions/upload-artifact@v3.1.3
with:
name: playwright-report
path: tools/e2e/playwright-report/
retention-days: 30
- name: Test - RUN
uses: kohlerdominik/docker-run-action@v1.1.0
with:
@ -103,6 +81,28 @@ jobs:
options: --name test3
volumes: ${{ github.workspace }}:/src
run: dotnet test /src/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj --filter Category!=NotAutomated
- name: Test - Install Playwright Dependencies
run: npm ci
working-directory: './tools/e2e'
- name: Test - Install Playwright Browsers
run: npx playwright install --with-deps
working-directory: './tools/e2e'
- name: Test - Run Playwright Tests
run: npx playwright test
working-directory: './tools/e2e'
env:
BASE__URL: http://localhost:8080
- name: Test - Upload Playwright Artifacts
if: always()
uses: actions/upload-artifact@v3.1.3
with:
name: playwright-report
path: tools/e2e/playwright-report/
retention-days: 30
- name: Test - Dump docker logs on failure
if: failure()

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

@ -8,8 +8,8 @@
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Json;

2
backend/extensions/Squidex.Extensions/Actions/DeepDetect/DeepDetectActionHandler.cs

@ -131,7 +131,7 @@ internal sealed partial class DeepDetectActionHandler : RuleActionHandler<DeepDe
var command = new AnnotateAsset
{
Tags = asset.TagNames,
AssetId = asset.AssetId,
AssetId = asset.Id,
AppId = asset.AppId,
Actor = job.Actor,
FromRule = true

2
backend/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs

@ -31,13 +31,11 @@ public sealed record EmailAction : RuleAction
[Editor(RuleFieldEditor.Text)]
public int ServerPort { get; set; }
[LocalizedRequired]
[Display(Name = "Username", Description = "The username for the SMTP server.")]
[Editor(RuleFieldEditor.Text)]
[Formattable]
public string ServerUsername { get; set; }
[LocalizedRequired]
[Display(Name = "Password", Description = "The password for the SMTP server.")]
[Editor(RuleFieldEditor.Password)]
public string ServerPassword { get; set; }

2
backend/extensions/Squidex.Extensions/Assets/Azure/AzureMetadataSource.cs

@ -88,7 +88,7 @@ public sealed class AzureMetadataSource : IAssetMetadataSource
}
}
public IEnumerable<string> Format(IAssetEntity asset)
public IEnumerable<string> Format(Asset asset)
{
yield break;
}

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

@ -31,7 +31,7 @@ public sealed class DoubleLinkedContentMiddleware : ICustomCommandMiddleware
if (context.Command is UpdateContent update && context.IsCompleted && update.SchemaId.Name == "source")
{
// After a change is made, the content is put to the command context.
var content = context.Result<IContentEntity>();
var content = context.Result<Content>();
var contentPrevious =
await contentLoader.GetAsync(
@ -85,7 +85,7 @@ public sealed class DoubleLinkedContentMiddleware : ICustomCommandMiddleware
}
}
private static async Task UpdateReferencing(CommandContext context, IContentEntity reference, ContentData data,
private static async Task UpdateReferencing(CommandContext context, Content reference, ContentData data,
CancellationToken ct)
{
// Also set the expected version, otherwise it will be overriden with the version from the request.

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

@ -9,7 +9,7 @@ using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Models;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Text;
using Squidex.Hosting;
@ -70,7 +70,7 @@ public sealed class AzureTextIndex : IInitializable, ITextIndex
await searchClient.IndexDocumentsAsync(batch, cancellationToken: ct);
}
public async Task<List<DomainId>?> SearchAsync(IAppEntity app, GeoQuery query, SearchScope scope,
public async Task<List<DomainId>?> SearchAsync(App app, GeoQuery query, SearchScope scope,
CancellationToken ct = default)
{
Guard.NotNull(app);
@ -83,7 +83,7 @@ public sealed class AzureTextIndex : IInitializable, ITextIndex
return result.OrderByDescending(x => x.Score).Select(x => x.Id).Distinct().ToList();
}
public async Task<List<DomainId>?> SearchAsync(IAppEntity app, TextQuery query, SearchScope scope,
public async Task<List<DomainId>?> SearchAsync(App app, TextQuery query, SearchScope scope,
CancellationToken ct = default)
{
Guard.NotNull(app);
@ -129,7 +129,7 @@ public sealed class AzureTextIndex : IInitializable, ITextIndex
return SearchAsync(result, text, filter, take, factor, ct);
}
private Task SearchByAppAsync(List<(DomainId, double)> result, string text, IAppEntity app, SearchScope scope, int take, double factor,
private Task SearchByAppAsync(List<(DomainId, double)> result, string text, App app, SearchScope scope, int take, double factor,
CancellationToken ct = default)
{
var searchField = GetServeField(scope);

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

@ -6,7 +6,7 @@
// ==========================================================================
using System.Text.RegularExpressions;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Text;
using Squidex.Hosting;
@ -61,7 +61,7 @@ public sealed partial class ElasticSearchTextIndex : ITextIndex, IInitializable
return elasticClient.BulkAsync(args, ct);
}
public async Task<List<DomainId>?> SearchAsync(IAppEntity app, GeoQuery query, SearchScope scope,
public async Task<List<DomainId>?> SearchAsync(App app, GeoQuery query, SearchScope scope,
CancellationToken ct = default)
{
Guard.NotNull(app);
@ -123,7 +123,7 @@ public sealed partial class ElasticSearchTextIndex : ITextIndex, IInitializable
return await SearchAsync(elasticQuery, ct);
}
public async Task<List<DomainId>?> SearchAsync(IAppEntity app, TextQuery query, SearchScope scope,
public async Task<List<DomainId>?> SearchAsync(App app, TextQuery query, SearchScope scope,
CancellationToken ct = default)
{
Guard.NotNull(app);

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

@ -55,7 +55,7 @@ internal sealed class CompositeUniqueValidator : IValidator
{
var filter = ClrFilter.And(filters);
var found = await contentRepository.QueryIdsAsync(context.Root.AppId.Id, context.Root.SchemaId.Id, filter, SearchScope.All);
var found = await contentRepository.QueryIdsAsync(context.Root.App, context.Root.Schema, filter, SearchScope.All);
if (found.Any(x => x.Id != context.Root.ContentId))
{

6
backend/src/Migrations/Migrations/ClearRules.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Entities.Rules.DomainObject;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.States;
@ -13,9 +13,9 @@ namespace Migrations.Migrations;
public sealed class ClearRules : IMigration
{
private readonly IStore<RuleDomainObject.State> store;
private readonly IStore<Rule> store;
public ClearRules(IStore<RuleDomainObject.State> store)
public ClearRules(IStore<Rule> store)
{
this.store = store;
}

6
backend/src/Migrations/Migrations/ClearSchemas.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Entities.Schemas.DomainObject;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.States;
@ -13,9 +13,9 @@ namespace Migrations.Migrations;
public sealed class ClearSchemas : IMigration
{
private readonly IStore<SchemaDomainObject.State> store;
private readonly IStore<Schema> store;
public ClearSchemas(IStore<SchemaDomainObject.State> store)
public ClearSchemas(IStore<Schema> store)
{
this.store = store;
}

8
backend/src/Migrations/Migrations/CreateAssetSlugs.cs

@ -5,8 +5,8 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.DomainObject;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.States;
@ -14,9 +14,9 @@ namespace Migrations.Migrations;
public sealed class CreateAssetSlugs : IMigration
{
private readonly ISnapshotStore<AssetDomainObject.State> stateForAssets;
private readonly ISnapshotStore<Asset> stateForAssets;
public CreateAssetSlugs(ISnapshotStore<AssetDomainObject.State> stateForAssets)
public CreateAssetSlugs(ISnapshotStore<Asset> stateForAssets)
{
this.stateForAssets = stateForAssets;
}
@ -28,7 +28,7 @@ public sealed class CreateAssetSlugs : IMigration
{
state.Slug = state.FileName.ToAssetSlug();
var job = new SnapshotWriteJob<AssetDomainObject.State>(key, state, version);
var job = new SnapshotWriteJob<Asset>(key, state, version);
await stateForAssets.WriteAsync(job, ct);
}

5
backend/src/Migrations/OldEvents/AppPatternAdded.cs

@ -6,7 +6,6 @@
// ==========================================================================
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
@ -19,7 +18,7 @@ namespace Migrations.OldEvents;
[EventType(nameof(AppPatternAdded))]
[Obsolete("New Event introduced")]
public sealed class AppPatternAdded : AppEvent, IMigratedStateEvent<AppDomainObject.State>
public sealed class AppPatternAdded : AppEvent, IMigratedStateEvent<App>
{
public DomainId PatternId { get; set; }
@ -29,7 +28,7 @@ public sealed class AppPatternAdded : AppEvent, IMigratedStateEvent<AppDomainObj
public string? Message { get; set; }
public IEvent Migrate(AppDomainObject.State state)
public IEvent Migrate(App state)
{
var newSettings = state.Settings with
{

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

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
@ -17,11 +17,11 @@ namespace Migrations.OldEvents;
[EventType(nameof(AppPatternDeleted))]
[Obsolete("New Event introduced")]
public sealed class AppPatternDeleted : AppEvent, IMigratedStateEvent<AppDomainObject.State>
public sealed class AppPatternDeleted : AppEvent, IMigratedStateEvent<App>
{
public DomainId PatternId { get; set; }
public IEvent Migrate(AppDomainObject.State state)
public IEvent Migrate(App state)
{
var newEvent = new AppSettingsUpdated
{

5
backend/src/Migrations/OldEvents/AppPatternUpdated.cs

@ -6,7 +6,6 @@
// ==========================================================================
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
@ -19,7 +18,7 @@ namespace Migrations.OldEvents;
[EventType(nameof(AppPatternUpdated))]
[Obsolete("New Event introduced")]
public sealed class AppPatternUpdated : AppEvent, IMigratedStateEvent<AppDomainObject.State>
public sealed class AppPatternUpdated : AppEvent, IMigratedStateEvent<App>
{
public DomainId PatternId { get; set; }
@ -29,7 +28,7 @@ public sealed class AppPatternUpdated : AppEvent, IMigratedStateEvent<AppDomainO
public string? Message { get; set; }
public IEvent Migrate(AppDomainObject.State state)
public IEvent Migrate(App state)
{
var newSettings = new AppSettings
{

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

@ -32,55 +32,59 @@ public sealed class SchemaCreated : SchemaEvent, IMigrated<IEvent>
public IEvent Migrate()
{
var type = Singleton ?
SchemaType.Singleton :
SchemaType.Default;
var schema = new Schema(Name, Properties, type);
if (Publish)
{
schema = schema.Publish();
}
var totalFields = 0;
var fields = new List<RootField>();
if (Fields != null)
{
var totalFields = 0;
foreach (var eventField in Fields)
{
totalFields++;
var partitioning = Partitioning.FromString(eventField.Partitioning);
var field =
eventField.Properties.CreateRootField(
totalFields,
eventField.Name, partitioning,
eventField);
var field = eventField.Properties.CreateRootField(totalFields, eventField.Name,
Partitioning.FromString(eventField.Partitioning)) with
{
IsLocked = eventField.IsLocked,
IsHidden = eventField.IsHidden,
IsDisabled = eventField.IsDisabled
};
if (field is ArrayField arrayField && eventField.Nested?.Length > 0)
{
var arrayFields = new List<NestedField>();
foreach (var nestedEventField in eventField.Nested)
{
totalFields++;
var nestedField =
nestedEventField.Properties.CreateNestedField(
totalFields,
nestedEventField.Name,
nestedEventField);
var nestedField = nestedEventField.Properties.CreateNestedField(totalFields, nestedEventField.Name) with
{
IsLocked = nestedEventField.IsLocked,
IsHidden = nestedEventField.IsHidden,
IsDisabled = nestedEventField.IsDisabled
};
arrayField = arrayField.AddField(nestedField);
arrayFields.Add(nestedField);
}
field = arrayField;
field = arrayField with { FieldCollection = FieldCollection<NestedField>.Create(arrayFields.ToArray()) };
}
schema = schema.AddField(field);
fields.Add(field);
}
}
var schema = new Schema
{
Name = Name,
Type = Singleton ?
SchemaType.Singleton :
SchemaType.Default,
IsPublished = Publish,
FieldCollection = FieldCollection<RootField>.Create(fields.ToArray())
};
return SimpleMapper.Map(this, new SchemaCreatedV2 { Schema = schema });
}
}

17
backend/src/Migrations/RebuilderExtensions.cs

@ -5,6 +5,11 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Domain.Apps.Entities.Assets.DomainObject;
using Squidex.Domain.Apps.Entities.Contents.DomainObject;
@ -24,7 +29,7 @@ public static class RebuilderExtensions
{
var streamFilter = StreamFilter.Prefix("app-");
return rebuilder.RebuildAsync<AppDomainObject, AppDomainObject.State>(streamFilter, batchSize, AllowedErrorRate, ct);
return rebuilder.RebuildAsync<AppDomainObject, App>(streamFilter, batchSize, AllowedErrorRate, ct);
}
public static Task RebuildSchemasAsync(this Rebuilder rebuilder, int batchSize,
@ -32,7 +37,7 @@ public static class RebuilderExtensions
{
var streamFilter = StreamFilter.Prefix("schema-");
return rebuilder.RebuildAsync<SchemaDomainObject, SchemaDomainObject.State>(streamFilter, batchSize, AllowedErrorRate, ct);
return rebuilder.RebuildAsync<SchemaDomainObject, Schema>(streamFilter, batchSize, AllowedErrorRate, ct);
}
public static Task RebuildRulesAsync(this Rebuilder rebuilder, int batchSize,
@ -40,7 +45,7 @@ public static class RebuilderExtensions
{
var streamFilter = StreamFilter.Prefix("rule-");
return rebuilder.RebuildAsync<RuleDomainObject, RuleDomainObject.State>(streamFilter, batchSize, AllowedErrorRate, ct);
return rebuilder.RebuildAsync<RuleDomainObject, Rule>(streamFilter, batchSize, AllowedErrorRate, ct);
}
public static Task RebuildAssetsAsync(this Rebuilder rebuilder, int batchSize,
@ -48,7 +53,7 @@ public static class RebuilderExtensions
{
var streamFilter = StreamFilter.Prefix("asset-");
return rebuilder.RebuildAsync<AssetDomainObject, AssetDomainObject.State>(streamFilter, batchSize, AllowedErrorRate, ct);
return rebuilder.RebuildAsync<AssetDomainObject, Asset>(streamFilter, batchSize, AllowedErrorRate, ct);
}
public static Task RebuildAssetFoldersAsync(this Rebuilder rebuilder, int batchSize,
@ -56,7 +61,7 @@ public static class RebuilderExtensions
{
var streamFilter = StreamFilter.Prefix("assetFolder-");
return rebuilder.RebuildAsync<AssetFolderDomainObject, AssetFolderDomainObject.State>(streamFilter, batchSize, AllowedErrorRate, ct);
return rebuilder.RebuildAsync<AssetFolderDomainObject, AssetFolder>(streamFilter, batchSize, AllowedErrorRate, ct);
}
public static Task RebuildContentAsync(this Rebuilder rebuilder, int batchSize,
@ -64,6 +69,6 @@ public static class RebuilderExtensions
{
var streamFilter = StreamFilter.Prefix("content-");
return rebuilder.RebuildAsync<ContentDomainObject, ContentDomainObject.State>(streamFilter, batchSize, AllowedErrorRate, ct);
return rebuilder.RebuildAsync<ContentDomainObject, WriteContent>(streamFilter, batchSize, AllowedErrorRate, ct);
}
}

19
backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs → backend/src/Squidex.Domain.Apps.Core.Model/AppEntity.cs

@ -5,20 +5,19 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Rules;
namespace Squidex.Domain.Apps.Core;
public interface IRuleEntity :
IEntity,
IEntityWithCreatedBy,
IEntityWithLastModifiedBy,
IEntityWithVersion
public record AppEntity : Entity
{
NamedId<DomainId> AppId { get; set; }
public NamedId<DomainId> AppId { get; init; }
Rule RuleDef { get; }
public bool IsDeleted { get; init; }
bool IsDeleted { get; }
public override DomainId UniqueId
{
get => DomainId.Combine(AppId?.Id ?? default, Id);
}
}

183
backend/src/Squidex.Domain.Apps.Core.Model/Apps/App.cs

@ -0,0 +1,183 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Diagnostics.Contracts;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Core.Apps;
public record App : Entity
{
public string Name { get; init; }
public string Label { get; init; }
public string Description { get; init; }
public DomainId? TeamId { get; init; }
public Contributors Contributors { get; init; } = Contributors.Empty;
public Roles Roles { get; init; } = Roles.Empty;
public AssignedPlan? Plan { get; init; }
public AppClients Clients { get; init; } = AppClients.Empty;
public AppImage? Image { get; init; }
public AppSettings Settings { get; init; } = AppSettings.Empty;
public AssetScripts AssetScripts { get; init; } = new AssetScripts();
public LanguagesConfig Languages { get; init; } = LanguagesConfig.English;
public Workflows Workflows { get; init; } = Workflows.Empty;
public bool IsDeleted { get; init; }
[Pure]
public App Annotate(string? label = null, string? description = null)
{
var result = this;
if (label != null && !string.Equals(Label, label, StringComparison.OrdinalIgnoreCase))
{
result = result with { Label = label };
}
if (description != null && !string.Equals(Description, description, StringComparison.OrdinalIgnoreCase))
{
result = result with { Description = description };
}
return result;
}
[Pure]
public App Transfer(DomainId? teamId)
{
if (Equals(TeamId, teamId))
{
return this;
}
return this with { TeamId = teamId };
}
[Pure]
public App ChangePlan(AssignedPlan? plan)
{
if (Equals(Plan?.PlanId, plan?.PlanId))
{
return this;
}
return this with { Plan = plan };
}
[Pure]
public App UpdateAssetScripts(AssetScripts? assetScripts)
{
if (Equals(AssetScripts, assetScripts) || assetScripts == null)
{
return this;
}
return this with { AssetScripts = assetScripts };
}
[Pure]
public App UpdateImage(AppImage? image)
{
if (Equals(Image, image))
{
return this;
}
return this with { Image = image };
}
[Pure]
public App UpdateSettings(AppSettings settings)
{
if (Equals(Settings, settings) || settings == null)
{
return this;
}
return this with { Settings = settings };
}
[Pure]
public App UpdateClients<T>(T state, Func<T, AppClients, AppClients> update)
{
var newClients = update(state, Clients);
if (ReferenceEquals(Clients, newClients))
{
return this;
}
return this with { Clients = newClients };
}
[Pure]
public App UpdateContributors<T>(T state, Func<T, Contributors, Contributors> update)
{
var newContributors = update(state, Contributors);
if (ReferenceEquals(Contributors, newContributors))
{
return this;
}
return this with { Contributors = newContributors };
}
[Pure]
public App UpdateLanguages<T>(T state, Func<T, LanguagesConfig, LanguagesConfig> update)
{
var newLanguages = update(state, Languages);
if (ReferenceEquals(Languages, newLanguages))
{
return this;
}
return this with { Languages = newLanguages };
}
[Pure]
public App UpdateRoles<T>(T state, Func<T, Roles, Roles> update)
{
var newRoles = update(state, Roles);
if (ReferenceEquals(Roles, newRoles))
{
return this;
}
return this with { Roles = newRoles };
}
[Pure]
public App UpdateWorkflows<T>(T state, Func<T, Workflows, Workflows> update)
{
var newWorkflows = update(state, Workflows);
if (ReferenceEquals(Workflows, newWorkflows))
{
return this;
}
return this with { Workflows = newWorkflows };
}
}

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

@ -17,11 +17,11 @@ public sealed record AppClient(string Name, string Secret)
public string Secret { get; } = Guard.NotNullOrEmpty(Secret);
public string Role { get; init; } = "Editor";
public long ApiCallsLimit { get; init; }
public long ApiTrafficLimit { get; init; }
public bool AllowAnonymous { get; init; }
public string Role { get; init; } = "Editor";
}

18
backend/src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs → backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppExtensions.cs

@ -6,38 +6,42 @@
// ==========================================================================
using System.Diagnostics.CodeAnalysis;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps;
namespace Squidex.Domain.Apps.Core.Apps;
public static class AppExtensions
{
public static NamedId<DomainId> NamedId(this IAppEntity app)
public static NamedId<DomainId> NamedId(this App app)
{
return new NamedId<DomainId>(app.Id, app.Name);
}
public static string DisplayName(this IAppEntity app)
public static PartitionResolver PartitionResolver(this App app)
{
return app.Languages.ToResolver();
}
public static string DisplayName(this App app)
{
return app.Label.Or(app.Name);
}
public static bool TryGetContributorRole(this IAppEntity app, string id, bool isFrontend, [MaybeNullWhen(false)] out Role role)
public static bool TryGetContributorRole(this App app, string id, bool isFrontend, [MaybeNullWhen(false)] out Role role)
{
role = null;
return app.Contributors.TryGetValue(id, out var roleName) && app.TryGetRole(roleName, isFrontend, out role);
}
public static bool TryGetClientRole(this IAppEntity app, string id, bool isFrontend, [MaybeNullWhen(false)] out Role role)
public static bool TryGetClientRole(this App app, string id, bool isFrontend, [MaybeNullWhen(false)] out Role role)
{
role = null;
return app.Clients.TryGetValue(id, out var client) && app.TryGetRole(client.Role, isFrontend, out role);
}
public static bool TryGetRole(this IAppEntity app, string roleName, bool isFrontend, [MaybeNullWhen(false)] out Role role)
public static bool TryGetRole(this App app, string roleName, bool isFrontend, [MaybeNullWhen(false)] out Role role)
{
return app.Roles.TryGet(app.Name, roleName, isFrontend, out role);
}

2
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguageConfigSurrogate.cs

@ -25,7 +25,7 @@ public sealed class LanguageConfigSurrogate : ISurrogate<LanguageConfig>
public LanguageConfig ToSource()
{
if (!IsOptional && (Fallback == null || Fallback.Length == 0))
if (!IsOptional && Fallback is not { Length: > 0 })
{
return LanguageConfig.Default;
}

1
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesSurrogate.cs

@ -47,6 +47,7 @@ public sealed class RolesSurrogate : Dictionary<string, JsonValue>, ISurrogate<R
var properties = JsonValue.Object();
var permissions = PermissionSet.Empty;
// Can either be an array of permissions or an object with properties and permissions.
if (value.Value is JsonArray a)
{
if (a.Count > 0)

81
backend/src/Squidex.Domain.Apps.Core.Model/Assets/Asset.cs

@ -0,0 +1,81 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Diagnostics.Contracts;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Assets;
public record Asset : AssetItem
{
public string FileName { get; set; }
public string FileHash { get; set; }
public string MimeType { get; set; }
public string Slug { get; set; }
public long FileSize { get; set; }
public long FileVersion { get; set; }
public long TotalSize { get; set; }
public bool IsProtected { get; set; }
public HashSet<string> Tags { get; set; } = [];
public AssetMetadata Metadata { get; set; } = [];
public AssetType Type { get; set; }
[Pure]
public Asset Move(DomainId parentId)
{
if (Equals(ParentId, parentId))
{
return this;
}
return this with { ParentId = parentId };
}
[Pure]
public Asset Annotate(string? fileName = null, string? slug = null, bool? isProtected = null,
HashSet<string>? tags = null, AssetMetadata? metadata = null)
{
var result = this;
if (fileName != null && !string.Equals(FileName, fileName, StringComparison.OrdinalIgnoreCase))
{
result = this with { FileName = fileName };
}
if (slug != null && !string.Equals(Slug, slug, StringComparison.OrdinalIgnoreCase))
{
result = this with { Slug = slug };
}
if (isProtected != null && IsProtected != isProtected.Value)
{
result = this with { IsProtected = isProtected.Value };
}
if (tags != null && !Tags.SetEquals(tags))
{
result = this with { Tags = tags };
}
if (metadata != null && !Metadata.EqualsDictionary(metadata))
{
result = this with { Metadata = metadata };
}
return result;
}
}

40
backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetFolder.cs

@ -0,0 +1,40 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Diagnostics.Contracts;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Assets;
public record AssetFolder : AssetItem
{
public string FolderName { get; init; }
[Pure]
public AssetFolder Move(DomainId parentId)
{
if (Equals(ParentId, parentId))
{
return this;
}
return this with { ParentId = parentId };
}
[Pure]
public AssetFolder Rename(string folderName)
{
Guard.NotNull(folderName);
if (string.Equals(FolderName, folderName, StringComparison.Ordinal))
{
return this;
}
return this with { FolderName = folderName };
}
}

14
backend/src/Squidex.Domain.Apps.Entities/IEntity.cs → backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetItem.cs

@ -5,16 +5,16 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using NodaTime;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities;
namespace Squidex.Domain.Apps.Core.Assets;
public interface IEntity : IWithId<DomainId>
public record AssetItem : AppEntity
{
Instant Created { get; }
public DomainId ParentId { get; init; }
Instant LastModified { get; }
DomainId UniqueId { get; }
public override DomainId UniqueId
{
get => DomainId.Combine(AppId.Id, Id);
}
}

8
backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetMetadata.cs

@ -37,28 +37,28 @@ public sealed class AssetMetadata : Dictionary<string, JsonValue>
public AssetMetadata SetPixelWidth(int value)
{
this[PixelWidth] = (double)value;
this[PixelWidth] = value;
return this;
}
public AssetMetadata SetPixelHeight(int value)
{
this[PixelHeight] = (double)value;
this[PixelHeight] = value;
return this;
}
public AssetMetadata SetVideoWidth(int value)
{
this[VideoWidth] = (double)value;
this[VideoWidth] = value;
return this;
}
public AssetMetadata SetVideoHeight(int value)
{
this[VideoHeight] = (double)value;
this[VideoHeight] = value;
return this;
}

24
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs → backend/src/Squidex.Domain.Apps.Core.Model/Contents/Content.cs

@ -5,26 +5,24 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.ValidateContent;
namespace Squidex.Domain.Apps.Core.Contents;
public interface IAssetInfo
public record Content : AppEntity
{
DomainId AssetId { get; }
public NamedId<DomainId> SchemaId { get; init; }
long FileSize { get; }
public Status? NewStatus { get; init; }
string FileName { get; }
public Status Status { get; init; }
string FileHash { get; }
public ContentData Data { get; set; }
string MimeType { get; }
public ScheduleJob? ScheduleJob { get; init; }
string Slug { get; }
AssetMetadata Metadata { get; }
AssetType Type { get; }
public bool IsPublished
{
get => Status == Status.Published;
}
}

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

@ -37,7 +37,7 @@ public sealed class ContentData : Dictionary<string, ContentFieldData?>, IEquata
public ContentData UseSameFields(ContentData? other)
{
if (other == null || other.Count == 0)
if (other is not { Count: > 0 })
{
return this;
}

8
backend/src/Squidex.Web/Pipeline/AppFeature.cs → backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentVersion.cs

@ -5,10 +5,10 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Entities.Apps;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Web.Pipeline;
namespace Squidex.Domain.Apps.Core.Contents;
public sealed record AppFeature(IAppEntity App) : IAppFeature;
public sealed record ContentVersion(Status Status, ContentData Data)
{
}

17
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowStepSurrogate.cs

@ -14,18 +14,26 @@ namespace Squidex.Domain.Apps.Core.Contents.Json;
public sealed class WorkflowStepSurrogate : ISurrogate<WorkflowStep>
{
public Dictionary<Status, WorkflowTransitionSurrogate> Transitions { get; set; }
[Obsolete("Old serialization format.")]
private bool noUpdateFlag;
[JsonPropertyName("noUpdateRules")]
public NoUpdate? NoUpdate { get; set; }
[JsonPropertyName("noUpdate")]
public bool NoUpdateFlag { get; set; }
[Obsolete("Old serialization format.")]
public bool NoUpdateFlag
{
// Because this property is old we old want to read it and never to write it.
set => noUpdateFlag = value;
}
public bool Validate { get; set; }
public string? Color { get; set; }
public Dictionary<Status, WorkflowTransitionSurrogate> Transitions { get; set; }
public void FromSource(WorkflowStep source)
{
SimpleMapper.Map(source, this);
@ -44,10 +52,13 @@ public sealed class WorkflowStepSurrogate : ISurrogate<WorkflowStep>
{
var noUpdate = NoUpdate;
if (NoUpdateFlag)
#pragma warning disable CS0618 // Type or member is obsolete
// The flag has been replaced with an object.
if (noUpdateFlag)
{
noUpdate = NoUpdate.Always;
}
#pragma warning restore CS0618 // Type or member is obsolete
var transitions =
Transitions?.ToReadonlyDictionary(

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

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Contents.Json;
@ -13,10 +14,19 @@ public sealed class WorkflowTransitionSurrogate : ISurrogate<WorkflowTransition>
{
public string? Expression { get; set; }
public string? Role { get; set; }
public string[]? Roles { get; set; }
public string? Role
{
set
{
if (!string.IsNullOrEmpty(value))
{
Roles = [value];
}
}
}
public void FromSource(WorkflowTransition source)
{
Roles = source.Roles?.ToArray();
@ -28,11 +38,6 @@ public sealed class WorkflowTransitionSurrogate : ISurrogate<WorkflowTransition>
{
var roles = Roles;
if (!string.IsNullOrEmpty(Role))
{
roles = [Role];
}
return WorkflowTransition.When(Expression, roles);
}
}
}

23
backend/src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs → backend/src/Squidex.Domain.Apps.Core.Model/Contents/ScheduleJob.cs

@ -6,29 +6,14 @@
// ==========================================================================
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
public sealed class ScheduleJob
{
public DomainId Id { get; }
public Instant DueTime { get; }
public Status Status { get; }
public RefToken ScheduledBy { get; }
public ScheduleJob(DomainId id, Status status, RefToken scheduledBy, Instant dueTime)
{
Id = id;
ScheduledBy = scheduledBy;
Status = status;
DueTime = dueTime;
}
namespace Squidex.Domain.Apps.Core.Contents;
public sealed record ScheduleJob(DomainId Id, Status Status, RefToken ScheduledBy, Instant DueTime)
{
public static ScheduleJob Build(Status status, RefToken scheduledBy, Instant dueTime)
{
return new ScheduleJob(DomainId.NewGuid(), status, scheduledBy, dueTime);

56
backend/src/Squidex.Domain.Apps.Core.Model/Contents/WriteContent.cs

@ -0,0 +1,56 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Contents;
public record WriteContent : AppEntity
{
public NamedId<DomainId> SchemaId { get; init; }
public ContentVersion? NewVersion { get; init; }
public ContentVersion CurrentVersion { get; init; }
public ScheduleJob? ScheduleJob { get; init; }
public ContentData EditingData
{
get => NewVersion?.Data ?? CurrentVersion.Data;
}
public Status EditingStatus
{
get => NewVersion?.Status ?? CurrentVersion.Status;
}
public bool IsPublished
{
get => (NewVersion?.Status ?? CurrentVersion?.Status ?? default) == Status.Published;
}
public Content ToContent()
{
return new Content
{
Id = Id,
AppId = AppId,
Created = Created,
CreatedBy = CreatedBy,
Data = NewVersion?.Data ?? CurrentVersion.Data,
IsDeleted = IsDeleted,
LastModified = LastModified,
LastModifiedBy = LastModifiedBy,
NewStatus = NewVersion?.Status,
ScheduleJob = ScheduleJob,
SchemaId = SchemaId,
Status = CurrentVersion.Status,
Version = Version
};
}
}

3
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/IEnrichedEntityEvent.cs

@ -9,6 +9,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
public interface IEnrichedEntityEvent : IWithId<DomainId>
public interface IEnrichedEntityEvent
{
public DomainId Id { get; }
}

50
backend/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleSorrgate.cs

@ -5,21 +5,25 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Text.Json.Serialization;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Rules.Json;
public sealed class RuleSorrgate : ISurrogate<Rule>
public sealed record RuleSorrgate : Rule, ISurrogate<Rule>
{
public RuleTrigger Trigger { get; set; }
[Obsolete("Old serialization format.")]
private Rule? ruleDef;
public RuleAction Action { get; set; }
public bool IsEnabled { get; set; }
public string Name { get; set; }
[JsonPropertyName("ruleDef")]
[Obsolete("Old serialization format.")]
public Rule? RuleDef
{
// Because this property is old we old want to read it and never to write it.
set => ruleDef = value;
}
public void FromSource(Rule source)
{
@ -28,25 +32,31 @@ public sealed class RuleSorrgate : ISurrogate<Rule>
public Rule ToSource()
{
var trigger = Trigger;
if (trigger is IMigrated<RuleTrigger> migrated)
{
trigger = migrated.Migrate();
}
var rule = new Rule(trigger, Action);
var result = this;
if (!IsEnabled)
#pragma warning disable CS0618 // Type or member is obsolete
if (ruleDef != null)
{
rule = rule.Disable();
// In previous versions, the actual rule was stored in a nested object.
return ruleDef with
{
Id = Id,
AppId = AppId,
Created = Created,
CreatedBy = CreatedBy,
IsDeleted = IsDeleted,
LastModified = LastModified,
LastModifiedBy = LastModifiedBy,
Version = Version,
};
}
#pragma warning restore CS0618 // Type or member is obsolete
if (Name != null)
if (result.Trigger is IMigrated<RuleTrigger> migrated)
{
rule = rule.Rename(Name);
return this with { Trigger = migrated.Migrate() };
}
return rule;
return this;
}
}

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

@ -10,38 +10,30 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules;
public sealed class Rule
public record Rule : AppEntity
{
public string? Name { get; private set; }
public string? Name { get; init; }
public RuleTrigger Trigger { get; private set; }
public RuleTrigger Trigger { get; init; }
public RuleAction Action { get; private set; }
public RuleAction Action { get; init; }
public bool IsEnabled { get; private set; } = true;
public bool IsEnabled { get; init; } = true;
public Rule(RuleTrigger trigger, RuleAction action)
public override DomainId UniqueId
{
Guard.NotNull(trigger);
Guard.NotNull(action);
Action = action;
Trigger = trigger;
get => DomainId.Combine(AppId.Id, Id);
}
[Pure]
public Rule Rename(string newName)
public Rule Rename(string? newName)
{
if (string.Equals(Name, newName, StringComparison.Ordinal))
{
return this;
}
return Clone(clone =>
{
clone.Name = newName;
});
return this with { Name = newName };
}
[Pure]
@ -52,10 +44,7 @@ public sealed class Rule
return this;
}
return Clone(clone =>
{
clone.IsEnabled = true;
});
return this with { IsEnabled = true };
}
[Pure]
@ -66,10 +55,7 @@ public sealed class Rule
return this;
}
return Clone(clone =>
{
clone.IsEnabled = false;
});
return this with { IsEnabled = false };
}
[Pure]
@ -87,10 +73,7 @@ public sealed class Rule
return this;
}
return Clone(clone =>
{
clone.Trigger = newTrigger;
});
return this with { Trigger = newTrigger };
}
[Pure]
@ -108,18 +91,6 @@ public sealed class Rule
return this;
}
return Clone(clone =>
{
clone.Action = newAction;
});
}
private Rule Clone(Action<Rule> updater)
{
var clone = (Rule)MemberwiseClone();
updater(clone);
return clone;
return this with { Action = newAction };
}
}

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

@ -10,7 +10,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules;
public sealed class RuleJob
public sealed record RuleJob
{
public DomainId Id { get; set; }

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

@ -6,33 +6,31 @@
// ==========================================================================
using System.Diagnostics.Contracts;
using System.Text.Json.Serialization;
namespace Squidex.Domain.Apps.Core.Schemas;
public sealed class ArrayField : RootField<ArrayFieldProperties>, IArrayField
public sealed record ArrayField : RootField<ArrayFieldProperties>, IArrayField
{
[JsonIgnore]
public IReadOnlyList<NestedField> Fields
{
get => FieldCollection.Ordered;
}
[JsonIgnore]
public IReadOnlyDictionary<long, NestedField> FieldsById
{
get => FieldCollection.ById;
}
[JsonIgnore]
public IReadOnlyDictionary<string, NestedField> FieldsByName
{
get => FieldCollection.ByName;
}
public FieldCollection<NestedField> FieldCollection { get; private set; } = FieldCollection<NestedField>.Empty;
public ArrayField(long id, string name, Partitioning partitioning, NestedField[] fields, ArrayFieldProperties? properties = null, IFieldSettings? settings = null)
: base(id, name, partitioning, properties, settings)
{
FieldCollection = new FieldCollection<NestedField>(fields);
}
public FieldCollection<NestedField> FieldCollection { get; init; } = FieldCollection<NestedField>.Empty;
[Pure]
public ArrayField DeleteField(long fieldId)
@ -67,9 +65,6 @@ public sealed class ArrayField : RootField<ArrayFieldProperties>, IArrayField
return this;
}
return (ArrayField)Clone(clone =>
{
((ArrayField)clone).FieldCollection = newFields;
});
return this with { FieldCollection = newFields };
}
}

6
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs

@ -29,12 +29,12 @@ public sealed record ArrayFieldProperties : FieldProperties
return visitor.Visit((IArrayField)field, args);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return Fields.Array(id, name, partitioning, this, settings);
return Fields.Array(id, name, partitioning, this);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
throw new NotSupportedException();
}

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

@ -72,13 +72,13 @@ public sealed record AssetsFieldProperties : FieldProperties
return visitor.Visit((IField<AssetsFieldProperties>)field, args);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return Fields.Assets(id, name, partitioning, this, settings);
return Fields.Assets(id, name, partitioning, this);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
return Fields.Assets(id, name, this, settings);
return Fields.Assets(id, name, this);
}
}

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

@ -27,13 +27,13 @@ public sealed record BooleanFieldProperties : FieldProperties
return visitor.Visit((IField<BooleanFieldProperties>)field, args);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return Fields.Boolean(id, name, partitioning, this, settings);
return Fields.Boolean(id, name, partitioning, this);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
return Fields.Boolean(id, name, this, settings);
return Fields.Boolean(id, name, this);
}
}

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

@ -36,13 +36,13 @@ public sealed record ComponentFieldProperties : FieldProperties
return visitor.Visit((IField<ComponentFieldProperties>)field, args);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return Fields.Component(id, name, partitioning, this, settings);
return Fields.Component(id, name, partitioning, this);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
return Fields.Component(id, name, this, settings);
return Fields.Component(id, name, this);
}
}

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

@ -44,13 +44,13 @@ public sealed record ComponentsFieldProperties : FieldProperties
return visitor.Visit((IField<ComponentsFieldProperties>)field, args);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return Fields.Components(id, name, partitioning, this, settings);
return Fields.Components(id, name, partitioning, this);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
return Fields.Components(id, name, this, settings);
return Fields.Components(id, name, this);
}
}

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

@ -35,13 +35,13 @@ public sealed record DateTimeFieldProperties : FieldProperties
return visitor.Visit((IField<DateTimeFieldProperties>)field, args);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return Fields.DateTime(id, name, partitioning, this, settings);
return Fields.DateTime(id, name, partitioning, this);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
return Fields.DateTime(id, name, this, settings);
return Fields.DateTime(id, name, this);
}
}

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

@ -78,9 +78,9 @@ public sealed class FieldCollection<T> where T : IField
fieldsOrdered = fields;
}
private FieldCollection(IEnumerable<T> fields)
public static FieldCollection<T> Create(params T[]? fields)
{
fieldsOrdered = fields.ToArray();
return fields is not { Length: > 0 } ? Empty : new FieldCollection<T>(fields);
}
[Pure]
@ -91,7 +91,7 @@ public sealed class FieldCollection<T> where T : IField
return this;
}
return new FieldCollection<T>(fieldsOrdered.Where(x => x.Id != fieldId));
return new FieldCollection<T>(fieldsOrdered.Where(x => x.Id != fieldId).ToArray());
}
[Pure]
@ -109,7 +109,7 @@ public sealed class FieldCollection<T> where T : IField
return this;
}
return new FieldCollection<T>(fieldsOrdered.OrderBy(f => ids.IndexOf(f.Id)));
return new FieldCollection<T>(fieldsOrdered.OrderBy(f => ids.IndexOf(f.Id)).ToArray());
}
[Pure]
@ -127,7 +127,7 @@ public sealed class FieldCollection<T> where T : IField
ThrowHelper.ArgumentException($"A field with ID {field.Id} already exists.", nameof(field));
}
return new FieldCollection<T>(fieldsOrdered.Union(Enumerable.Repeat(field, 1)));
return new FieldCollection<T>(fieldsOrdered.Union(Enumerable.Repeat(field, 1)).ToArray());
}
[Pure]
@ -153,6 +153,6 @@ public sealed class FieldCollection<T> where T : IField
return default!;
}
return new FieldCollection<T>(fieldsOrdered.Select(x => ReferenceEquals(x, field) ? newField : x));
return new FieldCollection<T>(fieldsOrdered.Select(x => ReferenceEquals(x, field) ? newField : x).ToArray());
}
}

113
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs

@ -5,15 +5,36 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Diagnostics.CodeAnalysis;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Schemas;
public sealed class FieldNames : ReadonlyList<string>
{
private const string MetaPrefix = "meta.";
private const string DataPrefix = "data.";
private static readonly HashSet<string> MetaFields =
[
"id",
"created",
"createdBy.avatar",
"createdBy.name",
"lastModified",
"lastModifiedBy.avatar",
"lastModifiedBy.name",
"status",
"status.color",
"status.next",
"version",
"translationStatus",
"translationStatusAverage"
];
public static readonly new FieldNames Empty = new FieldNames(new List<string>());
public FieldNames()
private FieldNames()
{
}
@ -22,26 +43,100 @@ public sealed class FieldNames : ReadonlyList<string>
{
}
public static FieldNames Create(params string[] names)
public static FieldNames Create(params string[]? names)
{
return new FieldNames(names.ToList());
return names?.Length > 0 ? new FieldNames(names.ToList()) : Empty;
}
public FieldNames Add(string field)
public FieldNames Remove(string field)
{
var list = this.ToList();
list.Add(field);
list.Remove(field);
return new FieldNames(list);
}
public FieldNames Remove(string field)
public static bool IsMetaField(string? name)
{
var list = this.ToList();
return name != null && MetaFields.Contains(name);
}
list.Remove(field);
public static bool IsDataField(string? name)
{
return name?.StartsWith(DataPrefix, StringComparison.OrdinalIgnoreCase) == true;
}
return new FieldNames(list);
public static bool IsDataField(string? name, [MaybeNullWhen(false)] out string dataField)
{
dataField = null!;
if (IsDataField(name))
{
dataField = name![MetaPrefix.Length..];
return true;
}
return false;
}
public FieldNames Migrate()
{
static bool IsOldMetaField(string name)
{
return name.StartsWith(MetaPrefix, StringComparison.OrdinalIgnoreCase);
}
var isNewVersion = this.All(x => IsDataField(x) || IsMetaField(x));
if (isNewVersion)
{
return this;
}
var result = this.ToList();
for (var i = 0; i < result.Count; i++)
{
var field = result[i];
if (IsOldMetaField(field))
{
field = field[MetaPrefix.Length..];
}
else
{
field = $"{DataPrefix}{field}";
}
result[i] = field;
}
return new FieldNames(result);
}
public static Schema Migrate(Schema schema)
{
Guard.NotNull(schema);
var fieldsInLists = schema.FieldsInLists;
var fieldsInRefs = schema.FieldsInReferences;
var migratedFieldsInLists = fieldsInLists.Migrate();
var migratedFieldsInRefs = fieldsInRefs.Migrate();
var newSchema = schema;
if (!ReferenceEquals(fieldsInLists, migratedFieldsInLists))
{
newSchema = newSchema.SetFieldsInLists(migratedFieldsInLists);
}
if (!ReferenceEquals(fieldsInRefs, migratedFieldsInRefs))
{
newSchema = newSchema.SetFieldsInReferences(migratedFieldsInRefs);
}
return newSchema;
}
}

4
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs

@ -27,7 +27,7 @@ public abstract record FieldProperties : NamedElementPropertiesBase
public abstract T Accept<T, TArgs>(IFieldVisitor<T, TArgs> visitor, IField field, TArgs args);
public abstract RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null);
public abstract RootField CreateRootField(long id, string name, Partitioning partitioning);
public abstract NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null);
public abstract NestedField CreateNestedField(long id, string name);
}

4
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRules.cs

@ -22,8 +22,8 @@ public sealed class FieldRules : ReadonlyList<FieldRule>
{
}
public static FieldRules Create(params FieldRule[] rules)
public static FieldRules Create(params FieldRule[]? rules)
{
return new FieldRules(rules.ToArray());
return rules is not { Length: > 0 } ? Empty : new FieldRules(rules.ToArray());
}
}

202
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs

@ -5,164 +5,166 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
#pragma warning disable SA1000 // Keywords should be spaced correctly
namespace Squidex.Domain.Apps.Core.Schemas;
public static class Fields
{
public static ArrayField Array(long id, string name, Partitioning partitioning,
ArrayFieldProperties? properties = null, IFieldSettings? settings = null, params NestedField[] fields)
ArrayFieldProperties? properties = null, params NestedField[] fields)
{
return new ArrayField(id, name, partitioning, fields, properties, settings);
return new ArrayField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new(), FieldCollection = new FieldCollection<NestedField>(fields) };
}
public static RootField<AssetsFieldProperties> Assets(long id, string name, Partitioning partitioning,
AssetsFieldProperties? properties = null, IFieldSettings? settings = null)
AssetsFieldProperties? properties = null)
{
return new RootField<AssetsFieldProperties>(id, name, partitioning, properties, settings);
return new RootField<AssetsFieldProperties> { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new () };
}
public static RootField<BooleanFieldProperties> Boolean(long id, string name, Partitioning partitioning,
BooleanFieldProperties? properties = null, IFieldSettings? settings = null)
BooleanFieldProperties? properties = null)
{
return new RootField<BooleanFieldProperties>(id, name, partitioning, properties, settings);
return new RootField<BooleanFieldProperties> { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() };
}
public static RootField<ComponentFieldProperties> Component(long id, string name, Partitioning partitioning,
ComponentFieldProperties? properties = null, IFieldSettings? settings = null)
ComponentFieldProperties? properties = null)
{
return new RootField<ComponentFieldProperties>(id, name, partitioning, properties, settings);
return new RootField<ComponentFieldProperties> { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() };
}
public static RootField<ComponentsFieldProperties> Components(long id, string name, Partitioning partitioning,
ComponentsFieldProperties? properties = null, IFieldSettings? settings = null)
ComponentsFieldProperties? properties = null)
{
return new RootField<ComponentsFieldProperties>(id, name, partitioning, properties, settings);
return new RootField<ComponentsFieldProperties> { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() };
}
public static RootField<DateTimeFieldProperties> DateTime(long id, string name, Partitioning partitioning,
DateTimeFieldProperties? properties = null, IFieldSettings? settings = null)
DateTimeFieldProperties? properties = null)
{
return new RootField<DateTimeFieldProperties>(id, name, partitioning, properties, settings);
return new RootField<DateTimeFieldProperties> { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() };
}
public static RootField<GeolocationFieldProperties> Geolocation(long id, string name, Partitioning partitioning,
GeolocationFieldProperties? properties = null, IFieldSettings? settings = null)
GeolocationFieldProperties? properties = null)
{
return new RootField<GeolocationFieldProperties>(id, name, partitioning, properties, settings);
return new RootField<GeolocationFieldProperties> { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() };
}
public static RootField<JsonFieldProperties> Json(long id, string name, Partitioning partitioning,
JsonFieldProperties? properties = null, IFieldSettings? settings = null)
JsonFieldProperties? properties = null)
{
return new RootField<JsonFieldProperties>(id, name, partitioning, properties, settings);
return new RootField<JsonFieldProperties> { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() };
}
public static RootField<NumberFieldProperties> Number(long id, string name, Partitioning partitioning,
NumberFieldProperties? properties = null, IFieldSettings? settings = null)
NumberFieldProperties? properties = null)
{
return new RootField<NumberFieldProperties>(id, name, partitioning, properties, settings);
return new RootField<NumberFieldProperties> { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() };
}
public static RootField<ReferencesFieldProperties> References(long id, string name, Partitioning partitioning,
ReferencesFieldProperties? properties = null, IFieldSettings? settings = null)
ReferencesFieldProperties? properties = null)
{
return new RootField<ReferencesFieldProperties>(id, name, partitioning, properties, settings);
return new RootField<ReferencesFieldProperties> { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() };
}
public static RootField<StringFieldProperties> String(long id, string name, Partitioning partitioning,
StringFieldProperties? properties = null, IFieldSettings? settings = null)
StringFieldProperties? properties = null)
{
return new RootField<StringFieldProperties>(id, name, partitioning, properties, settings);
return new RootField<StringFieldProperties> { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() };
}
public static RootField<TagsFieldProperties> Tags(long id, string name, Partitioning partitioning,
TagsFieldProperties? properties = null, IFieldSettings? settings = null)
TagsFieldProperties? properties = null)
{
return new RootField<TagsFieldProperties>(id, name, partitioning, properties, settings);
return new RootField<TagsFieldProperties> { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() };
}
public static RootField<UIFieldProperties> UI(long id, string name, Partitioning partitioning,
UIFieldProperties? properties = null, IFieldSettings? settings = null)
UIFieldProperties? properties = null)
{
return new RootField<UIFieldProperties>(id, name, partitioning, properties, settings);
return new RootField<UIFieldProperties> { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() };
}
public static NestedField<AssetsFieldProperties> Assets(long id, string name,
AssetsFieldProperties? properties = null, IFieldSettings? settings = null)
AssetsFieldProperties? properties = null)
{
return new NestedField<AssetsFieldProperties>(id, name, properties, settings);
return new NestedField<AssetsFieldProperties> { Id = id, Name = name, Properties = properties ?? new() };
}
public static NestedField<BooleanFieldProperties> Boolean(long id, string name,
BooleanFieldProperties? properties = null, IFieldSettings? settings = null)
BooleanFieldProperties? properties = null)
{
return new NestedField<BooleanFieldProperties>(id, name, properties, settings);
return new NestedField<BooleanFieldProperties> { Id = id, Name = name, Properties = properties ?? new() };
}
public static NestedField<ComponentFieldProperties> Component(long id, string name,
ComponentFieldProperties? properties = null, IFieldSettings? settings = null)
ComponentFieldProperties? properties = null)
{
return new NestedField<ComponentFieldProperties>(id, name, properties, settings);
return new NestedField<ComponentFieldProperties> { Id = id, Name = name, Properties = properties ?? new() };
}
public static NestedField<ComponentsFieldProperties> Components(long id, string name,
ComponentsFieldProperties? properties = null, IFieldSettings? settings = null)
ComponentsFieldProperties? properties = null)
{
return new NestedField<ComponentsFieldProperties>(id, name, properties, settings);
return new NestedField<ComponentsFieldProperties> { Id = id, Name = name, Properties = properties ?? new() };
}
public static NestedField<DateTimeFieldProperties> DateTime(long id, string name,
DateTimeFieldProperties? properties = null, IFieldSettings? settings = null)
DateTimeFieldProperties? properties = null)
{
return new NestedField<DateTimeFieldProperties>(id, name, properties, settings);
return new NestedField<DateTimeFieldProperties> { Id = id, Name = name, Properties = properties ?? new() };
}
public static NestedField<GeolocationFieldProperties> Geolocation(long id, string name,
GeolocationFieldProperties? properties = null, IFieldSettings? settings = null)
GeolocationFieldProperties? properties = null)
{
return new NestedField<GeolocationFieldProperties>(id, name, properties, settings);
return new NestedField<GeolocationFieldProperties> { Id = id, Name = name, Properties = properties ?? new() };
}
public static NestedField<JsonFieldProperties> Json(long id, string name,
JsonFieldProperties? properties = null, IFieldSettings? settings = null)
JsonFieldProperties? properties = null)
{
return new NestedField<JsonFieldProperties>(id, name, properties, settings);
return new NestedField<JsonFieldProperties> { Id = id, Name = name, Properties = properties ?? new() };
}
public static NestedField<NumberFieldProperties> Number(long id, string name,
NumberFieldProperties? properties = null, IFieldSettings? settings = null)
NumberFieldProperties? properties = null)
{
return new NestedField<NumberFieldProperties>(id, name, properties, settings);
return new NestedField<NumberFieldProperties> { Id = id, Name = name, Properties = properties ?? new() };
}
public static NestedField<ReferencesFieldProperties> References(long id, string name,
ReferencesFieldProperties? properties = null, IFieldSettings? settings = null)
ReferencesFieldProperties? properties = null)
{
return new NestedField<ReferencesFieldProperties>(id, name, properties, settings);
return new NestedField<ReferencesFieldProperties> { Id = id, Name = name, Properties = properties ?? new() };
}
public static NestedField<StringFieldProperties> String(long id, string name,
StringFieldProperties? properties = null, IFieldSettings? settings = null)
StringFieldProperties? properties = null)
{
return new NestedField<StringFieldProperties>(id, name, properties, settings);
return new NestedField<StringFieldProperties> { Id = id, Name = name, Properties = properties ?? new() };
}
public static NestedField<TagsFieldProperties> Tags(long id, string name,
TagsFieldProperties? properties = null, IFieldSettings? settings = null)
TagsFieldProperties? properties = null)
{
return new NestedField<TagsFieldProperties>(id, name, properties, settings);
return new NestedField<TagsFieldProperties> { Id = id, Name = name, Properties = properties ?? new() };
}
public static NestedField<UIFieldProperties> UI(long id, string name,
UIFieldProperties? properties = null, IFieldSettings? settings = null)
UIFieldProperties? properties = null)
{
return new NestedField<UIFieldProperties>(id, name, properties, settings);
return new NestedField<UIFieldProperties> { Id = id, Name = name, Properties = properties ?? new() };
}
public static Schema AddArray(this Schema schema, long id, string name, Partitioning partitioning,
Func<ArrayField, ArrayField>? handler = null, ArrayFieldProperties? properties = null, IFieldSettings? settings = null)
Func<ArrayField, ArrayField>? handler = null, ArrayFieldProperties? properties = null)
{
var field = Array(id, name, partitioning, properties, settings);
var field = Array(id, name, partitioning, properties);
if (handler != null)
{
@ -173,146 +175,146 @@ public static class Fields
}
public static Schema AddAssets(this Schema schema, long id, string name, Partitioning partitioning,
AssetsFieldProperties? properties = null, IFieldSettings? settings = null)
AssetsFieldProperties? properties = null)
{
return schema.AddField(Assets(id, name, partitioning, properties, settings));
return schema.AddField(Assets(id, name, partitioning, properties));
}
public static Schema AddBoolean(this Schema schema, long id, string name, Partitioning partitioning,
BooleanFieldProperties? properties = null, IFieldSettings? settings = null)
BooleanFieldProperties? properties = null)
{
return schema.AddField(Boolean(id, name, partitioning, properties, settings));
return schema.AddField(Boolean(id, name, partitioning, properties));
}
public static Schema AddComponent(this Schema schema, long id, string name, Partitioning partitioning,
ComponentFieldProperties? properties = null, IFieldSettings? settings = null)
ComponentFieldProperties? properties = null)
{
return schema.AddField(Component(id, name, partitioning, properties, settings));
return schema.AddField(Component(id, name, partitioning, properties));
}
public static Schema AddComponents(this Schema schema, long id, string name, Partitioning partitioning,
ComponentsFieldProperties? properties = null, IFieldSettings? settings = null)
ComponentsFieldProperties? properties = null)
{
return schema.AddField(Components(id, name, partitioning, properties, settings));
return schema.AddField(Components(id, name, partitioning, properties));
}
public static Schema AddDateTime(this Schema schema, long id, string name, Partitioning partitioning,
DateTimeFieldProperties? properties = null, IFieldSettings? settings = null)
DateTimeFieldProperties? properties = null)
{
return schema.AddField(DateTime(id, name, partitioning, properties, settings));
return schema.AddField(DateTime(id, name, partitioning, properties));
}
public static Schema AddGeolocation(this Schema schema, long id, string name, Partitioning partitioning,
GeolocationFieldProperties? properties = null, IFieldSettings? settings = null)
GeolocationFieldProperties? properties = null)
{
return schema.AddField(Geolocation(id, name, partitioning, properties, settings));
return schema.AddField(Geolocation(id, name, partitioning, properties));
}
public static Schema AddJson(this Schema schema, long id, string name, Partitioning partitioning,
JsonFieldProperties? properties = null, IFieldSettings? settings = null)
JsonFieldProperties? properties = null)
{
return schema.AddField(Json(id, name, partitioning, properties, settings));
return schema.AddField(Json(id, name, partitioning, properties));
}
public static Schema AddNumber(this Schema schema, long id, string name, Partitioning partitioning,
NumberFieldProperties? properties = null, IFieldSettings? settings = null)
NumberFieldProperties? properties = null)
{
return schema.AddField(Number(id, name, partitioning, properties, settings));
return schema.AddField(Number(id, name, partitioning, properties));
}
public static Schema AddReferences(this Schema schema, long id, string name, Partitioning partitioning,
ReferencesFieldProperties? properties = null, IFieldSettings? settings = null)
ReferencesFieldProperties? properties = null)
{
return schema.AddField(References(id, name, partitioning, properties, settings));
return schema.AddField(References(id, name, partitioning, properties));
}
public static Schema AddString(this Schema schema, long id, string name, Partitioning partitioning,
StringFieldProperties? properties = null, IFieldSettings? settings = null)
StringFieldProperties? properties = null)
{
return schema.AddField(String(id, name, partitioning, properties, settings));
return schema.AddField(String(id, name, partitioning, properties));
}
public static Schema AddTags(this Schema schema, long id, string name, Partitioning partitioning,
TagsFieldProperties? properties = null, IFieldSettings? settings = null)
TagsFieldProperties? properties = null)
{
return schema.AddField(Tags(id, name, partitioning, properties, settings));
return schema.AddField(Tags(id, name, partitioning, properties));
}
public static Schema AddUI(this Schema schema, long id, string name, Partitioning partitioning,
UIFieldProperties? properties = null, IFieldSettings? settings = null)
UIFieldProperties? properties = null)
{
return schema.AddField(UI(id, name, partitioning, properties, settings));
return schema.AddField(UI(id, name, partitioning, properties));
}
public static ArrayField AddAssets(this ArrayField field, long id, string name,
AssetsFieldProperties? properties = null, IFieldSettings? settings = null)
AssetsFieldProperties? properties = null)
{
return field.AddField(Assets(id, name, properties, settings));
return field.AddField(Assets(id, name, properties));
}
public static ArrayField AddBoolean(this ArrayField field, long id, string name,
BooleanFieldProperties? properties = null, IFieldSettings? settings = null)
BooleanFieldProperties? properties = null)
{
return field.AddField(Boolean(id, name, properties, settings));
return field.AddField(Boolean(id, name, properties));
}
public static ArrayField AddComponent(this ArrayField field, long id, string name,
ComponentFieldProperties? properties = null, IFieldSettings? settings = null)
ComponentFieldProperties? properties = null)
{
return field.AddField(Component(id, name, properties, settings));
return field.AddField(Component(id, name, properties));
}
public static ArrayField AddComponents(this ArrayField field, long id, string name,
ComponentsFieldProperties? properties = null, IFieldSettings? settings = null)
ComponentsFieldProperties? properties = null)
{
return field.AddField(Components(id, name, properties, settings));
return field.AddField(Components(id, name, properties));
}
public static ArrayField AddDateTime(this ArrayField field, long id, string name,
DateTimeFieldProperties? properties = null, IFieldSettings? settings = null)
DateTimeFieldProperties? properties = null)
{
return field.AddField(DateTime(id, name, properties, settings));
return field.AddField(DateTime(id, name, properties));
}
public static ArrayField AddGeolocation(this ArrayField field, long id, string name,
GeolocationFieldProperties? properties = null, IFieldSettings? settings = null)
GeolocationFieldProperties? properties = null)
{
return field.AddField(Geolocation(id, name, properties, settings));
return field.AddField(Geolocation(id, name, properties));
}
public static ArrayField AddJson(this ArrayField field, long id, string name,
JsonFieldProperties? properties = null, IFieldSettings? settings = null)
JsonFieldProperties? properties = null)
{
return field.AddField(Json(id, name, properties, settings));
return field.AddField(Json(id, name, properties));
}
public static ArrayField AddNumber(this ArrayField field, long id, string name,
NumberFieldProperties? properties = null, IFieldSettings? settings = null)
NumberFieldProperties? properties = null)
{
return field.AddField(Number(id, name, properties, settings));
return field.AddField(Number(id, name, properties));
}
public static ArrayField AddReferences(this ArrayField field, long id, string name,
ReferencesFieldProperties? properties = null, IFieldSettings? settings = null)
ReferencesFieldProperties? properties = null)
{
return field.AddField(References(id, name, properties, settings));
return field.AddField(References(id, name, properties));
}
public static ArrayField AddString(this ArrayField field, long id, string name,
StringFieldProperties? properties = null, IFieldSettings? settings = null)
StringFieldProperties? properties = null)
{
return field.AddField(String(id, name, properties, settings));
return field.AddField(String(id, name, properties));
}
public static ArrayField AddTags(this ArrayField field, long id, string name,
TagsFieldProperties? properties = null, IFieldSettings? settings = null)
TagsFieldProperties? properties = null)
{
return field.AddField(Tags(id, name, properties, settings));
return field.AddField(Tags(id, name, properties));
}
public static ArrayField AddUI(this ArrayField field, long id, string name,
UIFieldProperties? properties = null, IFieldSettings? settings = null)
UIFieldProperties? properties = null)
{
return field.AddField(UI(id, name, properties, settings));
return field.AddField(UI(id, name, properties));
}
}

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

@ -21,13 +21,13 @@ public sealed record GeolocationFieldProperties : FieldProperties
return visitor.Visit((IField<GeolocationFieldProperties>)field, args);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return Fields.Geolocation(id, name, partitioning, this, settings);
return Fields.Geolocation(id, name, partitioning, this);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
return Fields.Geolocation(id, name, this, settings);
return Fields.Geolocation(id, name, this);
}
}

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

@ -7,12 +7,18 @@
namespace Squidex.Domain.Apps.Core.Schemas;
public interface IField : IFieldSettings
public interface IField
{
long Id { get; }
string Name { get; }
bool IsHidden { get; }
bool IsLocked { get; }
bool IsDisabled { get; }
FieldProperties RawProperties { get; }
T Accept<T, TArgs>(IFieldVisitor<T, TArgs> visitor, TArgs args);

17
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldSettings.cs

@ -1,17 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Schemas;
public interface IFieldSettings
{
bool IsLocked { get; }
bool IsDisabled { get; }
bool IsHidden { get; }
}

56
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/FieldSurrogate.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Core.Schemas.Json;
public sealed class FieldSurrogate : IFieldSettings
public sealed class FieldSurrogate
{
public long Id { get; set; }
@ -25,7 +25,35 @@ public sealed class FieldSurrogate : IFieldSettings
public FieldSurrogate[]? Children { get; set; }
public RootField ToField()
public static FieldSurrogate FromSource(RootField source)
{
return new FieldSurrogate
{
Id = source.Id,
Name = source.Name,
Children = source is ArrayField array ? array.Fields.Select(FromSource).ToArray() : null,
IsLocked = source.IsLocked,
IsHidden = source.IsHidden,
IsDisabled = source.IsDisabled,
Partitioning = source.Partitioning.Key,
Properties = source.RawProperties
};
}
public static FieldSurrogate FromSource(NestedField source)
{
return new FieldSurrogate
{
Id = source.Id,
Name = source.Name,
IsLocked = source.IsLocked,
IsHidden = source.IsHidden,
IsDisabled = source.IsDisabled,
Properties = source.RawProperties
};
}
public RootField ToRootField()
{
var partitioning = Core.Partitioning.FromString(Partitioning);
@ -33,16 +61,32 @@ public sealed class FieldSurrogate : IFieldSettings
{
var nested = Children?.Select(n => n.ToNestedField()).ToArray() ?? [];
return new ArrayField(Id, Name, partitioning, nested, arrayProperties, this);
return Fields.Array(Id, Name, partitioning, arrayProperties) with
{
FieldCollection = new FieldCollection<NestedField>(nested),
IsLocked = IsLocked,
IsHidden = IsHidden,
IsDisabled = IsDisabled
};
}
else
{
return Properties.CreateRootField(Id, Name, partitioning, this);
return Properties.CreateRootField(Id, Name, partitioning) with
{
IsLocked = IsLocked,
IsHidden = IsHidden,
IsDisabled = IsDisabled
};
}
}
public NestedField ToNestedField()
{
return Properties.CreateNestedField(Id, Name, this);
return Properties.CreateNestedField(Id, Name) with
{
IsLocked = IsLocked,
IsHidden = IsHidden,
IsDisabled = IsDisabled
};
}
}
}

23
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/FieldsSurrogate.cs

@ -0,0 +1,23 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas.Json;
public sealed class FieldsSurrogate : List<FieldSurrogate>, ISurrogate<FieldCollection<RootField>>
{
public void FromSource(FieldCollection<RootField> source)
{
AddRange(source.Ordered.Select(FieldSurrogate.FromSource));
}
public FieldCollection<RootField> ToSource()
{
return new FieldCollection<RootField>(this.Select(x => x.ToRootField()).ToArray());
}
}

128
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaSurrogate.cs

@ -5,121 +5,53 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Text.Json.Serialization;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Json.System;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Schemas.Json;
public sealed class SchemaSurrogate : ISurrogate<Schema>
[JsonRename(nameof(FieldCollection), "fields")]
public record SchemaSurrogate : Schema, ISurrogate<Schema>
{
public string Name { get; set; }
[Obsolete("Old serialization format.")]
private Schema? schemaDef;
public string Category { get; set; }
public bool IsPublished { get; set; }
public SchemaType Type { get; set; }
public SchemaProperties Properties { get; set; }
public SchemaScripts? Scripts { get; set; }
public FieldNames? FieldsInLists { get; set; }
public FieldNames? FieldsInReferences { get; set; }
public FieldRules? FieldRules { get; set; }
public FieldSurrogate[] Fields { get; set; }
public ReadonlyDictionary<string, string>? PreviewUrls { get; set; }
public bool IsSingleton
[JsonPropertyName("schemaDef")]
[Obsolete("Old serialization format.")]
public Schema? SchemaDef
{
set
{
if (value)
{
Type = SchemaType.Singleton;
}
}
// Because this property is old we old want to read it and never to write it.
set => schemaDef = value;
}
public void FromSource(Schema source)
{
SimpleMapper.Map(source, this);
Fields =
source.Fields.Select(x =>
new FieldSurrogate
{
Id = x.Id,
Name = x.Name,
Children = CreateChildren(x),
IsHidden = x.IsHidden,
IsLocked = x.IsLocked,
IsDisabled = x.IsDisabled,
Partitioning = x.Partitioning.Key,
Properties = x.RawProperties
}).ToArray();
}
private static FieldSurrogate[]? CreateChildren(IField field)
{
if (field is ArrayField arrayField)
{
return arrayField.Fields.Select(x =>
new FieldSurrogate
{
Id = x.Id,
Name = x.Name,
IsHidden = x.IsHidden,
IsLocked = x.IsLocked,
IsDisabled = x.IsDisabled,
Properties = x.RawProperties
}).ToArray();
}
return null;
}
public Schema ToSource()
{
var fields = Fields?.Select(f => f.ToField()).ToArray() ?? [];
var schema = new Schema(Name, fields, Properties, IsPublished, Type);
if (!string.IsNullOrWhiteSpace(Category))
{
schema = schema.ChangeCategory(Category);
}
if (Scripts != null)
{
schema = schema.SetScripts(Scripts);
}
if (FieldsInLists?.Count > 0)
{
schema = schema.SetFieldsInLists(FieldsInLists);
}
if (FieldsInReferences?.Count > 0)
{
schema = schema.SetFieldsInReferences(FieldsInReferences);
}
if (FieldRules?.Count > 0)
{
schema = schema.SetFieldRules(FieldRules);
}
if (PreviewUrls?.Count > 0)
#pragma warning disable CS0618 // Type or member is obsolete
if (schemaDef != null)
{
schema = schema.SetPreviewUrls(PreviewUrls);
}
return schema;
// In previous versions, the actual schema was stored in a nested object.
return schemaDef with
{
Id = Id,
AppId = AppId,
Created = Created,
CreatedBy = CreatedBy,
IsDeleted = IsDeleted,
LastModified = LastModified,
LastModifiedBy = LastModifiedBy,
SchemaFieldsTotal = SchemaFieldsTotal,
Version = Version,
};
}
#pragma warning restore CS0618 // Type or member is obsolete
return SimpleMapper.Map(this, new Schema());
}
}

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

@ -21,13 +21,13 @@ public sealed record JsonFieldProperties : FieldProperties
return visitor.Visit((IField<JsonFieldProperties>)field, args);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return Fields.Json(id, name, partitioning, this, settings);
return Fields.Json(id, name, partitioning, this);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
return Fields.Json(id, name, this, settings);
return Fields.Json(id, name, this);
}
}

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

@ -9,26 +9,19 @@ using System.Diagnostics.Contracts;
namespace Squidex.Domain.Apps.Core.Schemas;
public abstract class NestedField : FieldBase, INestedField
public abstract record NestedField : INestedField
{
public bool IsLocked { get; private set; }
public long Id { get; init; }
public bool IsHidden { get; private set; }
public string Name { get; init; }
public bool IsDisabled { get; private set; }
public bool IsLocked { get; init; }
public abstract FieldProperties RawProperties { get; }
public bool IsHidden { get; init; }
protected NestedField(long id, string name, IFieldSettings? settings = null)
: base(id, name)
{
if (settings != null)
{
IsLocked = settings.IsLocked;
IsHidden = settings.IsHidden;
IsDisabled = settings.IsDisabled;
}
}
public bool IsDisabled { get; init; }
public abstract FieldProperties RawProperties { get; }
[Pure]
public NestedField Lock()
@ -38,10 +31,7 @@ public abstract class NestedField : FieldBase, INestedField
return this;
}
return Clone(clone =>
{
clone.IsLocked = true;
});
return this with { IsLocked = true };
}
[Pure]
@ -52,10 +42,7 @@ public abstract class NestedField : FieldBase, INestedField
return this;
}
return Clone(clone =>
{
clone.IsHidden = true;
});
return this with { IsHidden = true };
}
[Pure]
@ -66,10 +53,7 @@ public abstract class NestedField : FieldBase, INestedField
return this;
}
return Clone(clone =>
{
clone.IsHidden = false;
});
return this with { IsHidden = false };
}
[Pure]
@ -80,10 +64,7 @@ public abstract class NestedField : FieldBase, INestedField
return this;
}
return Clone(clone =>
{
clone.IsDisabled = true;
});
return this with { IsDisabled = true };
}
[Pure]
@ -94,22 +75,10 @@ public abstract class NestedField : FieldBase, INestedField
return this;
}
return Clone(clone =>
{
clone.IsDisabled = false;
});
return this with { IsDisabled = false };
}
public abstract T Accept<T, TArgs>(IFieldVisitor<T, TArgs> visitor, TArgs args);
public abstract NestedField Update(FieldProperties newProperties);
protected NestedField Clone(Action<NestedField> updater)
{
var clone = (NestedField)MemberwiseClone();
updater(clone);
return clone;
}
}

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

@ -10,21 +10,15 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas;
public class NestedField<T> : NestedField, IField<T> where T : FieldProperties, new()
public record NestedField<T> : NestedField, IField<T> where T : FieldProperties, new()
{
public T Properties { get; private set; }
public T Properties { get; init; }
public override FieldProperties RawProperties
{
get => Properties;
}
public NestedField(long id, string name, T? properties = null, IFieldSettings? settings = null)
: base(id, name, settings)
{
Properties = properties ?? new T();
}
[Pure]
public override NestedField Update(FieldProperties newProperties)
{
@ -35,10 +29,7 @@ public class NestedField<T> : NestedField, IField<T> where T : FieldProperties,
return this;
}
return Clone(clone =>
{
((NestedField<T>)clone).Properties = typedProperties;
});
return this with { Properties = typedProperties };
}
private static T ValidateProperties(FieldProperties newProperties)

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

@ -37,13 +37,13 @@ public sealed record NumberFieldProperties : FieldProperties
return visitor.Visit((IField<NumberFieldProperties>)field, args);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return Fields.Number(id, name, partitioning, this, settings);
return Fields.Number(id, name, partitioning, this);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
return Fields.Number(id, name, this, settings);
return Fields.Number(id, name, this);
}
}

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

@ -61,13 +61,13 @@ public sealed record ReferencesFieldProperties : FieldProperties
return visitor.Visit((IField<ReferencesFieldProperties>)field, args);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return Fields.References(id, name, partitioning, this, settings);
return Fields.References(id, name, partitioning, this);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
return Fields.References(id, name, this, settings);
return Fields.References(id, name, this);
}
}

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

@ -6,36 +6,24 @@
// ==========================================================================
using System.Diagnostics.Contracts;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas;
public abstract class RootField : FieldBase, IRootField
public abstract record RootField : IRootField
{
public Partitioning Partitioning { get; }
public long Id { get; init; }
public bool IsLocked { get; private set; }
public string Name { get; init; }
public bool IsHidden { get; private set; }
public Partitioning Partitioning { get; init; }
public bool IsDisabled { get; private set; }
public bool IsLocked { get; init; }
public abstract FieldProperties RawProperties { get; }
protected RootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
: base(id, name)
{
Guard.NotNull(partitioning);
public bool IsHidden { get; init; }
Partitioning = partitioning;
public bool IsDisabled { get; init; }
if (settings != null)
{
IsLocked = settings.IsLocked;
IsHidden = settings.IsHidden;
IsDisabled = settings.IsDisabled;
}
}
public abstract FieldProperties RawProperties { get; }
[Pure]
public RootField Lock()
@ -45,10 +33,7 @@ public abstract class RootField : FieldBase, IRootField
return this;
}
return Clone(clone =>
{
clone.IsLocked = true;
});
return this with { IsLocked = true };
}
[Pure]
@ -59,10 +44,7 @@ public abstract class RootField : FieldBase, IRootField
return this;
}
return Clone(clone =>
{
clone.IsHidden = true;
});
return this with { IsHidden = true };
}
[Pure]
@ -73,10 +55,7 @@ public abstract class RootField : FieldBase, IRootField
return this;
}
return Clone(clone =>
{
clone.IsHidden = false;
});
return this with { IsHidden = false };
}
[Pure]
@ -87,10 +66,7 @@ public abstract class RootField : FieldBase, IRootField
return this;
}
return Clone(clone =>
{
clone.IsDisabled = true;
});
return this with { IsDisabled = true };
}
[Pure]
@ -101,22 +77,10 @@ public abstract class RootField : FieldBase, IRootField
return this;
}
return Clone(clone =>
{
clone.IsDisabled = false;
});
return this with { IsDisabled = false };
}
public abstract T Accept<T, TArgs>(IFieldVisitor<T, TArgs> visitor, TArgs args);
public abstract RootField Update(FieldProperties newProperties);
protected RootField Clone(Action<RootField> updater)
{
var clone = (RootField)MemberwiseClone();
updater(clone);
return clone;
}
}

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

@ -10,21 +10,15 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas;
public class RootField<T> : RootField, IField<T> where T : FieldProperties, new()
public record RootField<T> : RootField, IField<T> where T : FieldProperties, new()
{
public T Properties { get; private set; }
public T Properties { get; init; }
public override FieldProperties RawProperties
{
get => Properties;
}
public RootField(long id, string name, Partitioning partitioning, T? properties = null, IFieldSettings? settings = null)
: base(id, name, partitioning, settings)
{
Properties = properties ?? new T();
}
[Pure]
public override RootField Update(FieldProperties newProperties)
{
@ -35,10 +29,7 @@ public class RootField<T> : RootField, IField<T> where T : FieldProperties, new(
return this;
}
return Clone(clone =>
{
((RootField<T>)clone).Properties = typedProperties;
});
return this with { Properties = typedProperties };
}
private static T ValidateProperties(FieldProperties newProperties)

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

@ -11,29 +11,31 @@ using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Schemas;
public sealed class Schema
public record Schema : AppEntity
{
public SchemaType Type { get; }
public SchemaType Type { get; init; }
public string Name { get; }
public string Name { get; init; }
public string? Category { get; private set; }
public string? Category { get; init; }
public bool IsPublished { get; private set; }
public bool IsPublished { get; init; }
public FieldCollection<RootField> FieldCollection { get; private set; } = FieldCollection<RootField>.Empty;
public FieldCollection<RootField> FieldCollection { get; init; } = FieldCollection<RootField>.Empty;
public FieldRules FieldRules { get; private set; } = FieldRules.Empty;
public FieldRules FieldRules { get; init; } = FieldRules.Empty;
public FieldNames FieldsInLists { get; private set; } = FieldNames.Empty;
public FieldNames FieldsInLists { get; init; } = FieldNames.Empty;
public FieldNames FieldsInReferences { get; private set; } = FieldNames.Empty;
public FieldNames FieldsInReferences { get; init; } = FieldNames.Empty;
public SchemaScripts Scripts { get; private set; } = new SchemaScripts();
public SchemaScripts Scripts { get; init; } = new SchemaScripts();
public SchemaProperties Properties { get; private set; } = new SchemaProperties();
public SchemaProperties Properties { get; init; } = new SchemaProperties();
public ReadonlyDictionary<string, string> PreviewUrls { get; private set; } = ReadonlyDictionary.Empty<string, string>();
public ReadonlyDictionary<string, string> PreviewUrls { get; init; } = ReadonlyDictionary.Empty<string, string>();
public long SchemaFieldsTotal { get; init; }
public IReadOnlyList<RootField> Fields
{
@ -50,126 +52,69 @@ public sealed class Schema
get => FieldCollection.ByName;
}
public Schema(string name, SchemaProperties? properties = null, SchemaType type = SchemaType.Default)
{
Guard.NotNullOrEmpty(name);
Name = name;
if (properties != null)
{
Properties = properties;
}
Type = type;
}
public Schema(string name, RootField[] fields, SchemaProperties? properties, bool isPublished = false, SchemaType type = SchemaType.Default)
: this(name, properties, type)
{
Guard.NotNull(fields);
FieldCollection = new FieldCollection<RootField>(fields);
IsPublished = isPublished;
}
[Pure]
public Schema Update(SchemaProperties? newProperties)
public Schema Update(SchemaProperties properties)
{
newProperties ??= new SchemaProperties();
Guard.NotNull(properties);
if (Properties.Equals(newProperties))
if (Properties.Equals(properties))
{
return this;
}
return Clone(clone =>
{
clone.Properties = newProperties;
});
return this with { Properties = properties };
}
[Pure]
public Schema SetScripts(SchemaScripts? newScripts)
public Schema SetScripts(SchemaScripts scripts)
{
newScripts ??= new SchemaScripts();
Guard.NotNull(scripts);
if (Scripts.Equals(newScripts))
if (Scripts.Equals(scripts))
{
return this;
}
return Clone(clone =>
{
clone.Scripts = newScripts;
});
return this with { Scripts = scripts };
}
[Pure]
public Schema SetFieldsInLists(FieldNames? names)
public Schema SetFieldsInLists(FieldNames names)
{
names ??= FieldNames.Empty;
Guard.NotNull(names);
if (FieldsInLists.SequenceEqual(names))
{
return this;
}
return Clone(clone =>
{
clone.FieldsInLists = names;
});
}
[Pure]
public Schema SetFieldsInLists(params string[] names)
{
return SetFieldsInLists(new FieldNames(names));
return this with { FieldsInLists = names };
}
[Pure]
public Schema SetFieldsInReferences(FieldNames? names)
public Schema SetFieldsInReferences(FieldNames names)
{
names ??= FieldNames.Empty;
Guard.NotNull(names);
if (FieldsInReferences.SequenceEqual(names))
{
return this;
}
return Clone(clone =>
{
clone.FieldsInReferences = names;
});
}
[Pure]
public Schema SetFieldsInReferences(params string[] names)
{
return SetFieldsInReferences(new FieldNames(names));
return this with { FieldsInReferences = names };
}
[Pure]
public Schema SetFieldRules(FieldRules? rules)
public Schema SetFieldRules(FieldRules rules)
{
rules ??= FieldRules.Empty;
Guard.NotNull(rules);
if (FieldRules.Equals(rules))
{
return this;
}
return Clone(clone =>
{
clone.FieldRules = rules;
});
}
[Pure]
public Schema SetFieldRules(params FieldRule[] rules)
{
return SetFieldRules(new FieldRules(rules));
return this with { FieldRules = rules };
}
[Pure]
@ -180,10 +125,7 @@ public sealed class Schema
return this;
}
return Clone(clone =>
{
clone.IsPublished = true;
});
return this with { IsPublished = true };
}
[Pure]
@ -194,10 +136,7 @@ public sealed class Schema
return this;
}
return Clone(clone =>
{
clone.IsPublished = false;
});
return this with { IsPublished = false };
}
[Pure]
@ -208,26 +147,20 @@ public sealed class Schema
return this;
}
return Clone(clone =>
{
clone.Category = category;
});
return this with { Category = category };
}
[Pure]
public Schema SetPreviewUrls(ReadonlyDictionary<string, string>? previewUrls)
public Schema SetPreviewUrls(ReadonlyDictionary<string, string> previewUrls)
{
previewUrls ??= ReadonlyDictionary.Empty<string, string>();
Guard.NotNull(previewUrls);
if (PreviewUrls.Equals(previewUrls))
{
return this;
}
return Clone(clone =>
{
clone.PreviewUrls = previewUrls;
});
return this with { PreviewUrls = previewUrls };
}
[Pure]
@ -238,12 +171,12 @@ public sealed class Schema
return this;
}
return Clone(clone =>
return this with
{
clone.FieldCollection = FieldCollection.Remove(fieldId);
clone.FieldsInLists = FieldsInLists.Remove(field.Name);
clone.FieldsInReferences = FieldsInReferences.Remove(field.Name);
});
FieldCollection = FieldCollection.Remove(fieldId),
FieldsInLists = FieldsInLists.Remove(field.Name),
FieldsInReferences = FieldsInReferences.Remove(field.Name)
};
}
[Pure]
@ -273,18 +206,6 @@ public sealed class Schema
return this;
}
return Clone(clone =>
{
clone.FieldCollection = newFields;
});
}
private Schema Clone(Action<Schema> updater)
{
var clone = (Schema)MemberwiseClone();
updater(clone);
return clone;
return this with { FieldCollection = newFields };
}
}

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

@ -12,6 +12,11 @@ namespace Squidex.Domain.Apps.Core.Schemas;
public static class SchemaExtensions
{
public static NamedId<DomainId> NamedId(this Schema schema)
{
return new NamedId<DomainId>(schema.Id, schema.Name);
}
public static long MaxId(this Schema schema)
{
var id = 0L;
@ -48,11 +53,6 @@ public static class SchemaExtensions
}
public static string DisplayName(this Schema schema)
{
return schema.Properties.Label.Or(schema.TypeName());
}
public static string DisplayNameUnchanged(this Schema schema)
{
return schema.Properties.Label.Or(schema.Name);
}

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

@ -62,13 +62,13 @@ public sealed record StringFieldProperties : FieldProperties
return visitor.Visit((IField<StringFieldProperties>)field, args);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return Fields.String(id, name, partitioning, this, settings);
return Fields.String(id, name, partitioning, this);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
return Fields.String(id, name, this, settings);
return Fields.String(id, name, this);
}
}

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

@ -37,13 +37,13 @@ public sealed record TagsFieldProperties : FieldProperties
return visitor.Visit((IField<TagsFieldProperties>)field, args);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return Fields.Tags(id, name, partitioning, this, settings);
return Fields.Tags(id, name, partitioning, this);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
return Fields.Tags(id, name, this, settings);
return Fields.Tags(id, name, this);
}
}

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

@ -21,13 +21,13 @@ public sealed record UIFieldProperties : FieldProperties
return visitor.Visit((IField<UIFieldProperties>)field, args);
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null)
public override RootField CreateRootField(long id, string name, Partitioning partitioning)
{
return new NestedField<UIFieldProperties>(id, name, this, settings);
return Fields.UI(id, name, partitioning, this);
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null)
public override NestedField CreateNestedField(long id, string name)
{
return new RootField<UIFieldProperties>(id, name, partitioning, this, settings);
return Fields.UI(id, name, this);
}
}

58
backend/src/Squidex.Domain.Apps.Core.Model/Teams/Team.cs

@ -0,0 +1,58 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Diagnostics.Contracts;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Core.Teams;
public record Team : Entity
{
public string Name { get; init; }
public Contributors Contributors { get; init; } = Contributors.Empty;
public AssignedPlan? Plan { get; init; }
[Pure]
public Team Rename(string name)
{
Guard.NotNull(name);
if (string.Equals(Name, name, StringComparison.Ordinal))
{
return this;
}
return this with { Name = name };
}
[Pure]
public Team ChangePlan(AssignedPlan? plan)
{
if (Equals(plan?.PlanId, Plan?.PlanId))
{
return this;
}
return this with { Plan = plan };
}
[Pure]
public Team UpdateContributors<T>(T state, Func<T, Contributors, Contributors> update)
{
var newContributors = update(state, Contributors);
if (ReferenceEquals(newContributors, Contributors))
{
return this;
}
return this with { Contributors = newContributors };
}
}

14
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/AddDefaultValues.cs

@ -22,6 +22,8 @@ public sealed class AddDefaultValues : IContentDataConverter, IContentItemConver
public bool IgnoreNonMasterFields { get; init; }
public HashSet<string>? FieldNames { get; init; }
public AddDefaultValues(PartitionResolver partitionResolver, IClock? clock = null)
{
this.partitionResolver = partitionResolver;
@ -33,6 +35,12 @@ public sealed class AddDefaultValues : IContentDataConverter, IContentItemConver
{
foreach (var field in schema.Fields)
{
// If the fields are set, we only enrich the given matching field names.
if (FieldNames?.Contains(field.Name) == false)
{
continue;
}
if (data.TryGetValue(field.Name, out var fieldData) && fieldData != null)
{
continue;
@ -53,6 +61,12 @@ public sealed class AddDefaultValues : IContentDataConverter, IContentItemConver
foreach (var partitionKey in partitioning.AllKeys)
{
// If the fields are set, we only enrich the given matching field names.
if (FieldNames?.Contains(field.Name) == false)
{
continue;
}
if (!partitioning.IsMaster(partitionKey) && IgnoreNonMasterFields)
{
continue;

2
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs

@ -46,7 +46,7 @@ public static class ContentConverterFlat
private static object? GetFirst(ContentFieldData? fieldData)
{
if (fieldData == null || fieldData.Count == 0)
if (fieldData is not { Count: > 0 })
{
return null;
}

2
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveAssetUrls.cs

@ -21,7 +21,7 @@ public sealed class ResolveAssetUrls : IContentValueConverter
{
this.appId = appId;
if (fields == null || fields.Count == 0)
if (fields is not { Count: > 0 })
{
shouldHandle = (field, parent) => false;
}

8
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveLanguages.cs

@ -18,6 +18,8 @@ public sealed class ResolveLanguages : IContentFieldConverter
public bool ResolveFallback { get; init; }
public HashSet<string>? FieldNames { get; init; }
public ResolveLanguages(LanguagesConfig languages, params Language[] filteredLanguages)
{
HashSet<string> languageCodes;
@ -44,6 +46,12 @@ public sealed class ResolveLanguages : IContentFieldConverter
public ContentFieldData? ConvertFieldAfter(IRootField field, ContentFieldData source)
{
if (FieldNames?.Contains(field.Name) == false)
{
// If the fields are set, we only enrich the given matching field names.
return source;
}
if (!field.Partitioning.Equals(Partitioning.Language))
{
return source;

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

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.ObjectModel;
using NJsonSchema;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;

8
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/JobResult.cs

@ -7,7 +7,6 @@
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.HandleRules;
@ -53,8 +52,6 @@ public sealed record JobResult
SkipReason = SkipReason.WrongEventForTrigger
};
public DomainId RuleId { get; set; }
public Rule? Rule { get; init; }
public RuleJob? Job { get; init; }
@ -67,6 +64,11 @@ public sealed record JobResult
public int Offset { get; set; }
public static JobResult Skipped(Rule rule, SkipReason reason)
{
return new JobResult { Rule = rule, SkipReason = reason };
}
public static JobResult ConditionDoesNotMatch(EnrichedEvent? enrichedEvent = null, RuleJob? job = null)
{
return new JobResult

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

@ -32,8 +32,6 @@ public readonly struct RuleContext
{
public NamedId<DomainId> AppId { get; init; }
public DomainId RuleId { get; init; }
public Rule Rule { get; init; }
public bool IncludeSkipped { get; init; }
@ -49,7 +47,7 @@ public readonly struct RuleContext
IncludeStale = IncludeStale,
Rules = new Dictionary<DomainId, Rule>
{
[RuleId] = Rule
[Rule.Id] = Rule
}.ToReadonlyDictionary()
};
}

98
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs

@ -38,8 +38,6 @@ public sealed class RuleService : IRuleService
public Rule Rule { get; init; }
public DomainId RuleId;
public IRuleActionHandler ActionHandler;
}
@ -115,7 +113,7 @@ public sealed class RuleService : IRuleService
continue;
}
job = await CreateJobAsync(enrichedEvent, actionHandler, context.RuleId, context.Rule, now);
job = await CreateJobAsync(enrichedEvent, actionHandler, context.Rule, now);
job.Offset = offset++;
}
catch (Exception ex)
@ -133,11 +131,7 @@ public sealed class RuleService : IRuleService
Guard.NotNull(@event);
// Each rule can has its own errors.
var states = context.Rules.Select(x => new RuleState
{
Rule = x.Value,
RuleId = x.Key
}).ToList();
var states = context.Rules.Select(x => new RuleState { Rule = x.Value }).ToList();
var allResults =
CreateJobs(@event, context, states, ct)
@ -145,13 +139,7 @@ public sealed class RuleService : IRuleService
{
log.LogError(ex, "Failed to create rule job.");
return states.Select(state =>
new JobResult
{
Rule = state.Rule,
RuleId = state.RuleId,
SkipReason = SkipReason.Failed,
});
return states.Select(state => JobResult.Skipped(state.Rule, SkipReason.Failed));
});
return allResults;
@ -164,12 +152,7 @@ public sealed class RuleService : IRuleService
{
foreach (var state in states)
{
yield return new JobResult
{
Rule = state.Rule,
RuleId = state.RuleId,
SkipReason = SkipReason.WrongEvent
};
yield return JobResult.Skipped(state.Rule, SkipReason.WrongEvent);
}
yield break;
@ -190,12 +173,7 @@ public sealed class RuleService : IRuleService
{
foreach (var state in states)
{
yield return new JobResult
{
Rule = state.Rule,
RuleId = state.RuleId,
SkipReason = SkipReason.FromRule
};
yield return JobResult.Skipped(state.Rule, SkipReason.FromRule);
}
yield break;
@ -222,12 +200,7 @@ public sealed class RuleService : IRuleService
{
foreach (var state in states)
{
yield return new JobResult
{
Rule = state.Rule,
RuleId = state.RuleId,
SkipReason = SkipReason.TooOld
};
yield return JobResult.Skipped(state.Rule, SkipReason.TooOld);
}
yield break;
@ -248,13 +221,7 @@ public sealed class RuleService : IRuleService
}
else
{
yield return new JobResult
{
Rule = state.Rule,
RuleId = state.RuleId,
SkipReason = SkipReason.Disabled
};
yield return JobResult.Skipped(state.Rule, SkipReason.Disabled);
continue;
}
}
@ -263,37 +230,19 @@ public sealed class RuleService : IRuleService
if (!ruleTriggerHandlers.TryGetValue(rule.Trigger.GetType(), out var triggerHandler))
{
yield return new JobResult
{
Rule = state.Rule,
RuleId = state.RuleId,
SkipReason = SkipReason.NoTrigger
};
yield return JobResult.Skipped(state.Rule, SkipReason.NoTrigger);
continue;
}
if (!triggerHandler.Handles(typed.Payload))
{
yield return new JobResult
{
Rule = state.Rule,
RuleId = state.RuleId,
SkipReason = SkipReason.WrongEventForTrigger
};
yield return JobResult.Skipped(state.Rule, SkipReason.WrongEventForTrigger);
continue;
}
if (!ruleActionHandlers.TryGetValue(actionType, out state.ActionHandler!))
{
yield return new JobResult
{
Rule = state.Rule,
RuleId = state.RuleId,
SkipReason = SkipReason.NoAction
};
yield return JobResult.Skipped(state.Rule, SkipReason.NoAction);
continue;
}
@ -305,13 +254,7 @@ public sealed class RuleService : IRuleService
}
else
{
yield return new JobResult
{
Rule = state.Rule,
RuleId = state.RuleId,
SkipReason = SkipReason.ConditionPrecheckDoesNotMatch
};
yield return JobResult.Skipped(state.Rule, SkipReason.ConditionPrecheckDoesNotMatch);
continue;
}
}
@ -333,13 +276,7 @@ public sealed class RuleService : IRuleService
{
log.LogError(ex, "Failed to create rule jobs from trigger.");
return states.Select(state =>
new JobResult
{
Rule = state.Rule,
RuleId = state.RuleId,
SkipReason = SkipReason.Failed,
});
return states.Select(state => JobResult.Skipped(state.Rule, SkipReason.Failed));
});
await foreach (var result in triggerResults.WithCancellation(ct))
@ -371,7 +308,6 @@ public sealed class RuleService : IRuleService
EnrichedEvent = enrichedEvent,
EnrichmentError = ex,
Rule = state.Rule,
RuleId = state.RuleId,
SkipReason = SkipReason.Failed,
});
});
@ -410,8 +346,8 @@ public sealed class RuleService : IRuleService
yield return new JobResult
{
EnrichedEvent = enrichedEvent,
EnrichmentError = null,
Rule = state.Rule,
RuleId = state.RuleId,
SkipReason = SkipReason.ConditionDoesNotMatch
};
@ -419,7 +355,7 @@ public sealed class RuleService : IRuleService
}
}
var result = await CreateJobAsync(enrichedEvent, state.ActionHandler, state.RuleId, state.Rule, now);
var result = await CreateJobAsync(enrichedEvent, state.ActionHandler, state.Rule, now);
// If the conditions matchs, we can skip creating a new object and save a few allocations.
if (skipped != SkipReason.None)
@ -431,7 +367,7 @@ public sealed class RuleService : IRuleService
}
}
private async Task<JobResult> CreateJobAsync(EnrichedEvent enrichedEvent, IRuleActionHandler actionHandler, DomainId ruleId, Rule rule, Instant now)
private async Task<JobResult> CreateJobAsync(EnrichedEvent enrichedEvent, IRuleActionHandler actionHandler, Rule rule, Instant now)
{
var actionType = rule.Action.GetType();
var actionName = typeRegistry.GetName<RuleAction>(actionType);
@ -448,7 +384,7 @@ public sealed class RuleService : IRuleService
EventName = enrichedEvent.Name,
ExecutionPartition = enrichedEvent.Partition,
Expires = expires,
RuleId = ruleId
RuleId = rule.Id
};
try
@ -464,8 +400,8 @@ public sealed class RuleService : IRuleService
return new JobResult
{
EnrichedEvent = enrichedEvent,
EnrichmentError = null,
Rule = rule,
RuleId = ruleId,
Job = job,
};
}

1
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetCommandScriptVars.cs

@ -6,7 +6,6 @@
// ==========================================================================
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Scripting.Internal;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Scripting;

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

@ -8,7 +8,6 @@
using Jint;
using Jint.Native;
using Jint.Runtime;
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper;

17
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/RootContext.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System.Collections.Concurrent;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
@ -23,9 +24,7 @@ public sealed class RootContext
public DomainId ContentId { get; }
public NamedId<DomainId> AppId { get; }
public NamedId<DomainId> SchemaId { get; }
public App App { get; }
public Schema Schema { get; }
@ -36,20 +35,14 @@ public sealed class RootContext
get => errors;
}
public RootContext(
IJsonSerializer serializer,
NamedId<DomainId> appId,
NamedId<DomainId> schemaId,
Schema schema,
DomainId contentId,
ResolvedComponents components)
public RootContext(App app, Schema schema, DomainId contentId, ResolvedComponents components,
IJsonSerializer serializer)
{
AppId = appId;
App = app;
Components = components;
ContentId = contentId;
Serializer = serializer;
Schema = schema;
SchemaId = schemaId;
}
public void AddError(IEnumerable<string> path, string message)

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

@ -20,7 +20,7 @@ public sealed class AggregateValidator : IValidator
public void Validate(object? value, ValidationContext context)
{
if (validators == null || validators.Length == 0)
if (validators is not { Length: > 0 })
{
return;
}

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

@ -15,7 +15,7 @@ using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators;
public delegate Task<IReadOnlyList<IAssetInfo>> CheckAssets(IEnumerable<DomainId> ids,
public delegate Task<IReadOnlyList<Asset>> CheckAssets(IEnumerable<DomainId> ids,
CancellationToken ct);
public sealed class AssetsValidator : IValidator
@ -63,7 +63,7 @@ public sealed class AssetsValidator : IValidator
foreach (var assetId in assetIds)
{
var assetPath = context.Path.Enqueue($"[{index}]");
var assetItem = assets.FirstOrDefault(x => x.AssetId == assetId);
var assetItem = assets.FirstOrDefault(x => x.Id == assetId);
if (assetItem == null)
{
@ -75,7 +75,7 @@ public sealed class AssetsValidator : IValidator
continue;
}
foundIds.Add(assetItem.AssetId);
foundIds.Add(assetItem.Id);
ValidateCommon(assetItem, assetPath, context);
ValidateType(assetItem, assetPath, context);
@ -116,7 +116,7 @@ public sealed class AssetsValidator : IValidator
}
}
private void ValidateCommon(IAssetInfo asset, ImmutableQueue<string> path, ValidationContext context)
private void ValidateCommon(Asset asset, ImmutableQueue<string> path, ValidationContext context)
{
if (properties.MinSize != null && asset.FileSize < properties.MinSize)
{
@ -138,7 +138,7 @@ public sealed class AssetsValidator : IValidator
}
}
private void ValidateType(IAssetInfo asset, ImmutableQueue<string> path, ValidationContext context)
private void ValidateType(Asset asset, ImmutableQueue<string> path, ValidationContext context)
{
var type = asset.MimeType == "image/svg+xml" ? AssetType.Image : asset.Type;

4
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs

@ -7,13 +7,13 @@
using MongoDB.Bson.Serialization.Attributes;
using NodaTime;
using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps;
public sealed class MongoAppEntity : MongoState<AppDomainObject.State>
public sealed class MongoAppEntity : MongoState<App>
{
[BsonRequired]
[BsonElement("_an")]

24
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs

@ -6,21 +6,25 @@
// ==========================================================================
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps;
public sealed class MongoAppRepository : MongoSnapshotStoreBase<AppDomainObject.State, MongoAppEntity>, IAppRepository, IDeleter
public sealed class MongoAppRepository : MongoSnapshotStoreBase<App, MongoAppEntity>, IAppRepository, IDeleter
{
public MongoAppRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "States_Apps";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection,
CancellationToken ct)
{
@ -38,13 +42,13 @@ public sealed class MongoAppRepository : MongoSnapshotStoreBase<AppDomainObject.
}, ct);
}
Task IDeleter.DeleteAppAsync(IAppEntity app,
Task IDeleter.DeleteAppAsync(App app,
CancellationToken ct)
{
return Collection.DeleteManyAsync(Filter.Eq(x => x.DocumentId, app.Id), ct);
}
public async Task<List<IAppEntity>> QueryAllAsync(string contributorId, IEnumerable<string> names,
public async Task<List<App>> QueryAllAsync(string contributorId, IEnumerable<string> names,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAppRepository/QueryAllAsync"))
@ -57,7 +61,7 @@ public sealed class MongoAppRepository : MongoSnapshotStoreBase<AppDomainObject.
}
}
public async Task<List<IAppEntity>> QueryAllAsync(DomainId teamId,
public async Task<List<App>> QueryAllAsync(DomainId teamId,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAppRepository/QueryAllAsync"))
@ -70,7 +74,7 @@ public sealed class MongoAppRepository : MongoSnapshotStoreBase<AppDomainObject.
}
}
public async Task<IAppEntity?> FindAsync(DomainId id,
public async Task<App?> FindAsync(DomainId id,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAppRepository/FindAsync"))
@ -83,7 +87,7 @@ public sealed class MongoAppRepository : MongoSnapshotStoreBase<AppDomainObject.
}
}
public async Task<IAppEntity?> FindAsync(string name,
public async Task<App?> FindAsync(string name,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAppRepository/FindAsyncByName"))
@ -96,9 +100,9 @@ public sealed class MongoAppRepository : MongoSnapshotStoreBase<AppDomainObject.
}
}
private static List<IAppEntity> RemoveDuplcateNames(List<MongoAppEntity> entities)
private static List<App> RemoveDuplcateNames(List<MongoAppEntity> entities)
{
var byName = new Dictionary<string, IAppEntity>();
var byName = new Dictionary<string, App>();
// Remove duplicate names, the latest wins.
foreach (var entity in entities.OrderBy(x => x.IndexedCreated))

26
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/AssetItemClassMap.cs

@ -0,0 +1,26 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using MongoDB.Bson.Serialization;
using Squidex.Domain.Apps.Core.Assets;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets;
internal static class AssetItemClassMap
{
public static void Register()
{
BsonClassMap.TryRegisterClassMap<AssetItem>(cm =>
{
cm.MapProperty(x => x.ParentId)
.SetElementName("pi")
.SetIgnoreIfDefault(true);
});
EntityClassMap.Register();
}
}

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

@ -5,11 +5,8 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using MongoDB.Bson.Serialization.Attributes;
using NodaTime;
using MongoDB.Bson.Serialization;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.DomainObject;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Reflection;
@ -17,118 +14,91 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets;
public sealed class MongoAssetEntity : IAssetEntity, IVersionedEntity<DomainId>
public record MongoAssetEntity : Asset, IVersionedEntity<DomainId>
{
[BsonId]
[BsonElement("_id")]
public DomainId DocumentId { get; set; }
[BsonRequired]
[BsonElement("_ai")]
public DomainId IndexedAppId { get; set; }
[BsonIgnoreIfDefault]
[BsonElement("id")]
public DomainId Id { get; set; }
[BsonIgnoreIfDefault]
[BsonElement("pi")]
public DomainId ParentId { get; set; }
[BsonRequired]
[BsonElement("ai")]
public NamedId<DomainId> AppId { get; set; }
[BsonRequired]
[BsonElement("ct")]
public Instant Created { get; set; }
[BsonRequired]
[BsonElement("mt")]
public Instant LastModified { get; set; }
[BsonRequired]
[BsonElement("mm")]
public string MimeType { get; set; }
[BsonRequired]
[BsonElement("fn")]
public string FileName { get; set; }
[BsonIgnoreIfDefault]
[BsonElement("fh")]
public string FileHash { get; set; }
[BsonIgnoreIfDefault]
[BsonElement("sl")]
public string Slug { get; set; }
[BsonRequired]
[BsonElement("fs")]
public long FileSize { get; set; }
[BsonRequired]
[BsonElement("fv")]
public long FileVersion { get; set; }
[BsonRequired]
[BsonElement("vs")]
public long Version { get; set; }
[BsonIgnoreIfDefault]
[BsonElement("ts")]
public long TotalSize { get; set; }
[BsonRequired]
[BsonElement("at")]
public AssetType Type { get; set; }
[BsonRequired]
[BsonElement("cb")]
public RefToken CreatedBy { get; set; }
[BsonRequired]
[BsonElement("mb")]
public RefToken LastModifiedBy { get; set; }
[BsonIgnoreIfNull]
[BsonElement("td")]
public HashSet<string> Tags { get; set; }
[BsonIgnoreIfDefault]
[BsonElement("pt")]
public bool IsProtected { get; set; }
[BsonRequired]
[BsonElement("dl")]
public bool IsDeleted { get; set; }
[BsonRequired]
[BsonElement("md")]
public AssetMetadata Metadata { get; set; }
public DomainId AssetId
{
get => Id;
}
public DomainId UniqueId
public static void RegisterClassMap()
{
get => DocumentId;
BsonClassMap.TryRegisterClassMap<MongoAssetEntity>(cm =>
{
cm.MapProperty(x => x.DocumentId)
.SetElementName("_id")
.SetIsRequired(true);
cm.MapProperty(x => x.IndexedAppId)
.SetElementName("_ai")
.SetIsRequired(true);
});
BsonClassMap.TryRegisterClassMap<Asset>(cm =>
{
cm.MapProperty(x => x.FileName)
.SetElementName("fn")
.SetIsRequired(true);
cm.MapProperty(x => x.FileHash)
.SetElementName("fh")
.SetIsRequired(true);
cm.MapProperty(x => x.FileSize)
.SetElementName("fs")
.SetIsRequired(true);
cm.MapProperty(x => x.FileVersion)
.SetElementName("fv")
.SetIsRequired(true);
cm.MapProperty(x => x.IsProtected)
.SetElementName("pt")
.SetIgnoreIfDefault(true);
cm.MapProperty(x => x.Metadata)
.SetElementName("md")
.SetIsRequired(true);
cm.MapProperty(x => x.MimeType)
.SetElementName("mm")
.SetIsRequired(true);
cm.MapProperty(x => x.Slug)
.SetElementName("sl")
.SetIsRequired(true);
cm.MapProperty(x => x.Tags)
.SetElementName("td")
.SetIgnoreIfNull(true);
cm.MapProperty(x => x.TotalSize)
.SetElementName("ts")
.SetIsRequired(true);
cm.MapProperty(x => x.Type)
.SetElementName("at")
.SetIsRequired(true);
});
AssetItemClassMap.Register();
}
public AssetDomainObject.State ToState()
public Asset ToState()
{
return SimpleMapper.Map(this, new AssetDomainObject.State());
return this;
}
public static MongoAssetEntity Create(SnapshotWriteJob<AssetDomainObject.State> job)
public static MongoAssetEntity Create(SnapshotWriteJob<Asset> job)
{
var entity = SimpleMapper.Map(job.Value, new MongoAssetEntity());
entity.DocumentId = job.Key;
entity.IndexedAppId = job.Value.AppId.Id;
return entity;
var entity = new MongoAssetEntity
{
DocumentId = job.Key,
// Both version and ID cannot be changed by the mapper method anymore.
Version = job.NewVersion,
// Use an app ID without the name to reduce the memory usage of the index.
IndexedAppId = job.Value.AppId.Id,
};
return SimpleMapper.Map(job.Value, entity);
}
}

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

@ -5,10 +5,8 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using MongoDB.Bson.Serialization.Attributes;
using NodaTime;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.DomainObject;
using MongoDB.Bson.Serialization;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Reflection;
@ -16,73 +14,51 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets;
public sealed class MongoAssetFolderEntity : IAssetFolderEntity, IVersionedEntity<DomainId>
public record MongoAssetFolderEntity : AssetFolder, IVersionedEntity<DomainId>
{
[BsonId]
[BsonElement("_id")]
public DomainId DocumentId { get; set; }
[BsonRequired]
[BsonElement("_ai")]
public DomainId IndexedAppId { get; set; }
[BsonRequired]
[BsonElement("id")]
public DomainId Id { get; set; }
[BsonRequired]
[BsonElement("pi")]
public DomainId ParentId { get; set; }
[BsonRequired]
[BsonElement("ai")]
public NamedId<DomainId> AppId { get; set; }
[BsonRequired]
[BsonElement("ct")]
public Instant Created { get; set; }
[BsonRequired]
[BsonElement("mt")]
public Instant LastModified { get; set; }
[BsonRequired]
[BsonElement("fn")]
public string FolderName { get; set; }
[BsonRequired]
[BsonElement("vs")]
public long Version { get; set; }
[BsonRequired]
[BsonElement("cb")]
public RefToken CreatedBy { get; set; }
[BsonRequired]
[BsonElement("mb")]
public RefToken LastModifiedBy { get; set; }
[BsonRequired]
[BsonElement("dl")]
public bool IsDeleted { get; set; }
public DomainId UniqueId
public static void RegisterClassMap()
{
get => DocumentId;
BsonClassMap.TryRegisterClassMap<MongoAssetFolderEntity>(cm =>
{
cm.MapProperty(x => x.DocumentId)
.SetElementName("_id")
.SetIsRequired(true);
cm.MapProperty(x => x.IndexedAppId)
.SetElementName("_ai")
.SetIsRequired(true);
});
BsonClassMap.TryRegisterClassMap<AssetFolder>(cm =>
{
cm.MapProperty(x => x.FolderName)
.SetElementName("fn")
.SetIsRequired(true);
});
AssetItemClassMap.Register();
}
public AssetFolderDomainObject.State ToState()
public AssetFolder ToState()
{
return SimpleMapper.Map(this, new AssetFolderDomainObject.State());
return this;
}
public static MongoAssetFolderEntity Create(SnapshotWriteJob<AssetFolderDomainObject.State> job)
public static MongoAssetFolderEntity Create(SnapshotWriteJob<AssetFolder> job)
{
var entity = SimpleMapper.Map(job.Value, new MongoAssetFolderEntity());
entity.DocumentId = job.Key;
entity.IndexedAppId = job.Value.AppId.Id;
return entity;
var entity = new MongoAssetFolderEntity
{
DocumentId = job.Key,
// Both version and ID cannot be changed by the mapper method anymore.
Version = job.NewVersion,
// Use an app ID without the name to reduce the memory usage of the index.
IndexedAppId = job.Value.AppId.Id,
};
return SimpleMapper.Map(job.Value, entity);
}
}

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

@ -6,7 +6,7 @@
// ==========================================================================
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
@ -15,6 +15,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets;
public sealed partial class MongoAssetFolderRepository : MongoRepositoryBase<MongoAssetFolderEntity>, IAssetFolderRepository
{
static MongoAssetFolderRepository()
{
MongoAssetFolderEntity.RegisterClassMap();
}
public MongoAssetFolderRepository(IMongoDatabase database)
: base(database)
{
@ -38,7 +43,7 @@ public sealed partial class MongoAssetFolderRepository : MongoRepositoryBase<Mon
}, ct);
}
public async Task<IResultList<IAssetFolderEntity>> QueryAsync(DomainId appId, DomainId? parentId,
public async Task<IResultList<AssetFolder>> QueryAsync(DomainId appId, DomainId? parentId,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/QueryAsync"))
@ -49,7 +54,7 @@ public sealed partial class MongoAssetFolderRepository : MongoRepositoryBase<Mon
await Collection.Find(filter).SortBy(x => x.FolderName)
.ToListAsync(ct);
return ResultList.Create<IAssetFolderEntity>(assetFolderEntities.Count, assetFolderEntities);
return ResultList.Create<AssetFolder>(assetFolderEntities.Count, assetFolderEntities);
}
}
@ -70,7 +75,7 @@ public sealed partial class MongoAssetFolderRepository : MongoRepositoryBase<Mon
}
}
public async Task<IAssetFolderEntity?> FindAssetFolderAsync(DomainId appId, DomainId id,
public async Task<AssetFolder?> FindAssetFolderAsync(DomainId appId, DomainId id,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/FindAssetFolderAsync"))

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

@ -6,33 +6,34 @@
// ==========================================================================
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets.DomainObject;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Translations;
#pragma warning disable MA0048 // File name must match type name
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets;
public sealed partial class MongoAssetFolderRepository : ISnapshotStore<AssetFolderDomainObject.State>, IDeleter
public sealed partial class MongoAssetFolderRepository : ISnapshotStore<AssetFolder>, IDeleter
{
Task IDeleter.DeleteAppAsync(IAppEntity app,
Task IDeleter.DeleteAppAsync(App app,
CancellationToken ct)
{
return Collection.DeleteManyAsync(Filter.Eq(x => x.IndexedAppId, app.Id), ct);
}
IAsyncEnumerable<SnapshotResult<AssetFolderDomainObject.State>> ISnapshotStore<AssetFolderDomainObject.State>.ReadAllAsync(
IAsyncEnumerable<SnapshotResult<AssetFolder>> ISnapshotStore<AssetFolder>.ReadAllAsync(
CancellationToken ct)
{
var documents = Collection.Find(FindAll, Batching.Options).ToAsyncEnumerable(ct);
return documents.Select(x => new SnapshotResult<AssetFolderDomainObject.State>(x.DocumentId, x.ToState(), x.Version, true));
return documents.Select(x => new SnapshotResult<AssetFolder>(x.DocumentId, x.ToState(), x.Version, true));
}
async Task<SnapshotResult<AssetFolderDomainObject.State>> ISnapshotStore<AssetFolderDomainObject.State>.ReadAsync(DomainId key,
async Task<SnapshotResult<AssetFolder>> ISnapshotStore<AssetFolder>.ReadAsync(DomainId key,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/ReadAsync"))
@ -43,25 +44,25 @@ public sealed partial class MongoAssetFolderRepository : ISnapshotStore<AssetFol
if (existing != null)
{
return new SnapshotResult<AssetFolderDomainObject.State>(existing.DocumentId, existing.ToState(), existing.Version);
return new SnapshotResult<AssetFolder>(existing.DocumentId, existing.ToState(), existing.Version);
}
return new SnapshotResult<AssetFolderDomainObject.State>(default, null!, EtagVersion.Empty);
return new SnapshotResult<AssetFolder>(default, null!, EtagVersion.Empty);
}
}
async Task ISnapshotStore<AssetFolderDomainObject.State>.WriteAsync(SnapshotWriteJob<AssetFolderDomainObject.State> job,
async Task ISnapshotStore<AssetFolder>.WriteAsync(SnapshotWriteJob<AssetFolder> job,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/WriteAsync"))
{
var entityJob = job.As(MongoAssetFolderEntity.Create(job));
await Collection.UpsertVersionedAsync(entityJob, ct);
await Collection.UpsertVersionedAsync(entityJob, Field.Of<AssetFolder>(x => nameof(x.Version)), ct);
}
}
async Task ISnapshotStore<AssetFolderDomainObject.State>.WriteManyAsync(IEnumerable<SnapshotWriteJob<AssetFolderDomainObject.State>> jobs,
async Task ISnapshotStore<AssetFolder>.WriteManyAsync(IEnumerable<SnapshotWriteJob<AssetFolder>> jobs,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/WriteManyAsync"))
@ -83,7 +84,7 @@ public sealed partial class MongoAssetFolderRepository : ISnapshotStore<AssetFol
}
}
async Task ISnapshotStore<AssetFolderDomainObject.State>.RemoveAsync(DomainId key,
async Task ISnapshotStore<AssetFolder>.RemoveAsync(DomainId key,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/RemoveAsync"))

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

@ -8,7 +8,7 @@
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors;
using Squidex.Infrastructure;
@ -22,6 +22,11 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
{
private readonly MongoCountCollection countCollection;
static MongoAssetRepository()
{
MongoAssetEntity.RegisterClassMap();
}
public MongoAssetRepository(IMongoDatabase database, ILogger<MongoAssetRepository> log)
: base(database)
{
@ -66,7 +71,7 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
}, ct);
}
public async IAsyncEnumerable<IAssetEntity> StreamAll(DomainId appId,
public async IAsyncEnumerable<Asset> StreamAll(DomainId appId,
[EnumeratorCancellation] CancellationToken ct = default)
{
var find = Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted);
@ -83,7 +88,7 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
}
}
public async Task<IResultList<IAssetEntity>> QueryAsync(DomainId appId, DomainId? parentId, Q q,
public async Task<IResultList<Asset>> QueryAsync(DomainId appId, DomainId? parentId, Q q,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/QueryAsync"))
@ -117,7 +122,7 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
}
}
return ResultList.Create(assetTotal, assetEntities.OfType<IAssetEntity>());
return ResultList.Create(assetTotal, assetEntities.OfType<Asset>());
}
else
{
@ -153,7 +158,7 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
}
}
return ResultList.Create<IAssetEntity>(assetTotal, assetEntities);
return ResultList.Create<Asset>(assetTotal, assetEntities);
}
}
catch (MongoQueryException ex) when (ex.Message.Contains("17406", StringComparison.Ordinal))
@ -193,7 +198,7 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
}
}
public async Task<IAssetEntity?> FindAssetByHashAsync(DomainId appId, string hash, string fileName, long fileSize,
public async Task<Asset?> FindAssetByHashAsync(DomainId appId, string hash, string fileName, long fileSize,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/FindAssetByHashAsync"))
@ -206,7 +211,7 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
}
}
public async Task<IAssetEntity?> FindAssetBySlugAsync(DomainId appId, string slug, bool allowDeleted,
public async Task<Asset?> FindAssetBySlugAsync(DomainId appId, string slug, bool allowDeleted,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/FindAssetBySlugAsync"))
@ -219,7 +224,7 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
}
}
public async Task<IAssetEntity?> FindAssetAsync(DomainId appId, DomainId id, bool allowDeleted,
public async Task<Asset?> FindAssetAsync(DomainId appId, DomainId id, bool allowDeleted,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/FindAssetAsync"))
@ -232,7 +237,7 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase<MongoAsse
}
}
public async Task<IAssetEntity?> FindAssetAsync(DomainId id,
public async Task<Asset?> FindAssetAsync(DomainId id,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/FindAssetAsync"))

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

@ -6,8 +6,8 @@
// ==========================================================================
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets.DomainObject;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
@ -16,23 +16,23 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets;
public sealed partial class MongoAssetRepository : ISnapshotStore<AssetDomainObject.State>, IDeleter
public sealed partial class MongoAssetRepository : ISnapshotStore<Asset>, IDeleter
{
Task IDeleter.DeleteAppAsync(IAppEntity app,
Task IDeleter.DeleteAppAsync(App app,
CancellationToken ct)
{
return Collection.DeleteManyAsync(Filter.Eq(x => x.IndexedAppId, app.Id), ct);
}
IAsyncEnumerable<SnapshotResult<AssetDomainObject.State>> ISnapshotStore<AssetDomainObject.State>.ReadAllAsync(
IAsyncEnumerable<SnapshotResult<Asset>> ISnapshotStore<Asset>.ReadAllAsync(
CancellationToken ct)
{
var documents = Collection.Find(FindAll, Batching.Options).ToAsyncEnumerable(ct);
return documents.Select(x => new SnapshotResult<AssetDomainObject.State>(x.DocumentId, x.ToState(), x.Version));
return documents.Select(x => new SnapshotResult<Asset>(x.DocumentId, x.ToState(), x.Version));
}
async Task<SnapshotResult<AssetDomainObject.State>> ISnapshotStore<AssetDomainObject.State>.ReadAsync(DomainId key,
async Task<SnapshotResult<Asset>> ISnapshotStore<Asset>.ReadAsync(DomainId key,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/ReadAsync"))
@ -43,25 +43,25 @@ public sealed partial class MongoAssetRepository : ISnapshotStore<AssetDomainObj
if (existing != null)
{
return new SnapshotResult<AssetDomainObject.State>(existing.DocumentId, existing.ToState(), existing.Version);
return new SnapshotResult<Asset>(existing.DocumentId, existing.ToState(), existing.Version);
}
return new SnapshotResult<AssetDomainObject.State>(default, null!, EtagVersion.Empty);
return new SnapshotResult<Asset>(default, null!, EtagVersion.Empty);
}
}
async Task ISnapshotStore<AssetDomainObject.State>.WriteAsync(SnapshotWriteJob<AssetDomainObject.State> job,
async Task ISnapshotStore<Asset>.WriteAsync(SnapshotWriteJob<Asset> job,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/WriteAsync"))
{
var entityJob = job.As(MongoAssetEntity.Create(job));
await Collection.UpsertVersionedAsync(entityJob, ct);
await Collection.UpsertVersionedAsync(entityJob, Field.Of<Asset>(x => nameof(x.Version)), ct);
}
}
async Task ISnapshotStore<AssetDomainObject.State>.WriteManyAsync(IEnumerable<SnapshotWriteJob<AssetDomainObject.State>> jobs,
async Task ISnapshotStore<Asset>.WriteManyAsync(IEnumerable<SnapshotWriteJob<Asset>> jobs,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/WriteManyAsync"))
@ -83,7 +83,7 @@ public sealed partial class MongoAssetRepository : ISnapshotStore<AssetDomainObj
}
}
async Task ISnapshotStore<AssetDomainObject.State>.RemoveAsync(DomainId key,
async Task ISnapshotStore<Asset>.RemoveAsync(DomainId key,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/RemoveAsync"))

34
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs

@ -8,11 +8,11 @@
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using NodaTime;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries;
@ -108,19 +108,19 @@ public sealed class MongoContentCollection : MongoRepositoryBase<MongoContentEnt
cancellationToken: ct);
}
public IAsyncEnumerable<IContentEntity> StreamAll(DomainId appId, HashSet<DomainId>? schemaIds,
public IAsyncEnumerable<Content> StreamAll(DomainId appId, HashSet<DomainId>? schemaIds,
CancellationToken ct)
{
return queryAsStream.StreamAll(appId, schemaIds, ct);
}
public IAsyncEnumerable<IContentEntity> StreamReferencing(DomainId appId, DomainId reference, int take,
public IAsyncEnumerable<Content> StreamReferencing(DomainId appId, DomainId reference, int take,
CancellationToken ct)
{
return queryReferrers.StreamReferencing(appId, reference, take, ct);
}
public IAsyncEnumerable<IContentEntity> QueryScheduledWithoutDataAsync(Instant now,
public IAsyncEnumerable<Content> QueryScheduledWithoutDataAsync(Instant now,
CancellationToken ct)
{
return queryScheduled.QueryAsync(now, ct);
@ -135,7 +135,7 @@ public sealed class MongoContentCollection : MongoRepositoryBase<MongoContentEnt
}
}
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, List<ISchemaEntity> schemas, Q q,
public async Task<IResultList<Content>> QueryAsync(App app, List<Schema> schemas, Q q,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentCollection/QueryAsync"))
@ -162,7 +162,7 @@ public sealed class MongoContentCollection : MongoRepositoryBase<MongoContentEnt
return await queryByQuery.QueryAsync(app, schemas, q, ct);
}
return ResultList.Empty<IContentEntity>();
return ResultList.Empty<Content>();
}
catch (MongoCommandException ex) when (ex.Code == 96)
{
@ -175,7 +175,7 @@ public sealed class MongoContentCollection : MongoRepositoryBase<MongoContentEnt
}
}
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Q q,
public async Task<IResultList<Content>> QueryAsync(App app, Schema schema, Q q,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentCollection/QueryAsync"))
@ -215,7 +215,7 @@ public sealed class MongoContentCollection : MongoRepositoryBase<MongoContentEnt
}
}
public async Task<IContentEntity?> FindContentAsync(ISchemaEntity schema, DomainId id,
public async Task<Content?> FindContentAsync(Schema schema, DomainId id,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentCollection/FindContentAsync"))
@ -224,30 +224,30 @@ public sealed class MongoContentCollection : MongoRepositoryBase<MongoContentEnt
}
}
public async Task<IReadOnlyList<ContentIdStatus>> QueryIdsAsync(DomainId appId, HashSet<DomainId> ids,
public async Task<IReadOnlyList<ContentIdStatus>> QueryIdsAsync(App app, HashSet<DomainId> ids,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentCollection/QueryIdsAsync"))
{
return await queryByIds.QueryIdsAsync(appId, ids, ct);
return await queryByIds.QueryIdsAsync(app, ids, ct);
}
}
public async Task<IReadOnlyList<ContentIdStatus>> QueryIdsAsync(DomainId appId, DomainId schemaId, FilterNode<ClrValue> filterNode,
public async Task<IReadOnlyList<ContentIdStatus>> QueryIdsAsync(App app, Schema schema, FilterNode<ClrValue> filterNode,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentCollection/QueryIdsAsync"))
{
return await queryByQuery.QueryIdsAsync(appId, schemaId, filterNode, ct);
return await queryByQuery.QueryIdsAsync(app, schema, filterNode, ct);
}
}
public async Task<bool> HasReferrersAsync(DomainId appId, DomainId reference,
public async Task<bool> HasReferrersAsync(App app, DomainId reference,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentCollection/HasReferrersAsync"))
{
return await queryReferrers.CheckExistsAsync(appId, reference, ct);
return await queryReferrers.CheckExistsAsync(app, reference, ct);
}
}
@ -282,7 +282,7 @@ public sealed class MongoContentCollection : MongoRepositoryBase<MongoContentEnt
await queryInDedicatedCollection.UpsertVersionedAsync(session, job, ct);
}
await Collection.UpsertVersionedAsync(session, job, ct);
await Collection.UpsertVersionedAsync(session, job, Field.Of<MongoContentEntity>(x => nameof(x.Version)), ct);
}
public async Task RemoveAsync(DomainId key,

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

@ -5,182 +5,224 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.DomainObject;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents;
public sealed class MongoContentEntity : IContentEntity, IVersionedEntity<DomainId>
public record MongoContentEntity : Content, IVersionedEntity<DomainId>
{
[BsonId]
[BsonElement("_id")]
public DomainId DocumentId { get; set; }
[BsonRequired]
[BsonElement("_ai")]
public DomainId IndexedAppId { get; set; }
[BsonRequired]
[BsonElement("_si")]
public DomainId IndexedSchemaId { get; set; }
[BsonRequired]
[BsonElement("ai")]
public NamedId<DomainId> AppId { get; set; }
[BsonRequired]
[BsonElement("si")]
public NamedId<DomainId> SchemaId { get; set; }
public Instant? ScheduledAt { get; set; }
[BsonRequired]
[BsonElement("rf")]
public HashSet<DomainId>? ReferencedIds { get; set; }
[BsonRequired]
[BsonElement("id")]
public DomainId Id { get; set; }
public ContentData? NewData { get; set; }
[BsonRequired]
[BsonElement("ss")]
public Status Status { get; set; }
public TranslationStatus? TranslationStatus { get; set; }
[BsonIgnoreIfNull]
[BsonElement("ns")]
public Status? NewStatus { get; set; }
public bool IsSnapshot { get; set; }
[BsonIgnoreIfNull]
[BsonElement("do")]
public ContentData Data { get; set; }
public static void RegisterClassMap()
{
BsonClassMap.TryRegisterClassMap<MongoContentEntity>(cm =>
{
cm.MapProperty(x => x.DocumentId)
.SetElementName("_id")
.SetIsRequired(true);
[BsonIgnoreIfNull]
[BsonElement("dd")]
public ContentData? DraftData { get; set; }
cm.MapProperty(x => x.IndexedAppId)
.SetElementName("_ai")
.SetIsRequired(true);
[BsonIgnoreIfNull]
[BsonElement("sa")]
public Instant? ScheduledAt { get; set; }
cm.MapProperty(x => x.IndexedSchemaId)
.SetElementName("_si")
.SetIsRequired(true);
[BsonRequired]
[BsonElement("ct")]
public Instant Created { get; set; }
cm.MapProperty(x => x.ReferencedIds)
.SetElementName("rf")
.SetIsRequired(true);
[BsonRequired]
[BsonElement("mt")]
public Instant LastModified { get; set; }
cm.MapProperty(x => x.ScheduledAt)
.SetElementName("sa")
.SetIgnoreIfNull(true);
[BsonRequired]
[BsonElement("vs")]
public long Version { get; set; }
cm.MapProperty(x => x.NewData)
.SetElementName("dd")
.SetIgnoreIfNull(true);
[BsonIgnoreIfDefault]
[BsonElement("dl")]
public bool IsDeleted { get; set; }
cm.MapProperty(x => x.TranslationStatus)
.SetElementName("ts")
.SetIgnoreIfNull(true);
[BsonIgnoreIfDefault]
[BsonElement("is")]
public bool IsSnapshot { get; set; }
cm.MapProperty(x => x.IsSnapshot)
.SetElementName("is")
.SetIgnoreIfDefault(true);
});
[BsonIgnoreIfDefault]
[BsonElement("sj")]
public ScheduleJob? ScheduleJob { get; set; }
BsonClassMap.TryRegisterClassMap<Content>(cm =>
{
cm.MapProperty(x => x.SchemaId)
.SetElementName("si")
.SetIsRequired(true);
[BsonRequired]
[BsonElement("cb")]
public RefToken CreatedBy { get; set; }
cm.MapProperty(x => x.Data)
.SetElementName("do")
.SetIgnoreIfNull(true);
[BsonRequired]
[BsonElement("mb")]
public RefToken LastModifiedBy { get; set; }
cm.MapProperty(x => x.NewStatus)
.SetElementName("ns")
.SetIgnoreIfNull(true);
[BsonIgnoreIfNull]
[BsonElement("ts")]
public TranslationStatus? TranslationStatus { get; set; }
cm.MapProperty(x => x.Status)
.SetElementName("ss")
.SetIsRequired(true);
public DomainId UniqueId
{
get => DocumentId;
cm.MapProperty(x => x.ScheduleJob)
.SetElementName("sj")
.SetIgnoreIfDefault(true);
});
EntityClassMap.Register();
}
public ContentDomainObject.State ToState()
public WriteContent ToState()
{
var state = SimpleMapper.Map(this, new ContentDomainObject.State());
if (DraftData != null && NewStatus.HasValue)
if (NewData != null && NewStatus.HasValue)
{
state.NewVersion = new ContentVersion(NewStatus.Value, Data);
state.CurrentVersion = new ContentVersion(Status, DraftData);
return new WriteContent
{
Id = Id,
AppId = AppId,
Created = Created,
CreatedBy = CreatedBy,
CurrentVersion = new ContentVersion(Status, NewData),
IsDeleted = IsDeleted,
LastModified = LastModified,
LastModifiedBy = LastModifiedBy,
NewVersion = new ContentVersion(NewStatus.Value, Data),
ScheduleJob = ScheduleJob,
SchemaId = SchemaId,
Version = Version
};
}
else
{
state.NewVersion = null;
state.CurrentVersion = new ContentVersion(Status, Data);
return new WriteContent
{
Id = Id,
AppId = AppId,
Created = Created,
CreatedBy = CreatedBy,
CurrentVersion = new ContentVersion(Status, Data),
IsDeleted = IsDeleted,
LastModified = LastModified,
LastModifiedBy = LastModifiedBy,
NewVersion = null,
ScheduleJob = ScheduleJob,
SchemaId = SchemaId,
Version = Version
};
}
return state;
}
public static async Task<MongoContentEntity> CreatePublishedAsync(SnapshotWriteJob<ContentDomainObject.State> job, IAppProvider appProvider,
public static async Task<MongoContentEntity> CreatePublishedAsync(SnapshotWriteJob<WriteContent> job, IAppProvider appProvider,
CancellationToken ct)
{
var entity = await CreateContentAsync(job.Value.CurrentVersion.Data, job, appProvider, ct);
var source = job.Value;
entity.ScheduledAt = null;
entity.ScheduleJob = null;
entity.NewStatus = null;
var (referencedIds, translationStatus) = await CreateExtendedValuesAsync(source, source.CurrentVersion.Data, appProvider, ct);
return entity;
return new MongoContentEntity
{
Id = source.Id,
DocumentId = job.Key,
IndexedAppId = source.AppId.Id,
IndexedSchemaId = source.SchemaId.Id,
AppId = source.AppId,
Created = source.Created,
CreatedBy = source.CreatedBy,
Data = source.CurrentVersion.Data,
IsDeleted = source.IsDeleted,
IsSnapshot = false,
LastModified = source.LastModified,
LastModifiedBy = source.LastModifiedBy,
NewData = null,
NewStatus = null,
ReferencedIds = referencedIds,
ScheduledAt = null,
ScheduleJob = null,
SchemaId = source.SchemaId,
Status = source.CurrentVersion.Status,
TranslationStatus = translationStatus,
Version = source.Version,
};
}
public static async Task<MongoContentEntity> CreateCompleteAsync(SnapshotWriteJob<ContentDomainObject.State> job, IAppProvider appProvider,
public static async Task<MongoContentEntity> CreateCompleteAsync(SnapshotWriteJob<WriteContent> job, IAppProvider appProvider,
CancellationToken ct)
{
var entity = await CreateContentAsync(job.Value.Data, job, appProvider, ct);
var source = job.Value;
entity.ScheduledAt = job.Value.ScheduleJob?.DueTime;
entity.ScheduleJob = job.Value.ScheduleJob;
entity.NewStatus = job.Value.NewStatus;
entity.DraftData = job.Value.NewVersion != null ? job.Value.CurrentVersion.Data : null;
entity.IsSnapshot = true;
var (referencedIds, translationStatus) = await CreateExtendedValuesAsync(source, source.EditingData, appProvider, ct);
return entity;
return new MongoContentEntity
{
Id = source.Id,
DocumentId = job.Key,
IndexedAppId = source.AppId.Id,
IndexedSchemaId = source.SchemaId.Id,
AppId = source.AppId,
Created = source.Created,
CreatedBy = source.CreatedBy,
Data = source.EditingData,
IsDeleted = source.IsDeleted,
IsSnapshot = true,
LastModified = source.LastModified,
LastModifiedBy = source.LastModifiedBy,
NewData = source.NewVersion != null ? source.CurrentVersion.Data : null,
NewStatus = source.NewVersion?.Status,
ReferencedIds = referencedIds,
ScheduledAt = source.ScheduleJob?.DueTime,
ScheduleJob = source.ScheduleJob,
SchemaId = source.SchemaId,
Status = source.CurrentVersion.Status,
TranslationStatus = translationStatus,
Version = source.Version,
};
}
private static async Task<MongoContentEntity> CreateContentAsync(ContentData data, SnapshotWriteJob<ContentDomainObject.State> job, IAppProvider appProvider,
private static async Task<(HashSet<DomainId>, TranslationStatus?)> CreateExtendedValuesAsync(WriteContent content, ContentData data, IAppProvider appProvider,
CancellationToken ct)
{
var entity = SimpleMapper.Map(job.Value, new MongoContentEntity());
entity.Data = data;
entity.DocumentId = job.Value.UniqueId;
entity.IndexedAppId = job.Value.AppId.Id;
entity.IndexedSchemaId = job.Value.SchemaId.Id;
entity.ReferencedIds ??= [];
entity.Version = job.NewVersion;
var referencedIds = new HashSet<DomainId>();
var (app, schema) = await appProvider.GetAppWithSchemaAsync(entity.IndexedAppId, entity.IndexedSchemaId, true, ct);
var (app, schema) = await appProvider.GetAppWithSchemaAsync(content.AppId.Id, content.SchemaId.Id, true, ct);
if (app == null || schema == null)
{
return entity;
return (referencedIds, null);
}
if (data.CanHaveReference())
{
var components = await appProvider.GetComponentsAsync(schema, ct: ct);
entity.Data.AddReferencedIds(schema.SchemaDef, entity.ReferencedIds, components);
data.AddReferencedIds(schema, referencedIds, components);
}
entity.TranslationStatus = TranslationStatus.Create(data, schema.SchemaDef, app.Languages);
var translationStatus = TranslationStatus.Create(data, schema, app.Languages);
return entity;
return (referencedIds, translationStatus);
}
}

29
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -10,11 +10,11 @@ using Microsoft.Extensions.Options;
using MongoDB.Driver;
using MongoDB.Driver.Core.Clusters;
using NodaTime;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Hosting;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
@ -39,6 +39,7 @@ public partial class MongoContentRepository : MongoBase<MongoContentEntity>, ICo
BsonEscapedDictionarySerializer<JsonValue, ContentFieldData>.Register();
BsonEscapedDictionarySerializer<ContentFieldData, ContentData>.Register();
BsonStringSerializer<Status>.Register();
MongoContentEntity.RegisterClassMap();
}
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider,
@ -69,58 +70,58 @@ public partial class MongoContentRepository : MongoBase<MongoContentEntity>, ICo
CanUseTransactions = clusteredAsReplica && clusterVersion >= 4 && options.UseTransactions;
}
public IAsyncEnumerable<IContentEntity> StreamAll(DomainId appId, HashSet<DomainId>? schemaIds, SearchScope scope,
public IAsyncEnumerable<Content> StreamAll(DomainId appId, HashSet<DomainId>? schemaIds, SearchScope scope,
CancellationToken ct = default)
{
return GetCollection(scope).StreamAll(appId, schemaIds, ct);
}
public IAsyncEnumerable<IContentEntity> StreamReferencing(DomainId appId, DomainId reference, int take, SearchScope scope,
public IAsyncEnumerable<Content> StreamReferencing(DomainId appId, DomainId reference, int take, SearchScope scope,
CancellationToken ct = default)
{
return GetCollection(scope).StreamReferencing(appId, reference, take, ct);
}
public IAsyncEnumerable<IContentEntity> StreamScheduledWithoutDataAsync(Instant now, SearchScope scope,
public IAsyncEnumerable<Content> StreamScheduledWithoutDataAsync(Instant now, SearchScope scope,
CancellationToken ct = default)
{
return GetCollection(scope).QueryScheduledWithoutDataAsync(now, ct);
}
public Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, List<ISchemaEntity> schemas, Q q, SearchScope scope,
public Task<IResultList<Content>> QueryAsync(App app, List<Schema> schemas, Q q, SearchScope scope,
CancellationToken ct = default)
{
return GetCollection(scope).QueryAsync(app, schemas, q, ct);
}
public Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Q q, SearchScope scope,
public Task<IResultList<Content>> QueryAsync(App app, Schema schema, Q q, SearchScope scope,
CancellationToken ct = default)
{
return GetCollection(scope).QueryAsync(app, schema, q, ct);
}
public Task<IContentEntity?> FindContentAsync(IAppEntity app, ISchemaEntity schema, DomainId id, SearchScope scope,
public Task<Content?> FindContentAsync(App app, Schema schema, DomainId id, SearchScope scope,
CancellationToken ct = default)
{
return GetCollection(scope).FindContentAsync(schema, id, ct);
}
public Task<IReadOnlyList<ContentIdStatus>> QueryIdsAsync(DomainId appId, HashSet<DomainId> ids, SearchScope scope,
public Task<IReadOnlyList<ContentIdStatus>> QueryIdsAsync(App app, HashSet<DomainId> ids, SearchScope scope,
CancellationToken ct = default)
{
return GetCollection(scope).QueryIdsAsync(appId, ids, ct);
return GetCollection(scope).QueryIdsAsync(app, ids, ct);
}
public Task<bool> HasReferrersAsync(DomainId appId, DomainId reference, SearchScope scope,
public Task<bool> HasReferrersAsync(App app, DomainId reference, SearchScope scope,
CancellationToken ct = default)
{
return GetCollection(scope).HasReferrersAsync(appId, reference, ct);
return GetCollection(scope).HasReferrersAsync(app, reference, ct);
}
public Task<IReadOnlyList<ContentIdStatus>> QueryIdsAsync(DomainId appId, DomainId schemaId, FilterNode<ClrValue> filterNode, SearchScope scope,
public Task<IReadOnlyList<ContentIdStatus>> QueryIdsAsync(App app, Schema schema, FilterNode<ClrValue> filterNode, SearchScope scope,
CancellationToken ct = default)
{
return GetCollection(scope).QueryIdsAsync(appId, schemaId, filterNode, ct);
return GetCollection(scope).QueryIdsAsync(app, schema, filterNode, ct);
}
public Task ResetScheduledAsync(DomainId documentId, SearchScope scope,

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

@ -6,9 +6,8 @@
// ==========================================================================
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents.DomainObject;
using Squidex.Infrastructure;
using Squidex.Infrastructure.States;
@ -16,16 +15,16 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents;
public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject.State>, IDeleter
public partial class MongoContentRepository : ISnapshotStore<WriteContent>, IDeleter
{
IAsyncEnumerable<SnapshotResult<ContentDomainObject.State>> ISnapshotStore<ContentDomainObject.State>.ReadAllAsync(
IAsyncEnumerable<SnapshotResult<WriteContent>> ISnapshotStore<WriteContent>.ReadAllAsync(
CancellationToken ct)
{
return collectionComplete.StreamAll(ct)
.Select(x => new SnapshotResult<ContentDomainObject.State>(x.DocumentId, x.ToState(), x.Version, true));
.Select(x => new SnapshotResult<WriteContent>(x.DocumentId, x.ToState(), x.Version, true));
}
async Task<SnapshotResult<ContentDomainObject.State>> ISnapshotStore<ContentDomainObject.State>.ReadAsync(DomainId key,
async Task<SnapshotResult<WriteContent>> ISnapshotStore<WriteContent>.ReadAsync(DomainId key,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentRepository/ReadAsync"))
@ -36,14 +35,14 @@ public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject
// Support for all versions, where we do not have full snapshots in the collection.
if (existing?.IsSnapshot == true)
{
return new SnapshotResult<ContentDomainObject.State>(existing.DocumentId, existing.ToState(), existing.Version);
return new SnapshotResult<WriteContent>(existing.DocumentId, existing.ToState(), existing.Version);
}
return new SnapshotResult<ContentDomainObject.State>(default, null!, EtagVersion.Empty);
return new SnapshotResult<WriteContent>(default, null!, EtagVersion.Empty);
}
}
async Task IDeleter.DeleteAppAsync(IAppEntity app,
async Task IDeleter.DeleteAppAsync(App app,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentRepository/DeleteAppAsync"))
@ -53,7 +52,7 @@ public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject
}
}
async Task ISnapshotStore<ContentDomainObject.State>.ClearAsync(
async Task ISnapshotStore<WriteContent>.ClearAsync(
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentRepository/ClearAsync"))
@ -63,7 +62,7 @@ public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject
}
}
async Task ISnapshotStore<ContentDomainObject.State>.RemoveAsync(DomainId key,
async Task ISnapshotStore<WriteContent>.RemoveAsync(DomainId key,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentRepository/RemoveAsync"))
@ -80,7 +79,7 @@ public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject
}
}
async Task ISnapshotStore<ContentDomainObject.State>.WriteAsync(SnapshotWriteJob<ContentDomainObject.State> job,
async Task ISnapshotStore<WriteContent>.WriteAsync(SnapshotWriteJob<WriteContent> job,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentRepository/WriteAsync"))
@ -114,7 +113,7 @@ public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject
}
}
async Task ISnapshotStore<ContentDomainObject.State>.WriteManyAsync(IEnumerable<SnapshotWriteJob<ContentDomainObject.State>> jobs,
async Task ISnapshotStore<WriteContent>.WriteManyAsync(IEnumerable<SnapshotWriteJob<WriteContent>> jobs,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentRepository/WriteManyAsync"))
@ -158,7 +157,7 @@ public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject
}
}
private async Task UpsertPublishedAsync(SnapshotWriteJob<ContentDomainObject.State> job,
private async Task UpsertPublishedAsync(SnapshotWriteJob<WriteContent> job,
CancellationToken ct)
{
if (ShouldWritePublished(job.Value))
@ -173,7 +172,7 @@ public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject
}
}
private async Task UpsertVersionedPublishedAsync(IClientSessionHandle session, SnapshotWriteJob<ContentDomainObject.State> job,
private async Task UpsertVersionedPublishedAsync(IClientSessionHandle session, SnapshotWriteJob<WriteContent> job,
CancellationToken ct)
{
if (ShouldWritePublished(job.Value))
@ -188,7 +187,7 @@ public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject
}
}
private async Task UpsertCompleteAsync(SnapshotWriteJob<ContentDomainObject.State> job,
private async Task UpsertCompleteAsync(SnapshotWriteJob<WriteContent> job,
CancellationToken ct)
{
var entityJob = job.As(await MongoContentEntity.CreateCompleteAsync(job, appProvider, ct));
@ -196,7 +195,7 @@ public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject
await collectionComplete.UpsertAsync(entityJob, ct);
}
private async Task UpsertVersionedCompleteAsync(IClientSessionHandle session, SnapshotWriteJob<ContentDomainObject.State> job,
private async Task UpsertVersionedCompleteAsync(IClientSessionHandle session, SnapshotWriteJob<WriteContent> job,
CancellationToken ct)
{
var entityJob = job.As(await MongoContentEntity.CreateCompleteAsync(job, appProvider, ct));
@ -204,13 +203,13 @@ public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject
await collectionComplete.UpsertVersionedAsync(session, entityJob, ct);
}
private static bool ShouldWritePublished(ContentDomainObject.State value)
private static bool ShouldWritePublished(WriteContent value)
{
// Only published content is written to the published collection.
return value.Status == Status.Published && !value.IsDeleted;
return value.CurrentVersion.Status == Status.Published && !value.IsDeleted;
}
private static bool IsValid(ContentDomainObject.State state)
private static bool IsValid(WriteContent state)
{
// Some data is corrupt and might throw an exception during migration if we do not skip them.
return

16
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Extensions.cs

@ -10,6 +10,7 @@ using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.MongoDb.Queries;
@ -35,7 +36,7 @@ public static class Extensions
get => propertyMap ??=
BsonClassMap.LookupClassMap(typeof(MongoContentEntity)).AllMemberMaps
.Where(x =>
x.MemberName != nameof(MongoContentEntity.DraftData) &&
x.MemberName != nameof(MongoContentEntity.NewData) &&
x.MemberName != nameof(MongoContentEntity.Data))
.ToDictionary(
x => x.MemberName,
@ -179,11 +180,18 @@ public static class Extensions
if (fields?.Any() == true)
{
var dataField = Field.Of<MongoContentEntity>(x => nameof(x.Data));
var dataPrefix = Field.Of<MongoContentEntity>(x => nameof(x.Data));
foreach (var field in fields)
{
projections.Add(projector.Include($"{dataField}.{field}"));
var dataField = field;
if (FieldNames.IsDataField(field, out var fieldName))
{
dataField = fieldName;
}
projections.Add(projector.Include($"{dataPrefix}.{dataField}"));
}
foreach (var field in PropertyMap.Values)
@ -193,7 +201,7 @@ public static class Extensions
}
else
{
projections.Add(projector.Exclude(Field.Of<MongoContentEntity>(x => nameof(x.DraftData))));
projections.Add(projector.Exclude(Field.Of<MongoContentEntity>(x => nameof(x.NewData))));
}
return projector.Combine(projections);

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

@ -7,14 +7,14 @@
using System.Runtime.CompilerServices;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations;
public sealed class QueryAsStream : OperationBase
{
public async IAsyncEnumerable<IContentEntity> StreamAll(DomainId appId, HashSet<DomainId>? schemaIds,
public async IAsyncEnumerable<Content> StreamAll(DomainId appId, HashSet<DomainId>? schemaIds,
[EnumeratorCancellation] CancellationToken ct)
{
var filter = CreateFilter(appId, schemaIds);

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryById.cs

@ -6,15 +6,15 @@
// ==========================================================================
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations;
internal sealed class QueryById : OperationBase
{
public async Task<IContentEntity?> QueryAsync(ISchemaEntity schema, DomainId id,
public async Task<Content?> QueryAsync(Schema schema, DomainId id,
CancellationToken ct)
{
var filter = Filter.Eq(x => x.DocumentId, DomainId.Combine(schema.AppId, id));

17
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByIds.cs

@ -6,10 +6,9 @@
// ==========================================================================
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.MongoDb;
@ -20,28 +19,28 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations;
internal sealed class QueryByIds : OperationBase
{
public async Task<IReadOnlyList<ContentIdStatus>> QueryIdsAsync(DomainId appId, HashSet<DomainId> ids,
public async Task<IReadOnlyList<ContentIdStatus>> QueryIdsAsync(App app, HashSet<DomainId> ids,
CancellationToken ct)
{
if (ids == null || ids.Count == 0)
if (ids is not { Count: > 0 })
{
return ReadonlyList.Empty<ContentIdStatus>();
}
// Create a filter from the Ids and ensure that the content ids match to the app ID.
var filter = CreateFilter(appId, null, ids, null);
var filter = CreateFilter(app.Id, null, ids, null);
var contentEntities = await Collection.FindStatusAsync(filter, ct);
return contentEntities.Select(x => new ContentIdStatus(x.IndexedSchemaId, x.Id, x.Status)).ToList();
}
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, List<ISchemaEntity> schemas, Q q,
public async Task<IResultList<Content>> QueryAsync(App app, List<Schema> schemas, Q q,
CancellationToken ct)
{
if (q.Ids == null || q.Ids.Count == 0)
if (q.Ids is not { Count: > 0 })
{
return ResultList.Empty<IContentEntity>();
return ResultList.Empty<Content>();
}
// We need to translate the query names to the document field names in MongoDB.

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

Loading…
Cancel
Save