From 3560fbaab226f5ed436d4c2a6a1956cc3fee427a Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 7 Dec 2023 18:40:18 +0100 Subject: [PATCH] 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. --- .github/workflows/dev.yml | 44 +-- .github/workflows/release.yml | 44 +-- .../CreateContentActionHandler.cs | 2 +- .../DeepDetect/DeepDetectActionHandler.cs | 2 +- .../Actions/Email/EmailAction.cs | 2 - .../Assets/Azure/AzureMetadataSource.cs | 2 +- .../DoubleLinkedContentMiddleware.cs | 4 +- .../Text/Azure/AzureTextIndex.cs | 8 +- .../ElasticSearch/ElasticSearchTextIndex.cs | 6 +- .../Validation/CompositeUniqueValidator.cs | 2 +- .../src/Migrations/Migrations/ClearRules.cs | 6 +- .../src/Migrations/Migrations/ClearSchemas.cs | 6 +- .../Migrations/Migrations/CreateAssetSlugs.cs | 8 +- .../Migrations/OldEvents/AppPatternAdded.cs | 5 +- .../Migrations/OldEvents/AppPatternDeleted.cs | 6 +- .../Migrations/OldEvents/AppPatternUpdated.cs | 5 +- .../src/Migrations/OldEvents/SchemaCreated.cs | 58 ++-- backend/src/Migrations/RebuilderExtensions.cs | 17 +- .../AppEntity.cs} | 19 +- .../Apps/App.cs | 183 ++++++++++ .../Apps/AppClient.cs | 4 +- .../Apps/AppExtensions.cs | 18 +- .../Apps/Json/LanguageConfigSurrogate.cs | 2 +- .../Apps/Json/RolesSurrogate.cs | 1 + .../Assets/Asset.cs | 81 +++++ .../Assets/AssetFolder.cs | 40 +++ .../Assets/AssetItem.cs} | 14 +- .../Assets/AssetMetadata.cs | 8 +- .../Contents/Content.cs} | 24 +- .../Contents/ContentData.cs | 2 +- .../Contents/ContentVersion.cs} | 8 +- .../Contents/Json/WorkflowStepSurrogate.cs | 17 +- .../Json/WorkflowTransitionSurrogate.cs | 21 +- .../Contents/ScheduleJob.cs | 23 +- .../Contents/WriteContent.cs | 56 +++ .../EnrichedEvents/IEnrichedEntityEvent.cs | 3 +- .../Rules/Json/RuleSorrgate.cs | 50 +-- .../Rules/Rule.cs | 55 +-- .../Rules/RuleJob.cs | 2 +- .../Schemas/ArrayField.cs | 19 +- .../Schemas/ArrayFieldProperties.cs | 6 +- .../Schemas/AssetsFieldProperties.cs | 8 +- .../Schemas/BooleanFieldProperties.cs | 8 +- .../Schemas/ComponentFieldProperties.cs | 8 +- .../Schemas/ComponentsFieldProperties.cs | 8 +- .../Schemas/DateTimeFieldProperties.cs | 8 +- .../Schemas/FieldCollection.cs | 12 +- .../Schemas/FieldNames.cs | 113 +++++- .../Schemas/FieldProperties.cs | 4 +- .../Schemas/FieldRules.cs | 4 +- .../Schemas/Fields.cs | 202 +++++------ .../Schemas/GeolocationFieldProperties.cs | 8 +- .../Schemas/IField.cs | 8 +- .../Schemas/IFieldSettings.cs | 17 - .../Schemas/Json/FieldSurrogate.cs | 56 ++- .../Schemas/Json/FieldsSurrogate.cs | 23 ++ .../Schemas/Json/SchemaSurrogate.cs | 128 ++----- .../Schemas/JsonFieldProperties.cs | 8 +- .../Schemas/NestedField.cs | 57 +-- .../Schemas/NestedField{T}.cs | 15 +- .../Schemas/NumberFieldProperties.cs | 8 +- .../Schemas/ReferencesFieldProperties.cs | 8 +- .../Schemas/RootField.cs | 62 +--- .../Schemas/RootField{T}.cs | 15 +- .../Schemas/Schema.cs | 165 +++------ .../Schemas/SchemaExtensions.cs | 10 +- .../Schemas/StringFieldProperties.cs | 8 +- .../Schemas/TagsFieldProperties.cs | 8 +- .../Schemas/UIFieldProperties.cs | 8 +- .../Teams/Team.cs | 58 ++++ .../ConvertContent/AddDefaultValues.cs | 14 + .../ConvertContent/ContentConverterFlat.cs | 2 +- .../ConvertContent/ResolveAssetUrls.cs | 2 +- .../ConvertContent/ResolveLanguages.cs | 8 + .../GenerateJsonSchema/JsonTypeVisitor.cs | 1 - .../HandleRules/JobResult.cs | 8 +- .../HandleRules/RuleContext.cs | 4 +- .../HandleRules/RuleService.cs | 98 +----- .../Scripting/AssetCommandScriptVars.cs | 1 - .../ContentWrapper/ContentDataProperty.cs | 1 - .../ValidateContent/RootContext.cs | 17 +- .../Validators/AggregateValidator.cs | 2 +- .../Validators/AssetsValidator.cs | 10 +- .../Apps/MongoAppEntity.cs | 4 +- .../Apps/MongoAppRepository.cs | 24 +- .../Assets/AssetItemClassMap.cs | 26 ++ .../Assets/MongoAssetEntity.cs | 180 ++++------ .../Assets/MongoAssetFolderEntity.cs | 96 ++---- .../Assets/MongoAssetFolderRepository.cs | 13 +- ...ongoAssetFolderRepository_SnapshotStore.cs | 27 +- .../Assets/MongoAssetRepository.cs | 23 +- .../MongoAssetRepository_SnapshotStore.cs | 26 +- .../Contents/MongoContentCollection.cs | 34 +- .../Contents/MongoContentEntity.cs | 254 ++++++++------ .../Contents/MongoContentRepository.cs | 29 +- .../MongoContentRepository_SnapshotStore.cs | 39 +-- .../Contents/Operations/Extensions.cs | 16 +- .../Contents/Operations/QueryAsStream.cs | 4 +- .../Contents/Operations/QueryById.cs | 6 +- .../Contents/Operations/QueryByIds.cs | 17 +- .../Contents/Operations/QueryByQuery.cs | 19 +- .../Operations/QueryInDedicatedCollection.cs | 13 +- .../Contents/Operations/QueryReferences.cs | 12 +- .../Contents/Operations/QueryReferrers.cs | 9 +- .../Contents/Operations/QueryScheduled.cs | 12 +- .../EntityClassMap.cs | 56 +++ .../History/MongoHistoryEventRepository.cs | 6 +- .../Rules/MongoRuleEntity.cs | 4 +- .../Rules/MongoRuleEventEntity.cs | 19 +- .../Rules/MongoRuleEventRepository.cs | 12 +- .../Rules/MongoRuleRepository.cs | 18 +- .../Schemas/MongoSchemaEntity.cs | 6 +- .../Schemas/MongoSchemaRepository.cs | 22 +- .../Schemas/MongoSchemasHash.cs | 9 +- .../Teams/MongoTeamEntity.cs | 4 +- .../Teams/MongoTeamRepository.cs | 16 +- .../Text/AtlasTextIndex.cs | 4 +- .../Text/MongoTextIndexBase.cs | 10 +- .../Text/MongoTextIndexerState.cs | 6 +- .../AppProvider.cs | 38 +- .../AppProviderExtensions.cs | 11 +- .../Apps/AppEntityExtensions.cs | 18 - .../Apps/AppEventDeleter.cs | 3 +- .../Apps/AppSettingsSearchSource.cs | 1 + .../Apps/AppUISettings.cs | 3 +- .../Apps/AppUsageDeleter.cs | 3 +- .../Apps/BackupApps.cs | 3 +- .../{_AppCommand.cs => AppCommand.cs} | 9 - .../Commands/AppCommandBase.cs} | 7 +- .../Apps/DefaultAppLogStore.cs | 3 +- .../Apps/DomainObject/AppCommandMiddleware.cs | 3 +- .../DomainObject/AppDomainObject.State.cs | 271 +++++---------- .../Apps/DomainObject/AppDomainObject.cs | 6 +- .../Apps/DomainObject/Guards/GuardApp.cs | 9 +- .../DomainObject/Guards/GuardAppClients.cs | 6 +- .../Guards/GuardAppContributors.cs | 4 +- .../DomainObject/Guards/GuardAppLanguages.cs | 8 +- .../Apps/DomainObject/Guards/GuardAppRoles.cs | 8 +- .../DomainObject/Guards/GuardAppWorkflows.cs | 7 +- .../Apps/IAppEntity.cs | 49 --- .../Apps/Indexes/AppsIndex.cs | 73 ++-- .../Apps/Indexes/IAppsIndex.cs | 9 +- .../Apps/Repositories/IAppRepository.cs | 9 +- .../Apps/RolePermissionsProvider.cs | 7 +- .../Templates/TemplateCommandMiddleware.cs | 7 +- .../Assets/AssetCache.cs | 2 +- .../Assets/AssetDuplicate.cs | 2 +- .../Assets/AssetEntity.cs | 69 ---- .../Assets/AssetTagsDeleter.cs | 4 +- .../Assets/AssetUsageTracker.cs | 4 +- .../Assets/AssetsFluidExtension.cs | 5 +- .../Assets/AssetsJintExtension.cs | 6 +- .../Assets/AssetsSearchSource.cs | 2 +- .../Assets/BackupAssets.cs | 5 +- .../{_AssetCommand.cs => AssetCommand.cs} | 11 - .../Assets/Commands/AssetCommandBase.cs | 18 + .../Assets/Commands/AssetFolderCommand.cs | 20 ++ ...erCommand.cs => AssetFolderCommandBase.cs} | 13 - .../Assets/DefaultAssetFileStore.cs | 4 +- .../DomainObject/AssetCommandMiddleware.cs | 9 +- .../DomainObject/AssetDomainObject.State.cs | 169 ++------- .../Assets/DomainObject/AssetDomainObject.cs | 11 +- .../AssetFolderDomainObject.State.cs | 70 ++-- .../DomainObject/AssetFolderDomainObject.cs | 7 +- .../DomainObject/AssetFolderOperation.cs | 7 +- .../Assets/DomainObject/AssetOperation.cs | 7 +- .../AssetsBulkUpdateCommandMiddleware.cs | 2 +- .../Guards/ValidationExtensions.cs | 2 +- ...IAssetFolderEntity.cs => EnrichedAsset.cs} | 10 +- .../Assets/FileTagAssetMetadataSource.cs | 2 +- .../Assets/FileTypeAssetMetadataSource.cs | 3 +- .../Assets/IAssetCache.cs | 2 +- .../Assets/IAssetEntity.cs | 28 -- .../Assets/IAssetLoader.cs | 3 +- .../Assets/IAssetMetadataSource.cs | 3 +- .../Assets/IAssetQueryService.cs | 15 +- .../Assets/IEnrichedAssetEntity.cs | 17 - .../Assets/ImageAssetMetadataSource.cs | 2 +- .../Assets/Queries/AssetEnricher.cs | 9 +- .../Assets/Queries/AssetLoader.cs | 9 +- .../Assets/Queries/AssetQueryService.cs | 41 +-- .../Assets/Queries/FilterTagTransformer.cs | 3 +- .../Assets/Queries/IAssetEnricher.cs | 6 +- .../Assets/Queries/IAssetEnricherStep.cs | 2 +- .../Assets/Queries/Steps/CalculateTokens.cs | 2 +- .../Assets/Queries/Steps/ConvertTags.cs | 4 +- .../Assets/Queries/Steps/EnrichForCaching.cs | 2 +- .../Queries/Steps/EnrichWithMetadataText.cs | 2 +- .../Assets/Queries/Steps/ScriptAsset.cs | 4 +- .../Repositories/IAssetFolderRepository.cs | 5 +- .../Assets/Repositories/IAssetRepository.cs | 13 +- .../Assets/SvgAssetMetadataSource.cs | 3 +- .../Assets/Transformations.cs | 2 +- .../Backup/BackupReader.cs | 2 +- .../Backup/BackupService.cs | 4 +- .../Backup/IBackupJob.cs | 4 +- .../Billing/IBillingManager.cs | 24 +- .../Billing/IUsageGate.cs | 12 +- .../Billing/NoopBillingManager.cs | 24 +- .../Billing/UsageGate.cs | 15 +- .../Collaboration/EmailUserNotifications.cs | 10 +- .../Collaboration/IUserNotifications.cs | 10 +- .../Collaboration/NoopUserNotifications.cs | 10 +- .../Contents/BackupContents.cs | 2 +- .../Contents/Commands/ContentCommand.cs} | 17 +- ...ontentCommand.cs => ContentCommandBase.cs} | 14 - .../Contents/ContentCache.cs | 2 +- .../Contents/ContentExtensions.cs | 18 - .../Contents/ContentHeaders.cs | 6 - .../Contents/ContentSchedulerProcess.cs | 3 +- .../Contents/ContentsSearchSource.cs | 8 +- .../Contents/Counter/CounterService.cs | 4 +- .../Contents/DefaultContentWorkflow.cs | 102 ------ .../Contents/DefaultWorkflowsValidator.cs | 4 +- .../DomainObject/ContentCommandMiddleware.cs | 10 +- .../DomainObject/ContentDomainObject.State.cs | 228 ++++++------ .../DomainObject/ContentDomainObject.cs | 55 ++- .../Contents/DomainObject/ContentOperation.cs | 15 +- .../Contents/DomainObject/ContentVersion.cs | 37 -- .../ContentsBulkUpdateCommandMiddleware.cs | 5 +- .../Guards/ScriptingExtensions.cs | 34 +- .../DomainObject/Guards/SecurityExtensions.cs | 2 +- .../Guards/SingletonExtensions.cs | 10 +- .../Guards/ValidationExtensions.cs | 21 +- .../DomainObject/Guards/WorkflowExtensions.cs | 18 +- .../Contents/DynamicContentWorkflow.cs | 27 +- .../{ContentEntity.cs => EnrichedContent.cs} | 45 +-- .../GraphQL/CachingGraphQLResolver.cs | 8 +- .../GraphQL/GraphQLExecutionContext.cs | 26 +- .../GraphQL/Types/ApplicationMutations.cs | 3 +- .../GraphQL/Types/ApplicationQueries.cs | 2 +- .../GraphQL/Types/Assets/AssetActions.cs | 2 +- .../GraphQL/Types/Assets/AssetGraphType.cs | 6 +- .../Types/Assets/AssetsResultGraphType.cs | 4 +- .../Contents/GraphQL/Types/Builder.cs | 10 +- .../GraphQL/Types/Contents/ContentActions.cs | 8 +- .../GraphQL/Types/Contents/ContentFields.cs | 2 +- .../Types/Contents/ContentGraphType.cs | 7 +- .../Contents/ContentInterfaceGraphType.cs | 2 +- .../Types/Contents/ContentResolvers.cs | 6 +- .../Types/Contents/ContentResultGraphType.cs | 3 +- .../Types/Contents/ContentUnionGraphType.cs | 3 +- .../GraphQL/Types/Contents/DataGraphType.cs | 1 - .../Types/Contents/DataInputGraphType.cs | 1 - .../GraphQL/Types/Contents/SchemaInfo.cs | 9 +- .../Contents/GraphQL/Types/FieldMap.cs | 2 +- .../Types/Primitives/EntityResolvers.cs | 17 +- .../GraphQL/Types/SharedExtensions.cs | 5 + .../Contents/IContentCache.cs | 2 +- .../Contents/IContentEntity.cs | 32 -- .../Contents/IContentLoader.cs | 3 +- .../Contents/IContentQueryService.cs | 14 +- .../Contents/IContentWorkflow.cs | 20 +- .../Contents/IEnrichedContentEntity.cs | 34 -- .../Contents/Queries/ContentEnricher.cs | 16 +- .../Contents/Queries/ContentLoader.cs | 11 +- .../Contents/Queries/ContentQueryParser.cs | 27 +- .../Contents/Queries/ContentQueryService.cs | 49 +-- .../Contents/Queries/GeoQueryTransformer.cs | 8 +- .../Contents/Queries/IContentEnricher.cs | 6 +- .../Contents/Queries/IContentEnricherStep.cs | 5 +- .../Contents/Queries/QueryExecutionContext.cs | 14 +- .../Contents/Queries/Steps/CalculateTokens.cs | 2 +- .../Contents/Queries/Steps/ConvertData.cs | 57 ++- .../Queries/Steps/EnrichForCaching.cs | 2 +- .../Queries/Steps/EnrichWithSchema.cs | 8 +- .../Queries/Steps/EnrichWithWorkflows.cs | 22 +- .../Contents/Queries/Steps/ResolveAssets.cs | 19 +- .../Queries/Steps/ResolveReferences.cs | 25 +- .../Contents/Queries/Steps/ScriptContent.cs | 10 +- .../Contents/ReferencesFluidExtension.cs | 3 +- .../Contents/ReferencesJintExtension.cs | 4 +- .../Repositories/IContentRepository.cs | 22 +- .../Contents/Text/ITextIndex.cs | 6 +- .../Validation/DependencyValidatorsFactory.cs | 8 +- .../Squidex.Domain.Apps.Entities/Context.cs | 12 +- .../DomainObjectState.cs | 81 ----- .../EntityExtensions.cs | 42 +++ .../IAppProvider.cs | 34 +- .../Squidex.Domain.Apps.Entities/IDeleter.cs | 4 +- .../IEntityWithTags.cs | 13 - .../IEntityWithVersion.cs | 13 - .../Invitation/InviteUserCommandMiddleware.cs | 12 +- .../OperationContextBase.cs | 4 +- .../Rules/BackupRules.cs | 3 +- .../Rules/Commands/RuleCommand.cs | 20 ++ .../{_RuleCommand.cs => RuleCommandBase.cs} | 13 - .../Rules/DomainObject/Guards/GuardRule.cs | 3 +- .../Guards/RuleTriggerValidator.cs | 6 +- .../DomainObject/RuleDomainObject.State.cs | 110 +++--- .../Rules/DomainObject/RuleDomainObject.cs | 9 +- ...IEnrichedRuleEntity.cs => EnrichedRule.cs} | 8 +- .../Rules/IRuleEnricher.cs | 6 +- .../Rules/IRuleEventEntity.cs | 7 +- .../Rules/IRuleQueryService.cs | 2 +- .../Rules/Indexes/IRulesIndex.cs | 3 +- .../Rules/Indexes/RulesIndex.cs | 5 +- .../Rules/Queries/RuleEnricher.cs | 33 +- .../Rules/Queries/RuleQueryService.cs | 4 +- .../Rules/Repositories/IRuleRepository.cs | 3 +- .../Rules/RuleCommandMiddleware.cs | 3 +- .../Rules/RuleEnqueuer.cs | 4 +- .../Rules/RuleEntity.cs | 46 --- .../Rules/RuleQueueWriter.cs | 2 +- .../Rules/Runner/DefaultRuleRunnerService.cs | 12 +- .../Rules/Runner/IRuleRunnerService.cs | 6 +- .../Rules/Runner/RuleRunnerProcessor.cs | 11 +- .../Schemas/BackupSchemas.cs | 3 +- .../Schemas/Commands/IUpsertCommand.cs | 87 ++--- .../{_SchemaCommand.cs => SchemaCommand.cs} | 11 - .../Schemas/Commands/SchemaCommandBase.cs | 18 + .../Schemas/Commands/SynchronizeSchema.cs | 4 +- .../Schemas/Commands/UpsertSchemaFieldBase.cs | 2 +- .../DomainObject/Guards/GuardSchema.cs | 216 ++++++------ .../DomainObject/SchemaDomainObject.State.cs | 281 +++++++-------- .../DomainObject/SchemaDomainObject.cs | 37 +- .../Schemas/ISchemaEntity.cs | 24 -- .../Schemas/ISchemasHash.cs | 7 +- .../Schemas/Indexes/ISchemasIndex.cs | 7 +- .../Schemas/Indexes/SchemasIndex.cs | 74 ++-- .../MigrateFieldNamesCommandMiddleware.cs | 32 ++ .../Schemas/Repositories/ISchemaRepository.cs | 7 +- .../Schemas/SchemaExtensions.cs | 35 -- .../Schemas/SchemasSearchSource.cs | 10 +- .../{_TeamCommand.cs => TeamCommand.cs} | 9 - .../Commands/TeamCommandBase.cs} | 7 +- .../Guards/GuardTeamContributors.cs | 5 +- .../DomainObject/TeamDomainObject.State.cs | 80 ++--- .../Teams/DomainObject/TeamDomainObject.cs | 7 +- .../Teams/ITeamEntity.cs | 23 -- .../Teams/Indexes/ITeamsIndex.cs | 5 +- .../Teams/Indexes/TeamsIndex.cs | 7 +- .../Teams/Repositories/ITeamRepository.cs | 5 +- .../Teams/TeamExtensions.cs | 3 +- .../Apps/AppPlanChanged.cs | 6 - .../Schemas/SchemaCreatedFieldBase.cs | 2 +- .../Teams/TeamPlanChanged.cs | 6 - .../MongoEventStoreSubscription.cs | 2 +- .../MongoDb/BsonDomainIdSerializer.cs | 15 +- .../BsonEscapedDictionarySerializer.cs | 15 +- .../MongoDb/BsonInstantSerializer.cs | 13 +- .../MongoDb/BsonJsonValueSerializer.cs | 15 +- .../MongoDb/BsonStringSerializer.cs | 14 +- .../MongoDb/IVersionedEntity.cs | 4 +- .../MongoDb/MongoExtensions.cs | 54 +-- .../States/MongoSnapshotStoreBase.cs | 5 +- .../CollectionExtensions.cs | 14 + .../Collections/ReadonlyList.cs | 2 +- .../Commands/CachingDomainObjectMiddleware.cs | 2 +- .../Commands/DomainObject.cs | 27 +- .../Commands/{IDomainState.cs => Entity.cs} | 21 +- .../Commands/Rebuilder.cs | 14 +- .../DisposableObjectBase.cs | 5 +- .../EventSourcing/EnvelopeExtensions.cs | 2 +- backend/src/Squidex.Infrastructure/IWithId.cs | 13 - .../System/JsonIgnoreReadonlyProperties.cs | 35 ++ .../Json/System/JsonRenameAttribute.cs | 35 ++ .../Json/System/PolymorphicConverter.cs | 37 +- .../Json/System/PolymorphicTypeResolver.cs | 51 --- .../Queries/FilterSchema.cs | 2 +- .../Queries/PropertyPath.cs | 2 +- .../States/BatchContext.cs | 2 +- .../States/Persistence.cs | 12 +- .../StringExtensions.cs | 2 +- .../Timers/CompletionTimer.cs | 7 +- .../Identity/SquidexClaimsExtensions.cs | 3 - .../Squidex.Shared/PermissionExtensions.cs | 1 - backend/src/Squidex.Shared/PermissionIds.cs | 1 - .../src/Squidex.Shared/Users/ClientUser.cs | 2 - backend/src/Squidex.Shared/Users/IUser.cs | 1 - .../src/Squidex.Shared/Users/IUserResolver.cs | 4 - backend/src/Squidex.Web/ApiController.cs | 18 +- .../src/Squidex.Web/ApiPermissionAttribute.cs | 9 +- .../ETagCommandMiddleware.cs | 3 +- .../EnrichWithAppIdCommandMiddleware.cs | 2 +- .../EnrichWithSchemaIdCommandMiddleware.cs | 8 +- .../EnrichWithTeamIdCommandMiddleware.cs | 7 +- backend/src/Squidex.Web/ETagExtensions.cs | 10 +- backend/src/Squidex.Web/FodyWeavers.xml | 3 - backend/src/Squidex.Web/FodyWeavers.xsd | 26 -- backend/src/Squidex.Web/IAppFeature.cs | 15 - backend/src/Squidex.Web/ISchemaFeature.cs | 15 - backend/src/Squidex.Web/ITeamFeature.cs | 15 - .../src/Squidex.Web/Pipeline/AppResolver.cs | 10 +- .../src/Squidex.Web/Pipeline/SchemaFeature.cs | 14 - .../Squidex.Web/Pipeline/SchemaResolver.cs | 15 +- .../src/Squidex.Web/Pipeline/TeamFeature.cs | 14 - .../src/Squidex.Web/Pipeline/TeamResolver.cs | 2 +- .../Squidex.Web/Pipeline/UsageMiddleware.cs | 3 +- backend/src/Squidex.Web/Resources.cs | 6 +- .../Controllers/Apps/AppAssetsController.cs | 6 +- .../Controllers/Apps/AppClientsController.cs | 6 +- .../Apps/AppContributorsController.cs | 8 +- .../Apps/AppLanguagesController.cs | 6 +- .../Controllers/Apps/AppRolesController.cs | 5 +- .../Controllers/Apps/AppSettingsController.cs | 6 +- .../Apps/AppWorkflowsController.cs | 6 +- .../Api/Controllers/Apps/AppsController.cs | 6 +- .../Api/Controllers/Apps/Models/AppDto.cs | 6 +- .../Controllers/Apps/Models/AppLanguageDto.cs | 3 +- .../Apps/Models/AppLanguagesDto.cs | 4 +- .../Controllers/Apps/Models/AppSettingsDto.cs | 4 +- .../Apps/Models/AssetScriptsDto.cs | 4 +- .../Api/Controllers/Apps/Models/ClientsDto.cs | 4 +- .../Api/Controllers/Apps/Models/RoleDto.cs | 7 +- .../Api/Controllers/Apps/Models/RolesDto.cs | 4 +- .../Controllers/Apps/Models/WorkflowsDto.cs | 4 +- .../Assets/AssetContentController.cs | 6 +- .../Assets/AssetFoldersController.cs | 11 +- .../Controllers/Assets/AssetsController.cs | 2 +- .../Assets/Models/AssetContentQueryDto.cs | 8 +- .../Api/Controllers/Assets/Models/AssetDto.cs | 2 +- .../Assets/Models/AssetFolderDto.cs | 4 +- .../Assets/Models/AssetFoldersDto.cs | 4 +- .../Controllers/Assets/Models/AssetsDto.cs | 2 +- .../Comments/CommentsController.cs | 2 +- .../Contents/ContentsController.cs | 4 +- .../Controllers/Contents/Generator/Builder.cs | 5 +- .../Generator/SchemasOpenApiGenerator.cs | 25 +- .../Controllers/Contents/Models/ContentDto.cs | 4 +- .../Contents/Models/ContentsDto.cs | 12 +- .../Areas/Api/Controllers/ContributorsDto.cs | 8 +- .../Controllers/Rules/Models/CreateRuleDto.cs | 2 +- .../Api/Controllers/Rules/Models/RuleDto.cs | 9 +- .../Api/Controllers/Rules/Models/RulesDto.cs | 2 +- .../Api/Controllers/Rules/RulesController.cs | 4 +- .../Controllers/Schemas/Models/SchemaDto.cs | 12 +- .../Controllers/Schemas/Models/SchemasDto.cs | 4 +- .../Schemas/SchemaFieldsController.cs | 4 +- .../Controllers/Schemas/SchemasController.cs | 12 +- .../Api/Controllers/Teams/Models/TeamDto.cs | 6 +- .../Teams/TeamContributorsController.cs | 8 +- .../Api/Controllers/Teams/TeamsController.cs | 6 +- backend/src/Squidex/Areas/Frontend/Startup.cs | 1 - .../Config/AlwaysAddScopeHandler.cs | 1 - .../Controllers/Account/AccountController.cs | 2 +- .../Squidex/Config/Domain/SchemasServices.cs | 4 + .../Config/Domain/SerializationServices.cs | 9 +- .../Squidex/Config/Domain/StoreServices.cs | 26 +- backend/src/Squidex/Config/Web/WebServices.cs | 2 - backend/src/Squidex/Squidex.csproj | 2 +- .../Model/Apps/App.json | 112 ++++++ .../Model/Apps/AppTests.cs | 237 +++++++++++++ .../Model/Apps/App_Old.json | 113 ++++++ .../Model/Assets/Asset.json | 27 ++ .../Model/Assets/AssetFolder.json | 12 + .../Model/Assets/AssetFolderTests.cs | 73 ++++ .../Model/Assets/AssetTests.cs | 165 +++++++++ .../Model/Contents/Content.json | 24 ++ .../Model/Contents/ContentTests.cs | 34 ++ .../Model/Contents/TranslationStatusTests.cs | 8 +- .../Model/Contents/WorkflowJsonTests.cs | 2 +- .../Model/Contents/WriteContent.json | 33 ++ .../Model/Contents/WriteContentTests.cs | 106 ++++++ .../Model/Rules/Rule.json} | 27 +- .../Model/Rules/RuleTests.cs | 77 +++-- .../Model/Rules/Rule_Old.json} | 17 +- .../Model/Schemas/FieldNamesTests.cs | 134 ++++++++ .../Model/Schemas/Schema.json | 325 ++++++++++++++++++ .../Model/Schemas/SchemaFieldTests.cs | 6 - .../Model/Schemas/SchemaTests.cs | 108 +++--- .../Model/Schemas/Schema_Old.json} | 13 +- .../Model/Teams/Team.json | 16 + .../Model/Teams/TeamTests.cs | 87 +++++ .../ConvertContent/ContentConversionTests.cs | 2 +- .../ConvertContent/DefaultValuesTests.cs | 35 +- .../ConvertContent/FieldConvertersTests.cs | 34 +- .../ConvertContent/ValueConvertersTests.cs | 12 +- .../SchemaSynchronizerTests.cs | 139 ++++---- .../ReferenceExtractionTests.cs | 4 +- .../ReferenceFormattingTests.cs | 6 +- .../HandleRules/RuleServiceTests.cs | 7 +- .../Scripting/ScriptingCompleterTests.cs | 2 +- .../ValidateContent/ArrayFieldTests.cs | 2 +- .../ValidateContent/AssetsFieldTests.cs | 3 +- .../ValidateContent/ComponentFieldTests.cs | 2 +- .../ValidateContent/ComponentsFieldTests.cs | 2 +- .../ValidateContent/ContentValidationTests.cs | 2 +- .../ValidateContent/UIFieldTests.cs | 6 +- .../ValidationTestExtensions.cs | 12 +- .../Validators/AssetsValidatorTests.cs | 54 +-- .../Validators/ComponentValidatorTests.cs | 2 +- .../Squidex.Domain.Apps.Core.Tests.csproj | 36 ++ .../TestHelpers/TestAssets.cs | 50 +-- .../TestHelpers/TestSchema.cs | 44 ++- .../TestHelpers/TestUtils.cs | 50 ++- .../AppProviderExtensionsTests.cs | 157 ++++----- .../AppProviderTests.cs | 9 +- .../Apps/AppPermanentDeleterTests.cs | 10 +- .../Apps/AppUISettingsTests.cs | 6 +- .../Apps/BackupAppsTests.cs | 11 +- .../DomainObject/AppCommandMiddlewareTests.cs | 7 +- .../Apps/DomainObject/AppDomainObjectTests.cs | 55 +-- .../Apps/DomainObject/AppState.json | 134 -------- .../Apps/DomainObject/AppStateTests.cs | 40 --- .../Guards/GuardAppClientsTests.cs | 56 +-- .../Guards/GuardAppContributorsTests.cs | 52 ++- .../DomainObject/Guards/GuardAppRolesTests.cs | 66 +++- .../Apps/DomainObject/Guards/GuardAppTests.cs | 41 ++- .../Guards/GuardAppWorkflowTests.cs | 9 +- .../Apps/Indexes/AppsIndexTests.cs | 13 +- .../Apps/RolePermissionsProviderTests.cs | 9 +- .../Assets/AssetChangedTriggerHandlerTests.cs | 15 +- .../Assets/AssetUsageTrackerTests.cs | 8 +- .../Assets/AssetsFluidExtensionTests.cs | 8 +- .../Assets/AssetsJintExtensionTests.cs | 6 +- .../Assets/AssetsSearchSourceTests.cs | 13 +- .../Assets/BackupAssetsTests.cs | 5 +- .../Assets/DefaultAssetFileStoreTests.cs | 4 +- .../AssetCommandMiddlewareTests.cs | 58 ++-- .../DomainObject/AssetDomainObjectTests.cs | 28 +- .../AssetFolderDomainObjectTests.cs | 5 +- .../Guards/GuardAssetFolderTests.cs | 41 +-- .../DomainObject/Guards/GuardAssetTests.cs | 53 +-- .../Guards/ScriptingExtensionsTests.cs | 26 +- .../Assets/FileTagAssetMetadataSourceTests.cs | 12 +- .../FileTypeAssetMetadataSourceTests.cs | 4 +- .../Assets/ImageAssetMetadataSourceTests.cs | 12 +- .../Assets/MongoDb/AssetMappingTests.cs | 56 +-- .../Assets/MongoDb/AssetQueryTests.cs | 2 + .../Assets/MongoDb/AssetsQueryFixture.cs | 26 +- .../MongoDb/AssetsQueryIntegrationTests.cs | 3 +- .../Assets/Queries/AssetEnricherTests.cs | 16 +- .../Assets/Queries/AssetLoaderTests.cs | 19 +- .../Assets/Queries/AssetQueryServiceTests.cs | 110 +++--- .../Assets/Queries/CalculateTokensTests.cs | 5 - .../Assets/Queries/ConvertTagsTests.cs | 31 +- .../Assets/Queries/EnrichForCachingTests.cs | 10 +- .../Queries/EnrichWithMetadataTextTests.cs | 16 +- .../Assets/Queries/ScriptAssetTests.cs | 14 +- .../Billing/NoopBillingManagerTests.cs | 25 +- .../Billing/UsageGateTests.cs | 62 ++-- .../Billing/UsageNotifierWorkerTest.cs | 4 +- .../CommentTriggerHandlerTests.cs | 12 +- .../Contents/BackupContentsTests.cs | 2 +- .../ContentChangedTriggerHandlerTests.cs | 35 +- .../Contents/ContentSchedulerProcessTests.cs | 16 +- .../Contents/ContentsSearchSourceTests.cs | 29 +- .../Contents/DefaultContentWorkflowTests.cs | 197 ----------- .../ContentCommandMiddlewareTests.cs | 38 +- .../DomainObject/ContentDomainObjectTests.cs | 76 ++-- ...ontentsBulkUpdateCommandMiddlewareTests.cs | 33 +- .../DomainObject/Guards/GuardContentTests.cs | 88 +++-- .../Contents/DynamicContentWorkflowTests.cs | 76 ++-- .../GraphQL/GraphQLIntrospectionTests.cs | 93 ++--- .../Contents/GraphQL/GraphQLMutationTests.cs | 4 +- .../Contents/GraphQL/GraphQLQueriesTests.cs | 8 +- .../Contents/GraphQL/GraphQLTestBase.cs | 3 +- .../Contents/GraphQL/TestApp.cs | 20 +- .../Contents/GraphQL/TestAsset.cs | 6 +- .../Contents/GraphQL/TestContent.cs | 68 ++-- .../Contents/GraphQL/TestQuery.cs | 2 +- .../Contents/GraphQL/TestSchemas.cs | 162 +++++---- .../Contents/MongoDb/ContentMappingTests.cs | 90 +---- .../Contents/MongoDb/ContentQueryTests.cs | 79 ++--- .../Contents/MongoDb/ContentsQueryFixture.cs | 76 ++-- .../MongoDb/ContentsQueryTestsBase.cs | 15 +- .../Contents/Queries/CalculateTokensTests.cs | 5 - .../Contents/Queries/ContentEnricherTests.cs | 25 +- .../Contents/Queries/ContentLoaderTests.cs | 25 +- .../Queries/ContentQueryParserTests.cs | 10 +- .../Queries/ContentQueryServiceTests.cs | 65 ++-- .../Contents/Queries/ConvertDataTests.cs | 54 ++- .../Contents/Queries/EnrichForCachingTests.cs | 10 +- .../Contents/Queries/EnrichWithSchemaTests.cs | 5 - .../Queries/EnrichWithWorkflowsTests.cs | 20 +- .../Contents/Queries/ResolveAssetsTests.cs | 65 ++-- .../Queries/ResolveReferencesTests.cs | 106 +++--- .../Contents/Queries/ScriptContentTests.cs | 47 ++- .../Contents/ReferencesFluidExtensionTests.cs | 4 +- .../Contents/ReferencesJintExtensionTests.cs | 9 +- .../EntityExtensions.cs | 64 ++++ .../InvitationEventConsumerTests.cs | 10 +- .../InviteUserCommandMiddlewareTests.cs | 12 +- .../Rules/BackupRulesTests.cs | 3 +- .../DomainObject/Guards/GuardRuleTests.cs | 18 +- .../Triggers/ContentChangedTriggerTests.cs | 4 +- .../RuleCommandMiddlewareTests.cs | 19 +- .../DomainObject/RuleDomainObjectTests.cs | 24 +- .../Rules/DomainObject/RuleStateTests.cs | 63 ---- .../Rules/Indexes/RulesIndexTests.cs | 11 +- .../Rules/Queries/RuleEnricherTests.cs | 9 +- .../Rules/Queries/RuleQueryServiceTests.cs | 9 +- .../Rules/RuleEnqueuerTests.cs | 50 +-- .../UsageTracking/UsageTriggerHandlerTests.cs | 15 +- .../Schemas/BackupSchemasTests.cs | 3 +- .../Guards/GuardSchemaFieldTests.cs | 98 +++--- .../DomainObject/Guards/GuardSchemaTests.cs | 59 ++-- .../DomainObject/SchemaDomainObjectTests.cs | 209 +++++++---- .../Schemas/DomainObject/SchemaStateTests.cs | 40 --- .../Schemas/Indexes/SchemasIndexTests.cs | 13 +- ...MigrateFieldNamesCommandMiddlewareTests.cs | 106 ++++++ .../Schemas/MongoDb/SchemasHashTests.cs | 54 +-- .../SchemaChangedTriggerHandlerTests.cs | 15 +- .../Schemas/SchemaCommandsTests.cs | 126 +++++-- .../Schemas/SchemasSearchSourceTests.cs | 40 +-- .../Search/SearchManagerTests.cs | 2 +- .../Squidex.Domain.Apps.Entities.Tests.csproj | 12 - .../Guards/GuardTeamContributorsTests.cs | 39 ++- .../DomainObject/TeamDomainObjectTests.cs | 31 +- .../Teams/Indexes/TeamsIndexTests.cs | 40 +-- .../TestHelpers/GivenContext.cs | 182 +++++++++- .../TestHelpers/HandlerTestBase.cs | 2 +- .../TestHelpers/Mocks.cs | 61 ---- .../Commands/CommandContextTests.cs | 4 +- .../Commands/DomainObjectTests.cs | 4 +- .../Json/System/PolymorphicConverterTests.cs | 4 +- .../Log/BackgroundRequestLogStoreTests.cs | 4 +- .../TestHelpers/MyDomainObject.cs | 5 + .../TestHelpers/MyDomainState.cs | 9 +- .../BackgroundUsageTrackerTests.cs | 4 +- .../ValidationExceptionTests.cs | 1 - .../ApiPermissionAttributeTests.cs | 9 +- .../ETagCommandMiddlewareTests.cs | 8 +- ...nrichWithSchemaIdCommandMiddlewareTests.cs | 8 +- .../EnrichWithTeamIdCommandMiddlewareTests.cs | 6 +- .../Pipeline/AppResolverTests.cs | 33 +- .../Pipeline/SchemaResolverTests.cs | 15 +- .../Pipeline/TeamResolverTests.cs | 16 +- .../Pipeline/UsageMiddlewareTests.cs | 19 +- .../content-inspection.component.ts | 10 +- .../contents/content-list-header.component.ts | 4 +- .../shared/services/contents.service.spec.ts | 40 +++ .../app/shared/services/contents.service.ts | 14 +- .../app/shared/services/schemas.service.ts | 40 +-- .../src/app/shared/services/schemas.spec.ts | 32 +- .../app/shared/state/table-settings.spec.ts | 22 +- .../TestSuite.ApiTests/ContentQueryTests.cs | 32 +- .../TestSuite.ApiTests/GraphQLTests.cs | 2 +- .../TestSuite.ApiTests.csproj | 10 +- .../TestSuite.LoadTests.csproj | 2 +- .../TestSuite.Shared/TestSuite.Shared.csproj | 8 +- 632 files changed, 8028 insertions(+), 7324 deletions(-) rename backend/src/{Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs => Squidex.Domain.Apps.Core.Model/AppEntity.cs} (57%) create mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Apps/App.cs rename backend/src/{Squidex.Domain.Apps.Entities => Squidex.Domain.Apps.Core.Model}/Apps/AppExtensions.cs (60%) create mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Assets/Asset.cs create mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetFolder.cs rename backend/src/{Squidex.Domain.Apps.Entities/IEntity.cs => Squidex.Domain.Apps.Core.Model/Assets/AssetItem.cs} (64%) rename backend/src/{Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs => Squidex.Domain.Apps.Core.Model/Contents/Content.cs} (51%) rename backend/src/{Squidex.Web/Pipeline/AppFeature.cs => Squidex.Domain.Apps.Core.Model/Contents/ContentVersion.cs} (77%) rename backend/src/{Squidex.Domain.Apps.Entities => Squidex.Domain.Apps.Core.Model}/Contents/ScheduleJob.cs (56%) create mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Contents/WriteContent.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldSettings.cs create mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/FieldsSurrogate.cs create mode 100644 backend/src/Squidex.Domain.Apps.Core.Model/Teams/Team.cs create mode 100644 backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/AssetItemClassMap.cs create mode 100644 backend/src/Squidex.Domain.Apps.Entities.MongoDb/EntityClassMap.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Apps/AppEntityExtensions.cs rename backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/{_AppCommand.cs => AppCommand.cs} (69%) rename backend/src/Squidex.Domain.Apps.Entities/{IEntityWithLastModifiedBy.cs => Apps/Commands/AppCommandBase.cs} (65%) delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs rename backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/{_AssetCommand.cs => AssetCommand.cs} (66%) create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommandBase.cs create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetFolderCommand.cs rename backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/{_AssetFolderCommand.cs => AssetFolderCommandBase.cs} (66%) rename backend/src/Squidex.Domain.Apps.Entities/Assets/{IAssetFolderEntity.cs => EnrichedAsset.cs} (66%) delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Assets/IEnrichedAssetEntity.cs rename backend/src/{Squidex.Domain.Apps.Core.Model/Schemas/FieldBase.cs => Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs} (58%) rename backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/{_ContentCommand.cs => ContentCommandBase.cs} (70%) delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/ContentExtensions.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultContentWorkflow.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentVersion.cs rename backend/src/Squidex.Domain.Apps.Entities/Contents/{ContentEntity.cs => EnrichedContent.cs} (56%) delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs create mode 100644 backend/src/Squidex.Domain.Apps.Entities/EntityExtensions.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/IEntityWithTags.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/IEntityWithVersion.cs create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommand.cs rename backend/src/Squidex.Domain.Apps.Entities/Rules/Commands/{_RuleCommand.cs => RuleCommandBase.cs} (68%) rename backend/src/Squidex.Domain.Apps.Entities/Rules/{IEnrichedRuleEntity.cs => EnrichedRule.cs} (71%) delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEntity.cs rename backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/{_SchemaCommand.cs => SchemaCommand.cs} (66%) create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommandBase.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Schemas/MigrateFieldNamesCommandMiddleware.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs rename backend/src/Squidex.Domain.Apps.Entities/Teams/Commands/{_TeamCommand.cs => TeamCommand.cs} (69%) rename backend/src/Squidex.Domain.Apps.Entities/{IEntityWithCreatedBy.cs => Teams/Commands/TeamCommandBase.cs} (65%) delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Teams/ITeamEntity.cs rename backend/src/Squidex.Infrastructure/Commands/{IDomainState.cs => Entity.cs} (51%) delete mode 100644 backend/src/Squidex.Infrastructure/IWithId.cs create mode 100644 backend/src/Squidex.Infrastructure/Json/System/JsonIgnoreReadonlyProperties.cs create mode 100644 backend/src/Squidex.Infrastructure/Json/System/JsonRenameAttribute.cs delete mode 100644 backend/src/Squidex.Infrastructure/Json/System/PolymorphicTypeResolver.cs delete mode 100644 backend/src/Squidex.Web/FodyWeavers.xml delete mode 100644 backend/src/Squidex.Web/FodyWeavers.xsd delete mode 100644 backend/src/Squidex.Web/IAppFeature.cs delete mode 100644 backend/src/Squidex.Web/ISchemaFeature.cs delete mode 100644 backend/src/Squidex.Web/ITeamFeature.cs delete mode 100644 backend/src/Squidex.Web/Pipeline/SchemaFeature.cs delete mode 100644 backend/src/Squidex.Web/Pipeline/TeamFeature.cs create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/App.json create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppTests.cs create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/App_Old.json create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/Asset.json create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetFolder.json create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetFolderTests.cs create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetTests.cs create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/Content.json create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentTests.cs create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WriteContent.json create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WriteContentTests.cs rename backend/tests/{Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleState.json => Squidex.Domain.Apps.Core.Tests/Model/Rules/Rule.json} (57%) rename backend/tests/{Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleState_Old.json => Squidex.Domain.Apps.Core.Tests/Model/Rules/Rule_Old.json} (82%) create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldNamesTests.cs create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Schema.json rename backend/tests/{Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaState.json => Squidex.Domain.Apps.Core.Tests/Model/Schemas/Schema_Old.json} (99%) create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Teams/Team.json create mode 100644 backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Teams/TeamTests.cs delete mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppState.json delete mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppStateTests.cs delete mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs create mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/EntityExtensions.cs delete mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleStateTests.cs delete mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaStateTests.cs create mode 100644 backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MigrateFieldNamesCommandMiddlewareTests.cs diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 861aa51e8..b6be43aab 100644 --- a/.github/workflows/dev.yml +++ b/.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() diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1540b332b..81e235218 100644 --- a/.github/workflows/release.yml +++ b/.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() diff --git a/backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs index 657db7908..ad6baaa3e 100644 --- a/backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs +++ b/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; diff --git a/backend/extensions/Squidex.Extensions/Actions/DeepDetect/DeepDetectActionHandler.cs b/backend/extensions/Squidex.Extensions/Actions/DeepDetect/DeepDetectActionHandler.cs index d8d44af72..812fc4ee0 100644 --- a/backend/extensions/Squidex.Extensions/Actions/DeepDetect/DeepDetectActionHandler.cs +++ b/backend/extensions/Squidex.Extensions/Actions/DeepDetect/DeepDetectActionHandler.cs @@ -131,7 +131,7 @@ internal sealed partial class DeepDetectActionHandler : RuleActionHandler Format(IAssetEntity asset) + public IEnumerable Format(Asset asset) { yield break; } diff --git a/backend/extensions/Squidex.Extensions/Samples/Middleware/DoubleLinkedContentMiddleware.cs b/backend/extensions/Squidex.Extensions/Samples/Middleware/DoubleLinkedContentMiddleware.cs index 665c2f7da..f784dfdf0 100644 --- a/backend/extensions/Squidex.Extensions/Samples/Middleware/DoubleLinkedContentMiddleware.cs +++ b/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(); + var content = context.Result(); 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. diff --git a/backend/extensions/Squidex.Extensions/Text/Azure/AzureTextIndex.cs b/backend/extensions/Squidex.Extensions/Text/Azure/AzureTextIndex.cs index ae281076e..4efa49669 100644 --- a/backend/extensions/Squidex.Extensions/Text/Azure/AzureTextIndex.cs +++ b/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?> SearchAsync(IAppEntity app, GeoQuery query, SearchScope scope, + public async Task?> 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?> SearchAsync(IAppEntity app, TextQuery query, SearchScope scope, + public async Task?> 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); diff --git a/backend/extensions/Squidex.Extensions/Text/ElasticSearch/ElasticSearchTextIndex.cs b/backend/extensions/Squidex.Extensions/Text/ElasticSearch/ElasticSearchTextIndex.cs index 93760635d..d0538a1c0 100644 --- a/backend/extensions/Squidex.Extensions/Text/ElasticSearch/ElasticSearchTextIndex.cs +++ b/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?> SearchAsync(IAppEntity app, GeoQuery query, SearchScope scope, + public async Task?> 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?> SearchAsync(IAppEntity app, TextQuery query, SearchScope scope, + public async Task?> SearchAsync(App app, TextQuery query, SearchScope scope, CancellationToken ct = default) { Guard.NotNull(app); diff --git a/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs b/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs index 2ff3abe8c..eb952b9ba 100644 --- a/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs +++ b/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)) { diff --git a/backend/src/Migrations/Migrations/ClearRules.cs b/backend/src/Migrations/Migrations/ClearRules.cs index 444d8e651..95af15b4d 100644 --- a/backend/src/Migrations/Migrations/ClearRules.cs +++ b/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 store; + private readonly IStore store; - public ClearRules(IStore store) + public ClearRules(IStore store) { this.store = store; } diff --git a/backend/src/Migrations/Migrations/ClearSchemas.cs b/backend/src/Migrations/Migrations/ClearSchemas.cs index 553db3215..9ec8c429e 100644 --- a/backend/src/Migrations/Migrations/ClearSchemas.cs +++ b/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 store; + private readonly IStore store; - public ClearSchemas(IStore store) + public ClearSchemas(IStore store) { this.store = store; } diff --git a/backend/src/Migrations/Migrations/CreateAssetSlugs.cs b/backend/src/Migrations/Migrations/CreateAssetSlugs.cs index 4ed6fe549..3480f8f57 100644 --- a/backend/src/Migrations/Migrations/CreateAssetSlugs.cs +++ b/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 stateForAssets; + private readonly ISnapshotStore stateForAssets; - public CreateAssetSlugs(ISnapshotStore stateForAssets) + public CreateAssetSlugs(ISnapshotStore stateForAssets) { this.stateForAssets = stateForAssets; } @@ -28,7 +28,7 @@ public sealed class CreateAssetSlugs : IMigration { state.Slug = state.FileName.ToAssetSlug(); - var job = new SnapshotWriteJob(key, state, version); + var job = new SnapshotWriteJob(key, state, version); await stateForAssets.WriteAsync(job, ct); } diff --git a/backend/src/Migrations/OldEvents/AppPatternAdded.cs b/backend/src/Migrations/OldEvents/AppPatternAdded.cs index 1559011f6..c28fec8e3 100644 --- a/backend/src/Migrations/OldEvents/AppPatternAdded.cs +++ b/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 +public sealed class AppPatternAdded : AppEvent, IMigratedStateEvent { public DomainId PatternId { get; set; } @@ -29,7 +28,7 @@ public sealed class AppPatternAdded : AppEvent, IMigratedStateEvent +public sealed class AppPatternDeleted : AppEvent, IMigratedStateEvent { public DomainId PatternId { get; set; } - public IEvent Migrate(AppDomainObject.State state) + public IEvent Migrate(App state) { var newEvent = new AppSettingsUpdated { diff --git a/backend/src/Migrations/OldEvents/AppPatternUpdated.cs b/backend/src/Migrations/OldEvents/AppPatternUpdated.cs index 523cc79cc..ca2be86c5 100644 --- a/backend/src/Migrations/OldEvents/AppPatternUpdated.cs +++ b/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 +public sealed class AppPatternUpdated : AppEvent, IMigratedStateEvent { public DomainId PatternId { get; set; } @@ -29,7 +28,7 @@ public sealed class AppPatternUpdated : AppEvent, IMigratedStateEvent 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(); 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(); + 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.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.Create(fields.ToArray()) + }; + return SimpleMapper.Map(this, new SchemaCreatedV2 { Schema = schema }); } } diff --git a/backend/src/Migrations/RebuilderExtensions.cs b/backend/src/Migrations/RebuilderExtensions.cs index 73aea3227..334949a5b 100644 --- a/backend/src/Migrations/RebuilderExtensions.cs +++ b/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(streamFilter, batchSize, AllowedErrorRate, ct); + return rebuilder.RebuildAsync(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(streamFilter, batchSize, AllowedErrorRate, ct); + return rebuilder.RebuildAsync(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(streamFilter, batchSize, AllowedErrorRate, ct); + return rebuilder.RebuildAsync(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(streamFilter, batchSize, AllowedErrorRate, ct); + return rebuilder.RebuildAsync(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(streamFilter, batchSize, AllowedErrorRate, ct); + return rebuilder.RebuildAsync(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(streamFilter, batchSize, AllowedErrorRate, ct); + return rebuilder.RebuildAsync(streamFilter, batchSize, AllowedErrorRate, ct); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs b/backend/src/Squidex.Domain.Apps.Core.Model/AppEntity.cs similarity index 57% rename from backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/AppEntity.cs index 7d1c9e209..ce8f226c8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs +++ b/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 AppId { get; set; } + public NamedId 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); + } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/App.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/App.cs new file mode 100644 index 000000000..bef501f3f --- /dev/null +++ b/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 state, Func update) + { + var newClients = update(state, Clients); + + if (ReferenceEquals(Clients, newClients)) + { + return this; + } + + return this with { Clients = newClients }; + } + + [Pure] + public App UpdateContributors(T state, Func update) + { + var newContributors = update(state, Contributors); + + if (ReferenceEquals(Contributors, newContributors)) + { + return this; + } + + return this with { Contributors = newContributors }; + } + + [Pure] + public App UpdateLanguages(T state, Func update) + { + var newLanguages = update(state, Languages); + + if (ReferenceEquals(Languages, newLanguages)) + { + return this; + } + + return this with { Languages = newLanguages }; + } + + [Pure] + public App UpdateRoles(T state, Func update) + { + var newRoles = update(state, Roles); + + if (ReferenceEquals(Roles, newRoles)) + { + return this; + } + + return this with { Roles = newRoles }; + } + + [Pure] + public App UpdateWorkflows(T state, Func update) + { + var newWorkflows = update(state, Workflows); + + if (ReferenceEquals(Workflows, newWorkflows)) + { + return this; + } + + return this with { Workflows = newWorkflows }; + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs index e277f0e50..3450e900d 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs +++ b/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"; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppExtensions.cs similarity index 60% rename from backend/src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppExtensions.cs index 3ff58c0a8..c116f9a68 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs +++ b/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 NamedId(this IAppEntity app) + public static NamedId NamedId(this App app) { return new NamedId(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); } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguageConfigSurrogate.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguageConfigSurrogate.cs index 0112e2636..11d05f6b9 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguageConfigSurrogate.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguageConfigSurrogate.cs @@ -25,7 +25,7 @@ public sealed class LanguageConfigSurrogate : ISurrogate public LanguageConfig ToSource() { - if (!IsOptional && (Fallback == null || Fallback.Length == 0)) + if (!IsOptional && Fallback is not { Length: > 0 }) { return LanguageConfig.Default; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesSurrogate.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesSurrogate.cs index 68cae9d2e..8968043dc 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesSurrogate.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesSurrogate.cs @@ -47,6 +47,7 @@ public sealed class RolesSurrogate : Dictionary, ISurrogate 0) diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Assets/Asset.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Assets/Asset.cs new file mode 100644 index 000000000..7a4f69a70 --- /dev/null +++ b/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 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? 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; + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetFolder.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetFolder.cs new file mode 100644 index 000000000..2168a9162 --- /dev/null +++ b/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 }; + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/IEntity.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetItem.cs similarity index 64% rename from backend/src/Squidex.Domain.Apps.Entities/IEntity.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetItem.cs index fbbdde95a..0758a3360 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/IEntity.cs +++ b/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 +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); + } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetMetadata.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetMetadata.cs index 82b1687ac..4d9b29cd7 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetMetadata.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetMetadata.cs @@ -37,28 +37,28 @@ public sealed class AssetMetadata : Dictionary 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; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Content.cs similarity index 51% rename from backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Contents/Content.cs index 0895b2680..42087fdec 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs +++ b/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 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; + } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs index 8c58bf46f..ed7cb6b38 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs @@ -37,7 +37,7 @@ public sealed class ContentData : Dictionary, IEquata public ContentData UseSameFields(ContentData? other) { - if (other == null || other.Count == 0) + if (other is not { Count: > 0 }) { return this; } diff --git a/backend/src/Squidex.Web/Pipeline/AppFeature.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentVersion.cs similarity index 77% rename from backend/src/Squidex.Web/Pipeline/AppFeature.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentVersion.cs index 1d2325c13..5fab45c3b 100644 --- a/backend/src/Squidex.Web/Pipeline/AppFeature.cs +++ b/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) +{ +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowStepSurrogate.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowStepSurrogate.cs index 48c290238..4fbd93046 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowStepSurrogate.cs +++ b/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 { - public Dictionary 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 Transitions { get; set; } + public void FromSource(WorkflowStep source) { SimpleMapper.Map(source, this); @@ -44,10 +52,13 @@ public sealed class WorkflowStepSurrogate : ISurrogate { 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( diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowTransitionSurrogate.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowTransitionSurrogate.cs index a653ecf28..7ab17d5e8 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowTransitionSurrogate.cs +++ b/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 { 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 { var roles = Roles; - if (!string.IsNullOrEmpty(Role)) - { - roles = [Role]; - } - return WorkflowTransition.When(Expression, roles); } -} \ No newline at end of file +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/ScheduleJob.cs similarity index 56% rename from backend/src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs rename to backend/src/Squidex.Domain.Apps.Core.Model/Contents/ScheduleJob.cs index 1638b29c1..1efcc60f9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs +++ b/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); diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Contents/WriteContent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Contents/WriteContent.cs new file mode 100644 index 000000000..14e580356 --- /dev/null +++ b/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 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 + }; + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/IEnrichedEntityEvent.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/IEnrichedEntityEvent.cs index ea27f47f3..e24023dbe 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/IEnrichedEntityEvent.cs +++ b/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 +public interface IEnrichedEntityEvent { + public DomainId Id { get; } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleSorrgate.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleSorrgate.cs index f454c912d..99b2564e0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleSorrgate.cs +++ b/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 +public sealed record RuleSorrgate : Rule, ISurrogate { - 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 public Rule ToSource() { - var trigger = Trigger; - - if (trigger is IMigrated 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 migrated) { - rule = rule.Rename(Name); + return this with { Trigger = migrated.Migrate() }; } - return rule; + return this; } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs index 42c7ee86a..96815cd05 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs +++ b/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 updater) - { - var clone = (Rule)MemberwiseClone(); - - updater(clone); - - return clone; + return this with { Action = newAction }; } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs index 30a3f1bae..28743229a 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs +++ b/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; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs index b8edb2566..50c36f2b0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs +++ b/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, IArrayField +public sealed record ArrayField : RootField, IArrayField { + [JsonIgnore] public IReadOnlyList Fields { get => FieldCollection.Ordered; } + [JsonIgnore] public IReadOnlyDictionary FieldsById { get => FieldCollection.ById; } + [JsonIgnore] public IReadOnlyDictionary FieldsByName { get => FieldCollection.ByName; } - public FieldCollection FieldCollection { get; private set; } = FieldCollection.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(fields); - } + public FieldCollection FieldCollection { get; init; } = FieldCollection.Empty; [Pure] public ArrayField DeleteField(long fieldId) @@ -67,9 +65,6 @@ public sealed class ArrayField : RootField, IArrayField return this; } - return (ArrayField)Clone(clone => - { - ((ArrayField)clone).FieldCollection = newFields; - }); + return this with { FieldCollection = newFields }; } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs index 3a6e74c40..a0bbf1931 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs +++ b/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(); } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs index dbf985636..85abfadf4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs @@ -72,13 +72,13 @@ public sealed record AssetsFieldProperties : FieldProperties return visitor.Visit((IField)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); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs index 95685c06c..48164115b 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs @@ -27,13 +27,13 @@ public sealed record BooleanFieldProperties : FieldProperties return visitor.Visit((IField)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); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ComponentFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ComponentFieldProperties.cs index f459fb911..48c7dbbb7 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ComponentFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ComponentFieldProperties.cs @@ -36,13 +36,13 @@ public sealed record ComponentFieldProperties : FieldProperties return visitor.Visit((IField)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); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ComponentsFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ComponentsFieldProperties.cs index 09a7bf436..769de7aa6 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ComponentsFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ComponentsFieldProperties.cs @@ -44,13 +44,13 @@ public sealed record ComponentsFieldProperties : FieldProperties return visitor.Visit((IField)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); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs index d9cd239ba..eb4743b71 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs @@ -35,13 +35,13 @@ public sealed record DateTimeFieldProperties : FieldProperties return visitor.Visit((IField)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); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs index 465702f11..65572e0e5 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs @@ -78,9 +78,9 @@ public sealed class FieldCollection where T : IField fieldsOrdered = fields; } - private FieldCollection(IEnumerable fields) + public static FieldCollection Create(params T[]? fields) { - fieldsOrdered = fields.ToArray(); + return fields is not { Length: > 0 } ? Empty : new FieldCollection(fields); } [Pure] @@ -91,7 +91,7 @@ public sealed class FieldCollection where T : IField return this; } - return new FieldCollection(fieldsOrdered.Where(x => x.Id != fieldId)); + return new FieldCollection(fieldsOrdered.Where(x => x.Id != fieldId).ToArray()); } [Pure] @@ -109,7 +109,7 @@ public sealed class FieldCollection where T : IField return this; } - return new FieldCollection(fieldsOrdered.OrderBy(f => ids.IndexOf(f.Id))); + return new FieldCollection(fieldsOrdered.OrderBy(f => ids.IndexOf(f.Id)).ToArray()); } [Pure] @@ -127,7 +127,7 @@ public sealed class FieldCollection where T : IField ThrowHelper.ArgumentException($"A field with ID {field.Id} already exists.", nameof(field)); } - return new FieldCollection(fieldsOrdered.Union(Enumerable.Repeat(field, 1))); + return new FieldCollection(fieldsOrdered.Union(Enumerable.Repeat(field, 1)).ToArray()); } [Pure] @@ -153,6 +153,6 @@ public sealed class FieldCollection where T : IField return default!; } - return new FieldCollection(fieldsOrdered.Select(x => ReferenceEquals(x, field) ? newField : x)); + return new FieldCollection(fieldsOrdered.Select(x => ReferenceEquals(x, field) ? newField : x).ToArray()); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs index 7c86bfb27..b811bdee0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs +++ b/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 { + private const string MetaPrefix = "meta."; + private const string DataPrefix = "data."; + private static readonly HashSet 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()); - public FieldNames() + private FieldNames() { } @@ -22,26 +43,100 @@ public sealed class FieldNames : ReadonlyList { } - 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; } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs index b85a3e924..34733f5ca 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs @@ -27,7 +27,7 @@ public abstract record FieldProperties : NamedElementPropertiesBase public abstract T Accept(IFieldVisitor 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); } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRules.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRules.cs index 3c79c44b9..85569f834 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRules.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRules.cs @@ -22,8 +22,8 @@ public sealed class FieldRules : ReadonlyList { } - 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()); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs index 3bed967a9..1bccf355d 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs +++ b/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(fields) }; } public static RootField Assets(long id, string name, Partitioning partitioning, - AssetsFieldProperties? properties = null, IFieldSettings? settings = null) + AssetsFieldProperties? properties = null) { - return new RootField(id, name, partitioning, properties, settings); + return new RootField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new () }; } public static RootField Boolean(long id, string name, Partitioning partitioning, - BooleanFieldProperties? properties = null, IFieldSettings? settings = null) + BooleanFieldProperties? properties = null) { - return new RootField(id, name, partitioning, properties, settings); + return new RootField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() }; } public static RootField Component(long id, string name, Partitioning partitioning, - ComponentFieldProperties? properties = null, IFieldSettings? settings = null) + ComponentFieldProperties? properties = null) { - return new RootField(id, name, partitioning, properties, settings); + return new RootField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() }; } public static RootField Components(long id, string name, Partitioning partitioning, - ComponentsFieldProperties? properties = null, IFieldSettings? settings = null) + ComponentsFieldProperties? properties = null) { - return new RootField(id, name, partitioning, properties, settings); + return new RootField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() }; } public static RootField DateTime(long id, string name, Partitioning partitioning, - DateTimeFieldProperties? properties = null, IFieldSettings? settings = null) + DateTimeFieldProperties? properties = null) { - return new RootField(id, name, partitioning, properties, settings); + return new RootField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() }; } public static RootField Geolocation(long id, string name, Partitioning partitioning, - GeolocationFieldProperties? properties = null, IFieldSettings? settings = null) + GeolocationFieldProperties? properties = null) { - return new RootField(id, name, partitioning, properties, settings); + return new RootField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() }; } public static RootField Json(long id, string name, Partitioning partitioning, - JsonFieldProperties? properties = null, IFieldSettings? settings = null) + JsonFieldProperties? properties = null) { - return new RootField(id, name, partitioning, properties, settings); + return new RootField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() }; } public static RootField Number(long id, string name, Partitioning partitioning, - NumberFieldProperties? properties = null, IFieldSettings? settings = null) + NumberFieldProperties? properties = null) { - return new RootField(id, name, partitioning, properties, settings); + return new RootField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() }; } public static RootField References(long id, string name, Partitioning partitioning, - ReferencesFieldProperties? properties = null, IFieldSettings? settings = null) + ReferencesFieldProperties? properties = null) { - return new RootField(id, name, partitioning, properties, settings); + return new RootField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() }; } public static RootField String(long id, string name, Partitioning partitioning, - StringFieldProperties? properties = null, IFieldSettings? settings = null) + StringFieldProperties? properties = null) { - return new RootField(id, name, partitioning, properties, settings); + return new RootField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() }; } public static RootField Tags(long id, string name, Partitioning partitioning, - TagsFieldProperties? properties = null, IFieldSettings? settings = null) + TagsFieldProperties? properties = null) { - return new RootField(id, name, partitioning, properties, settings); + return new RootField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() }; } public static RootField UI(long id, string name, Partitioning partitioning, - UIFieldProperties? properties = null, IFieldSettings? settings = null) + UIFieldProperties? properties = null) { - return new RootField(id, name, partitioning, properties, settings); + return new RootField { Id = id, Name = name, Partitioning = partitioning, Properties = properties ?? new() }; } public static NestedField Assets(long id, string name, - AssetsFieldProperties? properties = null, IFieldSettings? settings = null) + AssetsFieldProperties? properties = null) { - return new NestedField(id, name, properties, settings); + return new NestedField { Id = id, Name = name, Properties = properties ?? new() }; } public static NestedField Boolean(long id, string name, - BooleanFieldProperties? properties = null, IFieldSettings? settings = null) + BooleanFieldProperties? properties = null) { - return new NestedField(id, name, properties, settings); + return new NestedField { Id = id, Name = name, Properties = properties ?? new() }; } public static NestedField Component(long id, string name, - ComponentFieldProperties? properties = null, IFieldSettings? settings = null) + ComponentFieldProperties? properties = null) { - return new NestedField(id, name, properties, settings); + return new NestedField { Id = id, Name = name, Properties = properties ?? new() }; } public static NestedField Components(long id, string name, - ComponentsFieldProperties? properties = null, IFieldSettings? settings = null) + ComponentsFieldProperties? properties = null) { - return new NestedField(id, name, properties, settings); + return new NestedField { Id = id, Name = name, Properties = properties ?? new() }; } public static NestedField DateTime(long id, string name, - DateTimeFieldProperties? properties = null, IFieldSettings? settings = null) + DateTimeFieldProperties? properties = null) { - return new NestedField(id, name, properties, settings); + return new NestedField { Id = id, Name = name, Properties = properties ?? new() }; } public static NestedField Geolocation(long id, string name, - GeolocationFieldProperties? properties = null, IFieldSettings? settings = null) + GeolocationFieldProperties? properties = null) { - return new NestedField(id, name, properties, settings); + return new NestedField { Id = id, Name = name, Properties = properties ?? new() }; } public static NestedField Json(long id, string name, - JsonFieldProperties? properties = null, IFieldSettings? settings = null) + JsonFieldProperties? properties = null) { - return new NestedField(id, name, properties, settings); + return new NestedField { Id = id, Name = name, Properties = properties ?? new() }; } public static NestedField Number(long id, string name, - NumberFieldProperties? properties = null, IFieldSettings? settings = null) + NumberFieldProperties? properties = null) { - return new NestedField(id, name, properties, settings); + return new NestedField { Id = id, Name = name, Properties = properties ?? new() }; } public static NestedField References(long id, string name, - ReferencesFieldProperties? properties = null, IFieldSettings? settings = null) + ReferencesFieldProperties? properties = null) { - return new NestedField(id, name, properties, settings); + return new NestedField { Id = id, Name = name, Properties = properties ?? new() }; } public static NestedField String(long id, string name, - StringFieldProperties? properties = null, IFieldSettings? settings = null) + StringFieldProperties? properties = null) { - return new NestedField(id, name, properties, settings); + return new NestedField { Id = id, Name = name, Properties = properties ?? new() }; } public static NestedField Tags(long id, string name, - TagsFieldProperties? properties = null, IFieldSettings? settings = null) + TagsFieldProperties? properties = null) { - return new NestedField(id, name, properties, settings); + return new NestedField { Id = id, Name = name, Properties = properties ?? new() }; } public static NestedField UI(long id, string name, - UIFieldProperties? properties = null, IFieldSettings? settings = null) + UIFieldProperties? properties = null) { - return new NestedField(id, name, properties, settings); + return new NestedField { Id = id, Name = name, Properties = properties ?? new() }; } public static Schema AddArray(this Schema schema, long id, string name, Partitioning partitioning, - Func? handler = null, ArrayFieldProperties? properties = null, IFieldSettings? settings = null) + Func? 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)); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs index 68997a0e8..490f7913f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs @@ -21,13 +21,13 @@ public sealed record GeolocationFieldProperties : FieldProperties return visitor.Visit((IField)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); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IField.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IField.cs index 89c23fa05..a7e1062a9 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IField.cs +++ b/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(IFieldVisitor visitor, TArgs args); diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldSettings.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldSettings.cs deleted file mode 100644 index c3661933c..000000000 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldSettings.cs +++ /dev/null @@ -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; } -} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/FieldSurrogate.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/FieldSurrogate.cs index 7440728b2..34d146dce 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/FieldSurrogate.cs +++ b/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(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 + }; } -} \ No newline at end of file +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/FieldsSurrogate.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/FieldsSurrogate.cs new file mode 100644 index 000000000..20341df8e --- /dev/null +++ b/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, ISurrogate> +{ + public void FromSource(FieldCollection source) + { + AddRange(source.Ordered.Select(FieldSurrogate.FromSource)); + } + + public FieldCollection ToSource() + { + return new FieldCollection(this.Select(x => x.ToRootField()).ToArray()); + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaSurrogate.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaSurrogate.cs index 01cda0c08..aa00632c2 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaSurrogate.cs +++ b/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 +[JsonRename(nameof(FieldCollection), "fields")] +public record SchemaSurrogate : Schema, ISurrogate { - 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? 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()); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs index 7d71d7438..b3ec0ff91 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs @@ -21,13 +21,13 @@ public sealed record JsonFieldProperties : FieldProperties return visitor.Visit((IField)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); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField.cs index bb12b9b9c..de5d2799f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField.cs +++ b/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(IFieldVisitor visitor, TArgs args); public abstract NestedField Update(FieldProperties newProperties); - - protected NestedField Clone(Action updater) - { - var clone = (NestedField)MemberwiseClone(); - - updater(clone); - - return clone; - } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs index e8bd96ff4..3ebcced37 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs +++ b/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 : NestedField, IField where T : FieldProperties, new() +public record NestedField : NestedField, IField 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 : NestedField, IField where T : FieldProperties, return this; } - return Clone(clone => - { - ((NestedField)clone).Properties = typedProperties; - }); + return this with { Properties = typedProperties }; } private static T ValidateProperties(FieldProperties newProperties) diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs index c13c27dfb..51e7f0f18 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs @@ -37,13 +37,13 @@ public sealed record NumberFieldProperties : FieldProperties return visitor.Visit((IField)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); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs index a35b58cd5..bbddb267b 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs @@ -61,13 +61,13 @@ public sealed record ReferencesFieldProperties : FieldProperties return visitor.Visit((IField)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); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField.cs index ea6ca6ba7..cba6f8cb0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField.cs +++ b/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(IFieldVisitor visitor, TArgs args); public abstract RootField Update(FieldProperties newProperties); - - protected RootField Clone(Action updater) - { - var clone = (RootField)MemberwiseClone(); - - updater(clone); - - return clone; - } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs index 68a49be0f..9cc1434d0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs +++ b/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 : RootField, IField where T : FieldProperties, new() +public record RootField : RootField, IField 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 : RootField, IField where T : FieldProperties, new( return this; } - return Clone(clone => - { - ((RootField)clone).Properties = typedProperties; - }); + return this with { Properties = typedProperties }; } private static T ValidateProperties(FieldProperties newProperties) diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs index df63ea34e..002e4a57b 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs +++ b/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 FieldCollection { get; private set; } = FieldCollection.Empty; + public FieldCollection FieldCollection { get; init; } = FieldCollection.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 PreviewUrls { get; private set; } = ReadonlyDictionary.Empty(); + public ReadonlyDictionary PreviewUrls { get; init; } = ReadonlyDictionary.Empty(); + + public long SchemaFieldsTotal { get; init; } public IReadOnlyList 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(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? previewUrls) + public Schema SetPreviewUrls(ReadonlyDictionary previewUrls) { - previewUrls ??= ReadonlyDictionary.Empty(); + 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 updater) - { - var clone = (Schema)MemberwiseClone(); - - updater(clone); - - return clone; + return this with { FieldCollection = newFields }; } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs index 1bce68247..7a10558ca 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs +++ b/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 NamedId(this Schema schema) + { + return new NamedId(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); } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs index 817af70ba..7fc1d7c13 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs @@ -62,13 +62,13 @@ public sealed record StringFieldProperties : FieldProperties return visitor.Visit((IField)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); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs index eaf335f5c..21fee3c4e 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs @@ -37,13 +37,13 @@ public sealed record TagsFieldProperties : FieldProperties return visitor.Visit((IField)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); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldProperties.cs index a5773cad7..5a684de6d 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldProperties.cs @@ -21,13 +21,13 @@ public sealed record UIFieldProperties : FieldProperties return visitor.Visit((IField)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(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(id, name, partitioning, this, settings); + return Fields.UI(id, name, this); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Teams/Team.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Teams/Team.cs new file mode 100644 index 000000000..7644bd06b --- /dev/null +++ b/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 state, Func update) + { + var newContributors = update(state, Contributors); + + if (ReferenceEquals(newContributors, Contributors)) + { + return this; + } + + return this with { Contributors = newContributors }; + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/AddDefaultValues.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/AddDefaultValues.cs index 66ab847a4..665b2163f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/AddDefaultValues.cs +++ b/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? 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; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs index e9561f560..0bf27f7de 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs +++ b/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; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveAssetUrls.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveAssetUrls.cs index 1079872ba..53c582122 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveAssetUrls.cs +++ b/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; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveLanguages.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveLanguages.cs index 63d0c8743..d37bc41fc 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ResolveLanguages.cs +++ b/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? FieldNames { get; init; } + public ResolveLanguages(LanguagesConfig languages, params Language[] filteredLanguages) { HashSet 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; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs index 63f88fa17..8dd3ee5f4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs +++ b/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; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/JobResult.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/JobResult.cs index 3833137c9..6d378e90a 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/JobResult.cs +++ b/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 diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleContext.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleContext.cs index c26298023..b6b39c4a4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleContext.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleContext.cs @@ -32,8 +32,6 @@ public readonly struct RuleContext { public NamedId 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 { - [RuleId] = Rule + [Rule.Id] = Rule }.ToReadonlyDictionary() }; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs index 6ae083c90..8220089c3 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs +++ b/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 CreateJobAsync(EnrichedEvent enrichedEvent, IRuleActionHandler actionHandler, DomainId ruleId, Rule rule, Instant now) + private async Task CreateJobAsync(EnrichedEvent enrichedEvent, IRuleActionHandler actionHandler, Rule rule, Instant now) { var actionType = rule.Action.GetType(); var actionName = typeRegistry.GetName(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, }; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetCommandScriptVars.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetCommandScriptVars.cs index 2040639ff..15bc32301 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetCommandScriptVars.cs +++ b/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; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs index 238ddf44e..214a7d048 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs +++ b/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; diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/RootContext.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/RootContext.cs index 5a98b554c..14786e200 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/RootContext.cs +++ b/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 AppId { get; } - - public NamedId 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 appId, - NamedId 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 path, string message) diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs index 7eec9f0e0..4012d2c3c 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs +++ b/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; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs index 2dc443a37..d7fde413d 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs +++ b/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> CheckAssets(IEnumerable ids, +public delegate Task> CheckAssets(IEnumerable 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 path, ValidationContext context) + private void ValidateCommon(Asset asset, ImmutableQueue 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 path, ValidationContext context) + private void ValidateType(Asset asset, ImmutableQueue path, ValidationContext context) { var type = asset.MimeType == "image/svg+xml" ? AssetType.Image : asset.Type; diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs index d0068d071..1dd6a81f0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs +++ b/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 +public sealed class MongoAppEntity : MongoState { [BsonRequired] [BsonElement("_an")] diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs index 5908e3359..a8d9d7bfa 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs +++ b/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, IAppRepository, IDeleter +public sealed class MongoAppRepository : MongoSnapshotStoreBase, IAppRepository, IDeleter { public MongoAppRepository(IMongoDatabase database) : base(database) { } + protected override string CollectionName() + { + return "States_Apps"; + } + protected override Task SetupCollectionAsync(IMongoCollection collection, CancellationToken ct) { @@ -38,13 +42,13 @@ public sealed class MongoAppRepository : MongoSnapshotStoreBase x.DocumentId, app.Id), ct); } - public async Task> QueryAllAsync(string contributorId, IEnumerable names, + public async Task> QueryAllAsync(string contributorId, IEnumerable names, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoAppRepository/QueryAllAsync")) @@ -57,7 +61,7 @@ public sealed class MongoAppRepository : MongoSnapshotStoreBase> QueryAllAsync(DomainId teamId, + public async Task> QueryAllAsync(DomainId teamId, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoAppRepository/QueryAllAsync")) @@ -70,7 +74,7 @@ public sealed class MongoAppRepository : MongoSnapshotStoreBase FindAsync(DomainId id, + public async Task FindAsync(DomainId id, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoAppRepository/FindAsync")) @@ -83,7 +87,7 @@ public sealed class MongoAppRepository : MongoSnapshotStoreBase FindAsync(string name, + public async Task FindAsync(string name, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoAppRepository/FindAsyncByName")) @@ -96,9 +100,9 @@ public sealed class MongoAppRepository : MongoSnapshotStoreBase RemoveDuplcateNames(List entities) + private static List RemoveDuplcateNames(List entities) { - var byName = new Dictionary(); + var byName = new Dictionary(); // Remove duplicate names, the latest wins. foreach (var entity in entities.OrderBy(x => x.IndexedCreated)) diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/AssetItemClassMap.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/AssetItemClassMap.cs new file mode 100644 index 000000000..49c41a718 --- /dev/null +++ b/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(cm => + { + cm.MapProperty(x => x.ParentId) + .SetElementName("pi") + .SetIgnoreIfDefault(true); + }); + + EntityClassMap.Register(); + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs index 2fbf2c496..e0b2bce57 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs +++ b/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 +public record MongoAssetEntity : Asset, IVersionedEntity { - [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 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 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(cm => + { + cm.MapProperty(x => x.DocumentId) + .SetElementName("_id") + .SetIsRequired(true); + + cm.MapProperty(x => x.IndexedAppId) + .SetElementName("_ai") + .SetIsRequired(true); + }); + + BsonClassMap.TryRegisterClassMap(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 job) + public static MongoAssetEntity Create(SnapshotWriteJob 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); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderEntity.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderEntity.cs index 1df4a7bff..19c65a6ef 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderEntity.cs +++ b/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 +public record MongoAssetFolderEntity : AssetFolder, IVersionedEntity { - [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 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(cm => + { + cm.MapProperty(x => x.DocumentId) + .SetElementName("_id") + .SetIsRequired(true); + + cm.MapProperty(x => x.IndexedAppId) + .SetElementName("_ai") + .SetIsRequired(true); + }); + + BsonClassMap.TryRegisterClassMap(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 job) + public static MongoAssetFolderEntity Create(SnapshotWriteJob 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); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs index 3405acabe..bafd626c2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs +++ b/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, IAssetFolderRepository { + static MongoAssetFolderRepository() + { + MongoAssetFolderEntity.RegisterClassMap(); + } + public MongoAssetFolderRepository(IMongoDatabase database) : base(database) { @@ -38,7 +43,7 @@ public sealed partial class MongoAssetFolderRepository : MongoRepositoryBase> QueryAsync(DomainId appId, DomainId? parentId, + public async Task> QueryAsync(DomainId appId, DomainId? parentId, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/QueryAsync")) @@ -49,7 +54,7 @@ public sealed partial class MongoAssetFolderRepository : MongoRepositoryBase x.FolderName) .ToListAsync(ct); - return ResultList.Create(assetFolderEntities.Count, assetFolderEntities); + return ResultList.Create(assetFolderEntities.Count, assetFolderEntities); } } @@ -70,7 +75,7 @@ public sealed partial class MongoAssetFolderRepository : MongoRepositoryBase FindAssetFolderAsync(DomainId appId, DomainId id, + public async Task FindAssetFolderAsync(DomainId appId, DomainId id, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/FindAssetFolderAsync")) diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository_SnapshotStore.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository_SnapshotStore.cs index 106e86d92..3a6c794d1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository_SnapshotStore.cs +++ b/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, IDeleter +public sealed partial class MongoAssetFolderRepository : ISnapshotStore, 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> ISnapshotStore.ReadAllAsync( + IAsyncEnumerable> ISnapshotStore.ReadAllAsync( CancellationToken ct) { var documents = Collection.Find(FindAll, Batching.Options).ToAsyncEnumerable(ct); - return documents.Select(x => new SnapshotResult(x.DocumentId, x.ToState(), x.Version, true)); + return documents.Select(x => new SnapshotResult(x.DocumentId, x.ToState(), x.Version, true)); } - async Task> ISnapshotStore.ReadAsync(DomainId key, + async Task> ISnapshotStore.ReadAsync(DomainId key, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/ReadAsync")) @@ -43,25 +44,25 @@ public sealed partial class MongoAssetFolderRepository : ISnapshotStore(existing.DocumentId, existing.ToState(), existing.Version); + return new SnapshotResult(existing.DocumentId, existing.ToState(), existing.Version); } - return new SnapshotResult(default, null!, EtagVersion.Empty); + return new SnapshotResult(default, null!, EtagVersion.Empty); } } - async Task ISnapshotStore.WriteAsync(SnapshotWriteJob job, + async Task ISnapshotStore.WriteAsync(SnapshotWriteJob 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(x => nameof(x.Version)), ct); } } - async Task ISnapshotStore.WriteManyAsync(IEnumerable> jobs, + async Task ISnapshotStore.WriteManyAsync(IEnumerable> jobs, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/WriteManyAsync")) @@ -83,7 +84,7 @@ public sealed partial class MongoAssetFolderRepository : ISnapshotStore.RemoveAsync(DomainId key, + async Task ISnapshotStore.RemoveAsync(DomainId key, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/RemoveAsync")) diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs index 585eb0c40..3659caea0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs +++ b/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 log) : base(database) { @@ -66,7 +71,7 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase StreamAll(DomainId appId, + public async IAsyncEnumerable 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> QueryAsync(DomainId appId, DomainId? parentId, Q q, + public async Task> 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()); + return ResultList.Create(assetTotal, assetEntities.OfType()); } else { @@ -153,7 +158,7 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase(assetTotal, assetEntities); + return ResultList.Create(assetTotal, assetEntities); } } catch (MongoQueryException ex) when (ex.Message.Contains("17406", StringComparison.Ordinal)) @@ -193,7 +198,7 @@ public sealed partial class MongoAssetRepository : MongoRepositoryBase FindAssetByHashAsync(DomainId appId, string hash, string fileName, long fileSize, + public async Task 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 FindAssetBySlugAsync(DomainId appId, string slug, bool allowDeleted, + public async Task 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 FindAssetAsync(DomainId appId, DomainId id, bool allowDeleted, + public async Task 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 FindAssetAsync(DomainId id, + public async Task FindAssetAsync(DomainId id, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoAssetRepository/FindAssetAsync")) diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs index db0c6bacf..337689b47 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs +++ b/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, IDeleter +public sealed partial class MongoAssetRepository : ISnapshotStore, 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> ISnapshotStore.ReadAllAsync( + IAsyncEnumerable> ISnapshotStore.ReadAllAsync( CancellationToken ct) { var documents = Collection.Find(FindAll, Batching.Options).ToAsyncEnumerable(ct); - return documents.Select(x => new SnapshotResult(x.DocumentId, x.ToState(), x.Version)); + return documents.Select(x => new SnapshotResult(x.DocumentId, x.ToState(), x.Version)); } - async Task> ISnapshotStore.ReadAsync(DomainId key, + async Task> ISnapshotStore.ReadAsync(DomainId key, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoAssetRepository/ReadAsync")) @@ -43,25 +43,25 @@ public sealed partial class MongoAssetRepository : ISnapshotStore(existing.DocumentId, existing.ToState(), existing.Version); + return new SnapshotResult(existing.DocumentId, existing.ToState(), existing.Version); } - return new SnapshotResult(default, null!, EtagVersion.Empty); + return new SnapshotResult(default, null!, EtagVersion.Empty); } } - async Task ISnapshotStore.WriteAsync(SnapshotWriteJob job, + async Task ISnapshotStore.WriteAsync(SnapshotWriteJob 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(x => nameof(x.Version)), ct); } } - async Task ISnapshotStore.WriteManyAsync(IEnumerable> jobs, + async Task ISnapshotStore.WriteManyAsync(IEnumerable> jobs, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoAssetRepository/WriteManyAsync")) @@ -83,7 +83,7 @@ public sealed partial class MongoAssetRepository : ISnapshotStore.RemoveAsync(DomainId key, + async Task ISnapshotStore.RemoveAsync(DomainId key, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoAssetRepository/RemoveAsync")) diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs index 0d4ac30fb..c768b4d65 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs +++ b/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 StreamAll(DomainId appId, HashSet? schemaIds, + public IAsyncEnumerable StreamAll(DomainId appId, HashSet? schemaIds, CancellationToken ct) { return queryAsStream.StreamAll(appId, schemaIds, ct); } - public IAsyncEnumerable StreamReferencing(DomainId appId, DomainId reference, int take, + public IAsyncEnumerable StreamReferencing(DomainId appId, DomainId reference, int take, CancellationToken ct) { return queryReferrers.StreamReferencing(appId, reference, take, ct); } - public IAsyncEnumerable QueryScheduledWithoutDataAsync(Instant now, + public IAsyncEnumerable QueryScheduledWithoutDataAsync(Instant now, CancellationToken ct) { return queryScheduled.QueryAsync(now, ct); @@ -135,7 +135,7 @@ public sealed class MongoContentCollection : MongoRepositoryBase> QueryAsync(IAppEntity app, List schemas, Q q, + public async Task> QueryAsync(App app, List schemas, Q q, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoContentCollection/QueryAsync")) @@ -162,7 +162,7 @@ public sealed class MongoContentCollection : MongoRepositoryBase(); + return ResultList.Empty(); } catch (MongoCommandException ex) when (ex.Code == 96) { @@ -175,7 +175,7 @@ public sealed class MongoContentCollection : MongoRepositoryBase> QueryAsync(IAppEntity app, ISchemaEntity schema, Q q, + public async Task> QueryAsync(App app, Schema schema, Q q, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoContentCollection/QueryAsync")) @@ -215,7 +215,7 @@ public sealed class MongoContentCollection : MongoRepositoryBase FindContentAsync(ISchemaEntity schema, DomainId id, + public async Task FindContentAsync(Schema schema, DomainId id, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoContentCollection/FindContentAsync")) @@ -224,30 +224,30 @@ public sealed class MongoContentCollection : MongoRepositoryBase> QueryIdsAsync(DomainId appId, HashSet ids, + public async Task> QueryIdsAsync(App app, HashSet 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> QueryIdsAsync(DomainId appId, DomainId schemaId, FilterNode filterNode, + public async Task> QueryIdsAsync(App app, Schema schema, FilterNode 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 HasReferrersAsync(DomainId appId, DomainId reference, + public async Task 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(x => nameof(x.Version)), ct); } public async Task RemoveAsync(DomainId key, diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs index 113c96605..dd3142a78 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs +++ b/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 +public record MongoContentEntity : Content, IVersionedEntity { - [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 AppId { get; set; } - - [BsonRequired] - [BsonElement("si")] - public NamedId SchemaId { get; set; } + public Instant? ScheduledAt { get; set; } - [BsonRequired] - [BsonElement("rf")] public HashSet? 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(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(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 CreatePublishedAsync(SnapshotWriteJob job, IAppProvider appProvider, + public static async Task CreatePublishedAsync(SnapshotWriteJob 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 CreateCompleteAsync(SnapshotWriteJob job, IAppProvider appProvider, + public static async Task CreateCompleteAsync(SnapshotWriteJob 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 CreateContentAsync(ContentData data, SnapshotWriteJob job, IAppProvider appProvider, + private static async Task<(HashSet, 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(); - 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); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index 305148f4f..276b37a85 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/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, ICo BsonEscapedDictionarySerializer.Register(); BsonEscapedDictionarySerializer.Register(); BsonStringSerializer.Register(); + MongoContentEntity.RegisterClassMap(); } public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, @@ -69,58 +70,58 @@ public partial class MongoContentRepository : MongoBase, ICo CanUseTransactions = clusteredAsReplica && clusterVersion >= 4 && options.UseTransactions; } - public IAsyncEnumerable StreamAll(DomainId appId, HashSet? schemaIds, SearchScope scope, + public IAsyncEnumerable StreamAll(DomainId appId, HashSet? schemaIds, SearchScope scope, CancellationToken ct = default) { return GetCollection(scope).StreamAll(appId, schemaIds, ct); } - public IAsyncEnumerable StreamReferencing(DomainId appId, DomainId reference, int take, SearchScope scope, + public IAsyncEnumerable StreamReferencing(DomainId appId, DomainId reference, int take, SearchScope scope, CancellationToken ct = default) { return GetCollection(scope).StreamReferencing(appId, reference, take, ct); } - public IAsyncEnumerable StreamScheduledWithoutDataAsync(Instant now, SearchScope scope, + public IAsyncEnumerable StreamScheduledWithoutDataAsync(Instant now, SearchScope scope, CancellationToken ct = default) { return GetCollection(scope).QueryScheduledWithoutDataAsync(now, ct); } - public Task> QueryAsync(IAppEntity app, List schemas, Q q, SearchScope scope, + public Task> QueryAsync(App app, List schemas, Q q, SearchScope scope, CancellationToken ct = default) { return GetCollection(scope).QueryAsync(app, schemas, q, ct); } - public Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Q q, SearchScope scope, + public Task> QueryAsync(App app, Schema schema, Q q, SearchScope scope, CancellationToken ct = default) { return GetCollection(scope).QueryAsync(app, schema, q, ct); } - public Task FindContentAsync(IAppEntity app, ISchemaEntity schema, DomainId id, SearchScope scope, + public Task FindContentAsync(App app, Schema schema, DomainId id, SearchScope scope, CancellationToken ct = default) { return GetCollection(scope).FindContentAsync(schema, id, ct); } - public Task> QueryIdsAsync(DomainId appId, HashSet ids, SearchScope scope, + public Task> QueryIdsAsync(App app, HashSet ids, SearchScope scope, CancellationToken ct = default) { - return GetCollection(scope).QueryIdsAsync(appId, ids, ct); + return GetCollection(scope).QueryIdsAsync(app, ids, ct); } - public Task HasReferrersAsync(DomainId appId, DomainId reference, SearchScope scope, + public Task 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> QueryIdsAsync(DomainId appId, DomainId schemaId, FilterNode filterNode, SearchScope scope, + public Task> QueryIdsAsync(App app, Schema schema, FilterNode 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, diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs index 2dea57f78..48a4d7d7e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs +++ b/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, IDeleter +public partial class MongoContentRepository : ISnapshotStore, IDeleter { - IAsyncEnumerable> ISnapshotStore.ReadAllAsync( + IAsyncEnumerable> ISnapshotStore.ReadAllAsync( CancellationToken ct) { return collectionComplete.StreamAll(ct) - .Select(x => new SnapshotResult(x.DocumentId, x.ToState(), x.Version, true)); + .Select(x => new SnapshotResult(x.DocumentId, x.ToState(), x.Version, true)); } - async Task> ISnapshotStore.ReadAsync(DomainId key, + async Task> ISnapshotStore.ReadAsync(DomainId key, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoContentRepository/ReadAsync")) @@ -36,14 +35,14 @@ public partial class MongoContentRepository : ISnapshotStore(existing.DocumentId, existing.ToState(), existing.Version); + return new SnapshotResult(existing.DocumentId, existing.ToState(), existing.Version); } - return new SnapshotResult(default, null!, EtagVersion.Empty); + return new SnapshotResult(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.ClearAsync( + async Task ISnapshotStore.ClearAsync( CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoContentRepository/ClearAsync")) @@ -63,7 +62,7 @@ public partial class MongoContentRepository : ISnapshotStore.RemoveAsync(DomainId key, + async Task ISnapshotStore.RemoveAsync(DomainId key, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoContentRepository/RemoveAsync")) @@ -80,7 +79,7 @@ public partial class MongoContentRepository : ISnapshotStore.WriteAsync(SnapshotWriteJob job, + async Task ISnapshotStore.WriteAsync(SnapshotWriteJob job, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoContentRepository/WriteAsync")) @@ -114,7 +113,7 @@ public partial class MongoContentRepository : ISnapshotStore.WriteManyAsync(IEnumerable> jobs, + async Task ISnapshotStore.WriteManyAsync(IEnumerable> jobs, CancellationToken ct) { using (Telemetry.Activities.StartActivity("MongoContentRepository/WriteManyAsync")) @@ -158,7 +157,7 @@ public partial class MongoContentRepository : ISnapshotStore job, + private async Task UpsertPublishedAsync(SnapshotWriteJob job, CancellationToken ct) { if (ShouldWritePublished(job.Value)) @@ -173,7 +172,7 @@ public partial class MongoContentRepository : ISnapshotStore job, + private async Task UpsertVersionedPublishedAsync(IClientSessionHandle session, SnapshotWriteJob job, CancellationToken ct) { if (ShouldWritePublished(job.Value)) @@ -188,7 +187,7 @@ public partial class MongoContentRepository : ISnapshotStore job, + private async Task UpsertCompleteAsync(SnapshotWriteJob job, CancellationToken ct) { var entityJob = job.As(await MongoContentEntity.CreateCompleteAsync(job, appProvider, ct)); @@ -196,7 +195,7 @@ public partial class MongoContentRepository : ISnapshotStore job, + private async Task UpsertVersionedCompleteAsync(IClientSessionHandle session, SnapshotWriteJob job, CancellationToken ct) { var entityJob = job.As(await MongoContentEntity.CreateCompleteAsync(job, appProvider, ct)); @@ -204,13 +203,13 @@ public partial class MongoContentRepository : ISnapshotStore 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(x => nameof(x.Data)); + var dataPrefix = Field.Of(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(x => nameof(x.DraftData)))); + projections.Add(projector.Exclude(Field.Of(x => nameof(x.NewData)))); } return projector.Combine(projections); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs index 08aa4ce38..35ce6e128 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs +++ b/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 StreamAll(DomainId appId, HashSet? schemaIds, + public async IAsyncEnumerable StreamAll(DomainId appId, HashSet? schemaIds, [EnumeratorCancellation] CancellationToken ct) { var filter = CreateFilter(appId, schemaIds); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryById.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryById.cs index 05783a0c0..d2e7e576a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryById.cs +++ b/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 QueryAsync(ISchemaEntity schema, DomainId id, + public async Task QueryAsync(Schema schema, DomainId id, CancellationToken ct) { var filter = Filter.Eq(x => x.DocumentId, DomainId.Combine(schema.AppId, id)); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByIds.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByIds.cs index 22b0d3eda..c315a30b4 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByIds.cs +++ b/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> QueryIdsAsync(DomainId appId, HashSet ids, + public async Task> QueryIdsAsync(App app, HashSet ids, CancellationToken ct) { - if (ids == null || ids.Count == 0) + if (ids is not { Count: > 0 }) { return ReadonlyList.Empty(); } // 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> QueryAsync(IAppEntity app, List schemas, Q q, + public async Task> QueryAsync(App app, List schemas, Q q, CancellationToken ct) { - if (q.Ids == null || q.Ids.Count == 0) + if (q.Ids is not { Count: > 0 }) { - return ResultList.Empty(); + return ResultList.Empty(); } // We need to translate the query names to the document field names in MongoDB. diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs index 3c002cba5..3596a9c2f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.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.MongoDb; using Squidex.Infrastructure.MongoDb.Queries; @@ -42,13 +41,13 @@ internal sealed class QueryByQuery : OperationBase .Descending(x => x.LastModified)); } - public async Task> QueryIdsAsync(DomainId appId, DomainId schemaId, FilterNode filterNode, + public async Task> QueryIdsAsync(App app, Schema schema, FilterNode filterNode, CancellationToken ct) { // We need to translate the query names to the document field names in MongoDB. - var adjustedFilter = filterNode.AdjustToModel(appId); + var adjustedFilter = filterNode.AdjustToModel(app.Id); - var filter = BuildFilter(appId, schemaId, adjustedFilter); + var filter = BuildFilter(app.Id, schema.Id, adjustedFilter); var contentEntities = await Collection.FindStatusAsync(filter, ct); var contentResults = contentEntities.Select(x => new ContentIdStatus(x.IndexedSchemaId, x.Id, x.Status)).ToList(); @@ -56,7 +55,7 @@ internal sealed class QueryByQuery : OperationBase return contentResults; } - public async Task> QueryAsync(IAppEntity app, List schemas, Q q, + public async Task> QueryAsync(App app, List schemas, Q q, CancellationToken ct) { // We need to translate the query names to the document field names in MongoDB. @@ -84,10 +83,10 @@ internal sealed class QueryByQuery : OperationBase } } - return ResultList.Create(contentTotal, contentEntities); + return ResultList.Create(contentTotal, contentEntities); } - public async Task> QueryAsync(ISchemaEntity schema, Q q, + public async Task> QueryAsync(Schema schema, Q q, CancellationToken ct) { // We need to translate the query names to the document field names in MongoDB. @@ -123,7 +122,7 @@ internal sealed class QueryByQuery : OperationBase } } - return ResultList.Create(contentTotal, contentEntities); + return ResultList.Create(contentTotal, contentEntities); } private static FilterDefinition BuildFilter(DomainId appId, DomainId schemaId, diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs index 2210a6b05..3c002afed 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs @@ -7,10 +7,9 @@ using System.Collections.Concurrent; 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.MongoDb; using Squidex.Infrastructure.MongoDb.Queries; @@ -64,7 +63,7 @@ internal sealed class QueryInDedicatedCollection : MongoBase #pragma warning restore MA0106 // Avoid closure by using an overload with the 'factoryArgument' parameter } - public async Task> QueryIdsAsync(IAppEntity app, ISchemaEntity schema, FilterNode filterNode, + public async Task> QueryIdsAsync(App app, Schema schema, FilterNode filterNode, CancellationToken ct) { // We need to translate the filter names to the document field names in MongoDB. @@ -79,7 +78,7 @@ internal sealed class QueryInDedicatedCollection : MongoBase return contentResults; } - public async Task> QueryAsync(ISchemaEntity schema, Q q, + public async Task> QueryAsync(Schema schema, Q q, CancellationToken ct) { // We need to translate the query names to the document field names in MongoDB. @@ -108,7 +107,7 @@ internal sealed class QueryInDedicatedCollection : MongoBase } } - return ResultList.Create(contentTotal, contentEntities); + return ResultList.Create(contentTotal, contentEntities); } public async Task UpsertAsync(SnapshotWriteJob job, @@ -124,7 +123,7 @@ internal sealed class QueryInDedicatedCollection : MongoBase { var collection = await GetCollectionAsync(job.Value.AppId.Id, job.Value.SchemaId.Id); - await collection.UpsertVersionedAsync(session, job, ct); + await collection.UpsertVersionedAsync(session, job, Field.Of(x => nameof(x.Version)), ct); } public async Task RemoveAsync(MongoContentEntity value, diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferences.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferences.cs index 30dc85950..c91d9b208 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferences.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferences.cs @@ -7,9 +7,9 @@ using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Contents; -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; @@ -34,7 +34,7 @@ internal sealed class QueryReferences : OperationBase this.queryByIds = queryByIds; } - public async Task> QueryAsync(IAppEntity app, List schemas, Q q, + public async Task> QueryAsync(App app, List schemas, Q q, CancellationToken ct) { var find = @@ -49,9 +49,9 @@ internal sealed class QueryReferences : OperationBase throw new DomainObjectNotFoundException(q.Referencing.ToString()); } - if (contentEntity.ReferencedIds == null || contentEntity.ReferencedIds.Count == 0) + if (contentEntity.ReferencedIds is not { Count: > 0 }) { - return ResultList.Empty(); + return ResultList.Empty(); } q = q.WithReferencing(default).WithIds(contentEntity.ReferencedIds!); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs index 6bbf1fe8a..e2eb91279 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs @@ -7,7 +7,8 @@ using System.Runtime.CompilerServices; using MongoDB.Driver; -using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; @@ -23,10 +24,10 @@ internal sealed class QueryReferrers : OperationBase .Ascending(x => x.IsDeleted)); } - public async Task CheckExistsAsync(DomainId appId, DomainId reference, + public async Task CheckExistsAsync(App app, DomainId reference, CancellationToken ct) { - var filter = BuildFilter(appId, reference); + var filter = BuildFilter(app.Id, reference); var hasReferrerAsync = await Collection.Find(filter).Only(x => x.Id) @@ -35,7 +36,7 @@ internal sealed class QueryReferrers : OperationBase return hasReferrerAsync; } - public async IAsyncEnumerable StreamReferencing(DomainId appId, DomainId reference, int take, + public async IAsyncEnumerable StreamReferencing(DomainId appId, DomainId reference, int take, [EnumeratorCancellation] CancellationToken ct) { var filter = BuildFilter(appId, reference); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs index 174f10177..494e33859 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs @@ -7,9 +7,9 @@ using MongoDB.Driver; using NodaTime; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Contents; -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; @@ -28,7 +28,7 @@ internal sealed class QueryScheduled : OperationBase .Ascending(x => x.IndexedSchemaId)); } - public async Task> QueryAsync(IAppEntity app, List schemas, Q q, + public async Task> QueryAsync(App app, List schemas, Q q, CancellationToken ct) { var filter = CreateFilter(app.Id, schemas.Select(x => x.Id), q.ScheduledFrom!.Value, q.ScheduledTo!.Value); @@ -39,10 +39,10 @@ internal sealed class QueryScheduled : OperationBase return ResultList.Create(contentTotal, contentEntities); } - public IAsyncEnumerable QueryAsync(Instant now, + public IAsyncEnumerable QueryAsync(Instant now, CancellationToken ct) { - var find = Collection.Find(x => x.ScheduledAt < now && x.IsDeleted != true).Not(x => x.Data, x => x.DraftData); + var find = Collection.Find(x => x.ScheduledAt < now && x.IsDeleted != true).Not(x => x.Data, x => x.NewData); return find.ToAsyncEnumerable(ct); } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/EntityClassMap.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/EntityClassMap.cs new file mode 100644 index 000000000..43d7a155d --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/EntityClassMap.cs @@ -0,0 +1,56 @@ +// ========================================================================== +// 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; +using Squidex.Infrastructure.Commands; + +namespace Squidex.Domain.Apps.Entities.MongoDb; + +internal static class EntityClassMap +{ + public static void Register() + { + BsonClassMap.TryRegisterClassMap(cm => + { + cm.MapProperty(x => x.AppId) + .SetElementName("ai") + .SetIsRequired(true); + + cm.MapProperty(x => x.IsDeleted) + .SetElementName("dl") + .SetIsRequired(true); + }); + + BsonClassMap.TryRegisterClassMap(cm => + { + cm.MapProperty(x => x.Id) + .SetElementName("id") + .SetIsRequired(true); + + cm.MapProperty(x => x.LastModified) + .SetElementName("mt") + .SetIsRequired(true); + + cm.MapProperty(x => x.LastModifiedBy) + .SetElementName("mb") + .SetIsRequired(true); + + cm.MapProperty(x => x.Created) + .SetElementName("ct") + .SetIsRequired(true); + + cm.MapProperty(x => x.CreatedBy) + .SetElementName("cb") + .SetIsRequired(true); + + cm.MapProperty(x => x.Version) + .SetElementName("vs") + .SetIsRequired(true); + }); + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs index 1fc9f2cfd..5c3d7faf2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs @@ -7,7 +7,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Driver; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.History.Repositories; using Squidex.Infrastructure; @@ -19,7 +19,7 @@ public sealed class MongoHistoryEventRepository : MongoRepositoryBase(cm => + BsonClassMap.TryRegisterClassMap(cm => { cm.AutoMap(); @@ -60,7 +60,7 @@ public sealed class MongoHistoryEventRepository : MongoRepositoryBase x.OwnerId, app.Id), ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs index 2c8c0c7b6..ebd684e3a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs @@ -6,13 +6,13 @@ // ========================================================================== using MongoDB.Bson.Serialization.Attributes; -using Squidex.Domain.Apps.Entities.Rules.DomainObject; +using Squidex.Domain.Apps.Core.Rules; using Squidex.Infrastructure; using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.MongoDb.Rules; -public sealed class MongoRuleEntity : MongoState +public sealed class MongoRuleEntity : MongoState { [BsonRequired] [BsonElement("_ai")] diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs index a14d002a7..5fcff46fc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs @@ -22,7 +22,7 @@ public sealed class MongoRuleEventEntity : IRuleEventEntity { [BsonId] [BsonElement("_id")] - public DomainId JobId { get; set; } + public DomainId Id { get; set; } [BsonRequired] [BsonElement(nameof(AppId))] @@ -71,26 +71,11 @@ public sealed class MongoRuleEventEntity : IRuleEventEntity [BsonElement(nameof(NextAttempt))] public Instant? NextAttempt { get; set; } - DomainId IWithId.Id - { - get => JobId; - } - - DomainId IEntity.UniqueId - { - get => JobId; - } - public static MongoRuleEventEntity FromJob(RuleEventWrite item) { var (job, nextAttempt, error) = item; - var entity = new MongoRuleEventEntity - { - Job = job, - JobId = job.Id, - NextAttempt = nextAttempt - }; + var entity = new MongoRuleEventEntity { Job = job, Id = job.Id, NextAttempt = nextAttempt }; SimpleMapper.Map(job, entity); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs index 92c2a37d2..8921cfb79 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs @@ -7,8 +7,8 @@ using MongoDB.Driver; using NodaTime; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Infrastructure; @@ -49,7 +49,7 @@ public sealed class MongoRuleEventRepository : MongoRepositoryBase x.AppId, app.Id), ct); @@ -86,7 +86,7 @@ public sealed class MongoRuleEventRepository : MongoRepositoryBase x.JobId == id) + await Collection.Find(x => x.Id == id) .FirstOrDefaultAsync(ct); return ruleEvent; @@ -95,13 +95,13 @@ public sealed class MongoRuleEventRepository : MongoRepositoryBase x.JobId == id, Update.Set(x => x.NextAttempt, nextAttempt), cancellationToken: ct); + return Collection.UpdateOneAsync(x => x.Id == id, Update.Set(x => x.NextAttempt, nextAttempt), cancellationToken: ct); } public Task CancelByEventAsync(DomainId id, CancellationToken ct = default) { - return Collection.UpdateOneAsync(x => x.JobId == id, + return Collection.UpdateOneAsync(x => x.Id == id, Update .Set(x => x.NextAttempt, null) .Set(x => x.JobResult, RuleJobResult.Cancelled), @@ -134,7 +134,7 @@ public sealed class MongoRuleEventRepository : MongoRepositoryBase x.JobId == job.Id, + return Collection.UpdateOneAsync(x => x.Id == job.Id, Update .Set(x => x.Result, update.ExecutionResult) .Set(x => x.LastDump, update.ExecutionDump) diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs index dca4b4eaa..d23edd255 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs @@ -6,22 +6,26 @@ // ========================================================================== using MongoDB.Driver; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Rules; -using Squidex.Domain.Apps.Entities.Rules.DomainObject; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.MongoDb.Rules; -public sealed class MongoRuleRepository : MongoSnapshotStoreBase, IRuleRepository, IDeleter +public sealed class MongoRuleRepository : MongoSnapshotStoreBase, IRuleRepository, IDeleter { public MongoRuleRepository(IMongoDatabase database) : base(database) { } + protected override string CollectionName() + { + return "States_Rules"; + } + protected override Task SetupCollectionAsync(IMongoCollection collection, CancellationToken ct) { @@ -33,13 +37,13 @@ public sealed class MongoRuleRepository : MongoSnapshotStoreBase x.IndexedAppId, app.Id), ct); } - public async Task> QueryAllAsync(DomainId appId, + public async Task> QueryAllAsync(DomainId appId, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoRuleRepository/QueryAllAsync")) @@ -48,7 +52,7 @@ public sealed class MongoRuleRepository : MongoSnapshotStoreBase x.IndexedAppId == appId && !x.IndexedDeleted) .ToListAsync(ct); - return entities.Select(x => (IRuleEntity)x.Document).ToList(); + return entities.Select(x => x.Document).ToList(); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs index 21cd2664e..51aaaf448 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs @@ -7,13 +7,13 @@ using MongoDB.Bson.Serialization.Attributes; using NodaTime; -using Squidex.Domain.Apps.Entities.Schemas.DomainObject; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas; -public sealed class MongoSchemaEntity : MongoState +public sealed class MongoSchemaEntity : MongoState { [BsonRequired] [BsonElement("_ai")] @@ -40,7 +40,7 @@ public sealed class MongoSchemaEntity : MongoState IndexedAppId = Document.AppId.Id; IndexedDeleted = Document.IsDeleted; IndexedId = Document.Id; - IndexedName = Document.SchemaDef.Name; + IndexedName = Document.Name; IndexedCreated = Document.Created; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs index a82c472f6..8f43229dd 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs @@ -6,22 +6,26 @@ // ========================================================================== using MongoDB.Driver; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Domain.Apps.Entities.Schemas.DomainObject; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas; -public sealed class MongoSchemaRepository : MongoSnapshotStoreBase, ISchemaRepository, IDeleter +public sealed class MongoSchemaRepository : MongoSnapshotStoreBase, ISchemaRepository, IDeleter { public MongoSchemaRepository(IMongoDatabase database) : base(database) { } + protected override string CollectionName() + { + return "States_Schemas"; + } + protected override Task SetupCollectionAsync(IMongoCollection collection, CancellationToken ct) { @@ -34,13 +38,13 @@ public sealed class MongoSchemaRepository : MongoSnapshotStoreBase x.IndexedAppId, app.Id), ct); } - public async Task> QueryAllAsync(DomainId appId, CancellationToken ct = default) + public async Task> QueryAllAsync(DomainId appId, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoSchemaRepository/QueryAllAsync")) { @@ -48,11 +52,11 @@ public sealed class MongoSchemaRepository : MongoSnapshotStoreBase x.IndexedAppId == appId && !x.IndexedDeleted) .ToListAsync(ct); - return entities.Select(x => (ISchemaEntity)x.Document).ToList(); + return entities.Select(x => x.Document).ToList(); } } - public async Task FindAsync(DomainId appId, DomainId id, + public async Task FindAsync(DomainId appId, DomainId id, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoSchemaRepository/FindAsync")) @@ -65,7 +69,7 @@ public sealed class MongoSchemaRepository : MongoSnapshotStoreBase FindAsync(DomainId appId, string name, + public async Task FindAsync(DomainId appId, string name, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoSchemaRepository/FindAsyncByName")) diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs index e1bc24fbb..d46c7d131 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs @@ -7,7 +7,8 @@ using MongoDB.Driver; using NodaTime; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Events; using Squidex.Infrastructure; @@ -35,7 +36,7 @@ public sealed class MongoSchemasHash : MongoRepositoryBase x.AppId, app.Id), ct); @@ -74,7 +75,7 @@ public sealed class MongoSchemasHash : MongoRepositoryBase GetCurrentHashAsync(IAppEntity app, + public async Task<(Instant Create, string Hash)> GetCurrentHashAsync(App app, CancellationToken ct = default) { Guard.NotNull(app); @@ -95,7 +96,7 @@ public sealed class MongoSchemasHash : MongoRepositoryBase ComputeHashAsync(IAppEntity app, IEnumerable schemas, + public ValueTask ComputeHashAsync(App app, IEnumerable schemas, CancellationToken ct = default) { var ids = diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamEntity.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamEntity.cs index 68fd906de..4ca2f192d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamEntity.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamEntity.cs @@ -7,13 +7,13 @@ using MongoDB.Bson.Serialization.Attributes; using NodaTime; -using Squidex.Domain.Apps.Entities.Teams.DomainObject; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Infrastructure; using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.MongoDb.Teams; -public sealed class MongoTeamEntity : MongoState +public sealed class MongoTeamEntity : MongoState { [BsonRequired] [BsonElement("_ui")] diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamRepository.cs index 745aaa7fe..45941c902 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamRepository.cs @@ -6,21 +6,25 @@ // ========================================================================== using MongoDB.Driver; -using Squidex.Domain.Apps.Entities.Teams; -using Squidex.Domain.Apps.Entities.Teams.DomainObject; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities.Teams.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.MongoDb.Teams; -public sealed class MongoTeamRepository : MongoSnapshotStoreBase, ITeamRepository +public sealed class MongoTeamRepository : MongoSnapshotStoreBase, ITeamRepository { public MongoTeamRepository(IMongoDatabase database) : base(database) { } + protected override string CollectionName() + { + return "States_Teams"; + } + protected override Task SetupCollectionAsync(IMongoCollection collection, CancellationToken ct) { @@ -32,7 +36,7 @@ public sealed class MongoTeamRepository : MongoSnapshotStoreBase> QueryAllAsync(string contributorId, + public async Task> QueryAllAsync(string contributorId, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoTeamRepository/QueryAllAsync")) @@ -41,11 +45,11 @@ public sealed class MongoTeamRepository : MongoSnapshotStoreBase x.IndexedUserIds.Contains(contributorId)) .ToListAsync(ct); - return entities.Select(x => (ITeamEntity)x.Document).ToList(); + return entities.Select(x => x.Document).ToList(); } } - public async Task FindAsync(DomainId id, + public async Task FindAsync(DomainId id, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("MongoTeamRepository/FindAsync")) diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasTextIndex.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasTextIndex.cs index ef810c946..8afb76d56 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasTextIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasTextIndex.cs @@ -11,7 +11,7 @@ using Lucene.Net.Util; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; -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.Infrastructure; @@ -66,7 +66,7 @@ public sealed class AtlasTextIndex : MongoTextIndexBase?> SearchAsync(IAppEntity app, TextQuery query, SearchScope scope, + public override async Task?> SearchAsync(App app, TextQuery query, SearchScope scope, CancellationToken ct = default) { Guard.NotNull(app); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs index b4bcc8135..8840cbced 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs @@ -8,7 +8,7 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; -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.Infrastructure; @@ -41,7 +41,7 @@ public abstract class MongoTextIndexBase : MongoRepositoryBase : MongoRepositoryBase source); - async Task IDeleter.DeleteAppAsync(IAppEntity app, + async Task IDeleter.DeleteAppAsync(App app, CancellationToken ct) { await Collection.DeleteManyAsync(Filter.Eq(x => x.AppId, app.Id), ct); @@ -115,7 +115,7 @@ public abstract class MongoTextIndexBase : MongoRepositoryBase?> SearchAsync(IAppEntity app, GeoQuery query, SearchScope scope, + public virtual async Task?> SearchAsync(App app, GeoQuery query, SearchScope scope, CancellationToken ct = default) { Guard.NotNull(app); @@ -136,7 +136,7 @@ public abstract class MongoTextIndexBase : MongoRepositoryBase x.ContentId).ToList(); } - public virtual async Task?> SearchAsync(IAppEntity app, TextQuery query, SearchScope scope, + public virtual async Task?> SearchAsync(App app, TextQuery query, SearchScope scope, CancellationToken ct = default) { Guard.NotNull(app); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexerState.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexerState.cs index fed0981e3..85bdc0c24 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexerState.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexerState.cs @@ -7,7 +7,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Driver; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Contents.Text.State; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; @@ -18,7 +18,7 @@ public sealed class MongoTextIndexerState : MongoRepositoryBase(cm => + BsonClassMap.TryRegisterClassMap(cm => { cm.MapIdField(x => x.UniqueContentId); @@ -56,7 +56,7 @@ public sealed class MongoTextIndexerState : MongoRepositoryBase x.AppId, app.Id), ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs b/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs index dfdb7eb8a..1ef8d88a8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs @@ -6,13 +6,13 @@ // ========================================================================== using Squidex.Caching; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities.Apps.Indexes; -using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.Indexes; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Indexes; -using Squidex.Domain.Apps.Entities.Teams; using Squidex.Domain.Apps.Entities.Teams.Indexes; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; @@ -37,7 +37,7 @@ public sealed class AppProvider : IAppProvider this.indexForTeams = indexForTeams; } - public async Task<(IAppEntity?, ISchemaEntity?)> GetAppWithSchemaAsync(DomainId appId, DomainId id, bool canCache = false, + public async Task<(App?, Schema?)> GetAppWithSchemaAsync(DomainId appId, DomainId id, bool canCache = false, CancellationToken ct = default) { var app = await GetAppAsync(appId, canCache, ct); @@ -57,7 +57,7 @@ public sealed class AppProvider : IAppProvider return (app, schema); } - public async Task GetAppAsync(DomainId appId, bool canCache = false, + public async Task GetAppAsync(DomainId appId, bool canCache = false, CancellationToken ct = default) { var cacheKey = AppCacheKey(appId); @@ -75,7 +75,7 @@ public sealed class AppProvider : IAppProvider return app; } - public async Task GetAppAsync(string appName, bool canCache = false, + public async Task GetAppAsync(string appName, bool canCache = false, CancellationToken ct = default) { var cacheKey = AppCacheKey(appName); @@ -93,7 +93,7 @@ public sealed class AppProvider : IAppProvider return app; } - public async Task GetTeamAsync(DomainId teamId, + public async Task GetTeamAsync(DomainId teamId, CancellationToken ct = default) { var cacheKey = TeamCacheKey(teamId); @@ -106,7 +106,7 @@ public sealed class AppProvider : IAppProvider return team; } - public async Task GetSchemaAsync(DomainId appId, string name, bool canCache = false, + public async Task GetSchemaAsync(DomainId appId, string name, bool canCache = false, CancellationToken ct = default) { var cacheKey = SchemaCacheKey(appId, name); @@ -124,7 +124,7 @@ public sealed class AppProvider : IAppProvider return schema; } - public async Task GetSchemaAsync(DomainId appId, DomainId id, bool canCache = false, + public async Task GetSchemaAsync(DomainId appId, DomainId id, bool canCache = false, CancellationToken ct = default) { var cacheKey = SchemaCacheKey(appId, id); @@ -136,13 +136,13 @@ public sealed class AppProvider : IAppProvider if (schema != null) { - localCache.Add(SchemaCacheKey(appId, schema.SchemaDef.Name), schema); + localCache.Add(SchemaCacheKey(appId, schema.Name), schema); } return schema; } - public async Task> GetUserAppsAsync(string userId, PermissionSet permissions, + public async Task> GetUserAppsAsync(string userId, PermissionSet permissions, CancellationToken ct = default) { var apps = await GetOrCreate($"GetUserApps({userId})", () => @@ -153,7 +153,7 @@ public sealed class AppProvider : IAppProvider return apps?.ToList() ?? []; } - public async Task> GetTeamAppsAsync(DomainId teamId, + public async Task> GetTeamAppsAsync(DomainId teamId, CancellationToken ct = default) { var apps = await GetOrCreate($"GetTeamApps({teamId})", () => @@ -164,7 +164,7 @@ public sealed class AppProvider : IAppProvider return apps?.ToList() ?? []; } - public async Task> GetUserTeamsAsync(string userId, CancellationToken ct = default) + public async Task> GetUserTeamsAsync(string userId, CancellationToken ct = default) { var teams = await GetOrCreate($"GetUserTeams({userId})", () => { @@ -174,7 +174,7 @@ public sealed class AppProvider : IAppProvider return teams?.ToList() ?? []; } - public async Task> GetSchemasAsync(DomainId appId, + public async Task> GetSchemasAsync(DomainId appId, CancellationToken ct = default) { var schemas = await GetOrCreate($"GetSchemasAsync({appId})", () => @@ -187,14 +187,14 @@ public sealed class AppProvider : IAppProvider foreach (var schema in schemas) { localCache.Add(SchemaCacheKey(appId, schema.Id), schema); - localCache.Add(SchemaCacheKey(appId, schema.SchemaDef.Name), schema); + localCache.Add(SchemaCacheKey(appId, schema.Name), schema); } } return schemas?.ToList() ?? []; } - public async Task> GetRulesAsync(DomainId appId, + public async Task> GetRulesAsync(DomainId appId, CancellationToken ct = default) { var rules = await GetOrCreate($"GetRulesAsync({appId})", () => @@ -205,7 +205,7 @@ public sealed class AppProvider : IAppProvider return rules?.ToList() ?? []; } - public async Task GetRuleAsync(DomainId appId, DomainId id, + public async Task GetRuleAsync(DomainId appId, DomainId id, CancellationToken ct = default) { var rules = await GetRulesAsync(appId, ct); @@ -213,7 +213,7 @@ public sealed class AppProvider : IAppProvider return rules.Find(x => x.Id == id); } - public void RegisterAppForLocalContext(DomainId appId, IAppEntity app) + public void RegisterAppForLocalContext(DomainId appId, App app) { localCache.Add(AppCacheKey(appId), app); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/AppProviderExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/AppProviderExtensions.cs index 71078ab45..f9b07e806 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/AppProviderExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/AppProviderExtensions.cs @@ -6,7 +6,6 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Collections; @@ -14,7 +13,7 @@ namespace Squidex.Domain.Apps.Entities; public static class AppProviderExtensions { - public static async Task GetComponentsAsync(this IAppProvider appProvider, ISchemaEntity schema, + public static async Task GetComponentsAsync(this IAppProvider appProvider, Schema schema, CancellationToken ct = default) { Dictionary? result = null; @@ -33,7 +32,7 @@ public static class AppProviderExtensions if (schemaId == schema.Id) { result ??= []; - result[schemaId] = schema.SchemaDef; + result[schemaId] = schema; } else if (result == null || !result.TryGetValue(schemaId, out _)) { @@ -42,7 +41,7 @@ public static class AppProviderExtensions if (resolvedEntity != null) { result ??= []; - result[schemaId] = resolvedEntity.SchemaDef; + result[schemaId] = resolvedEntity; await ResolveSchemaAsync(resolvedEntity); } @@ -76,9 +75,9 @@ public static class AppProviderExtensions } } - async Task ResolveSchemaAsync(ISchemaEntity schema) + async Task ResolveSchemaAsync(Schema schema) { - foreach (var field in schema.SchemaDef.Fields) + foreach (var field in schema.Fields) { await ResolveFieldAsync(field); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppEntityExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppEntityExtensions.cs deleted file mode 100644 index 2073728b8..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppEntityExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core; - -namespace Squidex.Domain.Apps.Entities.Apps; - -public static class AppEntityExtensions -{ - public static PartitionResolver PartitionResolver(this IAppEntity entity) - { - return entity.Languages.ToResolver(); - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppEventDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppEventDeleter.cs index 08ac26e25..6abf7414f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppEventDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppEventDeleter.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Entities.Apps; @@ -20,7 +21,7 @@ public sealed class AppEventDeleter : IDeleter this.eventStore = eventStore; } - public Task DeleteAppAsync(IAppEntity app, + public Task DeleteAppAsync(App app, CancellationToken ct) { var streamFilter = StreamFilter.Prefix($"([a-zA-Z0-9]+)-{app.Id}"); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs index 0b3ea3c7f..366ece44d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Search; using Squidex.Infrastructure; using Squidex.Shared; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs index bbc59596a..dfcf9f35c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.States; @@ -118,7 +119,7 @@ public sealed class AppUISettings : IAppUISettings, IDeleter await ClearAsync(appId, contributorId, ct); } - async Task IDeleter.DeleteAppAsync(IAppEntity app, + async Task IDeleter.DeleteAppAsync(App app, CancellationToken ct) { await ClearAsync(app.Id, null, ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUsageDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUsageDeleter.cs index 5475df784..9f592b829 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUsageDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppUsageDeleter.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure.UsageTracking; namespace Squidex.Domain.Apps.Entities.Apps; @@ -18,7 +19,7 @@ public sealed class AppUsageDeleter : IDeleter this.apiUsageTracker = apiUsageTracker; } - public Task DeleteAppAsync(IAppEntity app, + public Task DeleteAppAsync(App app, CancellationToken ct) { return apiUsageTracker.DeleteAsync(app.Id.ToString(), ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs index 85d1a9ebc..ac58b1134 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Assets; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.DomainObject; using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Domain.Apps.Entities.Backup; @@ -116,7 +117,7 @@ public sealed class BackupApps : IBackupHandler private async Task RestoreAppTemporarilyAsync(RestoreContext context, CancellationToken ct) { - app = await rebuilder.RebuildStateAsync(context.AppId, ct); + app = await rebuilder.RebuildStateAsync(context.AppId, ct); // The app is only available in the context of the restore, so there is nothing to clear. appProvider.RegisterAppForLocalContext(context.AppId, app!.Snapshot); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/_AppCommand.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AppCommand.cs similarity index 69% rename from backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/_AppCommand.cs rename to backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AppCommand.cs index 8e87485d5..6f89813c8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/_AppCommand.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AppCommand.cs @@ -6,9 +6,6 @@ // ========================================================================== using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; - -#pragma warning disable MA0048 // File name must match type name namespace Squidex.Domain.Apps.Entities.Apps.Commands; @@ -21,9 +18,3 @@ public abstract class AppCommand : AppCommandBase, IAppCommand get => AppId.Id; } } - -// This command is needed as marker for middlewares. -public abstract class AppCommandBase : SquidexCommand, IAggregateCommand -{ - public abstract DomainId AggregateId { get; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/IEntityWithLastModifiedBy.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AppCommandBase.cs similarity index 65% rename from backend/src/Squidex.Domain.Apps.Entities/IEntityWithLastModifiedBy.cs rename to backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AppCommandBase.cs index 31b0c5b9f..2bd73bda5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/IEntityWithLastModifiedBy.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AppCommandBase.cs @@ -6,10 +6,11 @@ // ========================================================================== using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Entities; +namespace Squidex.Domain.Apps.Entities.Apps.Commands; -public interface IEntityWithLastModifiedBy +public abstract class AppCommandBase : SquidexCommand, IAggregateCommand { - RefToken LastModifiedBy { get; set; } + public abstract DomainId AggregateId { get; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs index eddb34b1f..f581c0b71 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs @@ -9,6 +9,7 @@ using System.Globalization; using System.Text; using CsvHelper; using CsvHelper.Configuration; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.Log; @@ -43,7 +44,7 @@ public sealed class DefaultAppLogStore : IAppLogStore, IDeleter this.requestLogStore = requestLogStore; } - Task IDeleter.DeleteAppAsync(IAppEntity app, + Task IDeleter.DeleteAppAsync(App app, CancellationToken ct) { return requestLogStore.DeleteAsync(app.Id.ToString(), ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppCommandMiddleware.cs index 2918f8281..fdb562d84 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppCommandMiddleware.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Assets; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Translations; @@ -42,7 +43,7 @@ public sealed class AppCommandMiddleware : AggregateCommandMiddleware EnrichResultAsync(CommandContext context, CommandResult result, CancellationToken ct) { - if (result.Payload is IAppEntity app) + if (result.Payload is App app) { contextProvider.Context.App = app; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.State.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.State.cs index df36687d4..0f644cc5e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.State.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.State.cs @@ -5,236 +5,135 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Text.Json.Serialization; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Core.Assets; -using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Apps; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; -using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Apps.DomainObject; public partial class AppDomainObject { - [CollectionName("Apps")] - public sealed class State : DomainObjectState, IAppEntity + protected override App Apply(App snapshot, Envelope @event) { - public string Name { get; set; } + var newSnapshot = snapshot; - public string Label { get; set; } - - public string Description { get; set; } - - public DomainId? TeamId { get; set; } - - public Contributors Contributors { get; set; } = Contributors.Empty; - - public Roles Roles { get; set; } = Roles.Empty; - - public AssignedPlan? Plan { get; set; } - - public AppClients Clients { get; set; } = AppClients.Empty; - - public AppImage? Image { get; set; } - - public AppSettings Settings { get; set; } = AppSettings.Empty; - - public AssetScripts AssetScripts { get; set; } = new AssetScripts(); - - public LanguagesConfig Languages { get; set; } = LanguagesConfig.English; - - public Workflows Workflows { get; set; } = Workflows.Empty; - - public bool IsDeleted { get; set; } - - [JsonIgnore] - public DomainId UniqueId - { - get => Id; - } - - public override bool ApplyEvent(IEvent @event) + switch (@event.Payload) { - switch (@event) - { - case AppCreated e: - { - Id = e.AppId.Id; - - SimpleMapper.Map(e, this); - return true; - } - - case AppUpdated e when Is.Change(Label, e.Label) || Is.Change(Description, e.Description): - { - SimpleMapper.Map(e, this); - return true; - } - - case AppTransfered e when Is.Change(TeamId, e.TeamId): - { - SimpleMapper.Map(e, this); - return true; - } + case AppCreated e: + newSnapshot = snapshot with { Id = e.AppId.Id }; + SimpleMapper.Map(e, newSnapshot); + break; - case AppSettingsUpdated e when Is.Change(Settings, e.Settings): - return UpdateSettings(e.Settings); + case AppUpdated e: + newSnapshot = snapshot.Annotate(e.Label, e.Description); + break; - case AppAssetsScriptsConfigured e when Is.Change(e.Scripts, AssetScripts): - return UpdateAssetScripts(e.Scripts); + case AppTransfered e: + newSnapshot = snapshot.Transfer(e.TeamId); + break; - case AppPlanChanged e when Is.Change(Plan?.PlanId, e.PlanId): - return UpdatePlan(e.ToPlan()); + case AppSettingsUpdated e: + newSnapshot = snapshot.UpdateSettings(e.Settings); + break; - case AppPlanReset e when Plan != null: - return UpdatePlan(null); + case AppAssetsScriptsConfigured e: + newSnapshot = snapshot.UpdateAssetScripts(e.Scripts); + break; - case AppImageUploaded e: - return UpdateImage(e.Image); + case AppPlanChanged e: + newSnapshot = snapshot.ChangePlan(new AssignedPlan(e.Actor, e.PlanId)); + break; - case AppImageRemoved e when Image != null: - return UpdateImage(null); + case AppPlanReset e: + newSnapshot = snapshot.ChangePlan(null); + break; - case AppContributorAssigned e: - return UpdateContributors(e, (e, c) => c.Assign(e.ContributorId, e.Role)); + case AppImageUploaded e: + newSnapshot = snapshot.UpdateImage(e.Image); + break; - case AppContributorRemoved e: - return UpdateContributors(e, (e, c) => c.Remove(e.ContributorId)); + case AppImageRemoved e: + newSnapshot = snapshot.UpdateImage(null); + break; - case AppClientAttached e: - return UpdateClients(e, (e, c) => c.Add(e.Id, e.Secret, e.Role)); + case AppContributorAssigned e: + newSnapshot = snapshot.UpdateContributors(e, (e, c) => c.Assign(e.ContributorId, e.Role)); + break; - case AppClientUpdated e: - return UpdateClients(e, (e, c) => c.Update(e.Id, e.Name, e.Role, e.ApiCallsLimit, e.ApiTrafficLimit, e.AllowAnonymous)); + case AppContributorRemoved e: + newSnapshot = snapshot.UpdateContributors(e, (e, c) => c.Remove(e.ContributorId)); + break; - case AppClientRevoked e: - return UpdateClients(e, (e, c) => c.Revoke(e.Id)); + case AppClientAttached e: + newSnapshot = snapshot.UpdateClients(e, (e, c) => c.Add(e.Id, e.Secret, e.Role)); + break; - case AppWorkflowAdded e: - return UpdateWorkflows(e, (e, w) => w.Add(e.WorkflowId, e.Name)); + case AppClientUpdated e: + newSnapshot = snapshot.UpdateClients(e, (e, c) => c.Update(e.Id, e.Name, e.Role, e.ApiCallsLimit, e.ApiTrafficLimit, e.AllowAnonymous)); + break; - case AppWorkflowUpdated e: - return UpdateWorkflows(e, (e, w) => w.Update(e.WorkflowId, e.Workflow)); + case AppClientRevoked e: + newSnapshot = snapshot.UpdateClients(e, (e, c) => c.Revoke(e.Id)); + break; - case AppWorkflowDeleted e: - return UpdateWorkflows(e, (e, w) => w.Remove(e.WorkflowId)); + case AppWorkflowAdded e: + newSnapshot = snapshot.UpdateWorkflows(e, (e, w) => w.Add(e.WorkflowId, e.Name)); + break; - case AppRoleAdded e: - return UpdateRoles(e, (e, r) => r.Add(e.Name)); + case AppWorkflowUpdated e: + newSnapshot = snapshot.UpdateWorkflows(e, (e, w) => w.Update(e.WorkflowId, e.Workflow)); + break; - case AppRoleUpdated e: - return UpdateRoles(e, (e, r) => r.Update(e.Name, e.ToPermissions(), e.Properties)); + case AppWorkflowDeleted e: + newSnapshot = snapshot.UpdateWorkflows(e, (e, w) => w.Remove(e.WorkflowId)); + break; - case AppRoleDeleted e: - return UpdateRoles(e, (e, r) => r.Remove(e.Name)); + case AppRoleAdded e: + newSnapshot = snapshot.UpdateRoles(e, (e, r) => r.Add(e.Name)); + break; - case AppLanguageAdded e: - return UpdateLanguages(e, (e, l) => l.Set(e.Language)); + case AppRoleUpdated e: + newSnapshot = snapshot.UpdateRoles(e, (e, r) => r.Update(e.Name, e.ToPermissions(), e.Properties)); + break; - case AppLanguageRemoved e: - return UpdateLanguages(e, (e, l) => l.Remove(e.Language)); + case AppRoleDeleted e: + newSnapshot = snapshot.UpdateRoles(e, (e, r) => r.Remove(e.Name)); + break; - case AppLanguageUpdated e: - return UpdateLanguages(e, (e, l) => - { - l = l.Set(e.Language, e.IsOptional, e.Fallback); + case AppLanguageAdded e: + newSnapshot = snapshot.UpdateLanguages(e, (e, l) => l.Set(e.Language)); + break; - if (e.IsMaster) - { - l = Languages.MakeMaster(e.Language); - } + case AppLanguageRemoved e: + newSnapshot = snapshot.UpdateLanguages(e, (e, l) => l.Remove(e.Language)); + break; - return l; - }); + case AppLanguageUpdated e: + return newSnapshot = snapshot.UpdateLanguages(e, (e, l) => + { + l = l.Set(e.Language, e.IsOptional, e.Fallback); - case AppDeleted: + if (e.IsMaster) { - Plan = null; - - IsDeleted = true; - return true; + l = l.MakeMaster(e.Language); } - } - - return false; - } - - private bool UpdateContributors(T @event, Func update) - { - var previous = Contributors; - Contributors = update(@event, previous); + return l; + }); - return !ReferenceEquals(previous, Contributors); + case AppDeleted: + newSnapshot = snapshot with { Plan = null, IsDeleted = true }; + break; } - private bool UpdateClients(T @event, Func update) + if (ReferenceEquals(newSnapshot, snapshot)) { - var previous = Clients; - - Clients = update(@event, previous); - - return !ReferenceEquals(previous, Clients); - } - - private bool UpdateLanguages(T @event, Func update) - { - var previous = Languages; - - Languages = update(@event, previous); - - return !ReferenceEquals(previous, Languages); - } - - private bool UpdateRoles(T @event, Func update) - { - var previous = Roles; - - Roles = update(@event, previous); - - return !ReferenceEquals(previous, Roles); - } - - private bool UpdateWorkflows(T @event, Func update) - { - var previous = Workflows; - - Workflows = update(@event, previous); - - return !ReferenceEquals(previous, Workflows); + return snapshot; } - private bool UpdateImage(AppImage? image) - { - Image = image; - - return true; - } - - private bool UpdateAssetScripts(AssetScripts? scripts) - { - AssetScripts = scripts ?? new AssetScripts(); - - return true; - } - - private bool UpdateSettings(AppSettings settings) - { - Settings = settings; - - return true; - } - - private bool UpdatePlan(AssignedPlan? plan) - { - Plan = plan; - - return true; - } + return newSnapshot.Apply(@event.To()); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs index f983f5c96..fbd8de90a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs @@ -24,18 +24,18 @@ using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.Apps.DomainObject; -public partial class AppDomainObject : DomainObject +public partial class AppDomainObject : DomainObject { private readonly IServiceProvider serviceProvider; - public AppDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log, + public AppDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log, IServiceProvider serviceProvider) : base(id, persistence, log) { this.serviceProvider = serviceProvider; } - protected override bool IsDeleted(State snapshot) + protected override bool IsDeleted(App snapshot) { return snapshot.IsDeleted; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs index d40b5bb27..a7e79f832 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardApp.cs @@ -6,9 +6,10 @@ // ========================================================================== using System.Security.Claims; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Billing; -using Squidex.Domain.Apps.Entities.Teams; using Squidex.Infrastructure; using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; @@ -127,7 +128,7 @@ public static class GuardApp }); } - public static Task CanTransfer(TransferToTeam command, IAppEntity app, IAppProvider appProvider, CancellationToken ct) + public static Task CanTransfer(TransferToTeam command, App app, IAppProvider appProvider, CancellationToken ct) { Guard.NotNull(command); @@ -151,7 +152,7 @@ public static class GuardApp } }); - static bool IsContributor(ITeamEntity team, RefToken actor) + static bool IsContributor(Team team, RefToken actor) { return team.Contributors.ContainsKey(actor.Identifier); } @@ -169,7 +170,7 @@ public static class GuardApp } } - public static void CanChangePlan(ChangePlan command, IAppEntity app, IBillingPlans billingPlans) + public static void CanChangePlan(ChangePlan command, App app, IBillingPlans billingPlans) { Guard.NotNull(command); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppClients.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppClients.cs index 15d6fa74b..5a48ccd41 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppClients.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppClients.cs @@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; public static class GuardAppClients { - public static void CanAttach(AttachClient command, IAppEntity app) + public static void CanAttach(AttachClient command, App app) { Guard.NotNull(command); @@ -34,7 +34,7 @@ public static class GuardAppClients }); } - public static void CanRevoke(RevokeClient command, IAppEntity app) + public static void CanRevoke(RevokeClient command, App app) { Guard.NotNull(command); @@ -49,7 +49,7 @@ public static class GuardAppClients }); } - public static void CanUpdate(UpdateClient command, IAppEntity app) + public static void CanUpdate(UpdateClient command, App app) { Guard.NotNull(command); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppContributors.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppContributors.cs index e993bdb83..ec14a1e05 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppContributors.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppContributors.cs @@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; public static class GuardAppContributors { - public static Task CanAssign(AssignContributor command, IAppEntity app, IUserResolver users, Plan plan) + public static Task CanAssign(AssignContributor command, App app, IUserResolver users, Plan plan) { Guard.NotNull(command); @@ -59,7 +59,7 @@ public static class GuardAppContributors }); } - public static void CanRemove(RemoveContributor command, IAppEntity app) + public static void CanRemove(RemoveContributor command, App app) { Guard.NotNull(command); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppLanguages.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppLanguages.cs index 33ae20423..b411f9ae5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppLanguages.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppLanguages.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; public static class GuardAppLanguages { - public static void CanAdd(AddLanguage command, IAppEntity app) + public static void CanAdd(AddLanguage command, App app) { Guard.NotNull(command); @@ -35,7 +35,7 @@ public static class GuardAppLanguages }); } - public static void CanRemove(RemoveLanguage command, IAppEntity app) + public static void CanRemove(RemoveLanguage command, App app) { Guard.NotNull(command); @@ -60,7 +60,7 @@ public static class GuardAppLanguages }); } - public static void CanUpdate(UpdateLanguage command, IAppEntity app) + public static void CanUpdate(UpdateLanguage command, App app) { Guard.NotNull(command); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppRoles.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppRoles.cs index 07b07f20c..a36123b8c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppRoles.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppRoles.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; public static class GuardAppRoles { - public static void CanAdd(AddRole command, IAppEntity app) + public static void CanAdd(AddRole command, App app) { Guard.NotNull(command); @@ -34,7 +34,7 @@ public static class GuardAppRoles }); } - public static void CanDelete(DeleteRole command, IAppEntity app) + public static void CanDelete(DeleteRole command, App app) { Guard.NotNull(command); @@ -65,7 +65,7 @@ public static class GuardAppRoles }); } - public static void CanUpdate(UpdateRole command, IAppEntity app) + public static void CanUpdate(UpdateRole command, App app) { Guard.NotNull(command); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppWorkflows.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppWorkflows.cs index 385acdb6b..8d82d79ac 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppWorkflows.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/Guards/GuardAppWorkflows.cs @@ -1,10 +1,11 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; @@ -28,7 +29,7 @@ public static class GuardAppWorkflows }); } - public static void CanUpdate(UpdateWorkflow command, IAppEntity app) + public static void CanUpdate(UpdateWorkflow command, App app) { Guard.NotNull(command); @@ -92,7 +93,7 @@ public static class GuardAppWorkflows }); } - public static void CanDelete(DeleteWorkflow command, IAppEntity app) + public static void CanDelete(DeleteWorkflow command, App app) { Guard.NotNull(command); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs deleted file mode 100644 index 928dd3a89..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs +++ /dev/null @@ -1,49 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Core.Assets; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Apps; - -public interface IAppEntity : - IEntity, - IEntityWithCreatedBy, - IEntityWithLastModifiedBy, - IEntityWithVersion -{ - string Name { get; } - - string? Label { get; } - - string? Description { get; } - - DomainId? TeamId { get; } - - Roles Roles { get; } - - AssignedPlan? Plan { get; } - - Contributors Contributors { get; } - - AppImage? Image { get; } - - AppClients Clients { get; } - - AppSettings Settings { get; } - - AssetScripts AssetScripts { get; } - - LanguagesConfig Languages { get; } - - Workflows Workflows { get; } - - bool IsDeleted { get; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs index 90ebc965c..d22e1be54 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Caching; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Repositories; using Squidex.Hosting; @@ -58,7 +59,7 @@ public sealed class AppsIndex : IAppsIndex, ICommandMiddleware, IInitializable return await namesState.ReserveAsync(id, name, ct); } - public async Task> GetAppsForUserAsync(string userId, PermissionSet permissions, + public async Task> GetAppsForUserAsync(string userId, PermissionSet permissions, CancellationToken ct = default) { using (var activity = Telemetry.Activities.StartActivity("AppsIndex/GetAppsForUserAsync")) @@ -67,16 +68,11 @@ public sealed class AppsIndex : IAppsIndex, ICommandMiddleware, IInitializable var apps = await appRepository.QueryAllAsync(userId, permissions.ToAppNames(), ct); - foreach (var app in apps.Where(IsValid)) - { - await CacheItAsync(app); - } - - return apps.Where(IsValid).ToList(); + return await apps.Where(IsValid).SelectAsync(PrepareAsync); } } - public async Task> GetAppsForTeamAsync(DomainId teamId, + public async Task> GetAppsForTeamAsync(DomainId teamId, CancellationToken ct = default) { using (var activity = Telemetry.Activities.StartActivity("AppsIndex/GetAppsForTeamAsync")) @@ -85,16 +81,11 @@ public sealed class AppsIndex : IAppsIndex, ICommandMiddleware, IInitializable var apps = await appRepository.QueryAllAsync(teamId, ct); - foreach (var app in apps.Where(IsValid)) - { - await CacheItAsync(app); - } - - return apps.Where(IsValid).ToList(); + return await apps.Where(IsValid).SelectAsync(PrepareAsync); } } - public async Task GetAppAsync(string name, bool canCache = false, + public async Task GetAppAsync(string name, bool canCache = false, CancellationToken ct = default) { using (var activity = Telemetry.Activities.StartActivity("AppsIndex/GetAppByNameAsync")) @@ -103,7 +94,7 @@ public sealed class AppsIndex : IAppsIndex, ICommandMiddleware, IInitializable if (canCache) { - if (appCache.TryGetValue(GetCacheKey(name), out var v) && v is IAppEntity cacheApp) + if (appCache.TryGetValue(GetCacheKey(name), out var v) && v is App cacheApp) { return cacheApp; } @@ -111,21 +102,16 @@ public sealed class AppsIndex : IAppsIndex, ICommandMiddleware, IInitializable var app = await appRepository.FindAsync(name, ct); - if (!IsValid(app)) + if (app == null || !IsValid(app)) { - app = null; + return null; } - if (app != null) - { - await CacheItAsync(app); - } - - return app; + return await PrepareAsync(app); } } - public async Task GetAppAsync(DomainId appId, bool canCache = false, + public async Task GetAppAsync(DomainId appId, bool canCache = false, CancellationToken ct = default) { using (var activity = Telemetry.Activities.StartActivity("AppsIndex/GetAppAsync")) @@ -134,7 +120,7 @@ public sealed class AppsIndex : IAppsIndex, ICommandMiddleware, IInitializable if (canCache) { - if (appCache.TryGetValue(GetCacheKey(appId), out var cached) && cached is IAppEntity cachedApp) + if (appCache.TryGetValue(GetCacheKey(appId), out var cached) && cached is App cachedApp) { return cachedApp; } @@ -142,17 +128,12 @@ public sealed class AppsIndex : IAppsIndex, ICommandMiddleware, IInitializable var app = await appRepository.FindAsync(appId, ct); - if (!IsValid(app)) - { - app = null; - } - - if (app != null) + if (app == null || !IsValid(app)) { - await CacheItAsync(app); + return null; } - return app; + return await PrepareAsync(app); } } @@ -234,28 +215,30 @@ public sealed class AppsIndex : IAppsIndex, ICommandMiddleware, IInitializable return $"{typeof(AppsIndex)}_Apps_Name_{name}"; } - private static bool IsValid(IAppEntity? app) + private static bool IsValid(App? app) { return app is { Version: > EtagVersion.Empty, IsDeleted: false }; } - private Task InvalidateItAsync(DomainId id, string name) + private async Task PrepareAsync(App app) { // Do not use cancellation here as we already so far. - return appCache.RemoveAsync(new[] + await appCache.AddAsync(new[] { - GetCacheKey(id), - GetCacheKey(name) - }); + new KeyValuePair(GetCacheKey(app.Id), app), + new KeyValuePair(GetCacheKey(app.Name), app), + }, CacheDuration); + + return app; } - private Task CacheItAsync(IAppEntity app) + private Task InvalidateItAsync(DomainId id, string name) { // Do not use cancellation here as we already so far. - return appCache.AddAsync(new[] + return appCache.RemoveAsync(new[] { - new KeyValuePair(GetCacheKey(app.Id), app), - new KeyValuePair(GetCacheKey(app.Name), app), - }, CacheDuration); + GetCacheKey(id), + GetCacheKey(name) + }); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/IAppsIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/IAppsIndex.cs index 703fb1170..1f65aa415 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/IAppsIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/IAppsIndex.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; @@ -12,16 +13,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes; public interface IAppsIndex { - Task> GetAppsForUserAsync(string userId, PermissionSet permissions, + Task> GetAppsForUserAsync(string userId, PermissionSet permissions, CancellationToken ct = default); - Task> GetAppsForTeamAsync(DomainId teamId, + Task> GetAppsForTeamAsync(DomainId teamId, CancellationToken ct = default); - Task GetAppAsync(string name, bool canCache = false, + Task GetAppAsync(string name, bool canCache = false, CancellationToken ct = default); - Task GetAppAsync(DomainId appId, bool canCache = false, + Task GetAppAsync(DomainId appId, bool canCache = false, CancellationToken ct = default); Task ReserveAsync(DomainId id, string name, diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Repositories/IAppRepository.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Repositories/IAppRepository.cs index 4770f5670..42b4feb4f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Repositories/IAppRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Repositories/IAppRepository.cs @@ -5,21 +5,22 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Apps.Repositories; public interface IAppRepository { - Task> QueryAllAsync(string contributorId, IEnumerable names, + Task> QueryAllAsync(string contributorId, IEnumerable names, CancellationToken ct = default); - Task> QueryAllAsync(DomainId teamId, + Task> QueryAllAsync(DomainId teamId, CancellationToken ct = default); - Task FindAsync(DomainId id, + Task FindAsync(DomainId id, CancellationToken ct = default); - Task FindAsync(string name, + Task FindAsync(string name, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs index 6bd60a527..90e298e27 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Reflection; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure.Security; using Squidex.Shared; @@ -42,7 +43,7 @@ public sealed class RolePermissionsProvider } } - public async Task> GetPermissionsAsync(IAppEntity app) + public async Task> GetPermissionsAsync(App app) { var schemaNames = await GetSchemaNamesAsync(app); @@ -76,7 +77,7 @@ public sealed class RolePermissionsProvider return result; } - private async Task> GetSchemaNamesAsync(IAppEntity app) + private async Task> GetSchemaNamesAsync(App app) { var schemas = await appProvider.GetSchemasAsync(app.Id); @@ -85,7 +86,7 @@ public sealed class RolePermissionsProvider Permission.Any }; - schemaNames.AddRange(schemas.Select(x => x.SchemaDef.Name)); + schemaNames.AddRange(schemas.Select(x => x.Name)); return schemaNames; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs index cf5c6148d..4d51db88f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs @@ -19,6 +19,7 @@ using Squidex.CLI.Commands.Implementation.Sync.Workflows; using Squidex.CLI.Configuration; using Squidex.ClientLibrary; using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure.Commands; using Squidex.Log; @@ -49,11 +50,11 @@ public sealed class TemplateCommandMiddleware : ICommandMiddleware if (context.IsCompleted && context.Command is CreateApp createApp) { - await ApplyTemplateAsync(context.Result(), createApp.Template); + await ApplyTemplateAsync(context.Result(), createApp.Template); } } - private async Task ApplyTemplateAsync(IAppEntity app, string? template) + private async Task ApplyTemplateAsync(App app, string? template) { if (string.IsNullOrWhiteSpace(template)) { @@ -109,7 +110,7 @@ public sealed class TemplateCommandMiddleware : ICommandMiddleware return new SyncService(fs, session); } - private ISession CreateSession(IAppEntity app) + private ISession CreateSession(App app) { var client = app.Clients.First(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCache.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCache.cs index ee347bc7d..cff04250b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCache.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetCache.cs @@ -12,7 +12,7 @@ using Squidex.Infrastructure.Caching; namespace Squidex.Domain.Apps.Entities.Assets; -public sealed class AssetCache : QueryCache, IAssetCache +public sealed class AssetCache : QueryCache, IAssetCache { public AssetCache(IMemoryCache? memoryCache, IOptions options) : base(options.Value.CanCache ? memoryCache : null) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDuplicate.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDuplicate.cs index 90c4e62be..fd40e1f87 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDuplicate.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetDuplicate.cs @@ -9,4 +9,4 @@ namespace Squidex.Domain.Apps.Entities.Assets; -public sealed record AssetDuplicate(IEnrichedAssetEntity Asset); +public sealed record AssetDuplicate(EnrichedAsset Asset); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs deleted file mode 100644 index 895af65b3..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetEntity.cs +++ /dev/null @@ -1,69 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using NodaTime; -using Squidex.Domain.Apps.Core.Assets; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Assets; - -public sealed class AssetEntity : IEnrichedAssetEntity -{ - public NamedId AppId { get; set; } - - public DomainId Id { get; set; } - - public DomainId ParentId { get; set; } - - public Instant Created { get; set; } - - public Instant LastModified { get; set; } - - public RefToken CreatedBy { get; set; } - - public RefToken LastModifiedBy { get; set; } - - public HashSet Tags { get; set; } - - public HashSet TagNames { get; set; } - - public long Version { get; set; } - - public string FileName { get; set; } - - public string FileHash { get; set; } - - public string MimeType { get; set; } - - public string Slug { get; set; } - - public string MetadataText { get; set; } - - public string? EditToken { get; set; } - - public long FileSize { get; set; } - - public long FileVersion { get; set; } - - public bool IsProtected { get; set; } - - public bool IsDeleted { get; set; } - - public AssetMetadata Metadata { get; set; } - - public AssetType Type { get; set; } - - public DomainId AssetId - { - get => Id; - } - - public DomainId UniqueId - { - get => DomainId.Combine(AppId, Id); - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetTagsDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetTagsDeleter.cs index 2fd13f0ca..4219513d4 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetTagsDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetTagsDeleter.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Tags; -using Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Domain.Apps.Entities.Assets; @@ -19,7 +19,7 @@ public sealed class AssetTagsDeleter : IDeleter this.tagService = tagService; } - public Task DeleteAppAsync(IAppEntity app, + public Task DeleteAppAsync(App app, CancellationToken ct) { return tagService.ClearAsync(app.Id, TagGroups.Assets, ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs index e5164e4a7..184f8df00 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Tags; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure.States; #pragma warning disable CS0649 @@ -40,7 +40,7 @@ public partial class AssetUsageTracker : IDeleter ClearCache(); } - Task IDeleter.DeleteAppAsync(IAppEntity app, + Task IDeleter.DeleteAppAsync(App app, CancellationToken ct) { return assetUsageTracker.DeleteUsageAsync(app.Id, ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsFluidExtension.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsFluidExtension.cs index 07f964a1a..7bbf16c1c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsFluidExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsFluidExtension.cs @@ -11,6 +11,7 @@ using Fluid.Ast; using Fluid.Values; using Microsoft.Extensions.DependencyInjection; using Squidex.Assets; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Templates; using Squidex.Infrastructure; @@ -127,7 +128,7 @@ public sealed class AssetsFluidExtension : IFluidExtension switch (objectValue.ToObjectValue()) { - case IAssetEntity asset: + case Asset asset: assetRef = asset.ToRef(); return true; @@ -139,7 +140,7 @@ public sealed class AssetsFluidExtension : IFluidExtension return true; } - private static async Task ResolveAssetAsync(IServiceProvider serviceProvider, DomainId appId, FluidValue id) + private static async Task ResolveAssetAsync(IServiceProvider serviceProvider, DomainId appId, FluidValue id) { var appProvider = serviceProvider.GetRequiredService(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs index 3a52bec92..09f43b730 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs @@ -13,12 +13,12 @@ using Jint.Runtime; using Jint.Runtime.Interop; using Microsoft.Extensions.DependencyInjection; using Squidex.Assets; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting.ContentWrapper; using Squidex.Domain.Apps.Core.Scripting.Internal; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Properties; using Squidex.Infrastructure; @@ -284,7 +284,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor switch (objectWrapper.Target) { - case IAssetEntity asset: + case Asset asset: assetRef = asset.ToRef(); return true; @@ -316,7 +316,7 @@ public sealed class AssetsJintExtension : IJintExtension, IScriptDescriptor return true; } - private async Task GetAppAsync(DomainId appId, + private async Task GetAppAsync(DomainId appId, CancellationToken ct) { var appProvider = serviceProvider.GetRequiredService(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsSearchSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsSearchSource.cs index f881e9492..8ef3392a3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsSearchSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsSearchSource.cs @@ -6,7 +6,7 @@ // ========================================================================== using Squidex.Domain.Apps.Core; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Search; using Squidex.Infrastructure.Queries; using Squidex.Shared; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs index 9cd749b19..a193baece 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Assets; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Entities.Assets.DomainObject; using Squidex.Domain.Apps.Entities.Backup; @@ -107,12 +108,12 @@ public sealed class BackupAssets : IBackupHandler { if (assetIds.Count > 0) { - await rebuilder.InsertManyAsync(assetIds, BatchSize, ct); + await rebuilder.InsertManyAsync(assetIds, BatchSize, ct); } if (assetFolderIds.Count > 0) { - await rebuilder.InsertManyAsync(assetFolderIds, BatchSize, ct); + await rebuilder.InsertManyAsync(assetFolderIds, BatchSize, ct); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/_AssetCommand.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommand.cs similarity index 66% rename from backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/_AssetCommand.cs rename to backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommand.cs index 72cd64af8..29113bfd3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/_AssetCommand.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommand.cs @@ -6,9 +6,6 @@ // ========================================================================== using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; - -#pragma warning disable MA0048 // File name must match type name namespace Squidex.Domain.Apps.Entities.Assets.Commands; @@ -23,11 +20,3 @@ public abstract class AssetCommand : AssetCommandBase get => DomainId.Combine(AppId, AssetId); } } - -// This command is needed as marker for middlewares. -public abstract class AssetCommandBase : SquidexCommand, IAppCommand, IAggregateCommand -{ - public NamedId AppId { get; set; } - - public abstract DomainId AggregateId { get; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommandBase.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommandBase.cs new file mode 100644 index 000000000..618a638ef --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetCommandBase.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; + +namespace Squidex.Domain.Apps.Entities.Assets.Commands; + +public abstract class AssetCommandBase : SquidexCommand, IAppCommand, IAggregateCommand +{ + public NamedId AppId { get; set; } + + public abstract DomainId AggregateId { get; } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetFolderCommand.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetFolderCommand.cs new file mode 100644 index 000000000..691765fd3 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetFolderCommand.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Assets.Commands; + +public abstract class AssetFolderCommand : AssetFolderCommandBase +{ + public DomainId AssetFolderId { get; set; } + + public override DomainId AggregateId + { + get => DomainId.Combine(AppId, AssetFolderId); + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/_AssetFolderCommand.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetFolderCommandBase.cs similarity index 66% rename from backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/_AssetFolderCommand.cs rename to backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetFolderCommandBase.cs index 4be89a15c..a940ba55f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/_AssetFolderCommand.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetFolderCommandBase.cs @@ -8,21 +8,8 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -#pragma warning disable MA0048 // File name must match type name - namespace Squidex.Domain.Apps.Entities.Assets.Commands; -public abstract class AssetFolderCommand : AssetFolderCommandBase -{ - public DomainId AssetFolderId { get; set; } - - public override DomainId AggregateId - { - get => DomainId.Combine(AppId, AssetFolderId); - } -} - -// This command is needed as marker for middlewares. public abstract class AssetFolderCommandBase : SquidexCommand, IAppCommand, IAggregateCommand { public NamedId AppId { get; set; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DefaultAssetFileStore.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DefaultAssetFileStore.cs index 4fec8c9a6..d3c356d9b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DefaultAssetFileStore.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DefaultAssetFileStore.cs @@ -8,7 +8,7 @@ using System.Text; using Microsoft.Extensions.Options; using Squidex.Assets; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Infrastructure; @@ -31,7 +31,7 @@ public sealed class DefaultAssetFileStore : IAssetFileStore, IDeleter this.options = options.Value; } - async Task IDeleter.DeleteAppAsync(IAppEntity app, + async Task IDeleter.DeleteAppAsync(App app, CancellationToken ct) { if (options.FolderPerApp) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs index 7fec21210..11459e171 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs @@ -7,6 +7,7 @@ using System.Security.Cryptography; using Squidex.Assets; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Queries; using Squidex.Infrastructure; @@ -14,7 +15,7 @@ using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject; -public sealed class AssetCommandMiddleware : CachingDomainObjectMiddleware +public sealed class AssetCommandMiddleware : CachingDomainObjectMiddleware { private readonly IAssetFileStore assetFileStore; private readonly IAssetEnricher assetEnricher; @@ -128,7 +129,7 @@ public sealed class AssetCommandMiddleware : CachingDomainObjectMiddleware, IAssetEntity + protected override Asset Apply(Asset snapshot, Envelope @event) { - public NamedId AppId { get; set; } + var newSnapshot = snapshot; - public DomainId ParentId { get; set; } - - public string FileName { get; set; } - - public string FileHash { get; set; } - - public string MimeType { get; set; } - - public string Slug { get; set; } - - public long FileVersion { get; set; } - - public long FileSize { get; set; } - - public long TotalSize { get; set; } - - public bool IsProtected { get; set; } - - public bool IsDeleted { get; set; } - - public HashSet Tags { get; set; } - - public AssetMetadata Metadata { get; set; } - - public AssetType Type { get; set; } - - [JsonIgnore] - public DomainId AssetId + switch (@event.Payload) { - get => Id; + case AssetCreated e: + newSnapshot = new Asset { Id = e.AssetId, TotalSize = e.FileSize }; + SimpleMapper.Map(e, newSnapshot); + break; + + case AssetUpdated e when !string.Equals(e.FileHash, snapshot.FileHash, StringComparison.Ordinal): + newSnapshot = snapshot with { TotalSize = snapshot.FileSize + e.FileSize }; + SimpleMapper.Map(e, newSnapshot); + break; + + case AssetAnnotated e: + newSnapshot = snapshot.Annotate(e.FileName, e.Slug, e.IsProtected, e.Tags, e.Metadata); + break; + + case AssetMoved e: + newSnapshot = snapshot.Move(e.ParentId); + break; + + case AssetDeleted: + newSnapshot = snapshot with { IsDeleted = true }; + break; } - [JsonIgnore] - public DomainId UniqueId + if (newSnapshot.Tags == null) { - get => DomainId.Combine(AppId, Id); + newSnapshot = newSnapshot with { Tags = [] }; } - public override bool ApplyEvent(IEvent @event) + if (newSnapshot.Metadata == null) { - switch (@event) - { - case AssetCreated e: - { - Id = e.AssetId; - - SimpleMapper.Map(e, this); - - if (string.IsNullOrWhiteSpace(Slug)) - { - Slug = FileName.ToAssetSlug(); - } - - TotalSize += e.FileSize; - - EnsureProperties(); - return true; - } - - case AssetUpdated e when Is.Change(e.FileHash, FileHash): - { - SimpleMapper.Map(e, this); - - TotalSize += e.FileSize; - - EnsureProperties(); - return true; - } - - case AssetAnnotated e: - { - var hasChanged = false; - - if (Is.OptionalChange(FileName, e.FileName)) - { - FileName = e.FileName; - - hasChanged = true; - } - - if (Is.OptionalChange(Slug, e.Slug)) - { - Slug = e.Slug; - - hasChanged = true; - } - - if (Is.OptionalChange(IsProtected, e.IsProtected)) - { - IsProtected = e.IsProtected.Value; - - hasChanged = true; - } - - if (Is.OptionalSetChange(Tags, e.Tags)) - { - Tags = e.Tags; - - hasChanged = true; - } - - if (Is.OptionalMapChange(Metadata, e.Metadata)) - { - Metadata = e.Metadata; - - hasChanged = true; - } - - EnsureProperties(); - return hasChanged; - } - - case AssetMoved e when e.ParentId != ParentId: - { - ParentId = e.ParentId; - - EnsureProperties(); - return true; - } - - case AssetDeleted: - { - IsDeleted = true; - return true; - } - } - - return false; + newSnapshot = newSnapshot with { Metadata = [] }; } - private void EnsureProperties() + if (ReferenceEquals(newSnapshot, snapshot)) { - if (Tags == null) - { - Tags = []; - } - - if (Metadata == null) - { - Metadata = []; - } + return snapshot; } + + return newSnapshot.Apply(@event.To()); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs index 0e2ce9846..e8a66e4d2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs @@ -6,6 +6,7 @@ // ========================================================================== using Microsoft.Extensions.Logging; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards; using Squidex.Domain.Apps.Events; @@ -20,18 +21,18 @@ using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject; -public partial class AssetDomainObject : DomainObject +public partial class AssetDomainObject : DomainObject { private readonly IServiceProvider serviceProvider; - public AssetDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log, + public AssetDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log, IServiceProvider serviceProvider) : base(id, persistence, log) { this.serviceProvider = serviceProvider; } - protected override bool IsDeleted(State snapshot) + protected override bool IsDeleted(Asset snapshot) { return snapshot.IsDeleted; } @@ -80,7 +81,7 @@ public partial class AssetDomainObject : DomainObject await CreateCore(c.AsCreate(), operation, ct); } - if (Is.OptionalChange(Snapshot.ParentId, c.ParentId)) + if (c.ParentId != null && c.ParentId != Snapshot.ParentId) { await MoveCore(c.AsMove(c.ParentId.Value), operation, ct); } @@ -95,7 +96,7 @@ public partial class AssetDomainObject : DomainObject await CreateCore(c, operation, ct); - if (Is.Change(Snapshot.ParentId, c.ParentId)) + if (c.ParentId != Snapshot.ParentId) { await MoveCore(c.AsMove(), operation, ct); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.State.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.State.cs index 8825f3d24..5d4c0adc5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.State.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.State.cs @@ -5,10 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Text.Json.Serialization; +using Squidex.Domain.Apps.Core.Assets; +using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Assets; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -16,54 +15,35 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject; public sealed partial class AssetFolderDomainObject { - public sealed class State : DomainObjectState, IAssetFolderEntity + protected override AssetFolder Apply(AssetFolder snapshot, Envelope @event) { - public NamedId AppId { get; set; } + var newSnapshot = snapshot; - public string FolderName { get; set; } - - public DomainId ParentId { get; set; } - - public bool IsDeleted { get; set; } - - [JsonIgnore] - public DomainId UniqueId + switch (@event.Payload) { - get => DomainId.Combine(AppId, Id); + case AssetFolderCreated e: + newSnapshot = new AssetFolder { Id = e.AssetFolderId }; + SimpleMapper.Map(e, newSnapshot); + break; + + case AssetFolderRenamed e: + newSnapshot = snapshot.Rename(e.FolderName); + break; + + case AssetFolderMoved e: + newSnapshot = snapshot.Move(e.ParentId); + break; + + case AssetFolderDeleted: + newSnapshot = snapshot with { IsDeleted = true }; + break; } - public override bool ApplyEvent(IEvent @event) + if (ReferenceEquals(newSnapshot, snapshot)) { - switch (@event) - { - case AssetFolderCreated e: - { - Id = e.AssetFolderId; - - SimpleMapper.Map(e, this); - return true; - } - - case AssetFolderRenamed e when Is.OptionalChange(FolderName, e.FolderName): - { - FolderName = e.FolderName; - return true; - } - - case AssetFolderMoved e when Is.Change(ParentId, e.ParentId): - { - ParentId = e.ParentId; - return true; - } - - case AssetFolderDeleted: - { - IsDeleted = true; - return true; - } - } - - return false; + return snapshot; } + + return newSnapshot.Apply(@event.To()); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs index 481c440a3..aca217c4c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs @@ -6,6 +6,7 @@ // ========================================================================== using Microsoft.Extensions.Logging; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards; using Squidex.Domain.Apps.Events; @@ -20,18 +21,18 @@ using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject; -public sealed partial class AssetFolderDomainObject : DomainObject +public sealed partial class AssetFolderDomainObject : DomainObject { private readonly IServiceProvider serviceProvider; - public AssetFolderDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log, + public AssetFolderDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log, IServiceProvider serviceProvider) : base(id, persistence, log) { this.serviceProvider = serviceProvider; } - protected override bool IsDeleted(State snapshot) + protected override bool IsDeleted(AssetFolder snapshot) { return Snapshot.IsDeleted; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderOperation.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderOperation.cs index 88505ca57..f0c9c9ee0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderOperation.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderOperation.cs @@ -6,20 +6,21 @@ // ========================================================================== using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject; -public sealed class AssetFolderOperation : OperationContextBase +public sealed class AssetFolderOperation : OperationContextBase { - public AssetFolderOperation(IServiceProvider serviceProvider, Func snapshot) + public AssetFolderOperation(IServiceProvider serviceProvider, Func snapshot) : base(serviceProvider, snapshot) { Guard.NotNull(serviceProvider); } - public static async Task CreateAsync(IServiceProvider services, AssetFolderCommand command, Func snapshot) + public static async Task CreateAsync(IServiceProvider services, AssetFolderCommand command, Func snapshot) { var appProvider = services.GetRequiredService(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetOperation.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetOperation.cs index 0f4bfcefa..c8f0b9532 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetOperation.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetOperation.cs @@ -6,20 +6,21 @@ // ========================================================================== using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject; -public sealed class AssetOperation : OperationContextBase +public sealed class AssetOperation : OperationContextBase { - public AssetOperation(IServiceProvider serviceProvider, Func snapshot) + public AssetOperation(IServiceProvider serviceProvider, Func snapshot) : base(serviceProvider, snapshot) { Guard.NotNull(serviceProvider); } - public static async Task CreateAsync(IServiceProvider services, AssetCommand command, Func snapshot) + public static async Task CreateAsync(IServiceProvider services, AssetCommand command, Func snapshot) { var appProvider = services.GetRequiredService(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetsBulkUpdateCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetsBulkUpdateCommandMiddleware.cs index 3615b8fe9..53aad8d05 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetsBulkUpdateCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetsBulkUpdateCommandMiddleware.cs @@ -57,7 +57,7 @@ public sealed class AssetsBulkUpdateCommandMiddleware : ICommandMiddleware return; } - if (bulkUpdates.Jobs == null || bulkUpdates.Jobs.Length == 0) + if (bulkUpdates.Jobs is not { Length: > 0 }) { context.Complete(new BulkUpdateResult()); return; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ValidationExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ValidationExtensions.cs index b31e1ae3e..a02a66621 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ValidationExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/Guards/ValidationExtensions.cs @@ -84,7 +84,7 @@ public static class ValidationExtensions { var contentRepository = operation.Resolve(); - var hasReferrer = await contentRepository.HasReferrersAsync(operation.App.Id, operation.CommandId, SearchScope.All, ct); + var hasReferrer = await contentRepository.HasReferrersAsync(operation.App, operation.CommandId, SearchScope.All, ct); if (hasReferrer) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetFolderEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/EnrichedAsset.cs similarity index 66% rename from backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetFolderEntity.cs rename to backend/src/Squidex.Domain.Apps.Entities/Assets/EnrichedAsset.cs index b50433948..3faa83b29 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetFolderEntity.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/EnrichedAsset.cs @@ -5,15 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Infrastructure; +using Squidex.Domain.Apps.Core.Assets; namespace Squidex.Domain.Apps.Entities.Assets; -public interface IAssetFolderEntity : IEntity, IEntityWithVersion +public record EnrichedAsset : Asset { - NamedId AppId { get; set; } + public HashSet TagNames { get; set; } - string FolderName { get; set; } + public string MetadataText { get; set; } - DomainId ParentId { get; set; } + public string? EditToken { get; set; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/FileTagAssetMetadataSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/FileTagAssetMetadataSource.cs index 0be7f4ae3..f33225744 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/FileTagAssetMetadataSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/FileTagAssetMetadataSource.cs @@ -145,7 +145,7 @@ public sealed class FileTagAssetMetadataSource : IAssetMetadataSource } } - public IEnumerable Format(IAssetEntity asset) + public IEnumerable Format(Asset asset) { if (asset.Type == AssetType.Video) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/FileTypeAssetMetadataSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/FileTypeAssetMetadataSource.cs index 9d5a3709e..6a0176525 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/FileTypeAssetMetadataSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/FileTypeAssetMetadataSource.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure; @@ -28,7 +29,7 @@ public sealed class FileTypeAssetMetadataSource : IAssetMetadataSource return Task.CompletedTask; } - public IEnumerable Format(IAssetEntity asset) + public IEnumerable Format(Asset asset) { yield break; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetCache.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetCache.cs index f1ff762b2..b7868bd57 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetCache.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetCache.cs @@ -10,6 +10,6 @@ using Squidex.Infrastructure.Caching; namespace Squidex.Domain.Apps.Entities.Assets; -public interface IAssetCache : IQueryCache +public interface IAssetCache : IQueryCache { } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs deleted file mode 100644 index d24b99387..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs +++ /dev/null @@ -1,28 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core.ValidateContent; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Assets; - -public interface IAssetEntity : - IEntity, - IEntityWithCreatedBy, - IEntityWithLastModifiedBy, - IEntityWithVersion, - IEntityWithTags, - IAssetInfo -{ - NamedId AppId { get; } - - DomainId ParentId { get; } - - bool IsProtected { get; } - - long FileVersion { get; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetLoader.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetLoader.cs index 6db149abb..c0e1d896c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetLoader.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetLoader.cs @@ -5,12 +5,13 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Assets; public interface IAssetLoader { - Task GetAsync(DomainId appId, DomainId id, long version = EtagVersion.Any, + Task GetAsync(DomainId appId, DomainId id, long version = EtagVersion.Any, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetMetadataSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetMetadataSource.cs index 137009737..9394cabfb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetMetadataSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetMetadataSource.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; namespace Squidex.Domain.Apps.Entities.Assets; @@ -16,5 +17,5 @@ public interface IAssetMetadataSource Task EnhanceAsync(UploadAssetCommand command, CancellationToken ct); - IEnumerable Format(IAssetEntity asset); + IEnumerable Format(Asset asset); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs index f8baa8296..b2248df66 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/IAssetQueryService.cs @@ -5,30 +5,31 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Assets; public interface IAssetQueryService { - Task> QueryAsync(Context context, DomainId? parentId, Q q, + Task> QueryAsync(Context context, DomainId? parentId, Q q, CancellationToken ct = default); - Task> QueryAssetFoldersAsync(Context context, DomainId? parentId, + Task> QueryAssetFoldersAsync(Context context, DomainId? parentId, CancellationToken ct = default); - Task> FindAssetFolderAsync(DomainId appId, DomainId id, + Task> FindAssetFolderAsync(DomainId appId, DomainId id, CancellationToken ct = default); - Task FindByHashAsync(Context context, string hash, string fileName, long fileSize, + Task FindByHashAsync(Context context, string hash, string fileName, long fileSize, CancellationToken ct = default); - Task FindAsync(Context context, DomainId id, bool allowDeleted = false, long version = EtagVersion.Any, + Task FindAsync(Context context, DomainId id, bool allowDeleted = false, long version = EtagVersion.Any, CancellationToken ct = default); - Task FindBySlugAsync(Context context, string slug, bool allowDeleted = false, + Task FindBySlugAsync(Context context, string slug, bool allowDeleted = false, CancellationToken ct = default); - Task FindGlobalAsync(Context context, DomainId id, + Task FindGlobalAsync(Context context, DomainId id, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/IEnrichedAssetEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/IEnrichedAssetEntity.cs deleted file mode 100644 index da70c0dba..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/IEnrichedAssetEntity.cs +++ /dev/null @@ -1,17 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Domain.Apps.Entities.Assets; - -public interface IEnrichedAssetEntity : IAssetEntity -{ - HashSet TagNames { get; } - - string MetadataText { get; } - - string? EditToken { get; set; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs index e678ed018..0693c8522 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/ImageAssetMetadataSource.cs @@ -98,7 +98,7 @@ public sealed class ImageAssetMetadataSource : IAssetMetadataSource } } - public IEnumerable Format(IAssetEntity asset) + public IEnumerable Format(Asset asset) { if (asset.Type == AssetType.Image) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetEnricher.cs index 095d36e0c..9b29c2442 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetEnricher.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; @@ -19,7 +20,7 @@ public sealed class AssetEnricher : IAssetEnricher this.steps = steps; } - public async Task EnrichAsync(IAssetEntity asset, Context context, + public async Task EnrichAsync(Asset asset, Context context, CancellationToken ct) { Guard.NotNull(asset); @@ -30,7 +31,7 @@ public sealed class AssetEnricher : IAssetEnricher return enriched[0]; } - public async Task> EnrichAsync(IEnumerable assets, Context context, + public async Task> EnrichAsync(IEnumerable assets, Context context, CancellationToken ct) { Guard.NotNull(assets); @@ -38,7 +39,7 @@ public sealed class AssetEnricher : IAssetEnricher using (var activity = Telemetry.Activities.StartActivity("AssetEnricher/EnrichAsync")) { - var results = new List(); + var results = new List(); if (context.App != null) { @@ -55,7 +56,7 @@ public sealed class AssetEnricher : IAssetEnricher foreach (var asset in assets) { - var result = SimpleMapper.Map(asset, new AssetEntity()); + var result = SimpleMapper.Map(asset, new EnrichedAsset()); results.Add(result); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs index b314f111b..865f1bfc2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetLoader.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.DomainObject; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -22,7 +23,7 @@ public sealed class AssetLoader : IAssetLoader this.domainObjectCache = domainObjectCache; } - public async Task GetAsync(DomainId appId, DomainId id, long version = EtagVersion.Any, + public async Task GetAsync(DomainId appId, DomainId id, long version = EtagVersion.Any, CancellationToken ct = default) { var uniqueId = DomainId.Combine(appId, id); @@ -39,16 +40,16 @@ public sealed class AssetLoader : IAssetLoader return asset; } - private async Task GetCachedAsync(DomainId uniqueId, long version, + private async Task GetCachedAsync(DomainId uniqueId, long version, CancellationToken ct) { using (Telemetry.Activities.StartActivity("AssetLoader/GetCachedAsync")) { - return await domainObjectCache.GetAsync(uniqueId, version, ct); + return await domainObjectCache.GetAsync(uniqueId, version, ct); } } - private async Task GetAsync(DomainId uniqueId, long version, + private async Task GetAsync(DomainId uniqueId, long version, CancellationToken ct) { using (Telemetry.Activities.StartActivity("AssetLoader/GetAsync")) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs index ea39750e0..c5ebf9d61 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/AssetQueryService.cs @@ -6,6 +6,7 @@ // ========================================================================== using Microsoft.Extensions.Options; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Infrastructure; @@ -36,14 +37,14 @@ public sealed class AssetQueryService : IAssetQueryService this.queryParser = queryParser; } - public async Task> FindAssetFolderAsync(DomainId appId, DomainId id, + public async Task> FindAssetFolderAsync(DomainId appId, DomainId id, CancellationToken ct = default) { using (var activity = Telemetry.Activities.StartActivity("AssetQueryService/FindAssetFolderAsync")) { activity?.SetTag("assetId", id); - var result = new List(); + var result = new List(); while (id != DomainId.Empty) { @@ -64,7 +65,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - public async Task> QueryAssetFoldersAsync(Context context, DomainId? parentId, + public async Task> QueryAssetFoldersAsync(Context context, DomainId? parentId, CancellationToken ct = default) { using (var activity = Telemetry.Activities.StartActivity("AssetQueryService/QueryAssetFoldersAsync")) @@ -77,7 +78,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - public async Task FindByHashAsync(Context context, string hash, string fileName, long fileSize, + public async Task FindByHashAsync(Context context, string hash, string fileName, long fileSize, CancellationToken ct = default) { Guard.NotNull(context); @@ -99,7 +100,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - public async Task FindBySlugAsync(Context context, string slug, bool allowDeleted = false, + public async Task FindBySlugAsync(Context context, string slug, bool allowDeleted = false, CancellationToken ct = default) { Guard.NotNull(context); @@ -119,7 +120,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - public async Task FindGlobalAsync(Context context, DomainId id, + public async Task FindGlobalAsync(Context context, DomainId id, CancellationToken ct = default) { Guard.NotNull(context); @@ -139,7 +140,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - public async Task FindAsync(Context context, DomainId id, bool allowDeleted = false, long version = EtagVersion.Any, + public async Task FindAsync(Context context, DomainId id, bool allowDeleted = false, long version = EtagVersion.Any, CancellationToken ct = default) { Guard.NotNull(context); @@ -148,7 +149,7 @@ public sealed class AssetQueryService : IAssetQueryService { activity?.SetTag("assetId", id); - IAssetEntity? asset; + Asset? asset; if (version > EtagVersion.Empty) { @@ -168,14 +169,14 @@ public sealed class AssetQueryService : IAssetQueryService } } - public async Task> QueryAsync(Context context, DomainId? parentId, Q q, + public async Task> QueryAsync(Context context, DomainId? parentId, Q q, CancellationToken ct = default) { Guard.NotNull(context); if (q == null) { - return ResultList.Empty(); + return ResultList.Empty(); } using (Telemetry.Activities.StartActivity("AssetQueryService/QueryAsync")) @@ -193,7 +194,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - private async Task> TransformAsync(Context context, IResultList assets, + private async Task> TransformAsync(Context context, IResultList assets, CancellationToken ct) { var transformed = await TransformCoreAsync(context, assets, ct); @@ -201,7 +202,7 @@ public sealed class AssetQueryService : IAssetQueryService return ResultList.Create(assets.Total, transformed); } - private async Task TransformAsync(Context context, IAssetEntity asset, + private async Task TransformAsync(Context context, Asset asset, CancellationToken ct) { var transformed = await TransformCoreAsync(context, Enumerable.Repeat(asset, 1), ct); @@ -209,7 +210,7 @@ public sealed class AssetQueryService : IAssetQueryService return transformed[0]; } - private async Task> TransformCoreAsync(Context context, IEnumerable assets, + private async Task> TransformCoreAsync(Context context, IEnumerable assets, CancellationToken ct) { using (Telemetry.Activities.StartActivity("AssetQueryService/TransformCoreAsync")) @@ -230,7 +231,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - private async Task> QueryFoldersCoreAsync(Context context, DomainId? parentId, + private async Task> QueryFoldersCoreAsync(Context context, DomainId? parentId, CancellationToken ct) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) @@ -242,7 +243,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - private async Task> QueryCoreAsync(Context context, DomainId? parentId, Q q, + private async Task> QueryCoreAsync(Context context, DomainId? parentId, Q q, CancellationToken ct) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) @@ -254,7 +255,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - private async Task FindFolderCoreAsync(DomainId appId, DomainId id, + private async Task FindFolderCoreAsync(DomainId appId, DomainId id, CancellationToken ct) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) @@ -266,7 +267,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - private async Task FindByHashCoreAsync(Context context, string hash, string fileName, long fileSize, + private async Task FindByHashCoreAsync(Context context, string hash, string fileName, long fileSize, CancellationToken ct) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) @@ -278,7 +279,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - private async Task FindBySlugCoreAsync(Context context, string slug, bool allowDeleted, + private async Task FindBySlugCoreAsync(Context context, string slug, bool allowDeleted, CancellationToken ct) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) @@ -290,7 +291,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - private async Task FindCoreAsync(DomainId id, + private async Task FindCoreAsync(DomainId id, CancellationToken ct) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) @@ -302,7 +303,7 @@ public sealed class AssetQueryService : IAssetQueryService } } - private async Task FindCoreAsync(Context context, DomainId id, bool allowDeleted, + private async Task FindCoreAsync(Context context, DomainId id, bool allowDeleted, CancellationToken ct) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs index 244206a58..31beb45e1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Tags; using Squidex.Infrastructure; using Squidex.Infrastructure.Queries; @@ -33,7 +34,7 @@ internal sealed class FilterTagTransformer : AsyncTransformVisitor?> Visit(CompareFilter nodeIn, Args args) { - if (string.Equals(nodeIn.Path[0], nameof(IAssetEntity.Tags), StringComparison.OrdinalIgnoreCase) && nodeIn.Value.Value is string stringValue) + if (string.Equals(nodeIn.Path[0], nameof(Asset.Tags), StringComparison.OrdinalIgnoreCase) && nodeIn.Value.Value is string stringValue) { var tagNames = await args.TagService.GetTagIdsAsync(args.AppId, TagGroups.Assets, HashSet.Of(stringValue), args.CancellationToken); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/IAssetEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/IAssetEnricher.cs index 2917ee44d..81415938a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/IAssetEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/IAssetEnricher.cs @@ -5,13 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; + namespace Squidex.Domain.Apps.Entities.Assets.Queries; public interface IAssetEnricher { - Task EnrichAsync(IAssetEntity asset, Context context, + Task EnrichAsync(Asset asset, Context context, CancellationToken ct); - Task> EnrichAsync(IEnumerable assets, Context context, + Task> EnrichAsync(IEnumerable assets, Context context, CancellationToken ct); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/IAssetEnricherStep.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/IAssetEnricherStep.cs index c9dc530a9..f7f251394 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/IAssetEnricherStep.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/IAssetEnricherStep.cs @@ -9,7 +9,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries; public interface IAssetEnricherStep { - Task EnrichAsync(Context context, IEnumerable assets, + Task EnrichAsync(Context context, IEnumerable assets, CancellationToken ct); Task EnrichAsync(Context context, diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/CalculateTokens.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/CalculateTokens.cs index c4e006ea2..fc62b16fb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/CalculateTokens.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/CalculateTokens.cs @@ -21,7 +21,7 @@ public sealed class CalculateTokens : IAssetEnricherStep this.urlGenerator = urlGenerator; } - public Task EnrichAsync(Context context, IEnumerable assets, + public Task EnrichAsync(Context context, IEnumerable assets, CancellationToken ct) { if (context.NoAssetEnrichment()) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ConvertTags.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ConvertTags.cs index 2dc6afcaf..dcd7774c9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ConvertTags.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ConvertTags.cs @@ -19,7 +19,7 @@ public sealed class ConvertTags : IAssetEnricherStep this.tagService = tagService; } - public async Task EnrichAsync(Context context, IEnumerable assets, + public async Task EnrichAsync(Context context, IEnumerable assets, CancellationToken ct) { if (context.NoAssetEnrichment()) @@ -46,7 +46,7 @@ public sealed class ConvertTags : IAssetEnricherStep } } - private async Task> CalculateTagsAsync(DomainId appId, IEnumerable assets, + private async Task> CalculateTagsAsync(DomainId appId, IEnumerable assets, CancellationToken ct) { var uniqueIds = assets.Where(x => x.Tags != null).SelectMany(x => x.Tags).ToHashSet(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/EnrichForCaching.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/EnrichForCaching.cs index 394df0c68..cb616bf05 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/EnrichForCaching.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/EnrichForCaching.cs @@ -33,7 +33,7 @@ public sealed class EnrichForCaching : IAssetEnricherStep return Task.CompletedTask; } - public Task EnrichAsync(Context context, IEnumerable assets, + public Task EnrichAsync(Context context, IEnumerable assets, CancellationToken ct) { // Sometimes we just want to skip this for performance reasons. diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/EnrichWithMetadataText.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/EnrichWithMetadataText.cs index f9aa09cab..aab643c31 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/EnrichWithMetadataText.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/EnrichWithMetadataText.cs @@ -19,7 +19,7 @@ public sealed class EnrichWithMetadataText : IAssetEnricherStep this.assetMetadataSources = assetMetadataSources; } - public Task EnrichAsync(Context context, IEnumerable assets, + public Task EnrichAsync(Context context, IEnumerable assets, CancellationToken ct) { if (context.NoAssetEnrichment()) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ScriptAsset.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ScriptAsset.cs index e814e18fa..81e14f593 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ScriptAsset.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Queries/Steps/ScriptAsset.cs @@ -20,7 +20,7 @@ public sealed class ScriptAsset : IAssetEnricherStep this.scriptEngine = scriptEngine; } - public async Task EnrichAsync(Context context, IEnumerable assets, + public async Task EnrichAsync(Context context, IEnumerable assets, CancellationToken ct) { if (!ShouldEnrich(context)) @@ -61,7 +61,7 @@ public sealed class ScriptAsset : IAssetEnricherStep } } - private async Task ScriptAsync(AssetScriptVars sharedVars, string script, AssetEntity asset, + private async Task ScriptAsync(AssetScriptVars sharedVars, string script, EnrichedAsset asset, CancellationToken ct) { // Script vars are just wrappers over dictionaries for better performance. diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetFolderRepository.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetFolderRepository.cs index 3606e6515..6b6523d4f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetFolderRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetFolderRepository.cs @@ -5,18 +5,19 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Assets.Repositories; public interface IAssetFolderRepository { - Task> QueryAsync(DomainId appId, DomainId? parentId, + Task> QueryAsync(DomainId appId, DomainId? parentId, CancellationToken ct = default); Task> QueryChildIdsAsync(DomainId appId, DomainId? parentId, CancellationToken ct = default); - Task FindAssetFolderAsync(DomainId appId, DomainId id, + Task FindAssetFolderAsync(DomainId appId, DomainId id, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs index 04523a27f..09cc2b492 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs @@ -5,16 +5,17 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Assets.Repositories; public interface IAssetRepository { - IAsyncEnumerable StreamAll(DomainId appId, + IAsyncEnumerable StreamAll(DomainId appId, CancellationToken ct = default); - Task> QueryAsync(DomainId appId, DomainId? parentId, Q q, + Task> QueryAsync(DomainId appId, DomainId? parentId, Q q, CancellationToken ct = default); Task> QueryIdsAsync(DomainId appId, HashSet ids, @@ -23,15 +24,15 @@ public interface IAssetRepository Task> QueryChildIdsAsync(DomainId appId, DomainId parentId, CancellationToken ct = default); - Task FindAssetByHashAsync(DomainId appId, string hash, string fileName, long fileSize, + Task FindAssetByHashAsync(DomainId appId, string hash, string fileName, long fileSize, CancellationToken ct = default); - Task FindAssetBySlugAsync(DomainId appId, string slug, bool allowDeleted, + Task FindAssetBySlugAsync(DomainId appId, string slug, bool allowDeleted, CancellationToken ct = default); - Task FindAssetAsync(DomainId id, + Task FindAssetAsync(DomainId id, CancellationToken ct = default); - Task FindAssetAsync(DomainId appId, DomainId id, bool allowDeleted, + Task FindAssetAsync(DomainId appId, DomainId id, bool allowDeleted, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/SvgAssetMetadataSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/SvgAssetMetadataSource.cs index c5f1ebf1e..3b90ff281 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/SvgAssetMetadataSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/SvgAssetMetadataSource.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; @@ -53,7 +54,7 @@ public sealed class SvgAssetMetadataSource : IAssetMetadataSource } } - public IEnumerable Format(IAssetEntity asset) + public IEnumerable Format(Asset asset) { yield break; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/Transformations.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/Transformations.cs index e32626b5c..2f4da4ce5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/Transformations.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/Transformations.cs @@ -45,7 +45,7 @@ public static class Transformations @event.AssetType); } - public static AssetRef ToRef(this IAssetEntity asset) + public static AssetRef ToRef(this Asset asset) { return new AssetRef( asset.AppId, diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs index 94f4ad6aa..d4f1ab340 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs @@ -87,7 +87,7 @@ public sealed class BackupReader : DisposableObjectBase, IBackupReader { var attachmentEntry = archive.GetEntry(ArchiveHelper.GetAttachmentPath(name)); - if (attachmentEntry == null || attachmentEntry.Length == 0) + if (attachmentEntry is not { Length: > 0 }) { throw new FileNotFoundException("Cannot find attachment.", name); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupService.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupService.cs index 19f9d7613..2adb86034 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupService.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Backup.State; using Squidex.Infrastructure; using Squidex.Infrastructure.States; @@ -30,7 +30,7 @@ public sealed class BackupService : IBackupService, IDeleter restoreState = new SimpleState(persistenceFactoryRestore, GetType(), "Default"); } - Task IDeleter.DeleteAppAsync(IAppEntity app, + Task IDeleter.DeleteAppAsync(App app, CancellationToken ct) { return messaging.PublishAsync(new BackupClear(app.Id), ct: ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupJob.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupJob.cs index 13698b5e4..848110f6b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupJob.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupJob.cs @@ -10,8 +10,10 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Backup; -public interface IBackupJob : IWithId +public interface IBackupJob { + DomainId Id { get; } + Instant Started { get; } Instant? Stopped { get; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Billing/IBillingManager.cs b/backend/src/Squidex.Domain.Apps.Entities/Billing/IBillingManager.cs index 1a0b43780..ae03b19ce 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Billing/IBillingManager.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Billing/IBillingManager.cs @@ -5,40 +5,40 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Teams; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; namespace Squidex.Domain.Apps.Entities.Billing; public interface IBillingManager { - Task GetPortalLinkAsync(string userId, IAppEntity app, + Task GetPortalLinkAsync(string userId, App app, CancellationToken ct = default); - Task GetPortalLinkAsync(string userId, ITeamEntity team, + Task GetPortalLinkAsync(string userId, Team team, CancellationToken ct = default); - Task GetReferralInfoAsync(string userId, IAppEntity app, + Task GetReferralInfoAsync(string userId, App app, CancellationToken ct = default); - Task GetReferralInfoAsync(string userId, ITeamEntity team, + Task GetReferralInfoAsync(string userId, Team team, CancellationToken ct = default); - Task MustRedirectToPortalAsync(string userId, IAppEntity app, string? planId, + Task MustRedirectToPortalAsync(string userId, App app, string? planId, CancellationToken ct = default); - Task MustRedirectToPortalAsync(string userId, ITeamEntity team, string? planId, + Task MustRedirectToPortalAsync(string userId, Team team, string? planId, CancellationToken ct = default); - Task SubscribeAsync(string userId, IAppEntity app, string planId, + Task SubscribeAsync(string userId, App app, string planId, CancellationToken ct = default); - Task SubscribeAsync(string userId, ITeamEntity team, string planId, + Task SubscribeAsync(string userId, Team team, string planId, CancellationToken ct = default); - Task UnsubscribeAsync(string userId, IAppEntity app, + Task UnsubscribeAsync(string userId, App app, CancellationToken ct = default); - Task UnsubscribeAsync(string userId, ITeamEntity team, + Task UnsubscribeAsync(string userId, Team team, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Billing/IUsageGate.cs b/backend/src/Squidex.Domain.Apps.Entities/Billing/IUsageGate.cs index c9528dd24..06bbf5e7f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Billing/IUsageGate.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Billing/IUsageGate.cs @@ -5,26 +5,26 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Teams; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Billing; public interface IUsageGate { - Task IsBlockedAsync(IAppEntity app, string? clientId, DateOnly date, + Task IsBlockedAsync(App app, string? clientId, DateOnly date, CancellationToken ct = default); - Task TrackRequestAsync(IAppEntity app, string? clientId, DateOnly date, double costs, long elapsedMs, long bytes, + Task TrackRequestAsync(App app, string? clientId, DateOnly date, double costs, long elapsedMs, long bytes, CancellationToken ct = default); - Task<(Plan Plan, string PlanId, DomainId? TeamId)> GetPlanForAppAsync(IAppEntity app, bool canCache, + Task<(Plan Plan, string PlanId, DomainId? TeamId)> GetPlanForAppAsync(App app, bool canCache, CancellationToken ct = default); Task<(Plan Plan, string PlanId, DomainId? TeamId)> GetPlanForAppAsync(DomainId appId, bool canCache, CancellationToken ct = default); - Task<(Plan Plan, string PlanId)> GetPlanForTeamAsync(ITeamEntity team, + Task<(Plan Plan, string PlanId)> GetPlanForTeamAsync(Team team, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Billing/NoopBillingManager.cs b/backend/src/Squidex.Domain.Apps.Entities/Billing/NoopBillingManager.cs index e13553f22..dc2a5b746 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Billing/NoopBillingManager.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Billing/NoopBillingManager.cs @@ -5,68 +5,68 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Teams; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; namespace Squidex.Domain.Apps.Entities.Billing; public sealed class NoopBillingManager : IBillingManager { - public Task GetPortalLinkAsync(string userId, IAppEntity app, + public Task GetPortalLinkAsync(string userId, App app, CancellationToken ct = default) { return Task.FromResult(null); } - public Task GetPortalLinkAsync(string userId, ITeamEntity team, + public Task GetPortalLinkAsync(string userId, Team team, CancellationToken ct = default) { return Task.FromResult(null); } - public Task GetReferralInfoAsync(string userId, IAppEntity app, + public Task GetReferralInfoAsync(string userId, App app, CancellationToken ct = default) { return Task.FromResult(null); } - public Task GetReferralInfoAsync(string userId, ITeamEntity team, + public Task GetReferralInfoAsync(string userId, Team team, CancellationToken ct = default) { return Task.FromResult(null); } - public Task MustRedirectToPortalAsync(string userId, IAppEntity app, string? planId, + public Task MustRedirectToPortalAsync(string userId, App app, string? planId, CancellationToken ct = default) { return Task.FromResult(null); } - public Task MustRedirectToPortalAsync(string userId, ITeamEntity team, string? planId, + public Task MustRedirectToPortalAsync(string userId, Team team, string? planId, CancellationToken ct = default) { return Task.FromResult(null); } - public Task SubscribeAsync(string userId, IAppEntity app, string planId, + public Task SubscribeAsync(string userId, App app, string planId, CancellationToken ct = default) { return Task.CompletedTask; } - public Task SubscribeAsync(string userId, ITeamEntity team, string planId, + public Task SubscribeAsync(string userId, Team team, string planId, CancellationToken ct = default) { return Task.CompletedTask; } - public Task UnsubscribeAsync(string userId, IAppEntity app, + public Task UnsubscribeAsync(string userId, App app, CancellationToken ct = default) { return Task.CompletedTask; } - public Task UnsubscribeAsync(string userId, ITeamEntity team, + public Task UnsubscribeAsync(string userId, Team team, CancellationToken ct = default) { return Task.CompletedTask; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Billing/UsageGate.cs b/backend/src/Squidex.Domain.Apps.Entities/Billing/UsageGate.cs index 7fac6c658..f61bef387 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Billing/UsageGate.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Billing/UsageGate.cs @@ -8,8 +8,7 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Teams; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Infrastructure; using Squidex.Infrastructure.UsageTracking; using Squidex.Messaging; @@ -40,7 +39,7 @@ public sealed partial class UsageGate : IUsageGate this.usageTracker = usageTracker; } - public async Task TrackRequestAsync(IAppEntity app, string? clientId, DateOnly date, double costs, long elapsedMs, long bytes, + public async Task TrackRequestAsync(App app, string? clientId, DateOnly date, double costs, long elapsedMs, long bytes, CancellationToken ct = default) { var appId = app.Id.ToString(); @@ -53,7 +52,7 @@ public sealed partial class UsageGate : IUsageGate await apiUsageTracker.TrackAsync(date, appId, clientId, costs, elapsedMs, bytes, ct); } - public async Task IsBlockedAsync(IAppEntity app, string? clientId, DateOnly date, + public async Task IsBlockedAsync(App app, string? clientId, DateOnly date, CancellationToken ct = default) { Guard.NotNull(app); @@ -111,7 +110,7 @@ public sealed partial class UsageGate : IUsageGate return memoryCache.Set(appId, true, TimeSpan.FromHours(1)); } - private static string[] GetUsers(IAppEntity app) + private static string[] GetUsers(App app) { return app.Contributors.Where(x => x.Value == Role.Owner).Select(x => x.Key).ToArray(); } @@ -130,7 +129,7 @@ public sealed partial class UsageGate : IUsageGate return forecasted > limit; } - public Task<(Plan Plan, string PlanId, DomainId? TeamId)> GetPlanForAppAsync(IAppEntity app, bool canCache, + public Task<(Plan Plan, string PlanId, DomainId? TeamId)> GetPlanForAppAsync(App app, bool canCache, CancellationToken ct = default) { Guard.NotNull(app); @@ -179,7 +178,7 @@ public sealed partial class UsageGate : IUsageGate return await GetPlanCoreAsync(app, ct); } - private async Task<(Plan Plan, string PlanId, DomainId? TeamId)> GetPlanCoreAsync(IAppEntity app, + private async Task<(Plan Plan, string PlanId, DomainId? TeamId)> GetPlanCoreAsync(App app, CancellationToken ct) { if (app.TeamId != null) @@ -198,7 +197,7 @@ public sealed partial class UsageGate : IUsageGate } } - public Task<(Plan Plan, string PlanId)> GetPlanForTeamAsync(ITeamEntity team, + public Task<(Plan Plan, string PlanId)> GetPlanForTeamAsync(Team team, CancellationToken ct = default) { var (plan, planId) = billingPlans.GetActualPlan(team?.Plan?.PlanId); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Collaboration/EmailUserNotifications.cs b/backend/src/Squidex.Domain.Apps.Entities/Collaboration/EmailUserNotifications.cs index 02e1a7011..4b1a18fad 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Collaboration/EmailUserNotifications.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Collaboration/EmailUserNotifications.cs @@ -9,8 +9,8 @@ using System.Globalization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Teams; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Infrastructure; using Squidex.Infrastructure.Email; using Squidex.Shared.Identity; @@ -57,7 +57,7 @@ public sealed class EmailUserNotifications : IUserNotifications this.log = log; } - public Task SendUsageAsync(IUser user, IAppEntity app, long usage, long usageLimit, + public Task SendUsageAsync(IUser user, App app, long usage, long usageLimit, CancellationToken ct = default) { Guard.NotNull(user); @@ -76,7 +76,7 @@ public sealed class EmailUserNotifications : IUserNotifications user, vars, ct); } - public Task SendInviteAsync(IUser assigner, IUser user, IAppEntity app, + public Task SendInviteAsync(IUser assigner, IUser user, App app, CancellationToken ct = default) { Guard.NotNull(assigner); @@ -101,7 +101,7 @@ public sealed class EmailUserNotifications : IUserNotifications } } - public Task SendInviteAsync(IUser assigner, IUser user, ITeamEntity team, + public Task SendInviteAsync(IUser assigner, IUser user, Team team, CancellationToken ct = default) { Guard.NotNull(assigner); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Collaboration/IUserNotifications.cs b/backend/src/Squidex.Domain.Apps.Entities/Collaboration/IUserNotifications.cs index a7fbf5b35..7e78ec089 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Collaboration/IUserNotifications.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Collaboration/IUserNotifications.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Teams; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.Collaboration; @@ -15,12 +15,12 @@ public interface IUserNotifications { bool IsActive { get; } - Task SendUsageAsync(IUser user, IAppEntity app, long usage, long usageLimit, + Task SendUsageAsync(IUser user, App app, long usage, long usageLimit, CancellationToken ct = default); - Task SendInviteAsync(IUser assigner, IUser user, IAppEntity app, + Task SendInviteAsync(IUser assigner, IUser user, App app, CancellationToken ct = default); - Task SendInviteAsync(IUser assigner, IUser user, ITeamEntity team, + Task SendInviteAsync(IUser assigner, IUser user, Team team, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Collaboration/NoopUserNotifications.cs b/backend/src/Squidex.Domain.Apps.Entities/Collaboration/NoopUserNotifications.cs index ca80e1dc9..42cb3a2bd 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Collaboration/NoopUserNotifications.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Collaboration/NoopUserNotifications.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Teams; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.Collaboration; @@ -18,19 +18,19 @@ public sealed class NoopUserNotifications : IUserNotifications get => false; } - public Task SendInviteAsync(IUser assigner, IUser user, IAppEntity app, + public Task SendInviteAsync(IUser assigner, IUser user, App app, CancellationToken ct = default) { return Task.CompletedTask; } - public Task SendInviteAsync(IUser assigner, IUser user, ITeamEntity team, + public Task SendInviteAsync(IUser assigner, IUser user, Team team, CancellationToken ct = default) { return Task.CompletedTask; } - public Task SendUsageAsync(IUser user, IAppEntity app, long usage, long limit, + public Task SendUsageAsync(IUser user, App app, long usage, long limit, CancellationToken ct = default) { return Task.CompletedTask; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/BackupContents.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/BackupContents.cs index 484e2df66..fe914c03e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/BackupContents.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/BackupContents.cs @@ -194,7 +194,7 @@ public sealed class BackupContents : IBackupHandler var ids = contentIdsBySchemaId.Values.SelectMany(x => x); if (ids.Any()) { - await rebuilder.InsertManyAsync(ids, BatchSize, ct); + await rebuilder.InsertManyAsync(ids, BatchSize, ct); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldBase.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs similarity index 58% rename from backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldBase.cs rename to backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs index aa0af9e18..3e807a907 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldBase.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs @@ -7,21 +7,16 @@ using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Core.Schemas; +namespace Squidex.Domain.Apps.Entities.Contents.Commands; -public abstract class FieldBase +public abstract class ContentCommand : ContentCommandBase { - public long Id { get; } + public DomainId ContentId { get; set; } - public string Name { get; } + public bool DoNotScript { get; set; } - protected FieldBase(long id, string name) + public override DomainId AggregateId { - Guard.NotNullOrEmpty(name); - Guard.GreaterThan(id, 0); - - Id = id; - - Name = name; + get => DomainId.Combine(AppId, ContentId); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/_ContentCommand.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommandBase.cs similarity index 70% rename from backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/_ContentCommand.cs rename to backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommandBase.cs index dd5bb0289..b183d610a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/_ContentCommand.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommandBase.cs @@ -8,22 +8,8 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -#pragma warning disable MA0048 // File name must match type name - namespace Squidex.Domain.Apps.Entities.Contents.Commands; -public abstract class ContentCommand : ContentCommandBase -{ - public DomainId ContentId { get; set; } - - public bool DoNotScript { get; set; } - - public override DomainId AggregateId - { - get => DomainId.Combine(AppId, ContentId); - } -} - public abstract class ContentCommandBase : SquidexCommand, IAppCommand, ISchemaCommand, IAggregateCommand { public NamedId AppId { get; set; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCache.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCache.cs index daa9c874d..017c7d4c7 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCache.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCache.cs @@ -12,7 +12,7 @@ using Squidex.Infrastructure.Caching; namespace Squidex.Domain.Apps.Entities.Contents; -public sealed class ContentCache : QueryCache, IContentCache +public sealed class ContentCache : QueryCache, IContentCache { public ContentCache(IMemoryCache? memoryCache, IOptions options) : base(options.Value.CanCache ? memoryCache : null) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentExtensions.cs deleted file mode 100644 index 6e58836ae..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core.Contents; - -namespace Squidex.Domain.Apps.Entities.Contents; - -public static class ContentExtensions -{ - public static Status EditingStatus(this IContentEntity content) - { - return content.NewStatus ?? content.Status; - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs index 8330ca44f..da5e2c7a9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentHeaders.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; @@ -44,11 +43,6 @@ public static class ContentHeaders return context.Unpublished() || context.IsFrontendClient ? SearchScope.All : SearchScope.Published; } - public static bool IsPublished(this IContentEntity content) - { - return content.EditingStatus() == Status.Published; - } - public static bool NoCleanup(this Context context) { return context.AsBoolean(KeyNoCleanup); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerProcess.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerProcess.cs index 1844602d0..e5228d40f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerProcess.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerProcess.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging; using NodaTime; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Hosting; @@ -67,7 +68,7 @@ public sealed class ContentSchedulerProcess : IBackgroundProcess } } - private async Task TryPublishAsync(IContentEntity content) + private async Task TryPublishAsync(Content content) { var id = content.Id; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs index ab0370353..9e3132c0d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs @@ -7,10 +7,10 @@ using System.Text; using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.Search; using Squidex.Infrastructure; @@ -57,7 +57,7 @@ public sealed class ContentsSearchSource : ISearchSource var ids = await contentTextIndexer.SearchAsync(context.App, textQuery, context.Scope(), ct); - if (ids == null || ids.Count == 0) + if (ids is not { Count: > 0 }) { return result; } @@ -84,7 +84,7 @@ public sealed class ContentsSearchSource : ISearchSource { var schemas = await appProvider.GetSchemasAsync(context.App.Id, ct); - return schemas.Where(x => HasPermission(context, x.SchemaDef.Name)).Select(x => x.Id).ToList(); + return schemas.Where(x => HasPermission(context, x.Name)).Select(x => x.Id).ToList(); } private static bool HasPermission(Context context, string schemaName) @@ -92,7 +92,7 @@ public sealed class ContentsSearchSource : ISearchSource return context.UserPermissions.Allows(PermissionIds.AppContentsReadOwn, context.App.Name, schemaName); } - private static string FormatName(IEnrichedContentEntity content, string masterLanguage) + private static string FormatName(EnrichedContent content, string masterLanguage) { var sb = new StringBuilder(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterService.cs index dd4588b9f..b4b0e467c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Counter/CounterService.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.States; @@ -40,7 +40,7 @@ public sealed class CounterService : ICounterService, IDeleter this.persistenceFactory = persistenceFactory; } - async Task IDeleter.DeleteAppAsync(IAppEntity app, + async Task IDeleter.DeleteAppAsync(App app, CancellationToken ct) { var state = await GetStateAsync(app.Id, ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultContentWorkflow.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultContentWorkflow.cs deleted file mode 100644 index 12572b23c..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultContentWorkflow.cs +++ /dev/null @@ -1,102 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Security.Claims; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Entities.Schemas; - -namespace Squidex.Domain.Apps.Entities.Contents; - -public sealed class DefaultContentWorkflow : IContentWorkflow -{ - private static readonly StatusInfo InfoArchived = new StatusInfo(Status.Archived, StatusColors.Archived); - private static readonly StatusInfo InfoDraft = new StatusInfo(Status.Draft, StatusColors.Draft); - private static readonly StatusInfo InfoPublished = new StatusInfo(Status.Published, StatusColors.Published); - - private static readonly StatusInfo[] All = - [ - InfoArchived, - InfoDraft, - InfoPublished - ]; - - private static readonly Dictionary Flow = - new Dictionary - { - [Status.Archived] = (InfoArchived, new[] - { - InfoDraft - }), - [Status.Draft] = (InfoDraft, new[] - { - InfoArchived, - InfoPublished - }), - [Status.Published] = (InfoPublished, new[] - { - InfoDraft, - InfoArchived - }) - }; - - public ValueTask GetInitialStatusAsync(ISchemaEntity schema) - { - return ValueTask.FromResult(Status.Draft); - } - - public ValueTask CanPublishInitialAsync(ISchemaEntity schema, ClaimsPrincipal? user) - { - return ValueTask.FromResult(true); - } - - public ValueTask ShouldValidateAsync(ISchemaEntity schema, Status status) - { - var result = status == Status.Published && schema.SchemaDef.Properties.ValidateOnPublish; - - return ValueTask.FromResult(result); - } - - public ValueTask CanMoveToAsync(ISchemaEntity schema, Status status, Status next, ContentData data, ClaimsPrincipal? user) - { - var result = Flow.TryGetValue(status, out var step) && step.Transitions.Any(x => x.Status == next); - - return ValueTask.FromResult(result); - } - - public ValueTask CanMoveToAsync(IContentEntity content, Status status, Status next, ClaimsPrincipal? user) - { - var result = Flow.TryGetValue(status, out var step) && step.Transitions.Any(x => x.Status == next); - - return ValueTask.FromResult(result); - } - - public ValueTask CanUpdateAsync(IContentEntity content, Status status, ClaimsPrincipal? user) - { - var result = status != Status.Archived; - - return ValueTask.FromResult(result); - } - - public ValueTask GetInfoAsync(IContentEntity content, Status status) - { - var result = Flow.GetValueOrDefault(status).Info; - - return ValueTask.FromResult(result); - } - - public ValueTask GetNextAsync(IContentEntity content, Status status, ClaimsPrincipal? user) - { - var result = Flow.TryGetValue(status, out var step) ? step.Transitions : []; - - return ValueTask.FromResult(result); - } - - public ValueTask GetAllAsync(ISchemaEntity schema) - { - return ValueTask.FromResult(All); - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs index 9a0591a53..bd07fb72b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DefaultWorkflowsValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -41,7 +41,7 @@ public sealed class DefaultWorkflowsValidator : IWorkflowsValidator if (schema != null) { - errors.Add(T.Get("workflows.schemaOverlap", new { schema = schema.SchemaDef.Name })); + errors.Add(T.Get("workflows.schemaOverlap", new { schema = schema.Name })); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentCommandMiddleware.cs index 9ab52530a..06604008f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentCommandMiddleware.cs @@ -5,13 +5,14 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Queries; using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Contents.DomainObject; -public sealed class ContentCommandMiddleware : CachingDomainObjectMiddleware +public sealed class ContentCommandMiddleware : CachingDomainObjectMiddleware { private readonly IContentEnricher contentEnricher; private readonly IContextProvider contextProvider; @@ -32,7 +33,12 @@ public sealed class ContentCommandMiddleware : CachingDomainObjectMiddleware, IContentEntity + protected override WriteContent Apply(WriteContent snapshot, Envelope @event) { - public NamedId AppId { get; set; } + var newSnapshot = snapshot; - public NamedId SchemaId { get; set; } - - public ContentVersion? NewVersion { get; set; } - - public ContentVersion CurrentVersion { get; set; } - - public ScheduleJob? ScheduleJob { get; set; } - - public bool IsDeleted { get; set; } - - [JsonIgnore] - public DomainId UniqueId + ContentData Data() { - get => DomainId.Combine(AppId, Id); + return snapshot.NewVersion.Data ?? snapshot.CurrentVersion.Data; } - [JsonIgnore] - public ContentData Data + ContentData CurrentData() { - get => NewVersion?.Data ?? CurrentData; + return snapshot.CurrentVersion.Data; } - [JsonIgnore] - public ContentData CurrentData + switch (@event.Payload) { - get => CurrentVersion.Data; + case ContentCreated e: + newSnapshot = new WriteContent + { + Id = e.ContentId, + AppId = e.AppId, + ScheduleJob = null, + SchemaId = e.SchemaId, + CurrentVersion = new ContentVersion(e.Status, e.Data) + }; + + break; + + case ContentDraftCreated e: + newSnapshot = snapshot with + { + NewVersion = new ContentVersion(e.Status, e.MigratedData?.UseSameFields(CurrentData()) ?? CurrentData()), + // Implictely cancels any pending update jobs. + ScheduleJob = null, + }; + + break; + + case ContentDraftDeleted: + newSnapshot = snapshot with + { + NewVersion = null, + // Implictely cancels any pending update jobs. + ScheduleJob = null, + }; + + break; + + case ContentStatusChanged e when snapshot.NewVersion != null && e.Status == Status.Published: + newSnapshot = snapshot with + { + CurrentVersion = new ContentVersion(e.Status, snapshot.NewVersion.Data.UseSameFields(CurrentData())), + // Discards the draft version. + NewVersion = null, + // Implictely cancels any pending update jobs. + ScheduleJob = null, + }; + + break; + + case ContentStatusChanged e when snapshot.NewVersion != null: + newSnapshot = snapshot with + { + NewVersion = snapshot.NewVersion with { Status = e.Status }, + // Implictely cancels any pending update jobs. + ScheduleJob = null, + }; + + break; + + case ContentStatusChanged e: + newSnapshot = snapshot with + { + CurrentVersion = snapshot.CurrentVersion with { Status = e.Status }, + // Implictely cancels any pending update jobs. + ScheduleJob = null, + }; + + break; + + case ContentUpdated e when snapshot.NewVersion != null: + newSnapshot = snapshot with + { + NewVersion = snapshot.NewVersion with { Data = e.Data.UseSameFields(Data()) } + }; + break; + + case ContentUpdated e: + newSnapshot = snapshot with + { + CurrentVersion = snapshot.CurrentVersion with { Data = e.Data.UseSameFields(CurrentData()) } + }; + break; + + case ContentSchedulingCancelled: + newSnapshot = snapshot with { ScheduleJob = null }; + break; + + case ContentStatusScheduled e: + newSnapshot = snapshot with { ScheduleJob = ScheduleJob.Build(e.Status, e.Actor, e.DueTime) }; + break; + + case ContentDeleted: + newSnapshot = snapshot with { IsDeleted = true }; + break; } - [JsonIgnore] - public Status? NewStatus + if (ReferenceEquals(newSnapshot, snapshot)) { - get => NewVersion?.Status; + return snapshot; } - [JsonIgnore] - public Status Status - { - get => CurrentVersion?.Status ?? default; - } - - public override bool ApplyEvent(IEvent @event, EnvelopeHeaders headers) - { - switch (@event) - { - case ContentCreated e: - { - Id = e.ContentId; - - SimpleMapper.Map(e, this); - - CurrentVersion = new ContentVersion(e.Status, e.Data); - - break; - } - - case ContentDraftCreated e: - { - var newData = e.MigratedData?.UseSameFields(CurrentData) ?? CurrentData; - - NewVersion = new ContentVersion(e.Status, newData); - - // Implictely cancels any pending update jobs. - ScheduleJob = null; - break; - } - - case ContentDraftDeleted: - { - NewVersion = null; - - // Implictely cancels any pending update jobs. - ScheduleJob = null; - break; - } - - case ContentStatusChanged e: - { - ScheduleJob = null; - - if (NewVersion != null) - { - if (e.Status == Status.Published) - { - CurrentVersion = new ContentVersion(e.Status, NewVersion.Data.UseSameFields(CurrentData)); - - NewVersion = null; - } - else - { - NewVersion = NewVersion.WithStatus(e.Status); - } - } - else - { - CurrentVersion = CurrentVersion.WithStatus(e.Status); - } - - break; - } - - case ContentSchedulingCancelled: - { - ScheduleJob = null; - break; - } - - case ContentStatusScheduled e: - { - ScheduleJob = ScheduleJob.Build(e.Status, e.Actor, e.DueTime); - break; - } - - case ContentUpdated e: - { - if (NewVersion != null) - { - NewVersion = NewVersion.WithData(e.Data.UseSameFields(Data)); - } - else - { - CurrentVersion = CurrentVersion.WithData(e.Data.UseSameFields(CurrentData)); - } - - break; - } - - case ContentDeleted: - { - IsDeleted = true; - break; - } - } - - return true; - } + return newSnapshot.Apply(@event.To()); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs index 5f3778965..0fb171308 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs @@ -24,18 +24,18 @@ using Squidex.Shared; namespace Squidex.Domain.Apps.Entities.Contents.DomainObject; -public partial class ContentDomainObject : DomainObject +public partial class ContentDomainObject : DomainObject { private readonly IServiceProvider serviceProvider; - public ContentDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log, + public ContentDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log, IServiceProvider serviceProvider) : base(id, persistence, log) { this.serviceProvider = serviceProvider; } - protected override bool IsDeleted(State snapshot) + protected override bool IsDeleted(WriteContent snapshot) { return snapshot.IsDeleted; } @@ -88,7 +88,7 @@ public partial class ContentDomainObject : DomainObject x.Payload) - .OfType().FirstOrDefault(); - - if (previousEvent != null) - { - previousEvent.Data = newData; - } - else if (!newData.Equals(Snapshot.Data)) - { - Update(c, newData); - } + Update(c, newData); } } - if (c.CheckReferrers && Snapshot.IsPublished()) + if (c.CheckReferrers && Snapshot.IsPublished) { await operation.CheckReferrersAsync(ct); } if (!c.DoNotValidate && await operation.ShouldValidateAsync(c.Status)) { - await operation.ValidateContentAndInputAsync(Snapshot.Data, c.OptimizeValidation, true, ct); + await operation.ValidateContentAndInputAsync(Snapshot.EditingData, c.OptimizeValidation, true, ct); } ChangeStatus(c); @@ -320,7 +309,7 @@ public partial class ContentDomainObject : DomainObject +public sealed class ContentOperation : OperationContextBase { - public ISchemaEntity Schema { get; init; } + public Schema Schema { get; init; } public ResolvedComponents Components { get; init; } - public Schema SchemaDef - { - get => Schema.SchemaDef; - } - - public ContentOperation(IServiceProvider serviceProvider, Func snapshot) + public ContentOperation(IServiceProvider serviceProvider, Func snapshot) : base(serviceProvider, snapshot) { } - public static async Task CreateAsync(IServiceProvider services, ContentCommand command, Func snapshot) + public static async Task CreateAsync(IServiceProvider services, ContentCommand command, Func snapshot) { var appProvider = services.GetRequiredService(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentVersion.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentVersion.cs deleted file mode 100644 index 4e28b6d53..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentVersion.cs +++ /dev/null @@ -1,37 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Contents.DomainObject; - -public sealed class ContentVersion -{ - public Status Status { get; } - - public ContentData Data { get; } - - public ContentVersion(Status status, ContentData data) - { - Guard.NotNull(data); - - Status = status; - - Data = data; - } - - public ContentVersion WithStatus(Status status) - { - return new ContentVersion(status, Data); - } - - public ContentVersion WithData(ContentData data) - { - return new ContentVersion(Status, data); - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentsBulkUpdateCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentsBulkUpdateCommandMiddleware.cs index 9c54fe745..bb9a5036f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentsBulkUpdateCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentsBulkUpdateCommandMiddleware.cs @@ -5,8 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.Commands; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Reflection; @@ -62,7 +63,7 @@ public sealed class ContentsBulkUpdateCommandMiddleware : ICommandMiddleware return; } - if (bulkUpdates.Jobs == null || bulkUpdates.Jobs.Length == 0) + if (bulkUpdates.Jobs is not { Length: > 0 }) { context.Complete(new BulkUpdateResult()); return; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ScriptingExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ScriptingExtensions.cs index 5300e67c8..3fe91d53b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ScriptingExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ScriptingExtensions.cs @@ -22,7 +22,7 @@ public static class ScriptingExtensions public static Task ExecuteCreateScriptAsync(this ContentOperation operation, ContentData data, Status status, CancellationToken ct) { - var script = operation.SchemaDef.Scripts.Create; + var script = operation.Schema.Scripts.Create; if (string.IsNullOrWhiteSpace(script)) { @@ -47,7 +47,7 @@ public static class ScriptingExtensions public static Task ExecuteUpdateScriptAsync(this ContentOperation operation, ContentData data, CancellationToken ct) { - var script = operation.SchemaDef.Scripts.Update; + var script = operation.Schema.Scripts.Update; if (string.IsNullOrWhiteSpace(script)) { @@ -58,11 +58,11 @@ public static class ScriptingExtensions var vars = Enrich(operation, new ContentScriptVars { Data = data, - DataOld = operation.Snapshot.Data, - OldData = operation.Snapshot.Data, - OldStatus = operation.Snapshot.Status, + DataOld = operation.Snapshot.EditingData, + OldData = operation.Snapshot.EditingData, + OldStatus = operation.Snapshot.EditingStatus, Operation = "Update", - Status = operation.Snapshot.EditingStatus(), + Status = operation.Snapshot.EditingStatus, StatusOld = default }); @@ -72,23 +72,23 @@ public static class ScriptingExtensions public static Task ExecuteChangeScriptAsync(this ContentOperation operation, Status status, StatusChange change, CancellationToken ct) { - var script = operation.SchemaDef.Scripts.Change; + var script = operation.Schema.Scripts.Change; if (string.IsNullOrWhiteSpace(script)) { - return Task.FromResult(operation.Snapshot.Data); + return Task.FromResult(operation.Snapshot.EditingData); } // Script vars are just wrappers over dictionaries for better performance. var vars = Enrich(operation, new ContentScriptVars { - Data = operation.Snapshot.Data.Clone(), + Data = operation.Snapshot.EditingData.Clone(), DataOld = null, OldData = null, - OldStatus = operation.Snapshot.EditingStatus(), + OldStatus = operation.Snapshot.EditingStatus, Operation = change.ToString(), Status = status, - StatusOld = operation.Snapshot.EditingStatus(), + StatusOld = operation.Snapshot.EditingStatus, Validate = Validate(operation, status) }); @@ -98,7 +98,7 @@ public static class ScriptingExtensions public static Task ExecuteDeleteScriptAsync(this ContentOperation operation, bool permanent, CancellationToken ct) { - var script = operation.SchemaDef.Scripts.Delete; + var script = operation.Schema.Scripts.Delete; if (string.IsNullOrWhiteSpace(script)) { @@ -108,13 +108,13 @@ public static class ScriptingExtensions // Script vars are just wrappers over dictionaries for better performance. var vars = Enrich(operation, new ContentScriptVars { - Data = operation.Snapshot.Data, + Data = operation.Snapshot.EditingData, DataOld = null, OldData = null, - OldStatus = operation.Snapshot.EditingStatus(), + OldStatus = operation.Snapshot.EditingStatus, Operation = "Delete", Permanent = permanent, - Status = operation.Snapshot.EditingStatus(), + Status = operation.Snapshot.EditingStatus, StatusOld = default }); @@ -141,7 +141,7 @@ public static class ScriptingExtensions { var snapshot = operation.Snapshot; - operation.ValidateContentAndInputAsync(snapshot.Data, false, snapshot.IsPublished() || status == Status.Published, default).Wait(); + operation.ValidateContentAndInputAsync(snapshot.EditingData, false, snapshot.IsPublished || status == Status.Published, default).Wait(); } catch (AggregateException ex) when (ex.InnerException != null) { @@ -156,7 +156,7 @@ public static class ScriptingExtensions vars.AppName = operation.App.Name; vars.ContentId = operation.CommandId; vars.SchemaId = operation.Schema.Id; - vars.SchemaName = operation.Schema.SchemaDef.Name; + vars.SchemaName = operation.Schema.Name; vars.User = operation.User; return vars; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SecurityExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SecurityExtensions.cs index 35d38d108..9c0a004c6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SecurityExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SecurityExtensions.cs @@ -30,7 +30,7 @@ public static class SecurityExtensions throw new DomainForbiddenException(T.Get("common.errorNoPermission")); } - if (!permissions.Allows(permissionId, context.App.Name, context.Schema.SchemaDef.Name)) + if (!permissions.Allows(permissionId, context.App.Name, context.Schema.Name)) { throw new DomainForbiddenException(T.Get("common.errorNoPermission")); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SingletonExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SingletonExtensions.cs index e5ca5cd09..37f72ba17 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SingletonExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/SingletonExtensions.cs @@ -16,7 +16,7 @@ public static class SingletonExtensions { public static void MustNotCreateForUnpublishedSchema(this ContentOperation operation) { - if (!operation.SchemaDef.IsPublished && operation.SchemaDef.Type != SchemaType.Singleton) + if (!operation.Schema.IsPublished && operation.Schema.Type != SchemaType.Singleton) { throw new DomainException(T.Get("contents.schemaNotPublished")); } @@ -24,7 +24,7 @@ public static class SingletonExtensions public static void MustNotCreateComponent(this ContentOperation operation) { - if (operation.SchemaDef.Type == SchemaType.Component) + if (operation.Schema.Type == SchemaType.Component) { throw new DomainException(T.Get("contents.componentNotCreatable")); } @@ -32,7 +32,7 @@ public static class SingletonExtensions public static void MustNotCreateSingleton(this ContentOperation operation) { - if (operation.SchemaDef.Type == SchemaType.Singleton && operation.CommandId != operation.Schema.Id) + if (operation.Schema.Type == SchemaType.Singleton && operation.CommandId != operation.Schema.Id) { throw new DomainException(T.Get("contents.singletonNotCreatable")); } @@ -40,7 +40,7 @@ public static class SingletonExtensions public static void MustNotChangeSingleton(this ContentOperation operation, Status status) { - if (operation.SchemaDef.Type == SchemaType.Singleton && (operation.Snapshot.NewStatus == null || status != Status.Published)) + if (operation.Schema.Type == SchemaType.Singleton && (operation.Snapshot.NewVersion == null || status != Status.Published)) { throw new DomainException(T.Get("contents.singletonNotChangeable")); } @@ -48,7 +48,7 @@ public static class SingletonExtensions public static void MustNotDeleteSingleton(this ContentOperation operation) { - if (operation.SchemaDef.Type == SchemaType.Singleton) + if (operation.Schema.Type == SchemaType.Singleton) { throw new DomainException(T.Get("contents.singletonNotDeletable")); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ValidationExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ValidationExtensions.cs index a0a88bc8a..711bec438 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ValidationExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/ValidationExtensions.cs @@ -6,12 +6,11 @@ // ========================================================================== using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ValidateContent; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents.Repositories; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Translations; @@ -23,7 +22,7 @@ public static class ValidationExtensions { public static void MustDeleteDraft(this ContentOperation operation) { - if (operation.Snapshot.NewStatus == null) + if (operation.Snapshot.NewVersion == null) { throw new DomainException(T.Get("contents.draftToDeleteNotFound")); } @@ -31,7 +30,7 @@ public static class ValidationExtensions public static void MustCreateDraft(this ContentOperation operation) { - if (operation.Snapshot.EditingStatus() != Status.Published) + if (operation.Snapshot.EditingStatus != Status.Published) { throw new DomainException(T.Get("contents.draftNotCreateForUnpublished")); } @@ -92,7 +91,7 @@ public static class ValidationExtensions var converter = new ContentConverter( operation.Components, - operation.Schema.SchemaDef); + operation.Schema); converter.Add(new AddDefaultValues(operation.Partition()) { IgnoreRequiredFields = true }); return converter.Convert(data); @@ -103,7 +102,7 @@ public static class ValidationExtensions { var contentRepository = operation.Resolve(); - var hasReferrer = await contentRepository.HasReferrersAsync(operation.App.Id, operation.CommandId, SearchScope.All, ct); + var hasReferrer = await contentRepository.HasReferrersAsync(operation.App, operation.CommandId, SearchScope.All, ct); if (hasReferrer) { @@ -114,12 +113,12 @@ public static class ValidationExtensions private static ContentValidator GetValidator(this ContentOperation operation, bool optimize, bool published) { var rootContext = - new RootContext(operation.Resolve(), - operation.App.NamedId(), - operation.Schema.NamedId(), - operation.SchemaDef, + new RootContext( + operation.App, + operation.Schema, operation.CommandId, - operation.Components); + operation.Components, + operation.Resolve()); var validationContext = new ValidationContext(rootContext).Optimized(optimize).AsPublishing(published); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/WorkflowExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/WorkflowExtensions.cs index ddb57fdfd..0041d29b8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/WorkflowExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/Guards/WorkflowExtensions.cs @@ -30,15 +30,13 @@ public static class WorkflowExtensions public static async Task CheckTransitionAsync(this ContentOperation operation, Status status) { - if (operation.SchemaDef.Type != SchemaType.Singleton) + if (operation.Schema.Type != SchemaType.Singleton) { var workflow = GetWorkflow(operation); - var oldStatus = operation.Snapshot.EditingStatus(); - - if (!await workflow.CanMoveToAsync(operation.Snapshot, oldStatus, status, operation.User)) + if (!await workflow.CanMoveToAsync(operation.Snapshot.ToContent(), operation.Snapshot.EditingStatus, status, operation.User)) { - var values = new { oldStatus, newStatus = status }; + var values = new { oldStatus = operation.Snapshot.EditingStatus, newStatus = status }; operation.AddError(T.Get("contents.statusTransitionNotAllowed", values), nameof(status)); operation.ThrowOnErrors(); @@ -48,11 +46,11 @@ public static class WorkflowExtensions public static async Task CheckStatusAsync(this ContentOperation operation, Status status) { - if (operation.SchemaDef.Type != SchemaType.Singleton) + if (operation.Schema.Type != SchemaType.Singleton) { var workflow = GetWorkflow(operation); - var statusInfo = await workflow.GetInfoAsync(operation.Snapshot, status); + var statusInfo = await workflow.GetInfoAsync(operation.Snapshot.ToContent(), status); if (statusInfo == null) { @@ -68,11 +66,9 @@ public static class WorkflowExtensions { var workflow = GetWorkflow(operation); - var status = operation.Snapshot.EditingStatus(); - - if (!await workflow.CanUpdateAsync(operation.Snapshot, status, operation.User)) + if (!await workflow.CanUpdateAsync(operation.Snapshot.ToContent(), operation.Snapshot.EditingStatus, operation.User)) { - throw new DomainException(T.Get("contents.workflowErrorUpdate", new { status })); + throw new DomainException(T.Get("contents.workflowErrorUpdate", new { status = operation.Snapshot.EditingStatus })); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs index 2c5de56aa..682181ee5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs @@ -7,8 +7,8 @@ using System.Security.Claims; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Scripting; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents; @@ -25,35 +25,28 @@ public sealed class DynamicContentWorkflow : IContentWorkflow this.appProvider = appProvider; } - public async ValueTask GetAllAsync(ISchemaEntity schema) + public async ValueTask GetAllAsync(Schema schema) { var workflow = await GetWorkflowAsync(schema.AppId.Id, schema.Id); return workflow.Steps.Select(x => new StatusInfo(x.Key, GetColor(x.Value))).ToArray(); } - public async ValueTask CanPublishInitialAsync(ISchemaEntity schema, ClaimsPrincipal? user) + public async ValueTask CanPublishInitialAsync(Schema schema, ClaimsPrincipal? user) { var workflow = await GetWorkflowAsync(schema.AppId.Id, schema.Id); return workflow.TryGetTransition(workflow.Initial, Status.Published, out var transition) && IsTrue(transition, null, user); } - public async ValueTask CanMoveToAsync(ISchemaEntity schema, Status status, Status next, ContentData data, ClaimsPrincipal? user) - { - var workflow = await GetWorkflowAsync(schema.AppId.Id, schema.Id); - - return workflow.TryGetTransition(status, next, out var transition) && IsTrue(transition, data, user); - } - - public async ValueTask CanMoveToAsync(IContentEntity content, Status status, Status next, ClaimsPrincipal? user) + public async ValueTask CanMoveToAsync(Content content, Status status, Status next, ClaimsPrincipal? user) { var workflow = await GetWorkflowAsync(content.AppId.Id, content.SchemaId.Id); return workflow.TryGetTransition(status, next, out var transition) && IsTrue(transition, content.Data, user); } - public async ValueTask CanUpdateAsync(IContentEntity content, Status status, ClaimsPrincipal? user) + public async ValueTask CanUpdateAsync(Content content, Status status, ClaimsPrincipal? user) { var workflow = await GetWorkflowAsync(content.AppId.Id, content.SchemaId.Id); @@ -65,7 +58,7 @@ public sealed class DynamicContentWorkflow : IContentWorkflow return true; } - public async ValueTask ShouldValidateAsync(ISchemaEntity schema, Status status) + public async ValueTask ShouldValidateAsync(Schema schema, Status status) { var workflow = await GetWorkflowAsync(schema.AppId.Id, schema.Id); @@ -74,10 +67,10 @@ public sealed class DynamicContentWorkflow : IContentWorkflow return true; } - return status == Status.Published && schema.SchemaDef.Properties.ValidateOnPublish; + return status == Status.Published && schema.Properties.ValidateOnPublish; } - public async ValueTask GetInfoAsync(IContentEntity content, Status status) + public async ValueTask GetInfoAsync(Content content, Status status) { var workflow = await GetWorkflowAsync(content.AppId.Id, content.SchemaId.Id); @@ -89,7 +82,7 @@ public sealed class DynamicContentWorkflow : IContentWorkflow return null; } - public async ValueTask GetInitialStatusAsync(ISchemaEntity schema) + public async ValueTask GetInitialStatusAsync(Schema schema) { var workflow = await GetWorkflowAsync(schema.AppId.Id, schema.Id); @@ -98,7 +91,7 @@ public sealed class DynamicContentWorkflow : IContentWorkflow return status; } - public async ValueTask GetNextAsync(IContentEntity content, Status status, ClaimsPrincipal? user) + public async ValueTask GetNextAsync(Content content, Status status, ClaimsPrincipal? user) { var result = new List(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/EnrichedContent.cs similarity index 56% rename from backend/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs rename to backend/src/Squidex.Domain.Apps.Entities/Contents/EnrichedContent.cs index 1a598c757..11c635342 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/EnrichedContent.cs @@ -5,63 +5,30 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents; -public sealed class ContentEntity : IEnrichedContentEntity +public record EnrichedContent : Content { - public DomainId Id { get; set; } - - public NamedId AppId { get; set; } - - public NamedId SchemaId { get; set; } - - public long Version { get; set; } - - public Instant Created { get; set; } - - public Instant LastModified { get; set; } - - public RefToken CreatedBy { get; set; } - - public RefToken LastModifiedBy { get; set; } - - public ContentData Data { get; set; } - - public ContentData? ReferenceData { get; set; } - - public ScheduleJob? ScheduleJob { get; set; } - - public Status? NewStatus { get; set; } - - public Status Status { get; set; } - - public StatusInfo[]? NextStatuses { get; set; } - public bool CanUpdate { get; set; } public bool IsSingleton { get; set; } - public bool IsDeleted { get; set; } - - public string SchemaDisplayName { get; set; } - public string StatusColor { get; set; } public string? NewStatusColor { get; set; } public string? ScheduledStatusColor { get; set; } + public string SchemaDisplayName { get; set; } + public string? EditToken { get; set; } public RootField[]? ReferenceFields { get; set; } - public DomainId UniqueId - { - get => DomainId.Combine(AppId, Id); - } + public StatusInfo[]? NextStatuses { get; set; } + + public ContentData? ReferenceData { get; set; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs index e66c0325d..3a7e77a66 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLResolver.cs @@ -12,7 +12,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using NodaTime; using Squidex.Caching; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; @@ -58,14 +58,14 @@ public sealed class CachingGraphQLResolver : IConfigureExecution return await next(options); } - public async Task GetSchemaAsync(IAppEntity app) + public async Task GetSchemaAsync(App app) { var entry = await GetModelEntryAsync(app); return entry.Model; } - private Task GetModelEntryAsync(IAppEntity app) + private Task GetModelEntryAsync(App app) { if (options.CacheDuration <= TimeSpan.Zero) { @@ -86,7 +86,7 @@ public sealed class CachingGraphQLResolver : IConfigureExecution }); } - private async Task CreateModelAsync(IAppEntity app) + private async Task CreateModelAsync(App app) { var schemasList = await serviceProvider.GetRequiredService().GetSchemasAsync(app.Id); var schemasKey = await schemasHash.ComputeHashAsync(app, schemasList); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs index 17f23e182..37ad9d65c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs @@ -19,8 +19,8 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext { private const int MaxBatchSize = 5000; private const int MinBatchSize = 1; - private static readonly EmptyDataLoaderResult EmptyAssets = new EmptyDataLoaderResult(); - private static readonly EmptyDataLoaderResult EmptyContents = new EmptyDataLoaderResult(); + private static readonly EmptyDataLoaderResult EmptyAssets = new EmptyDataLoaderResult(); + private static readonly EmptyDataLoaderResult EmptyContents = new EmptyDataLoaderResult(); private readonly IDataLoaderContextAccessor dataLoaders; private readonly GraphQLOptions options; private readonly int batchSize; @@ -75,7 +75,7 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext } } - public IDataLoaderResult GetContent(DomainId schemaId, DomainId id, long version) + public IDataLoaderResult GetContent(DomainId schemaId, DomainId id, long version) { var cacheKey = $"{nameof(GetContent)}_{schemaId}_{id}_{version}"; @@ -85,7 +85,7 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext }).LoadAsync(); } - public IDataLoaderResult GetAsset(DomainId id, + public IDataLoaderResult GetAsset(DomainId id, TimeSpan cacheDuration) { var assets = GetAssets([id], cacheDuration); @@ -94,7 +94,7 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext return asset; } - public IDataLoaderResult GetContent(DomainId schemaId, DomainId id, HashSet? fields, + public IDataLoaderResult GetContent(DomainId schemaId, DomainId id, HashSet? fields, TimeSpan cacheDuration) { var contents = GetContents([id], fields, cacheDuration); @@ -103,10 +103,10 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext return content; } - public IDataLoaderResult GetAssets(List? ids, + public IDataLoaderResult GetAssets(List? ids, TimeSpan cacheDuration) { - if (ids == null || ids.Count == 0) + if (ids is not { Count: > 0 }) { return EmptyAssets; } @@ -114,10 +114,10 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext return GetAssetsLoader().LoadAsync(BuildKeys(ids, cacheDuration)).Then(x => x.NotNull().ToArray()); } - public IDataLoaderResult GetContents(List? ids, HashSet? fields, + public IDataLoaderResult GetContents(List? ids, HashSet? fields, TimeSpan cacheDuration) { - if (ids == null || ids.Count == 0) + if (ids is not { Count: > 0 }) { return EmptyContents; } @@ -130,7 +130,7 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext return GetContentsLoaderWithFields().LoadAsync(BuildKeys(ids, fields)).Then(x => x.NotNull().ToArray()); } - private IDataLoader, IEnrichedAssetEntity> GetAssetsLoader() + private IDataLoader, EnrichedAsset> GetAssetsLoader() { return dataLoaders.Context!.GetOrAddCachingLoader(AssetCache, nameof(GetAssetsLoader), async (batch, ct) => @@ -141,7 +141,7 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext }, maxBatchSize: batchSize); } - private IDataLoader, IEnrichedContentEntity> GetContentsLoader() + private IDataLoader, EnrichedContent> GetContentsLoader() { return dataLoaders.Context!.GetOrAddCachingLoader(ContentCache, nameof(GetContentsLoader), async (batch, ct) => @@ -152,9 +152,9 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext }, maxBatchSize: batchSize); } - private IDataLoader<(DomainId Id, HashSet Fields), IEnrichedContentEntity> GetContentsLoaderWithFields() + private IDataLoader<(DomainId Id, HashSet Fields), EnrichedContent> GetContentsLoaderWithFields() { - return dataLoaders.Context!.GetOrAddNonCachingBatchLoader<(DomainId Id, HashSet Fields), IEnrichedContentEntity>(nameof(GetContentsLoaderWithFields), + return dataLoaders.Context!.GetOrAddNonCachingBatchLoader<(DomainId Id, HashSet Fields), EnrichedContent>(nameof(GetContentsLoaderWithFields), async (batch, ct) => { var fields = batch.SelectMany(x => x.Fields).ToHashSet(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationMutations.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationMutations.cs index 59313878d..2c4522fb5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationMutations.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationMutations.cs @@ -6,9 +6,10 @@ // ========================================================================== using GraphQL.Types; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives; -using Squidex.Domain.Apps.Entities.Schemas; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationQueries.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationQueries.cs index 200b1a6c2..6669f6865 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationQueries.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ApplicationQueries.cs @@ -30,7 +30,7 @@ internal sealed class ApplicationQueries : ObjectGraphType continue; } - if (schemaInfo.Schema.SchemaDef.Type == SchemaType.Singleton) + if (schemaInfo.Schema.Type == SchemaType.Singleton) { // Mark the normal queries as deprecated to motivate using the new endpoint. var deprecation = $"Use 'find{schemaInfo.TypeName}Singleton' instead."; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Assets/AssetActions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Assets/AssetActions.cs index 8e7dc3e26..7779117bd 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Assets/AssetActions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Assets/AssetActions.cs @@ -31,7 +31,7 @@ internal static class AssetActions }, ]; - public static readonly IFieldResolver Resolver = Resolvers.Sync((source, fieldContext, _) => + public static readonly IFieldResolver Resolver = Resolvers.Sync((source, fieldContext, _) => { if (fieldContext.Arguments != null && fieldContext.Arguments.TryGetValue("path", out var path)) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Assets/AssetGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Assets/AssetGraphType.cs index 7a5587ceb..0065117dd 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Assets/AssetGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Assets/AssetGraphType.cs @@ -16,7 +16,7 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Assets; -internal sealed class AssetGraphType : SharedObjectGraphType +internal sealed class AssetGraphType : SharedObjectGraphType { public AssetGraphType() { @@ -267,12 +267,12 @@ internal sealed class AssetGraphType : SharedObjectGraphType(Func resolver) + private static IFieldResolver Resolve(Func resolver) { return Resolvers.Sync(resolver); } - private static IFieldResolver Resolve(Func resolver) + private static IFieldResolver Resolve(Func resolver) { return Resolvers.Sync(resolver); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Assets/AssetsResultGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Assets/AssetsResultGraphType.cs index 721d49baf..dc19e246a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Assets/AssetsResultGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Assets/AssetsResultGraphType.cs @@ -13,7 +13,7 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Assets; -internal sealed class AssetsResultGraphType : SharedObjectGraphType> +internal sealed class AssetsResultGraphType : SharedObjectGraphType> { public AssetsResultGraphType(IGraphType assetsList) { @@ -39,7 +39,7 @@ internal sealed class AssetsResultGraphType : SharedObjectGraphType(Func, T> resolver) + private static IFieldResolver ResolveList(Func, T> resolver) { return Resolvers.Sync(resolver); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs index 08960e3da..6b5b851cc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs @@ -8,15 +8,15 @@ using GraphQL; using GraphQL.Types; using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Dynamic; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Collections; using GraphQLSchema = GraphQL.Types.Schema; +using Schema = Squidex.Domain.Apps.Core.Schemas.Schema; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; @@ -50,7 +50,7 @@ internal sealed class Builder public FieldMap FieldMap { get; private set; } - public Builder(IAppEntity app, GraphQLOptions options) + public Builder(App app, GraphQLOptions options) { partitionResolver = app.PartitionResolver(); @@ -60,7 +60,7 @@ internal sealed class Builder this.options = options; } - public GraphQLSchema BuildSchema(IEnumerable schemas) + public GraphQLSchema BuildSchema(IEnumerable schemas) { // Do not add schema without fields. allSchemas.AddRange(SchemaInfo.Build(schemas, typeNames).Where(x => x.Fields.Count > 0)); @@ -138,7 +138,7 @@ internal sealed class Builder private static bool IsNormalSchema(SchemaInfo schema) { - return schema.Schema.SchemaDef.IsPublished && schema.Schema.SchemaDef.Type != SchemaType.Component; + return schema.Schema.IsPublished && schema.Schema.Type != SchemaType.Component; } public FieldGraphSchema GetGraphType(FieldInfo fieldInfo) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs index f19bf285f..b854af08a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs @@ -209,7 +209,7 @@ internal static class ContentActions fieldContext.CancellationToken); }); - public static readonly IFieldResolver Referencing = Resolvers.Async(async (source, fieldContext, context) => + public static readonly IFieldResolver Referencing = Resolvers.Async(async (source, fieldContext, context) => { var query = fieldContext.BuildODataQuery(); @@ -223,7 +223,7 @@ internal static class ContentActions fieldContext.CancellationToken); }); - public static readonly IFieldResolver ReferencingWithTotal = Resolvers.Async(async (source, fieldContext, context) => + public static readonly IFieldResolver ReferencingWithTotal = Resolvers.Async(async (source, fieldContext, context) => { var query = fieldContext.BuildODataQuery(); @@ -236,7 +236,7 @@ internal static class ContentActions fieldContext.CancellationToken); }); - public static readonly IFieldResolver References = Resolvers.Async(async (source, fieldContext, context) => + public static readonly IFieldResolver References = Resolvers.Async(async (source, fieldContext, context) => { var query = fieldContext.BuildODataQuery(); @@ -250,7 +250,7 @@ internal static class ContentActions fieldContext.CancellationToken); }); - public static readonly IFieldResolver ReferencesWithTotal = Resolvers.Async(async (source, fieldContext, context) => + public static readonly IFieldResolver ReferencesWithTotal = Resolvers.Async(async (source, fieldContext, context) => { var query = fieldContext.BuildODataQuery(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs index 9d4a954c1..31b4a7f0d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs @@ -225,7 +225,7 @@ internal static class ContentFields return Resolvers.Sync(resolver); } - private static IFieldResolver Resolve(Func resolver) + private static IFieldResolver Resolve(Func resolver) { return Resolvers.Sync(resolver); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs index 912366bab..beeb230e0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs @@ -7,12 +7,13 @@ using GraphQL.Types; using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; -internal sealed class ContentGraphType : ObjectGraphType +internal sealed class ContentGraphType : ObjectGraphType { // We need the schema identity at runtime. public DomainId SchemaId { get; set; } @@ -31,7 +32,7 @@ internal sealed class ContentGraphType : ObjectGraphType IsTypeOf = value => { - return value is IContentEntity content && content.SchemaId?.Id == schemaId; + return value is Content content && content.SchemaId?.Id == schemaId; }; AddField(ContentFields.Id); @@ -157,7 +158,7 @@ internal sealed class ContentGraphType : ObjectGraphType private static bool IsReference(SchemaInfo from, SchemaInfo to) { - return from.Schema.SchemaDef.Fields.Any(x => IsReferencing(x, to.Schema.Id)); + return from.Schema.Fields.Any(x => IsReferencing(x, to.Schema.Id)); } private static bool IsReferencing(IField field, DomainId schemaId) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentInterfaceGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentInterfaceGraphType.cs index cef2b131a..727346abe 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentInterfaceGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentInterfaceGraphType.cs @@ -9,7 +9,7 @@ using GraphQL.Types; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; -internal sealed class ContentInterfaceGraphType : InterfaceGraphType +internal sealed class ContentInterfaceGraphType : InterfaceGraphType { public ContentInterfaceGraphType() { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResolvers.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResolvers.cs index 9e9a31761..436399e15 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResolvers.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResolvers.cs @@ -43,17 +43,17 @@ internal static class ContentResolvers public static readonly IFieldResolver ListItems = ResolveList(x => x); - private static IFieldResolver Resolve(Func resolver) + private static IFieldResolver Resolve(Func resolver) { return Resolvers.Sync(resolver); } - private static IFieldResolver Resolve(Func resolver) + private static IFieldResolver Resolve(Func resolver) { return Resolvers.Sync(resolver); } - private static IFieldResolver ResolveList(Func, T> resolver) + private static IFieldResolver ResolveList(Func, T> resolver) { return Resolvers.Sync(resolver); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResultGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResultGraphType.cs index c25da48e3..e8eaa228b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResultGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResultGraphType.cs @@ -7,11 +7,12 @@ using GraphQL.Types; using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; -internal sealed class ContentResultGraphType : ObjectGraphType> +internal sealed class ContentResultGraphType : ObjectGraphType> { public ContentResultGraphType(ContentGraphType contentType, SchemaInfo schemaInfo) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentUnionGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentUnionGraphType.cs index 1d4ff2055..3a9ebd308 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentUnionGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentUnionGraphType.cs @@ -6,6 +6,7 @@ // ========================================================================== using GraphQL.Types; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; using Squidex.Infrastructure.Collections; @@ -52,7 +53,7 @@ internal sealed class ContentUnionGraphType : UnionGraphType ResolveType = value => { - if (value is IContentEntity content) + if (value is Content content) { return types.GetValueOrDefault(content.SchemaId.Id); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs index 185099888..4c7080f3a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs @@ -8,7 +8,6 @@ using GraphQL.Types; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs index 6db4255ea..430caf68a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs @@ -7,7 +7,6 @@ using GraphQL.Types; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs index 92ebb1174..2a00f83b0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs @@ -6,7 +6,6 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Text; #pragma warning disable MA0048 // File name must match type name @@ -15,7 +14,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; internal sealed class SchemaInfo { - public ISchemaEntity Schema { get; } + public Schema Schema { get; } public string DisplayName => Schema.DisplayName(); @@ -35,7 +34,7 @@ internal sealed class SchemaInfo public IReadOnlyList Fields { get; init; } - private SchemaInfo(ISchemaEntity schema, string typeName, ReservedNames typeNames) + private SchemaInfo(Schema schema, string typeName, ReservedNames typeNames) { Schema = schema; @@ -53,7 +52,7 @@ internal sealed class SchemaInfo return TypeName; } - public static IEnumerable Build(IEnumerable schemas, ReservedNames typeNames) + public static IEnumerable Build(IEnumerable schemas, ReservedNames typeNames) { foreach (var schema in schemas.OrderBy(x => x.Created)) { @@ -61,7 +60,7 @@ internal sealed class SchemaInfo yield return new SchemaInfo(schema, typeName, typeNames) { - Fields = FieldInfo.Build(schema.SchemaDef.Fields, $"{typeName}Data", typeNames).ToList() + Fields = FieldInfo.Build(schema.Fields, $"{typeName}Data", typeNames).ToList() }; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/FieldMap.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/FieldMap.cs index 8a4e850d0..4b5f0de88 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/FieldMap.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/FieldMap.cs @@ -24,7 +24,7 @@ internal sealed class FieldMap var fieldMap = schema.Fields.ToDictionary(x => x.FieldName, x => x.Field.Name); schemas[schema.Schema.Id.ToString()] = fieldMap; - schemas[schema.Schema.SchemaDef.Name] = fieldMap; + schemas[schema.Schema.Name] = fieldMap; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/EntityResolvers.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/EntityResolvers.cs index 4e920e6f9..659281631 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/EntityResolvers.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/EntityResolvers.cs @@ -7,20 +7,21 @@ using GraphQL.Resolvers; using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives; internal static class EntityResolvers { - public static readonly IFieldResolver Id = Resolve(x => x.Id.ToString()); - public static readonly IFieldResolver Created = Resolve(x => x.Created.ToDateTimeUtc()); - public static readonly IFieldResolver CreatedBy = Resolve(x => x.CreatedBy.ToString()); - public static readonly IFieldResolver CreatedByUser = ResolveUser(x => x.CreatedBy); - public static readonly IFieldResolver LastModified = Resolve(x => x.LastModified.ToDateTimeUtc()); - public static readonly IFieldResolver LastModifiedBy = Resolve(x => x.LastModifiedBy.ToString()); - public static readonly IFieldResolver LastModifiedByUser = ResolveUser(x => x.LastModifiedBy); - public static readonly IFieldResolver Version = Resolve(x => x.Version); + public static readonly IFieldResolver Id = Resolve(x => x.Id.ToString()); + public static readonly IFieldResolver Created = Resolve(x => x.Created.ToDateTimeUtc()); + public static readonly IFieldResolver CreatedBy = Resolve(x => x.CreatedBy.ToString()); + public static readonly IFieldResolver CreatedByUser = ResolveUser(x => x.CreatedBy); + public static readonly IFieldResolver LastModified = Resolve(x => x.LastModified.ToDateTimeUtc()); + public static readonly IFieldResolver LastModifiedBy = Resolve(x => x.LastModifiedBy.ToString()); + public static readonly IFieldResolver LastModifiedByUser = ResolveUser(x => x.LastModifiedBy); + public static readonly IFieldResolver Version = Resolve(x => x.Version); private static IFieldResolver Resolve(Func resolver) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs index 6cbf2aaa2..b9664d3c6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedExtensions.cs @@ -19,6 +19,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; public static class SharedExtensions { + internal static string EscapePartition(this string value) + { + return value.Replace('-', '_'); + } + internal static FieldType WithouthResolver(this FieldType source) { return new FieldType diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentCache.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentCache.cs index bb27f67b1..9e9b10851 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentCache.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentCache.cs @@ -10,6 +10,6 @@ using Squidex.Infrastructure.Caching; namespace Squidex.Domain.Apps.Entities.Contents; -public interface IContentCache : IQueryCache +public interface IContentCache : IQueryCache { } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs deleted file mode 100644 index f2fa92555..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs +++ /dev/null @@ -1,32 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Contents; - -public interface IContentEntity : - IEntity, - IEntityWithCreatedBy, - IEntityWithLastModifiedBy, - IEntityWithVersion -{ - NamedId AppId { get; } - - NamedId SchemaId { get; } - - Status? NewStatus { get; } - - Status Status { get; } - - ContentData Data { get; } - - ScheduleJob? ScheduleJob { get; } - - bool IsDeleted { get; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentLoader.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentLoader.cs index 4d4b2c458..61245137c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentLoader.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentLoader.cs @@ -5,12 +5,13 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents; public interface IContentLoader { - Task GetAsync(DomainId appId, DomainId id, long version = EtagVersion.Any, + Task GetAsync(DomainId appId, DomainId id, long version = EtagVersion.Any, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs index 86c9d1ef5..0885ac964 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs @@ -5,28 +5,28 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents; public interface IContentQueryService { - IAsyncEnumerable StreamAsync(Context context, string schemaIdOrName, int skip, + IAsyncEnumerable StreamAsync(Context context, string schemaIdOrName, int skip, CancellationToken ct = default); - Task> QueryAsync(Context context, Q q, + Task> QueryAsync(Context context, Q q, CancellationToken ct = default); - Task> QueryAsync(Context context, string schemaIdOrName, Q query, + Task> QueryAsync(Context context, string schemaIdOrName, Q query, CancellationToken ct = default); - Task FindAsync(Context context, string schemaIdOrName, DomainId id, long version = EtagVersion.Any, + Task FindAsync(Context context, string schemaIdOrName, DomainId id, long version = EtagVersion.Any, CancellationToken ct = default); - Task GetSchemaOrThrowAsync(Context context, string schemaIdOrName, + Task GetSchemaOrThrowAsync(Context context, string schemaIdOrName, CancellationToken ct = default); - Task GetSchemaAsync(Context context, string schemaIdOrNama, + Task GetSchemaAsync(Context context, string schemaIdOrNama, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentWorkflow.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentWorkflow.cs index 7caf8d78c..f81a93d35 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentWorkflow.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/IContentWorkflow.cs @@ -7,27 +7,25 @@ using System.Security.Claims; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Entities.Contents; public interface IContentWorkflow { - ValueTask GetInitialStatusAsync(ISchemaEntity schema); + ValueTask GetInitialStatusAsync(Schema schema); - ValueTask CanMoveToAsync(ISchemaEntity schema, Status status, Status next, ContentData data, ClaimsPrincipal? user); + ValueTask CanMoveToAsync(Content content, Status status, Status next, ClaimsPrincipal? user); - ValueTask CanMoveToAsync(IContentEntity content, Status status, Status next, ClaimsPrincipal? user); + ValueTask CanUpdateAsync(Content content, Status status, ClaimsPrincipal? user); - ValueTask CanUpdateAsync(IContentEntity content, Status status, ClaimsPrincipal? user); + ValueTask CanPublishInitialAsync(Schema schema, ClaimsPrincipal? user); - ValueTask CanPublishInitialAsync(ISchemaEntity schema, ClaimsPrincipal? user); + ValueTask ShouldValidateAsync(Schema schema, Status status); - ValueTask ShouldValidateAsync(ISchemaEntity schema, Status status); + ValueTask GetInfoAsync(Content content, Status status); - ValueTask GetInfoAsync(IContentEntity content, Status status); + ValueTask GetNextAsync(Content content, Status status, ClaimsPrincipal? user); - ValueTask GetNextAsync(IContentEntity content, Status status, ClaimsPrincipal? user); - - ValueTask GetAllAsync(ISchemaEntity schema); + ValueTask GetAllAsync(Schema schema); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs deleted file mode 100644 index a6638097d..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs +++ /dev/null @@ -1,34 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.Schemas; - -namespace Squidex.Domain.Apps.Entities.Contents; - -public interface IEnrichedContentEntity : IContentEntity -{ - bool CanUpdate { get; } - - bool IsSingleton { get; } - - string StatusColor { get; } - - string? NewStatusColor { get; } - - string? ScheduledStatusColor { get; } - - string SchemaDisplayName { get; } - - string? EditToken { get; } - - RootField[]? ReferenceFields { get; } - - StatusInfo[]? NextStatuses { get; } - - ContentData? ReferenceData { get; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs index 961ca8974..869835273 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentEnricher.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; @@ -24,7 +24,7 @@ public sealed class ContentEnricher : IContentEnricher this.appProvider = appProvider; } - public async Task EnrichAsync(IContentEntity content, bool cloneData, Context context, + public async Task EnrichAsync(Content content, bool cloneData, Context context, CancellationToken ct) { Guard.NotNull(content); @@ -34,7 +34,7 @@ public sealed class ContentEnricher : IContentEnricher return enriched[0]; } - public Task> EnrichAsync(IEnumerable contents, Context context, + public Task> EnrichAsync(IEnumerable contents, Context context, CancellationToken ct) { Guard.NotNull(contents); @@ -43,12 +43,12 @@ public sealed class ContentEnricher : IContentEnricher return EnrichInternalAsync(contents, false, context, ct); } - private async Task> EnrichInternalAsync(IEnumerable contents, bool cloneData, Context context, + private async Task> EnrichInternalAsync(IEnumerable contents, bool cloneData, Context context, CancellationToken ct) { using (var activity = Telemetry.Activities.StartActivity("ContentEnricher/EnrichInternalAsync")) { - var results = new List(); + var results = new List(); if (context.App != null) { @@ -65,7 +65,7 @@ public sealed class ContentEnricher : IContentEnricher foreach (var content in contents) { - var result = SimpleMapper.Map(content, new ContentEntity()); + var result = SimpleMapper.Map(content, new EnrichedContent()); if (cloneData) { @@ -80,9 +80,9 @@ public sealed class ContentEnricher : IContentEnricher if (context.App != null) { - var schemaCache = new Dictionary>(); + var schemaCache = new Dictionary>(); - Task<(ISchemaEntity, ResolvedComponents)> GetSchema(DomainId id) + Task<(Schema, ResolvedComponents)> GetSchema(DomainId id) { return schemaCache.GetOrAdd(id, async x => { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs index 12d031645..a55a3f428 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentLoader.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.DomainObject; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -22,7 +23,7 @@ public sealed class ContentLoader : IContentLoader this.domainObjectCache = domainObjectCache; } - public async Task GetAsync(DomainId appId, DomainId id, long version = EtagVersion.Any, + public async Task GetAsync(DomainId appId, DomainId id, long version = EtagVersion.Any, CancellationToken ct = default) { var uniqueId = DomainId.Combine(appId, id); @@ -39,19 +40,19 @@ public sealed class ContentLoader : IContentLoader return null; } - return content; + return content?.ToContent(); } - private async Task GetCachedAsync(DomainId uniqueId, long version, + private async Task GetCachedAsync(DomainId uniqueId, long version, CancellationToken ct) { using (Telemetry.Activities.StartActivity("ContentLoader/GetCachedAsync")) { - return await domainObjectCache.GetAsync(uniqueId, version, ct); + return await domainObjectCache.GetAsync(uniqueId, version, ct); } } - private async Task GetAsync(DomainId uniqueId, long version, + private async Task GetAsync(DomainId uniqueId, long version, CancellationToken ct) { using (Telemetry.Activities.StartActivity("ContentLoader/GetAsync")) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs index c427c4909..54c33953f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs @@ -9,11 +9,10 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Microsoft.OData; using Microsoft.OData.Edm; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.GenerateFilters; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents.Text; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects; @@ -44,7 +43,7 @@ public class ContentQueryParser this.options = options.Value; } - public virtual async Task ParseAsync(Context context, Q q, ISchemaEntity? schema = null, + public virtual async Task ParseAsync(Context context, Q q, Schema? schema = null, CancellationToken ct = default) { Guard.NotNull(context); @@ -75,7 +74,7 @@ public class ContentQueryParser } } - private async Task TransformFilterAsync(ClrQuery query, Context context, ISchemaEntity? schema, + private async Task TransformFilterAsync(ClrQuery query, Context context, Schema? schema, CancellationToken ct) { if (query.Filter != null && schema != null) @@ -119,7 +118,7 @@ public class ContentQueryParser query.FullText = null; } - private async Task ParseClrQueryAsync(Context context, Q q, ISchemaEntity? schema, + private async Task ParseClrQueryAsync(Context context, Q q, Schema? schema, CancellationToken ct) { var components = ResolvedComponents.Empty; @@ -181,7 +180,7 @@ public class ContentQueryParser } } - private ClrQuery ParseJson(Context context, ISchemaEntity? schema, Query query, + private ClrQuery ParseJson(Context context, Schema? schema, Query query, ResolvedComponents components) { var queryModel = BuildQueryModel(context, schema, components); @@ -189,7 +188,7 @@ public class ContentQueryParser return queryModel.Convert(query); } - private ClrQuery ParseJson(Context context, ISchemaEntity? schema, string json, + private ClrQuery ParseJson(Context context, Schema? schema, string json, ResolvedComponents components) { var queryModel = BuildQueryModel(context, schema, components); @@ -197,7 +196,7 @@ public class ContentQueryParser return queryModel.Parse(json, serializer); } - private ClrQuery ParseOData(Context context, ISchemaEntity? schema, string odata, + private ClrQuery ParseOData(Context context, Schema? schema, string odata, ResolvedComponents components) { try @@ -226,7 +225,7 @@ public class ContentQueryParser } } - private QueryModel BuildQueryModel(Context context, ISchemaEntity? schema, + private QueryModel BuildQueryModel(Context context, Schema? schema, ResolvedComponents components) { var cacheKey = BuildJsonCacheKey(context.App, schema, context.IsFrontendClient); @@ -235,13 +234,13 @@ public class ContentQueryParser { entry.AbsoluteExpirationRelativeToNow = CacheTime; - return ContentQueryModel.Build(schema?.SchemaDef, context.App.PartitionResolver(), components); + return ContentQueryModel.Build(schema, context.App.PartitionResolver(), components); })!; return result; } - private IEdmModel BuildEdmModel(Context context, ISchemaEntity? schema, + private IEdmModel BuildEdmModel(Context context, Schema? schema, ResolvedComponents components) { var cacheKey = BuildEmdCacheKey(context.App, schema, context.IsFrontendClient); @@ -250,13 +249,13 @@ public class ContentQueryParser { entry.AbsoluteExpirationRelativeToNow = CacheTime; - return BuildQueryModel(context, schema, components).ConvertToEdm("Contents", schema?.SchemaDef.Name ?? "Generic"); + return BuildQueryModel(context, schema, components).ConvertToEdm("Contents", schema?.Name ?? "Generic"); })!; return result; } - private static string BuildEmdCacheKey(IAppEntity app, ISchemaEntity? schema, bool withHidden) + private static string BuildEmdCacheKey(App app, Schema? schema, bool withHidden) { if (schema == null) { @@ -266,7 +265,7 @@ public class ContentQueryParser return $"EDM/{app.Version}/{schema.Id}_{schema.Version}/{withHidden}"; } - private static string BuildJsonCacheKey(IAppEntity app, ISchemaEntity? schema, bool withHidden) + private static string BuildJsonCacheKey(App app, Schema? schema, bool withHidden) { if (schema == null) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs index 245e952bd..7c3ec57fe 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs @@ -7,8 +7,9 @@ using System.Runtime.CompilerServices; using Microsoft.Extensions.Options; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.Repositories; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Translations; @@ -42,7 +43,7 @@ public sealed class ContentQueryService : IContentQueryService this.queryParser = queryParser; } - public async IAsyncEnumerable StreamAsync(Context context, string schemaIdOrName, int skip, + public async IAsyncEnumerable StreamAsync(Context context, string schemaIdOrName, int skip, [EnumeratorCancellation] CancellationToken ct = default) { Guard.NotNull(context); @@ -65,7 +66,7 @@ public sealed class ContentQueryService : IContentQueryService } } - public async Task FindAsync(Context context, string schemaIdOrName, DomainId id, long version = EtagVersion.Any, + public async Task FindAsync(Context context, string schemaIdOrName, DomainId id, long version = EtagVersion.Any, CancellationToken ct = default) { Guard.NotNull(context); @@ -77,7 +78,7 @@ public sealed class ContentQueryService : IContentQueryService var schema = await GetSchemaOrThrowAsync(context, schemaIdOrName, ct); - IContentEntity? content; + Content? content; // A special ID to always query the single content of the singleton. if (id.ToString().Equals(SingletonId, StringComparison.Ordinal)) @@ -103,7 +104,7 @@ public sealed class ContentQueryService : IContentQueryService } } - public async Task> QueryAsync(Context context, string schemaIdOrName, Q q, + public async Task> QueryAsync(Context context, string schemaIdOrName, Q q, CancellationToken ct = default) { Guard.NotNull(context); @@ -115,7 +116,7 @@ public sealed class ContentQueryService : IContentQueryService // Usually the query should not be null, but we never know. if (q == null) { - return ResultList.Empty(); + return ResultList.Empty(); } var schema = await GetSchemaOrThrowAsync(context, schemaIdOrName, ct); @@ -139,7 +140,7 @@ public sealed class ContentQueryService : IContentQueryService } } - public async Task> QueryAsync(Context context, Q q, + public async Task> QueryAsync(Context context, Q q, CancellationToken ct = default) { Guard.NotNull(context); @@ -149,7 +150,7 @@ public sealed class ContentQueryService : IContentQueryService // Usually the query should not be null, but we never know. if (q == null) { - return ResultList.Empty(); + return ResultList.Empty(); } var schemas = await GetSchemasAsync(context, ct); @@ -157,7 +158,7 @@ public sealed class ContentQueryService : IContentQueryService // If the user does not have a permission to query a single schema the database would return an empty result anyway. if (schemas.Count == 0) { - return ResultList.Empty(); + return ResultList.Empty(); } q = await ParseCoreAsync(context, q, null, ct); @@ -173,7 +174,7 @@ public sealed class ContentQueryService : IContentQueryService } } - private async Task> TransformAsync(Context context, IResultList contents, + private async Task> TransformAsync(Context context, IResultList contents, CancellationToken ct) { var transformed = await TransformCoreAsync(context, contents, ct); @@ -181,7 +182,7 @@ public sealed class ContentQueryService : IContentQueryService return ResultList.Create(contents.Total, transformed); } - private async Task TransformAsync(Context context, IContentEntity content, + private async Task TransformAsync(Context context, Content content, CancellationToken ct) { var transformed = await TransformCoreAsync(context, Enumerable.Repeat(content, 1), ct); @@ -189,7 +190,7 @@ public sealed class ContentQueryService : IContentQueryService return transformed[0]; } - private async Task> TransformCoreAsync(Context context, IEnumerable contents, + private async Task> TransformCoreAsync(Context context, IEnumerable contents, CancellationToken ct) { using (Telemetry.Activities.StartActivity("ContentQueryService/TransformCoreAsync")) @@ -198,7 +199,7 @@ public sealed class ContentQueryService : IContentQueryService } } - public async Task GetSchemaOrThrowAsync(Context context, string schemaIdOrName, + public async Task GetSchemaOrThrowAsync(Context context, string schemaIdOrName, CancellationToken ct = default) { var schema = await GetSchemaAsync(context, schemaIdOrName, ct); @@ -211,13 +212,13 @@ public sealed class ContentQueryService : IContentQueryService return schema; } - public async Task GetSchemaAsync(Context context, string schemaIdOrName, + public async Task GetSchemaAsync(Context context, string schemaIdOrName, CancellationToken ct = default) { Guard.NotNull(context); Guard.NotNullOrEmpty(schemaIdOrName); - ISchemaEntity? schema = null; + Schema? schema = null; var canCache = !context.IsFrontendClient; @@ -238,7 +239,7 @@ public sealed class ContentQueryService : IContentQueryService return schema; } - private async Task> GetSchemasAsync(Context context, + private async Task> GetSchemasAsync(Context context, CancellationToken ct) { var schemas = await appProvider.GetSchemasAsync(context.App.Id, ct); @@ -246,7 +247,7 @@ public sealed class ContentQueryService : IContentQueryService return schemas.Where(x => IsAccessible(x) && HasPermission(context, x, PermissionIds.AppContentsReadOwn)).ToList(); } - private async Task ParseCoreAsync(Context context, Q q, ISchemaEntity? schema, + private async Task ParseCoreAsync(Context context, Q q, Schema? schema, CancellationToken ct) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) @@ -258,7 +259,7 @@ public sealed class ContentQueryService : IContentQueryService } } - private async Task> QueryCoreAsync(Context context, Q q, ISchemaEntity schema, + private async Task> QueryCoreAsync(Context context, Q q, Schema schema, CancellationToken ct) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) @@ -270,7 +271,7 @@ public sealed class ContentQueryService : IContentQueryService } } - private async Task> QueryCoreAsync(Context context, Q q, List schemas, + private async Task> QueryCoreAsync(Context context, Q q, List schemas, CancellationToken ct) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) @@ -282,7 +283,7 @@ public sealed class ContentQueryService : IContentQueryService } } - private async Task FindCoreAsync(Context context, DomainId id, ISchemaEntity schema, + private async Task FindCoreAsync(Context context, DomainId id, Schema schema, CancellationToken ct) { using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct)) @@ -294,13 +295,13 @@ public sealed class ContentQueryService : IContentQueryService } } - private static bool IsAccessible(ISchemaEntity schema) + private static bool IsAccessible(Schema schema) { - return schema.SchemaDef.IsPublished; + return schema.IsPublished; } - private static bool HasPermission(Context context, ISchemaEntity schema, string permissionId) + private static bool HasPermission(Context context, Schema schema, string permissionId) { - return context.UserPermissions.Allows(permissionId, context.App.Name, schema.SchemaDef.Name); + return context.UserPermissions.Allows(permissionId, context.App.Name, schema.Name); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/GeoQueryTransformer.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/GeoQueryTransformer.cs index 230ccdb37..e3e50f81e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/GeoQueryTransformer.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/GeoQueryTransformer.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.Text; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure.Queries; #pragma warning disable SA1313 // Parameter names should begin with lower-case letter @@ -17,13 +17,13 @@ internal sealed class GeoQueryTransformer : AsyncTransformVisitor?> TransformAsync(FilterNode filter, Context context, ISchemaEntity schema, ITextIndex textIndex, + public static async Task?> TransformAsync(FilterNode filter, Context context, Schema schema, ITextIndex textIndex, CancellationToken ct) { var args = new Args(context, schema, textIndex, ct); @@ -42,7 +42,7 @@ internal sealed class GeoQueryTransformer : AsyncTransformVisitor 0 }) { return ClrFilter.Eq("id", "__notfound__"); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricher.cs index c1e220db0..71d664d70 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricher.cs @@ -5,13 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Contents; + namespace Squidex.Domain.Apps.Entities.Contents.Queries; public interface IContentEnricher { - Task EnrichAsync(IContentEntity content, bool cloneData, Context context, + Task EnrichAsync(Content content, bool cloneData, Context context, CancellationToken ct); - Task> EnrichAsync(IEnumerable contents, Context context, + Task> EnrichAsync(IEnumerable contents, Context context, CancellationToken ct); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricherStep.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricherStep.cs index 7e77906de..19be75461 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricherStep.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/IContentEnricherStep.cs @@ -6,18 +6,17 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; #pragma warning disable MA0048 // File name must match type name namespace Squidex.Domain.Apps.Entities.Contents.Queries; -public delegate Task<(ISchemaEntity Schema, ResolvedComponents Components)> ProvideSchema(DomainId id); +public delegate Task<(Schema Schema, ResolvedComponents Components)> ProvideSchema(DomainId id); public interface IContentEnricherStep { - Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, + Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct); Task EnrichAsync(Context context, diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/QueryExecutionContext.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/QueryExecutionContext.cs index 4f53aa40d..2f96bb1fe 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/QueryExecutionContext.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/QueryExecutionContext.cs @@ -44,16 +44,16 @@ public abstract class QueryExecutionContext : Dictionary Services = serviceProvider; } - public virtual Task FindContentAsync(string schemaIdOrName, DomainId id, long version, + public virtual Task FindContentAsync(string schemaIdOrName, DomainId id, long version, CancellationToken ct) { return ContentQuery.FindAsync(Context, schemaIdOrName, id, version, ct); } - public virtual async Task> QueryAssetsAsync(Q q, + public virtual async Task> QueryAssetsAsync(Q q, CancellationToken ct) { - IResultList assets; + IResultList assets; await maxRequests.WaitAsync(ct); try @@ -68,10 +68,10 @@ public abstract class QueryExecutionContext : Dictionary return assets; } - public virtual async Task> QueryContentsAsync(string schemaIdOrName, Q q, + public virtual async Task> QueryContentsAsync(string schemaIdOrName, Q q, CancellationToken ct) { - IResultList contents; + IResultList contents; await maxRequests.WaitAsync(ct); try @@ -86,7 +86,7 @@ public abstract class QueryExecutionContext : Dictionary return contents; } - public virtual async Task> QueryAssetsByIdsAsync(IEnumerable ids, + public virtual async Task> QueryAssetsByIdsAsync(IEnumerable ids, CancellationToken ct) { Guard.NotNull(ids); @@ -104,7 +104,7 @@ public abstract class QueryExecutionContext : Dictionary } } - public virtual async Task> QueryContentsByIdsAsync(IEnumerable ids, HashSet? fields, + public virtual async Task> QueryContentsByIdsAsync(IEnumerable ids, HashSet? fields, CancellationToken ct) { Guard.NotNull(ids); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/CalculateTokens.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/CalculateTokens.cs index 194fede52..a25a5d816 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/CalculateTokens.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/CalculateTokens.cs @@ -21,7 +21,7 @@ public sealed class CalculateTokens : IContentEnricherStep this.urlGenerator = urlGenerator; } - public Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, + public Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { var url = urlGenerator.Root(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs index 82a6685a8..0854e45eb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs @@ -6,10 +6,10 @@ // ========================================================================== using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Infrastructure; @@ -37,18 +37,23 @@ public sealed class ConvertData : IContentEnricherStep excludeChangedTypes = new ExcludeChangedTypes(serializer); } - public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, + public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { + // Get the references across all references to reduce number of database calls. var referenceCleaner = await CleanReferencesAsync(context, contents, schemas, ct); + // Get the fields, because they are the same for all schemas. + var fieldNames = GetFieldNames(context); + foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) { ct.ThrowIfCancellationRequested(); var (schema, components) = await schemas(group.Key); - var converter = GenerateConverter(context, components, schema.SchemaDef, referenceCleaner); + // Reuse the converter for all contents of this schema. + var converter = GenerateConverter(context, components, schema, fieldNames, referenceCleaner); foreach (var content in group) { @@ -57,7 +62,7 @@ public sealed class ConvertData : IContentEnricherStep } } - private async Task CleanReferencesAsync(Context context, IEnumerable contents, ProvideSchema schemas, + private async Task CleanReferencesAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { if (context.NoCleanup()) @@ -75,7 +80,7 @@ public sealed class ConvertData : IContentEnricherStep foreach (var content in group) { - content.Data.AddReferencedIds(schema.SchemaDef, ids, components); + content.Data.AddReferencedIds(schema, ids, components); } } @@ -97,7 +102,7 @@ public sealed class ConvertData : IContentEnricherStep private async Task> QueryContentIdsAsync(Context context, HashSet ids, CancellationToken ct) { - var result = await contentRepository.QueryIdsAsync(context.App.Id, ids, context.Scope(), ct); + var result = await contentRepository.QueryIdsAsync(context.App, ids, context.Scope(), ct); return result.Select(x => x.Id); } @@ -110,7 +115,7 @@ public sealed class ConvertData : IContentEnricherStep return result; } - private ContentConverter GenerateConverter(Context context, ResolvedComponents components, Schema schema, ValueReferencesConverter? cleanReferences) + private ContentConverter GenerateConverter(Context context, ResolvedComponents components, Schema schema, HashSet? fieldNames, ValueReferencesConverter? cleanReferences) { var converter = new ContentConverter(components, schema); @@ -130,7 +135,13 @@ public sealed class ConvertData : IContentEnricherStep if (!context.IsFrontendClient) { - converter.Add(new AddDefaultValues(context.App.PartitionResolver()) { IgnoreNonMasterFields = true }); + converter.Add(new AddDefaultValues(context.App.PartitionResolver()) + { + IgnoreNonMasterFields = true, + IgnoreRequiredFields = false, + // If field names are given we run the enrichment only on the specified fields. + FieldNames = fieldNames + }); } converter.Add( @@ -138,7 +149,9 @@ public sealed class ConvertData : IContentEnricherStep context.App.Languages, context.Languages().ToArray()) { - ResolveFallback = !context.IsFrontendClient && !context.NoResolveLanguages() + ResolveFallback = !context.IsFrontendClient && !context.NoResolveLanguages(), + // If field names are given we run the enrichment only on the specified fields. + FieldNames = fieldNames }); if (!context.IsFrontendClient) @@ -158,4 +171,30 @@ public sealed class ConvertData : IContentEnricherStep return converter; } + + private static HashSet? GetFieldNames(Context context) + { + var source = context.Fields(); + + if (source is not { Count: > 0 }) + { + return null; + } + + var fields = new HashSet(); + + foreach (var field in source) + { + if (FieldNames.IsDataField(field, out var dataField)) + { + fields.Add(dataField); + } + else + { + fields.Add(field); + } + } + + return fields.Count == 0 ? null : fields; + } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs index 0ffc5c687..f8d1b553b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichForCaching.cs @@ -32,7 +32,7 @@ public sealed class EnrichForCaching : IContentEnricherStep return Task.CompletedTask; } - public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, + public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { // Sometimes we just want to skip this for performance reasons. diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithSchema.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithSchema.cs index 6ca5a16c6..7c05bacb5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithSchema.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithSchema.cs @@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps; public sealed class EnrichWithSchema : IContentEnricherStep { - public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, + public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { // Group by schema, so we only fetch the schema once. @@ -21,18 +21,18 @@ public sealed class EnrichWithSchema : IContentEnricherStep var (schema, _) = await schemas(group.Key); - var schemaDisplayName = schema.SchemaDef.DisplayNameUnchanged(); + var schemaDisplayName = schema.DisplayName(); foreach (var content in group) { - content.IsSingleton = schema.SchemaDef.Type == SchemaType.Singleton; + content.IsSingleton = schema.Type == SchemaType.Singleton; content.SchemaDisplayName = schemaDisplayName; } if (context.IsFrontendClient) { - var referenceFields = schema.SchemaDef.ReferenceFields().ToArray(); + var referenceFields = schema.ReferenceFields().ToArray(); foreach (var content in group) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithWorkflows.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithWorkflows.cs index 5f9b9d947..f209c420b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithWorkflows.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/EnrichWithWorkflows.cs @@ -21,7 +21,7 @@ public sealed class EnrichWithWorkflows : IContentEnricherStep this.contentWorkflow = contentWorkflow; } - public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, + public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { var cache = new Dictionary<(DomainId, Status), StatusInfo>(); @@ -30,7 +30,7 @@ public sealed class EnrichWithWorkflows : IContentEnricherStep { ct.ThrowIfCancellationRequested(); - await EnrichColorAsync(content, content, cache); + await EnrichColorAsync(content, cache); if (ShouldEnrichWithStatuses(context)) { @@ -40,7 +40,7 @@ public sealed class EnrichWithWorkflows : IContentEnricherStep } } - private async Task EnrichNextsAsync(ContentEntity content, Context context) + private async Task EnrichNextsAsync(EnrichedContent content, Context context) { var editingStatus = content.NewStatus ?? content.Status; @@ -64,29 +64,27 @@ public sealed class EnrichWithWorkflows : IContentEnricherStep } } - private async Task EnrichCanUpdateAsync(ContentEntity content, Context context) + private async Task EnrichCanUpdateAsync(EnrichedContent content, Context context) { - var editingStatus = content.NewStatus ?? content.Status; - - content.CanUpdate = await contentWorkflow.CanUpdateAsync(content, editingStatus, context.UserPrincipal); + content.CanUpdate = await contentWorkflow.CanUpdateAsync(content, content.Status, context.UserPrincipal); } - private async Task EnrichColorAsync(ContentEntity content, ContentEntity result, Dictionary<(DomainId, Status), StatusInfo> cache) + private async Task EnrichColorAsync(EnrichedContent content, Dictionary<(DomainId, Status), StatusInfo> cache) { - result.StatusColor = await GetColorAsync(content, content.Status, cache); + content.StatusColor = await GetColorAsync(content, content.Status, cache); if (content.NewStatus != null) { - result.NewStatusColor = await GetColorAsync(content, content.NewStatus.Value, cache); + content.NewStatusColor = await GetColorAsync(content, content.NewStatus.Value, cache); } if (content.ScheduleJob != null) { - result.ScheduledStatusColor = await GetColorAsync(content, content.ScheduleJob.Status, cache); + content.ScheduledStatusColor = await GetColorAsync(content, content.ScheduleJob.Status, cache); } } - private async Task GetColorAsync(IContentEntity content, Status status, Dictionary<(DomainId, Status), StatusInfo> cache) + private async Task GetColorAsync(Content content, Status status, Dictionary<(DomainId, Status), StatusInfo> cache) { if (!cache.TryGetValue((content.SchemaId.Id, status), out var info)) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs index 9949e6528..58a51b801 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Assets; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Json.Objects; @@ -20,7 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps; public sealed class ResolveAssets : IContentEnricherStep { - private static readonly ILookup EmptyAssets = Enumerable.Empty().ToLookup(x => x.Id); + private static readonly ILookup EmptyAssets = Enumerable.Empty().ToLookup(x => x.Id); private readonly IUrlGenerator urlGenerator; private readonly IAssetQueryService assetQuery; @@ -33,7 +32,7 @@ public sealed class ResolveAssets : IContentEnricherStep this.requestCache = requestCache; } - public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, + public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { if (!ShouldEnrich(context)) @@ -62,12 +61,12 @@ public sealed class ResolveAssets : IContentEnricherStep } } - private void ResolveAssetsUrls(ISchemaEntity schema, ResolvedComponents components, - IGrouping contents, ILookup assets) + private void ResolveAssetsUrls(Schema schema, ResolvedComponents components, + IGrouping contents, ILookup assets) { HashSet? fieldIds = null; - foreach (var field in schema.SchemaDef.ResolvingAssets()) + foreach (var field in schema.ResolvingAssets()) { foreach (var content in contents) { @@ -115,14 +114,14 @@ public sealed class ResolveAssets : IContentEnricherStep } } - private static bool IsImage(IEnrichedAssetEntity asset) + private static bool IsImage(EnrichedAsset asset) { const int PreviewLimit = 10 * 1024; return asset.Type == AssetType.Image || (asset.MimeType == "image/svg+xml" && asset.FileSize < PreviewLimit); } - private async Task> GetAssetsAsync(Context context, HashSet ids, + private async Task> GetAssetsAsync(Context context, HashSet ids, CancellationToken ct) { if (ids.Count == 0) @@ -139,11 +138,11 @@ public sealed class ResolveAssets : IContentEnricherStep return assets.ToLookup(x => x.Id); } - private static void AddAssetIds(HashSet ids, ISchemaEntity schema, ResolvedComponents components, IEnumerable contents) + private static void AddAssetIds(HashSet ids, Schema schema, ResolvedComponents components, IEnumerable contents) { foreach (var content in contents) { - content.Data.AddReferencedIds(schema.SchemaDef.ResolvingAssets(), ids, components, 1); + content.Data.AddReferencedIds(schema.ResolvingAssets(), ids, components, 1); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs index bb7f95c55..e6dde476d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs @@ -8,7 +8,6 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Json.Objects; @@ -18,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps; public sealed class ResolveReferences : IContentEnricherStep { - private static readonly ILookup EmptyContents = Enumerable.Empty().ToLookup(x => x.Id); + private static readonly ILookup EmptyContents = Enumerable.Empty().ToLookup(x => x.Id); private readonly Lazy contentQuery; private readonly IRequestCache requestCache; @@ -33,7 +32,7 @@ public sealed class ResolveReferences : IContentEnricherStep this.requestCache = requestCache; } - public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, + public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { if (!ShouldEnrich(context)) @@ -62,14 +61,14 @@ public sealed class ResolveReferences : IContentEnricherStep } } - private async Task ResolveReferencesAsync(Context context, ISchemaEntity schema, ResolvedComponents components, - IEnumerable contents, ILookup references, ProvideSchema schemas) + private async Task ResolveReferencesAsync(Context context, Schema schema, ResolvedComponents components, + IEnumerable contents, ILookup references, ProvideSchema schemas) { HashSet? fieldIds = null; - var formatted = new Dictionary(); + var formatted = new Dictionary(); - foreach (var field in schema.SchemaDef.ResolvingReferences()) + foreach (var field in schema.ResolvingReferences()) { foreach (var content in contents) { @@ -124,12 +123,12 @@ public sealed class ResolveReferences : IContentEnricherStep } } - private static JsonObject Format(IContentEntity content, Context context, ISchemaEntity referencedSchema) + private static JsonObject Format(Content content, Context context, Schema referencedSchema) { - return content.Data.FormatReferences(referencedSchema.SchemaDef, context.App.Languages); + return content.Data.FormatReferences(referencedSchema, context.App.Languages); } - private static JsonObject CreateFallback(Context context, List referencedContents) + private static JsonObject CreateFallback(Context context, List referencedContents) { var text = T.Get("contents.listReferences", new { count = referencedContents.Count }); @@ -143,15 +142,15 @@ public sealed class ResolveReferences : IContentEnricherStep return value; } - private static void AddReferenceIds(HashSet ids, ISchemaEntity schema, ResolvedComponents components, IEnumerable contents) + private static void AddReferenceIds(HashSet ids, Schema schema, ResolvedComponents components, IEnumerable contents) { foreach (var content in contents) { - content.Data.AddReferencedIds(schema.SchemaDef.ResolvingReferences(), ids, components); + content.Data.AddReferencedIds(schema.ResolvingReferences(), ids, components); } } - private async Task> GetReferencesAsync(Context context, HashSet ids, + private async Task> GetReferencesAsync(Context context, HashSet ids, CancellationToken ct) { if (ids.Count == 0) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs index 2bf4440ec..94388d368 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs @@ -19,7 +19,7 @@ public sealed class ScriptContent : IContentEnricherStep this.scriptEngine = scriptEngine; } - public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, + public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { // Sometimes we just want to skip this for performance reasons. @@ -32,7 +32,7 @@ public sealed class ScriptContent : IContentEnricherStep { var (schema, _) = await schemas(group.Key); - var script = schema.SchemaDef.Scripts.Query; + var script = schema.Scripts.Query; if (string.IsNullOrWhiteSpace(script)) { @@ -45,11 +45,11 @@ public sealed class ScriptContent : IContentEnricherStep AppId = schema.AppId.Id, AppName = schema.AppId.Name, SchemaId = schema.Id, - SchemaName = schema.SchemaDef.Name, + SchemaName = schema.Name, User = context.UserPrincipal }; - var preScript = schema.SchemaDef.Scripts.QueryPre; + var preScript = schema.Scripts.QueryPre; if (!string.IsNullOrWhiteSpace(preScript)) { @@ -68,7 +68,7 @@ public sealed class ScriptContent : IContentEnricherStep } } - private async Task TransformAsync(ContentScriptVars sharedVars, string script, ContentEntity content, + private async Task TransformAsync(ContentScriptVars sharedVars, string script, EnrichedContent content, CancellationToken ct) { // Script vars are just wrappers over dictionaries for better performance. diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesFluidExtension.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesFluidExtension.cs index 8695544ab..161b33a92 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesFluidExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesFluidExtension.cs @@ -10,6 +10,7 @@ using Fluid; using Fluid.Ast; using Fluid.Values; using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Templates; using Squidex.Infrastructure; @@ -79,7 +80,7 @@ public sealed class ReferencesFluidExtension : IFluidExtension }); } - private static async Task ResolveContentAsync(IServiceProvider serviceProvider, DomainId appId, FluidValue id) + private static async Task ResolveContentAsync(IServiceProvider serviceProvider, DomainId appId, FluidValue id) { var appProvider = serviceProvider.GetRequiredService(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs index 48cad8743..c00b3c0b5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ReferencesJintExtension.cs @@ -9,9 +9,9 @@ using System.Security.Claims; using Jint.Native; using Jint.Runtime; using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting.Internal; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Properties; using Squidex.Infrastructure; @@ -134,7 +134,7 @@ public sealed class ReferencesJintExtension : IJintExtension, IScriptDescriptor }); } - private async Task GetAppAsync(DomainId appId) + private async Task GetAppAsync(DomainId appId) { var appProvider = serviceProvider.GetRequiredService(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs index bdf2818e5..8b9aa2d39 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs @@ -6,9 +6,9 @@ // ========================================================================== using NodaTime; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Queries; @@ -16,31 +16,31 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories; public interface IContentRepository { - IAsyncEnumerable StreamScheduledWithoutDataAsync(Instant now, SearchScope scope, + IAsyncEnumerable StreamScheduledWithoutDataAsync(Instant now, SearchScope scope, CancellationToken ct = default); - IAsyncEnumerable StreamAll(DomainId appId, HashSet? schemaIds, SearchScope scope, + IAsyncEnumerable StreamAll(DomainId appId, HashSet? schemaIds, SearchScope scope, CancellationToken ct = default); - IAsyncEnumerable StreamReferencing(DomainId appId, DomainId references, int take, SearchScope scope, + IAsyncEnumerable StreamReferencing(DomainId appId, DomainId references, int take, SearchScope scope, CancellationToken ct = default); - Task> QueryAsync(IAppEntity app, List schemas, Q q, SearchScope scope, + Task> QueryAsync(App app, List schemas, Q q, SearchScope scope, CancellationToken ct = default); - Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Q q, SearchScope scope, + Task> QueryAsync(App app, Schema schema, Q q, SearchScope scope, CancellationToken ct = default); - Task> QueryIdsAsync(DomainId appId, DomainId schemaId, FilterNode filterNode, SearchScope scope, + Task> QueryIdsAsync(App app, Schema schemaId, FilterNode filterNode, SearchScope scope, CancellationToken ct = default); - Task> QueryIdsAsync(DomainId appId, HashSet ids, SearchScope scope, + Task> QueryIdsAsync(App app, HashSet ids, SearchScope scope, CancellationToken ct = default); - Task FindContentAsync(IAppEntity app, ISchemaEntity schema, DomainId id, SearchScope scope, + Task FindContentAsync(App app, Schema schema, DomainId id, SearchScope scope, CancellationToken ct = default); - Task HasReferrersAsync(DomainId appId, DomainId reference, SearchScope scope, + Task HasReferrersAsync(App app, DomainId reference, SearchScope scope, CancellationToken ct = default); Task ResetScheduledAsync(DomainId documentId, SearchScope scope, diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndex.cs index ba14ad8f1..70c0cd578 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndex.cs @@ -5,17 +5,17 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.Text; public interface ITextIndex { - Task?> SearchAsync(IAppEntity app, TextQuery query, SearchScope scope, + Task?> SearchAsync(App app, TextQuery query, SearchScope scope, CancellationToken ct = default); - Task?> SearchAsync(IAppEntity app, GeoQuery query, SearchScope scope, + Task?> SearchAsync(App app, GeoQuery query, SearchScope scope, CancellationToken ct = default); Task ClearAsync( diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Validation/DependencyValidatorsFactory.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Validation/DependencyValidatorsFactory.cs index 6e4ebb113..533e5d8e6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Validation/DependencyValidatorsFactory.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Validation/DependencyValidatorsFactory.cs @@ -37,7 +37,7 @@ public sealed class DependencyValidatorsFactory : IValidatorsFactory { var checkAssets = new CheckAssets(async (ids, ct) => { - return await assetRepository.QueryAsync(context.Root.AppId.Id, null, Q.Empty.WithIds(ids), ct); + return await assetRepository.QueryAsync(context.Root.App.Id, null, Q.Empty.WithIds(ids), ct); }); yield return new AssetsValidator(isRequired, assetsField.Properties, checkAssets); @@ -47,7 +47,7 @@ public sealed class DependencyValidatorsFactory : IValidatorsFactory { var checkReferences = new CheckContentsByIds(async (ids, ct) => { - return await contentRepository.QueryIdsAsync(context.Root.AppId.Id, ids, SearchScope.All, ct); + return await contentRepository.QueryIdsAsync(context.Root.App, ids, SearchScope.All, ct); }); yield return new ReferencesValidator(isRequired, referencesField.Properties, checkReferences); @@ -57,7 +57,7 @@ public sealed class DependencyValidatorsFactory : IValidatorsFactory { var checkUniqueness = new CheckUniqueness(async (f, ct) => { - return await contentRepository.QueryIdsAsync(context.Root.AppId.Id, context.Root.SchemaId.Id, f, SearchScope.All, ct); + return await contentRepository.QueryIdsAsync(context.Root.App, context.Root.Schema, f, SearchScope.All, ct); }); yield return new UniqueValidator(checkUniqueness); @@ -67,7 +67,7 @@ public sealed class DependencyValidatorsFactory : IValidatorsFactory { var checkUniqueness = new CheckUniqueness(async (f, ct) => { - return await contentRepository.QueryIdsAsync(context.Root.AppId.Id, context.Root.SchemaId.Id, f, SearchScope.All, ct); + return await contentRepository.QueryIdsAsync(context.Root.App, context.Root.Schema, f, SearchScope.All, ct); }); yield return new UniqueValidator(checkUniqueness); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Context.cs b/backend/src/Squidex.Domain.Apps.Entities/Context.cs index 099c910bf..00ab30f5a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Context.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Context.cs @@ -6,7 +6,7 @@ // ========================================================================== using System.Security.Claims; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; using Squidex.Shared; @@ -27,18 +27,18 @@ public sealed class Context public ClaimsPrincipal UserPrincipal { get; } - public IAppEntity App { get; set; } + public App App { get; set; } public bool IsFrontendClient => UserPrincipal.IsInClient(DefaultClients.Frontend); - public Context(ClaimsPrincipal user, IAppEntity app) + public Context(ClaimsPrincipal user, App app) : this(app, user, user.Claims.Permissions(), EmptyHeaders) { Guard.NotNull(user); } private Context( - IAppEntity app, + App app, ClaimsPrincipal userPrincipal, ClaimsPermissions userPermissions, IReadOnlyDictionary headers) @@ -51,7 +51,7 @@ public sealed class Context Headers = headers; } - public static Context Anonymous(IAppEntity app) + public static Context Anonymous(App app) { var claimsIdentity = new ClaimsIdentity(); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); @@ -59,7 +59,7 @@ public sealed class Context return new Context(claimsPrincipal, app); } - public static Context Admin(IAppEntity app) + public static Context Admin(App app) { var claimsIdentity = new ClaimsIdentity(); var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); diff --git a/backend/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs b/backend/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs deleted file mode 100644 index 1206adc53..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs +++ /dev/null @@ -1,81 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using NodaTime; -using Squidex.Domain.Apps.Events; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.EventSourcing; - -namespace Squidex.Domain.Apps.Entities; - -public abstract class DomainObjectState : IDomainState where T : class -{ - public DomainId Id { get; set; } - - public RefToken CreatedBy { get; set; } - - public RefToken LastModifiedBy { get; set; } - - public Instant Created { get; set; } - - public Instant LastModified { get; set; } - - public long Version { get; set; } - - protected DomainObjectState() - { - Version = EtagVersion.Empty; - } - - public virtual bool ApplyEvent(IEvent @event, EnvelopeHeaders headers) - { - return ApplyEvent(@event); - } - - public virtual bool ApplyEvent(IEvent @event) - { - return false; - } - - public T Copy() - { - return (T)MemberwiseClone(); - } - - public T Apply(Envelope @event) - { - var payload = (SquidexEvent)@event.Payload; - - var clone = (DomainObjectState)MemberwiseClone(); - - if (!clone.ApplyEvent(@event.Payload, @event.Headers)) - { - return (this as T)!; - } - - var headers = @event.Headers; - - if (clone.Id == DomainId.Empty) - { - clone.Id = headers.AggregateId(); - } - - var timestamp = headers.Timestamp(); - - if (clone.CreatedBy == null) - { - clone.Created = timestamp; - clone.CreatedBy = payload.Actor; - } - - clone.LastModified = timestamp; - clone.LastModifiedBy = payload.Actor; - - return (clone as T)!; - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/EntityExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/EntityExtensions.cs new file mode 100644 index 000000000..dad751e51 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/EntityExtensions.cs @@ -0,0 +1,42 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Events; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Entities; + +public static class EntityExtensions +{ + public static T Apply(this T source, Envelope @event) where T : Entity + { + var headers = @event.Headers; + + var timestamp = headers.Timestamp(); + var created = source.Created; + var createdBy = source.CreatedBy; + + if (created == default) + { + created = timestamp; + } + + if (createdBy == null) + { + createdBy = @event.Payload.Actor; + } + + return source with + { + Created = created, + CreatedBy = createdBy, + LastModified = timestamp, + LastModifiedBy = @event.Payload.Actor + }; + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/IAppProvider.cs b/backend/src/Squidex.Domain.Apps.Entities/IAppProvider.cs index 591434707..bd324c52c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/IAppProvider.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/IAppProvider.cs @@ -5,10 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Rules; -using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Domain.Apps.Entities.Teams; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; @@ -16,41 +16,41 @@ namespace Squidex.Domain.Apps.Entities; public interface IAppProvider { - Task<(IAppEntity?, ISchemaEntity?)> GetAppWithSchemaAsync(DomainId appId, DomainId id, bool canCache = false, + Task<(App?, Schema?)> GetAppWithSchemaAsync(DomainId appId, DomainId id, bool canCache = false, CancellationToken ct = default); - Task GetTeamAsync(DomainId teamId, + Task GetTeamAsync(DomainId teamId, CancellationToken ct = default); - Task> GetUserTeamsAsync(string userId, + Task> GetUserTeamsAsync(string userId, CancellationToken ct = default); - Task GetAppAsync(DomainId appId, bool canCache = false, + Task GetAppAsync(DomainId appId, bool canCache = false, CancellationToken ct = default); - Task GetAppAsync(string appName, bool canCache = false, + Task GetAppAsync(string appName, bool canCache = false, CancellationToken ct = default); - Task> GetUserAppsAsync(string userId, PermissionSet permissions, + Task> GetUserAppsAsync(string userId, PermissionSet permissions, CancellationToken ct = default); - Task> GetTeamAppsAsync(DomainId teamId, + Task> GetTeamAppsAsync(DomainId teamId, CancellationToken ct = default); - Task GetSchemaAsync(DomainId appId, DomainId id, bool canCache = false, + Task GetSchemaAsync(DomainId appId, DomainId id, bool canCache = false, CancellationToken ct = default); - Task GetSchemaAsync(DomainId appId, string name, bool canCache = false, + Task GetSchemaAsync(DomainId appId, string name, bool canCache = false, CancellationToken ct = default); - Task> GetSchemasAsync(DomainId appId, + Task> GetSchemasAsync(DomainId appId, CancellationToken ct = default); - Task> GetRulesAsync(DomainId appId, + Task> GetRulesAsync(DomainId appId, CancellationToken ct = default); - Task GetRuleAsync(DomainId appId, DomainId id, + Task GetRuleAsync(DomainId appId, DomainId id, CancellationToken ct = default); - void RegisterAppForLocalContext(DomainId appId, IAppEntity app); + void RegisterAppForLocalContext(DomainId appId, App app); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/IDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/IDeleter.cs index f4cd2ac2f..c71220150 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/IDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/IDeleter.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities; @@ -14,7 +14,7 @@ public interface IDeleter { int Order => 0; - Task DeleteAppAsync(IAppEntity app, + Task DeleteAppAsync(App app, CancellationToken ct); Task DeleteContributorAsync(DomainId appId, string contributorId, diff --git a/backend/src/Squidex.Domain.Apps.Entities/IEntityWithTags.cs b/backend/src/Squidex.Domain.Apps.Entities/IEntityWithTags.cs deleted file mode 100644 index db596274c..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/IEntityWithTags.cs +++ /dev/null @@ -1,13 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Domain.Apps.Entities; - -public interface IEntityWithTags -{ - HashSet Tags { get; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/IEntityWithVersion.cs b/backend/src/Squidex.Domain.Apps.Entities/IEntityWithVersion.cs deleted file mode 100644 index 2ac3b0c5e..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/IEntityWithVersion.cs +++ /dev/null @@ -1,13 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Domain.Apps.Entities; - -public interface IEntityWithVersion -{ - long Version { get; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Invitation/InviteUserCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Invitation/InviteUserCommandMiddleware.cs index 7a0afcf02..4a5232a15 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Invitation/InviteUserCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Invitation/InviteUserCommandMiddleware.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Teams; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Shared.Users; @@ -39,9 +39,9 @@ public sealed class InviteUserCommandMiddleware : ICommandMiddleware await next(context, ct); - if (created && context.PlainResult is IAppEntity app) + if (created && context.PlainResult is App app) { - context.Complete(new InvitedResult { Entity = app }); + context.Complete(new InvitedResult { Entity = app }); } } else if (context.Command is AssignTeamContributor assignTeamContributor) @@ -56,9 +56,9 @@ public sealed class InviteUserCommandMiddleware : ICommandMiddleware await next(context, ct); - if (created && context.PlainResult is ITeamEntity team) + if (created && context.PlainResult is Team team) { - context.Complete(new InvitedResult { Entity = team }); + context.Complete(new InvitedResult { Entity = team }); } } else diff --git a/backend/src/Squidex.Domain.Apps.Entities/OperationContextBase.cs b/backend/src/Squidex.Domain.Apps.Entities/OperationContextBase.cs index 8d7673221..1a435923e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/OperationContextBase.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/OperationContextBase.cs @@ -7,7 +7,7 @@ using System.Security.Claims; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Validation; @@ -23,7 +23,7 @@ public abstract class OperationContextBase where TCommand : public RefToken Actor => Command.Actor; - public IAppEntity App { get; init; } + public App App { get; init; } public DomainId CommandId { get; init; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/BackupRules.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/BackupRules.cs index 40ac06aab..8c16f9527 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/BackupRules.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/BackupRules.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Backup; using Squidex.Domain.Apps.Entities.Rules.DomainObject; using Squidex.Domain.Apps.Events.Rules; @@ -48,7 +49,7 @@ public sealed class BackupRules : IBackupHandler { if (ruleIds.Count > 0) { - await rebuilder.InsertManyAsync(ruleIds, BatchSize, ct); + await rebuilder.InsertManyAsync(ruleIds, BatchSize, ct); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommand.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommand.cs new file mode 100644 index 000000000..832519737 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommand.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Rules.Commands; + +public abstract class RuleCommand : RuleCommandBase +{ + public DomainId RuleId { get; set; } + + public override DomainId AggregateId + { + get => DomainId.Combine(AppId, RuleId); + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Commands/_RuleCommand.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommandBase.cs similarity index 68% rename from backend/src/Squidex.Domain.Apps.Entities/Rules/Commands/_RuleCommand.cs rename to backend/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommandBase.cs index 0cbbcf67b..42d10e299 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Commands/_RuleCommand.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleCommandBase.cs @@ -8,21 +8,8 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -#pragma warning disable MA0048 // File name must match type name - namespace Squidex.Domain.Apps.Entities.Rules.Commands; -public abstract class RuleCommand : RuleCommandBase -{ - public DomainId RuleId { get; set; } - - public override DomainId AggregateId - { - get => DomainId.Combine(AppId, RuleId); - } -} - -// This command is needed as marker for middlewares. public abstract class RuleCommandBase : SquidexCommand, IAppCommand, IAggregateCommand { public NamedId AppId { get; set; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/Guards/GuardRule.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/Guards/GuardRule.cs index dd99e6520..36584a4a5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/Guards/GuardRule.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/Guards/GuardRule.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Validation; @@ -43,7 +44,7 @@ public static class GuardRule }); } - public static Task CanUpdate(UpdateRule command, IRuleEntity rule, IAppProvider appProvider) + public static Task CanUpdate(UpdateRule command, Rule rule, IAppProvider appProvider) { Guard.NotNull(command); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/Guards/RuleTriggerValidator.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/Guards/RuleTriggerValidator.cs index 431420395..8ad0f1851 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/Guards/RuleTriggerValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/Guards/RuleTriggerValidator.cs @@ -7,7 +7,7 @@ using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Triggers; -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; @@ -16,9 +16,9 @@ namespace Squidex.Domain.Apps.Entities.Rules.DomainObject.Guards; public sealed class RuleTriggerValidator : IRuleTriggerVisitor>> { - public Func> SchemaProvider { get; } + public Func> SchemaProvider { get; } - public RuleTriggerValidator(Func> schemaProvider) + public RuleTriggerValidator(Func> schemaProvider) { SchemaProvider = schemaProvider; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.State.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.State.cs index 31763114d..6322e78fe 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.State.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.State.cs @@ -5,98 +5,74 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Text.Json.Serialization; using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Rules; -using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.States; +using Squidex.Infrastructure.Reflection; namespace Squidex.Domain.Apps.Entities.Rules.DomainObject; public partial class RuleDomainObject { - [CollectionName("Rules")] - public sealed class State : DomainObjectState, IRuleEntity + protected override Rule Apply(Rule snapshot, Envelope @event) { - public NamedId AppId { get; set; } + var newSnapshot = snapshot; - public Rule RuleDef { get; set; } - - public bool IsDeleted { get; set; } - - [JsonIgnore] - public DomainId UniqueId - { - get => DomainId.Combine(AppId, Id); - } - - public override bool ApplyEvent(IEvent @event) + switch (@event.Payload) { - var previousRule = RuleDef; - - switch (@event) - { - case RuleCreated e: + case RuleCreated e: + newSnapshot = new Rule { Id = e.RuleId }; + SimpleMapper.Map(e, newSnapshot); + break; + + case RuleUpdated e: + { + if (e.Trigger != null) { - Id = e.RuleId; - - RuleDef = new Rule(e.Trigger, e.Action); - RuleDef = RuleDef.Rename(e.Name); - - AppId = e.AppId; - return true; + newSnapshot = newSnapshot.Update(e.Trigger); } - case RuleUpdated e: + if (e.Action != null) { - if (e.Trigger != null) - { - RuleDef = RuleDef.Update(e.Trigger); - } - - if (e.Action != null) - { - RuleDef = RuleDef.Update(e.Action); - } - - if (e.Name != null) - { - RuleDef = RuleDef.Rename(e.Name); - } - - if (e.IsEnabled == true) - { - RuleDef = RuleDef.Enable(); - } - else if (e.IsEnabled == false) - { - RuleDef = RuleDef.Disable(); - } - - break; + newSnapshot = newSnapshot.Update(e.Action); } - case RuleEnabled: + if (e.Name != null) { - RuleDef = RuleDef.Enable(); - break; + newSnapshot = newSnapshot.Rename(e.Name); } - case RuleDisabled: + if (e.IsEnabled == true) { - RuleDef = RuleDef.Disable(); - break; + newSnapshot = newSnapshot.Enable(); } - - case RuleDeleted: + else if (e.IsEnabled == false) { - IsDeleted = true; - return true; + newSnapshot = newSnapshot.Disable(); } - } - return !ReferenceEquals(previousRule, RuleDef); + break; + } + + case RuleEnabled: + newSnapshot = newSnapshot.Enable(); + break; + + case RuleDisabled: + newSnapshot = newSnapshot.Disable(); + break; + + case RuleDeleted: + newSnapshot = newSnapshot with { IsDeleted = true }; + break; } + + if (ReferenceEquals(newSnapshot, snapshot)) + { + return snapshot; + } + + return newSnapshot.Apply(@event.To()); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.cs index 51323332e..020a3e6f0 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.DomainObject.Guards; using Squidex.Domain.Apps.Events; @@ -21,18 +22,18 @@ using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Rules.DomainObject; -public partial class RuleDomainObject : DomainObject +public partial class RuleDomainObject : DomainObject { private readonly IServiceProvider serviceProvider; - public RuleDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log, + public RuleDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log, IServiceProvider serviceProvider) : base(id, persistence, log) { this.serviceProvider = serviceProvider; } - protected override bool IsDeleted(State snapshot) + protected override bool IsDeleted(Rule snapshot) { return snapshot.IsDeleted; } @@ -125,7 +126,7 @@ public partial class RuleDomainObject : DomainObject SimpleMapper.Map(command, @event); SimpleMapper.Map(Snapshot, @event); - await RuleEnqueuer().EnqueueAsync(Snapshot.Id, Snapshot.RuleDef, Envelope.Create(@event)); + await RuleEnqueuer().EnqueueAsync(Snapshot.Id, Snapshot, Envelope.Create(@event)); } private IRuleEnqueuer RuleEnqueuer() diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/IEnrichedRuleEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/EnrichedRule.cs similarity index 71% rename from backend/src/Squidex.Domain.Apps.Entities/Rules/IEnrichedRuleEntity.cs rename to backend/src/Squidex.Domain.Apps.Entities/Rules/EnrichedRule.cs index fa18d52b8..145a1e786 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/IEnrichedRuleEntity.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/EnrichedRule.cs @@ -5,11 +5,13 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Rules; + namespace Squidex.Domain.Apps.Entities.Rules; -public interface IEnrichedRuleEntity : IRuleEntity +public sealed record EnrichedRule : Rule { - long NumSucceeded { get; } + public long NumSucceeded { get; init; } - long NumFailed { get; } + public long NumFailed { get; init; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleEnricher.cs index 879ec8af1..ac34e1630 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleEnricher.cs @@ -5,13 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Rules; + namespace Squidex.Domain.Apps.Entities.Rules; public interface IRuleEnricher { - Task EnrichAsync(IRuleEntity rule, Context context, + Task EnrichAsync(Rule rule, Context context, CancellationToken ct); - Task> EnrichAsync(IEnumerable rules, Context context, + Task> EnrichAsync(IEnumerable rules, Context context, CancellationToken ct); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleEventEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleEventEntity.cs index 44101a8ce..460efbec4 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleEventEntity.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleEventEntity.cs @@ -8,13 +8,18 @@ using NodaTime; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Rules; -public interface IRuleEventEntity : IEntity +public interface IRuleEventEntity { + DomainId Id { get; } + RuleJob Job { get; } + Instant Created { get; } + Instant? NextAttempt { get; } RuleJobResult JobResult { get; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleQueryService.cs index 6a02ccfaa..5abe389fe 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/IRuleQueryService.cs @@ -9,6 +9,6 @@ namespace Squidex.Domain.Apps.Entities.Rules; public interface IRuleQueryService { - Task> QueryAsync(Context context, + Task> QueryAsync(Context context, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/IRulesIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/IRulesIndex.cs index dda080b0b..559fdcf82 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/IRulesIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/IRulesIndex.cs @@ -5,12 +5,13 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Rules; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Rules.Indexes; public interface IRulesIndex { - Task> GetRulesAsync(DomainId appId, + Task> GetRulesAsync(DomainId appId, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs index 730bccfbe..7e1d0c976 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Indexes/RulesIndex.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Infrastructure; @@ -19,7 +20,7 @@ public sealed class RulesIndex : IRulesIndex this.ruleRepository = ruleRepository; } - public async Task> GetRulesAsync(DomainId appId, + public async Task> GetRulesAsync(DomainId appId, CancellationToken ct = default) { using (Telemetry.Activities.StartActivity("RulesIndex/GetRulesAsync")) @@ -30,7 +31,7 @@ public sealed class RulesIndex : IRulesIndex } } - private static bool IsValid(IRuleEntity? rule) + private static bool IsValid(Rule? rule) { return rule is { Version: > EtagVersion.Empty, IsDeleted: false }; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleEnricher.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleEnricher.cs index 8d046d8da..eeeaa60db 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleEnricher.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Rules; using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Reflection; @@ -22,7 +23,7 @@ public sealed class RuleEnricher : IRuleEnricher this.requestCache = requestCache; } - public async Task EnrichAsync(IRuleEntity rule, Context context, + public async Task EnrichAsync(Rule rule, Context context, CancellationToken ct) { Guard.NotNull(rule); @@ -32,7 +33,7 @@ public sealed class RuleEnricher : IRuleEnricher return enriched[0]; } - public async Task> EnrichAsync(IEnumerable rules, Context context, + public async Task> EnrichAsync(IEnumerable rules, Context context, CancellationToken ct) { Guard.NotNull(rules); @@ -40,36 +41,36 @@ public sealed class RuleEnricher : IRuleEnricher using (Telemetry.Activities.StartActivity("RuleEnricher/EnrichAsync")) { - var results = new List(); - - foreach (var rule in rules) - { - var result = SimpleMapper.Map(rule, new RuleEntity()); - - results.Add(result); - } + var results = new List(); // Sometimes we just want to skip this for performance reasons. var enrichCacheKeys = !context.NoCacheKeys(); - foreach (var group in results.GroupBy(x => x.AppId.Id)) + foreach (var group in rules.GroupBy(x => x.AppId.Id)) { var statistics = await ruleUsageTracker.GetTotalByAppAsync(group.Key, ct); foreach (var rule in group) { + var result = SimpleMapper.Map(rule, new EnrichedRule()); + if (statistics.TryGetValue(rule.Id, out var statistic)) { - rule.NumFailed = statistic.TotalFailed; - rule.NumSucceeded = statistic.TotalSucceeded; + result = result with + { + NumFailed = statistic.TotalFailed, + NumSucceeded = statistic.TotalSucceeded + }; } if (enrichCacheKeys) { - requestCache.AddDependency(rule.UniqueId, rule.Version); - requestCache.AddDependency(rule.NumFailed); - requestCache.AddDependency(rule.NumSucceeded); + requestCache.AddDependency(result.UniqueId, result.Version); + requestCache.AddDependency(result.NumFailed); + requestCache.AddDependency(result.NumSucceeded); } + + results.Add(result); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs index 10763715f..844ddd951 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Queries/RuleQueryService.cs @@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Queries; public sealed class RuleQueryService : IRuleQueryService { - private static readonly List EmptyResults = []; + private static readonly List EmptyResults = []; private readonly IRulesIndex rulesIndex; private readonly IRuleEnricher ruleEnricher; @@ -21,7 +21,7 @@ public sealed class RuleQueryService : IRuleQueryService this.ruleEnricher = ruleEnricher; } - public async Task> QueryAsync(Context context, + public async Task> QueryAsync(Context context, CancellationToken ct = default) { var rules = await rulesIndex.GetRulesAsync(context.App.Id, ct); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleRepository.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleRepository.cs index 186ee465d..ce6b89e64 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleRepository.cs @@ -5,12 +5,13 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Rules; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Rules.Repositories; public interface IRuleRepository { - Task> QueryAllAsync(DomainId appId, + Task> QueryAllAsync(DomainId appId, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs index f03ca28fd..14ce3eb57 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.DomainObject; using Squidex.Infrastructure.Commands; @@ -29,7 +30,7 @@ public sealed class RuleCommandMiddleware : AggregateCommandMiddleware 0, IncludeSkipped = false, IncludeStale = false, - Rules = rules.ToDictionary(x => x.Id, x => x.RuleDef).ToReadonlyDictionary(), + Rules = rules.ToReadonlyDictionary(x => x.Id), MaxEvents = maxExtraEvents }; @@ -129,7 +129,7 @@ public sealed class RuleEnqueuer : IEventConsumer, IRuleEnqueuer } } - private Task> GetRulesAsync(DomainId appId) + private Task> GetRulesAsync(DomainId appId) { if (cacheDuration <= TimeSpan.Zero || cacheDuration == TimeSpan.MaxValue) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEntity.cs deleted file mode 100644 index e50638af6..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEntity.cs +++ /dev/null @@ -1,46 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using NodaTime; -using Squidex.Domain.Apps.Core.Rules; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Rules; - -public sealed class RuleEntity : IEnrichedRuleEntity -{ - public DomainId Id { get; set; } - - public NamedId AppId { get; set; } - - public NamedId SchemaId { get; set; } - - public long Version { get; set; } - - public Instant Created { get; set; } - - public Instant LastModified { get; set; } - - public RefToken CreatedBy { get; set; } - - public RefToken LastModifiedBy { get; set; } - - public Rule RuleDef { get; set; } - - public bool IsDeleted { get; set; } - - public long NumSucceeded { get; set; } - - public long NumFailed { get; set; } - - public Instant? LastExecuted { get; set; } - - public DomainId UniqueId - { - get => DomainId.Combine(AppId, Id); - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleQueueWriter.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleQueueWriter.cs index a84d38dfc..d20658e2e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleQueueWriter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleQueueWriter.cs @@ -55,7 +55,7 @@ internal sealed class RuleQueueWriter : IAsyncDisposable var totalCreated = 1; // Unfortunately we cannot write in batches here, because the result could be from multiple rules. - await ruleUsageTracker.TrackAsync(result.Job.AppId, result.RuleId, result.Job.Created.ToDateOnly(), totalCreated, 0, totalFailure); + await ruleUsageTracker.TrackAsync(result.Job.AppId, result.Rule?.Id ?? default, result.Job.Created.ToDateOnly(), totalCreated, 0, totalFailure); if (writes.Count >= 100) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs index 4c8e8dbb4..76a447515 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs @@ -41,10 +41,10 @@ public sealed class DefaultRuleRunnerService : IRuleRunnerService this.messaging = messaging; } - public Task> SimulateAsync(IRuleEntity rule, + public Task> SimulateAsync(Rule rule, CancellationToken ct = default) { - return SimulateAsync(rule.AppId, rule.Id, rule.RuleDef, ct); + return SimulateAsync(rule.AppId, rule.Id, rule, ct); } public async Task> SimulateAsync(NamedId appId, DomainId ruleId, Rule rule, @@ -107,14 +107,14 @@ public sealed class DefaultRuleRunnerService : IRuleRunnerService return simulatedEvents; } - public bool CanRunRule(IRuleEntity rule) + public bool CanRunRule(Rule rule) { - return rule.RuleDef.Trigger is not ManualTrigger; + return rule.Trigger is not ManualTrigger; } - public bool CanRunFromSnapshots(IRuleEntity rule) + public bool CanRunFromSnapshots(Rule rule) { - return rule.RuleDef.Trigger is not ManualTrigger && ruleService.CanCreateSnapshotEvents(rule.RuleDef); + return rule.Trigger is not ManualTrigger && ruleService.CanCreateSnapshotEvents(rule); } public Task CancelAsync(DomainId appId, diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/IRuleRunnerService.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/IRuleRunnerService.cs index 77beaea7a..d7f71b7b3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/IRuleRunnerService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/IRuleRunnerService.cs @@ -15,7 +15,7 @@ public interface IRuleRunnerService Task> SimulateAsync(NamedId appId, DomainId ruleId, Rule rule, CancellationToken ct = default); - Task> SimulateAsync(IRuleEntity rule, + Task> SimulateAsync(Rule rule, CancellationToken ct = default); Task RunAsync(DomainId appId, DomainId ruleId, bool fromSnapshots = false, @@ -27,7 +27,7 @@ public interface IRuleRunnerService Task GetRunningRuleIdAsync(DomainId appId, CancellationToken ct = default); - bool CanRunRule(IRuleEntity rule); + bool CanRunRule(Rule rule); - bool CanRunFromSnapshots(IRuleEntity rule); + bool CanRunFromSnapshots(Rule rule); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerProcessor.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerProcessor.cs index a9d2e5745..3699ce3e6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerProcessor.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerProcessor.cs @@ -185,13 +185,12 @@ public sealed class RuleRunnerProcessor run.Context = new RuleContext { AppId = rule.AppId, - Rule = rule.RuleDef, - RuleId = rule.Id, IncludeStale = true, - IncludeSkipped = true + IncludeSkipped = true, + Rule = rule, }; - if (run.Job.RunFromSnapshots && ruleService.CanCreateSnapshotEvents(rule.RuleDef)) + if (run.Job.RunFromSnapshots && ruleService.CanCreateSnapshotEvents(rule)) { await EnqueueFromSnapshotsAsync(run, ct); } @@ -239,7 +238,7 @@ public sealed class RuleRunnerProcessor throw result.EnrichmentError; } - log.LogWarning(result.EnrichmentError, "Failed to run rule with ID {ruleId}, continue with next job.", result.RuleId); + log.LogWarning(result.EnrichmentError, "Failed to run rule with ID {ruleId}, continue with next job.", result.Rule?.Id); } } } @@ -285,7 +284,7 @@ public sealed class RuleRunnerProcessor throw result.EnrichmentError; } - log.LogWarning(result.EnrichmentError, "Failed to run rule with ID {ruleId}, continue with next job.", result.RuleId); + log.LogWarning(result.EnrichmentError, "Failed to run rule with ID {ruleId}, continue with next job.", result.Rule?.Id); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/BackupSchemas.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/BackupSchemas.cs index 2cfa8296a..70ab3d77c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/BackupSchemas.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/BackupSchemas.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Backup; using Squidex.Domain.Apps.Entities.Schemas.DomainObject; using Squidex.Domain.Apps.Events.Schemas; @@ -48,7 +49,7 @@ public sealed class BackupSchemas : IBackupHandler { if (schemaIds.Count > 0) { - await rebuilder.InsertManyAsync(schemaIds, BatchSize, ct); + await rebuilder.InsertManyAsync(schemaIds, BatchSize, ct); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/IUpsertCommand.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/IUpsertCommand.cs index c0244c351..ceba6cb19 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/IUpsertCommand.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/IUpsertCommand.cs @@ -9,6 +9,7 @@ using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Collections; using SchemaField = Squidex.Domain.Apps.Entities.Schemas.Commands.UpsertSchemaField; +using SchemaFieldRules = Squidex.Domain.Apps.Core.Schemas.FieldRules; namespace Squidex.Domain.Apps.Entities.Schemas.Commands; @@ -34,81 +35,65 @@ public interface IUpsertCommand Schema ToSchema(string name, SchemaType type) { - var schema = new Schema(name, Properties, type); + var fields = new List(); - if (IsPublished) + if (Fields?.Length > 0) { - schema = schema.Publish(); - } - - if (Scripts != null) - { - schema = schema.SetScripts(Scripts); - } - - if (PreviewUrls != null) - { - schema = schema.SetPreviewUrls(PreviewUrls); - } - - if (FieldsInLists != null) - { - schema = schema.SetFieldsInLists(FieldsInLists); - } - - if (FieldsInReferences != null) - { - schema = schema.SetFieldsInReferences(FieldsInReferences); - } + var totalFields = 0; - if (FieldRules != null) - { - schema = schema.SetFieldRules(FieldRules.Select(x => x.ToFieldRule()).ToArray()); - } - - if (!string.IsNullOrWhiteSpace(Category)) - { - schema = schema.ChangeCategory(Category); - } - - var totalFields = 0; - - if (Fields != null) - { 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) with + { + IsLocked = eventField.IsLocked, + IsHidden = eventField.IsHidden, + IsDisabled = eventField.IsDisabled + }; if (field is ArrayField arrayField && eventField.Nested?.Length > 0) { + var arrayFields = new List(); + 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.Create(arrayFields.ToArray()) }; } - schema = schema.AddField(field); + fields.Add(field); } } + var schema = new Schema + { + Name = name, + Category = Category, + FieldCollection = FieldCollection.Create(fields.ToArray()), + FieldRules = SchemaFieldRules.Create(FieldRules?.Select(x => x.ToFieldRule()).ToArray()), + FieldsInLists = FieldsInLists ?? FieldNames.Empty, + FieldsInReferences = FieldsInReferences ?? FieldNames.Empty, + IsPublished = IsPublished, + PreviewUrls = PreviewUrls ?? ReadonlyDictionary.Empty(), + Properties = Properties ?? new SchemaProperties(), + Scripts = Scripts ?? new SchemaScripts(), + Type = type, + }; + return schema; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/_SchemaCommand.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommand.cs similarity index 66% rename from backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/_SchemaCommand.cs rename to backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommand.cs index 975bbad47..deb14e806 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/_SchemaCommand.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommand.cs @@ -6,9 +6,6 @@ // ========================================================================== using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; - -#pragma warning disable MA0048 // File name must match type name namespace Squidex.Domain.Apps.Entities.Schemas.Commands; @@ -21,11 +18,3 @@ public abstract class SchemaCommand : SchemaCommandBase, ISchemaCommand get => DomainId.Combine(AppId, SchemaId.Id); } } - -// This command is needed as marker for middlewares. -public abstract class SchemaCommandBase : SquidexCommand, IAppCommand, IAggregateCommand -{ - public NamedId AppId { get; set; } - - public abstract DomainId AggregateId { get; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommandBase.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommandBase.cs new file mode 100644 index 000000000..828dc26b3 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SchemaCommandBase.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; + +namespace Squidex.Domain.Apps.Entities.Schemas.Commands; + +public abstract class SchemaCommandBase : SquidexCommand, IAppCommand, IAggregateCommand +{ + public NamedId AppId { get; set; } + + public abstract DomainId AggregateId { get; } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs index ad984f9c1..e5ecf32ec 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs @@ -38,8 +38,6 @@ public sealed class SynchronizeSchema : SchemaCommand, IUpsertCommand, IAggregat public Schema BuildSchema(string name, SchemaType type) { - IUpsertCommand self = this; - - return self.ToSchema(name, type); + return ((IUpsertCommand)this).ToSchema(name, type); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaFieldBase.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaFieldBase.cs index f4ee30de6..677a19ec3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaFieldBase.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaFieldBase.cs @@ -9,7 +9,7 @@ using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Entities.Schemas.Commands; -public abstract class UpsertSchemaFieldBase : IFieldSettings +public abstract class UpsertSchemaFieldBase { public string Name { get; set; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/Guards/GuardSchema.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/Guards/GuardSchema.cs index a4dd427ac..56caba805 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/Guards/GuardSchema.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/Guards/GuardSchema.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -90,8 +90,13 @@ public static class GuardSchema Validate.It(e => { - ValidateFieldNames(schema, command.FieldsInLists, nameof(command.FieldsInLists), e, IsMetaField); - ValidateFieldNames(schema, command.FieldsInReferences, nameof(command.FieldsInReferences), e, IsNotAllowed); + bool? GetField(string name) + { + return schema.FieldsByName.GetValueOrDefault(name)?.IsUI(); + } + + ValidateFieldNames(command.FieldsInLists, nameof(command.FieldsInLists), e, GetField, true); + ValidateFieldNames(command.FieldsInReferences, nameof(command.FieldsInReferences), e, GetField, false); }); } @@ -125,8 +130,13 @@ public static class GuardSchema } } - ValidateFieldNames(command, command.FieldsInLists, nameof(command.FieldsInLists), e, IsMetaField); - ValidateFieldNames(command, command.FieldsInReferences, nameof(command.FieldsInReferences), e, IsNotAllowed); + bool? GetField(string name) + { + return command.Fields?.FirstOrDefault(x => x.Name == name)?.Properties.IsUIProperty(); + } + + ValidateFieldNames(command.FieldsInLists, nameof(command.FieldsInLists), e, GetField, true); + ValidateFieldNames(command.FieldsInReferences, nameof(command.FieldsInReferences), e, GetField, false); ValidateFieldRules(command.FieldRules, nameof(command.FieldRules), e); } @@ -136,39 +146,40 @@ public static class GuardSchema if (field == null) { e(Not.Defined("Field"), prefix); + return; } - else + + if (!field.Partitioning.IsValidPartitioning()) { - if (!field.Partitioning.IsValidPartitioning()) - { - e(Not.Valid(nameof(field.Partitioning)), $"{prefix}.{nameof(field.Partitioning)}"); - } + e(Not.Valid(nameof(field.Partitioning)), $"{prefix}.{nameof(field.Partitioning)}"); + } + + ValidateField(field, prefix, e); - ValidateField(field, prefix, e); + if (!(field.Nested?.Length > 0)) + { + return; + } - if (field.Nested?.Length > 0) + if (field.Properties is ArrayFieldProperties) + { + field.Nested.Foreach((nestedField, nestedIndex) => { - if (field.Properties is ArrayFieldProperties) - { - field.Nested.Foreach((nestedField, nestedIndex) => - { - var nestedPrefix = $"{prefix}.Nested[{nestedIndex}]"; + var nestedPrefix = $"{prefix}.Nested[{nestedIndex}]"; - ValidateNestedField(nestedField, nestedPrefix, e); - }); - } - else if (field.Nested.Length > 0) - { - e(T.Get("schemas.onlyArraysHaveNested"), $"{prefix}.{nameof(field.Partitioning)}"); - } + ValidateNestedField(nestedField, nestedPrefix, e); + }); + } + else if (field.Nested.Length > 0) + { + e(T.Get("schemas.onlyArraysHaveNested"), $"{prefix}.{nameof(field.Partitioning)}"); + } - foreach (var fieldName in field.Nested.Duplicates(x => x.Name)) - { - if (fieldName.IsPropertyName()) - { - e(T.Get("schemas.duplicateFieldName", new { field = fieldName }), $"{prefix}.Nested"); - } - } + foreach (var fieldName in field.Nested.Duplicates(x => x.Name)) + { + if (fieldName.IsPropertyName()) + { + e(T.Get("schemas.duplicateFieldName", new { field = fieldName }), $"{prefix}.Nested"); } } } @@ -178,16 +189,15 @@ public static class GuardSchema if (nestedField == null) { e(Not.Defined("Field"), prefix); + return; } - else - { - if (nestedField.Properties is ArrayFieldProperties) - { - e(T.Get("schemas.onylArraysInRoot"), $"{prefix}.{nameof(nestedField.Properties)}"); - } - ValidateField(nestedField, prefix, e); + if (nestedField.Properties is ArrayFieldProperties) + { + e(T.Get("schemas.onylArraysInRoot"), $"{prefix}.{nameof(nestedField.Properties)}"); } + + ValidateField(nestedField, prefix, e); } private static void ValidateField(UpsertSchemaFieldBase field, string prefix, AddValidation e) @@ -200,58 +210,72 @@ public static class GuardSchema if (field.Properties == null) { e(Not.Defined(nameof(field.Properties)), $"{prefix}.{nameof(field.Properties)}"); + return; } - else + + if (field.Properties.IsUIProperty()) { - if (field.Properties.IsUIProperty()) + if (field.IsHidden) { - if (field.IsHidden) - { - e(T.Get("schemas.uiFieldCannotBeHidden"), $"{prefix}.{nameof(field.IsHidden)}"); - } + e(T.Get("schemas.uiFieldCannotBeHidden"), $"{prefix}.{nameof(field.IsHidden)}"); + } - if (field.IsDisabled) - { - e(T.Get("schemas.uiFieldCannotBeDisabled"), $"{prefix}.{nameof(field.IsDisabled)}"); - } + if (field.IsDisabled) + { + e(T.Get("schemas.uiFieldCannotBeDisabled"), $"{prefix}.{nameof(field.IsDisabled)}"); } + } - var errors = FieldPropertiesValidator.Validate(field.Properties); + var errors = FieldPropertiesValidator.Validate(field.Properties); - errors.Foreach((x, _) => x.WithPrefix($"{prefix}.{nameof(field.Properties)}").AddTo(e)); - } + errors.Foreach((x, _) => x.WithPrefix($"{prefix}.{nameof(field.Properties)}").AddTo(e)); } - private static void ValidateFieldNames(Schema schema, FieldNames? fields, string path, AddValidation e, Func isAllowed) + private static void ValidateFieldNames(FieldNames? fields, string path, AddValidation e, Func getField, bool allowMeta) { - if (fields != null) + if (fields == null) { - fields.Foreach((fieldName, fieldIndex) => + return; + } + + fields.Foreach((fieldName, fieldIndex) => + { + var fieldPrefix = $"{path}[{fieldIndex}]"; + + if (string.IsNullOrWhiteSpace(fieldName)) { - var fieldPrefix = $"{path}[{fieldIndex}]"; + e(Not.Defined("Field"), fieldPrefix); + return; + } - var field = schema.FieldsByName.GetValueOrDefault(fieldName ?? string.Empty); + if (FieldNames.IsMetaField(fieldName) && allowMeta) + { + return; + } - if (string.IsNullOrWhiteSpace(fieldName)) - { - e(Not.Defined("Field"), fieldPrefix); - } - else if (field == null && !isAllowed(fieldName)) - { - e(T.Get("schemas.fieldNotInSchema"), fieldPrefix); - } - else if (field?.IsUI() == true) - { - e(T.Get("schemas.fieldCannotBeUIField"), fieldPrefix); - } - }); + if (!FieldNames.IsDataField(fieldName, out var dataField)) + { + e(T.Get("schemas.fieldNotInSchema"), fieldPrefix); + return; + } + + var field = getField(dataField); - foreach (var duplicate in fields.Duplicates()) + if (field == null) { - if (!string.IsNullOrWhiteSpace(duplicate)) - { - e(T.Get("schemas.duplicateFieldName", new { field = duplicate }), path); - } + e(T.Get("schemas.fieldNotInSchema"), fieldPrefix); + } + else if (field == true) + { + e(T.Get("schemas.fieldCannotBeUIField"), fieldPrefix); + } + }); + + foreach (var duplicate in fields.Duplicates()) + { + if (!string.IsNullOrWhiteSpace(duplicate)) + { + e(T.Get("schemas.duplicateFieldName", new { field = duplicate }), path); } } } @@ -274,50 +298,6 @@ public static class GuardSchema }); } - private static void ValidateFieldNames(IUpsertCommand command, FieldNames? fields, string path, AddValidation e, Func isAllowed) - { - if (fields != null) - { - fields.Foreach((fieldName, fieldIndex) => - { - var fieldPrefix = $"{path}[{fieldIndex}]"; - - var field = command?.Fields?.FirstOrDefault(x => x.Name == fieldName); - - if (string.IsNullOrWhiteSpace(fieldName)) - { - e(Not.Defined("Field"), fieldPrefix); - } - else if (field == null && !isAllowed(fieldName)) - { - e(T.Get("schemas.fieldNotInSchema"), fieldPrefix); - } - else if (field?.Properties?.IsUIProperty() == true) - { - e(T.Get("schemas.fieldCannotBeUIField"), fieldPrefix); - } - }); - - foreach (var duplicate in fields.Duplicates()) - { - if (!string.IsNullOrWhiteSpace(duplicate)) - { - e(T.Get("schemas.duplicateFieldName", new { field = duplicate }), path); - } - } - } - } - - private static bool IsMetaField(string field) - { - return field.StartsWith("meta.", StringComparison.Ordinal); - } - - private static bool IsNotAllowed(string field) - { - return false; - } - private static void ValidateFieldIds(ReorderFields c, IReadOnlyDictionary fields, AddValidation e) { if (c.FieldIds != null && (c.FieldIds.Length != fields.Count || c.FieldIds.Any(x => !fields.ContainsKey(x)))) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.State.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.State.cs index 0477231d3..78409c8fe 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.State.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.State.cs @@ -5,187 +5,136 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Text.Json.Serialization; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Schemas; -using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject; public sealed partial class SchemaDomainObject { - [CollectionName("Schemas")] - public sealed class State : DomainObjectState, ISchemaEntity + protected override Schema Apply(Schema snapshot, Envelope @event) { - public NamedId AppId { get; set; } + var newSnapshot = snapshot; - public Schema SchemaDef { get; set; } - - public long SchemaFieldsTotal { get; set; } - - public bool IsDeleted { get; set; } - - [JsonIgnore] - public DomainId UniqueId + switch (@event.Payload) { - get => DomainId.Combine(AppId, Id); + case SchemaCreated e: + newSnapshot = e.Schema with + { + Id = e.SchemaId.Id, + // The schema usually does not contain any metadata. + AppId = e.AppId, + // Just update the total count to be more reliabble. + SchemaFieldsTotal = e.Schema.MaxId() + }; + break; + + case FieldAdded e: + if (e.ParentFieldId != null) + { + var field = e.Properties.CreateNestedField(e.FieldId.Id, e.Name); + + newSnapshot = newSnapshot.UpdateField(e.ParentFieldId.Id, x => ((ArrayField)x).AddField(field)); + } + else + { + var partitioning = Partitioning.FromString(e.Partitioning); + + var field = e.Properties.CreateRootField(e.FieldId.Id, e.Name, partitioning); + + newSnapshot = newSnapshot.DeleteField(e.FieldId.Id); + newSnapshot = newSnapshot.AddField(field); + } + + newSnapshot = newSnapshot with { SchemaFieldsTotal = newSnapshot.MaxId() }; + break; + + case SchemaUIFieldsConfigured e: + if (e.FieldsInLists != null) + { + newSnapshot = newSnapshot.SetFieldsInLists(e.FieldsInLists); + } + + if (e.FieldsInReferences != null) + { + newSnapshot = newSnapshot.SetFieldsInReferences(e.FieldsInReferences); + } + + break; + + case SchemaCategoryChanged e: + newSnapshot = newSnapshot.ChangeCategory(e.Name); + break; + + case SchemaScriptsConfigured e: + newSnapshot = newSnapshot.SetScripts(e.Scripts ?? new ()); + break; + + case SchemaPreviewUrlsConfigured e: + newSnapshot = newSnapshot.SetPreviewUrls(e.PreviewUrls ?? new ()); + break; + + case SchemaFieldRulesConfigured e: + newSnapshot = newSnapshot.SetFieldRules(e.FieldRules ?? FieldRules.Empty); + break; + + case SchemaPublished: + newSnapshot = newSnapshot.Publish(); + break; + + case SchemaUnpublished: + newSnapshot = newSnapshot.Unpublish(); + break; + + case SchemaUpdated e: + newSnapshot = newSnapshot.Update(e.Properties); + break; + + case SchemaFieldsReordered e: + newSnapshot = newSnapshot.ReorderFields(e.FieldIds.ToList(), e.ParentFieldId?.Id); + break; + + case FieldUpdated e: + newSnapshot = newSnapshot.UpdateField(e.FieldId.Id, e.Properties, e.ParentFieldId?.Id); + break; + + case FieldLocked e: + newSnapshot = newSnapshot.LockField(e.FieldId.Id, e.ParentFieldId?.Id); + break; + + case FieldDisabled e: + newSnapshot = newSnapshot.DisableField(e.FieldId.Id, e.ParentFieldId?.Id); + break; + + case FieldEnabled e: + newSnapshot = newSnapshot.EnableField(e.FieldId.Id, e.ParentFieldId?.Id); + break; + + case FieldHidden e: + newSnapshot = newSnapshot.HideField(e.FieldId.Id, e.ParentFieldId?.Id); + break; + + case FieldShown e: + newSnapshot = newSnapshot.ShowField(e.FieldId.Id, e.ParentFieldId?.Id); + break; + + case FieldDeleted e: + newSnapshot = newSnapshot.DeleteField(e.FieldId.Id, e.ParentFieldId?.Id); + break; + + case SchemaDeleted: + newSnapshot = newSnapshot with { IsDeleted = true }; + break; } - public override bool ApplyEvent(IEvent @event) + if (ReferenceEquals(newSnapshot, snapshot)) { - var previousSchema = SchemaDef; - - switch (@event) - { - case SchemaCreated e: - { - Id = e.SchemaId.Id; - - SchemaDef = e.Schema; - SchemaFieldsTotal = e.Schema.MaxId(); - - AppId = e.AppId; - return true; - } - - case FieldAdded e: - { - if (e.ParentFieldId != null) - { - var field = e.Properties.CreateNestedField(e.FieldId.Id, e.Name); - - SchemaDef = SchemaDef.UpdateField(e.ParentFieldId.Id, x => ((ArrayField)x).AddField(field)); - } - else - { - var partitioning = Partitioning.FromString(e.Partitioning); - - var field = e.Properties.CreateRootField(e.FieldId.Id, e.Name, partitioning); - - SchemaDef = SchemaDef.DeleteField(e.FieldId.Id); - SchemaDef = SchemaDef.AddField(field); - } - - SchemaFieldsTotal = Math.Max(SchemaFieldsTotal, e.FieldId.Id); - break; - } - - case SchemaUIFieldsConfigured e: - { - if (e.FieldsInLists != null) - { - SchemaDef = SchemaDef.SetFieldsInLists(e.FieldsInLists); - } - - if (e.FieldsInReferences != null) - { - SchemaDef = SchemaDef.SetFieldsInReferences(e.FieldsInReferences); - } - - break; - } - - case SchemaCategoryChanged e: - { - SchemaDef = SchemaDef.ChangeCategory(e.Name); - break; - } - - case SchemaPreviewUrlsConfigured e: - { - SchemaDef = SchemaDef.SetPreviewUrls(e.PreviewUrls); - break; - } - - case SchemaScriptsConfigured e: - { - SchemaDef = SchemaDef.SetScripts(e.Scripts); - break; - } - - case SchemaFieldRulesConfigured e: - { - SchemaDef = SchemaDef.SetFieldRules(e.FieldRules); - break; - } - - case SchemaPublished: - { - SchemaDef = SchemaDef.Publish(); - break; - } - - case SchemaUnpublished: - { - SchemaDef = SchemaDef.Unpublish(); - break; - } - - case SchemaUpdated e: - { - SchemaDef = SchemaDef.Update(e.Properties); - break; - } - - case SchemaFieldsReordered e: - { - SchemaDef = SchemaDef.ReorderFields(e.FieldIds.ToList(), e.ParentFieldId?.Id); - break; - } - - case FieldUpdated e: - { - SchemaDef = SchemaDef.UpdateField(e.FieldId.Id, e.Properties, e.ParentFieldId?.Id); - break; - } - - case FieldLocked e: - { - SchemaDef = SchemaDef.LockField(e.FieldId.Id, e.ParentFieldId?.Id); - break; - } - - case FieldDisabled e: - { - SchemaDef = SchemaDef.DisableField(e.FieldId.Id, e.ParentFieldId?.Id); - break; - } - - case FieldEnabled e: - { - SchemaDef = SchemaDef.EnableField(e.FieldId.Id, e.ParentFieldId?.Id); - break; - } - - case FieldHidden e: - { - SchemaDef = SchemaDef.HideField(e.FieldId.Id, e.ParentFieldId?.Id); - break; - } - - case FieldShown e: - { - SchemaDef = SchemaDef.ShowField(e.FieldId.Id, e.ParentFieldId?.Id); - break; - } - - case FieldDeleted e: - { - SchemaDef = SchemaDef.DeleteField(e.FieldId.Id, e.ParentFieldId?.Id); - break; - } - - case SchemaDeleted: - { - IsDeleted = true; - return true; - } - } - - return !ReferenceEquals(previousSchema, SchemaDef); + return snapshot; } + + return newSnapshot.Apply(@event.To()); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs index 0ad9b3104..9a26e8c3e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs @@ -22,14 +22,14 @@ using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject; -public sealed partial class SchemaDomainObject : DomainObject +public sealed partial class SchemaDomainObject : DomainObject { - public SchemaDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log) + public SchemaDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log) : base(id, persistence, log) { } - protected override bool IsDeleted(State snapshot) + protected override bool IsDeleted(Schema snapshot) { return snapshot.IsDeleted; } @@ -62,7 +62,7 @@ public sealed partial class SchemaDomainObject : DomainObject { - GuardSchemaField.CanAdd(c, Snapshot.SchemaDef); + GuardSchemaField.CanAdd(c, Snapshot); AddField(c); @@ -92,7 +92,7 @@ public sealed partial class SchemaDomainObject : DomainObject { - GuardSchemaField.CanDelete(deleteField, Snapshot.SchemaDef); + GuardSchemaField.CanDelete(deleteField, Snapshot); DeleteField(c); @@ -102,7 +102,7 @@ public sealed partial class SchemaDomainObject : DomainObject { - GuardSchemaField.CanLock(lockField, Snapshot.SchemaDef); + GuardSchemaField.CanLock(lockField, Snapshot); LockField(c); @@ -112,7 +112,7 @@ public sealed partial class SchemaDomainObject : DomainObject { - GuardSchemaField.CanHide(c, Snapshot.SchemaDef); + GuardSchemaField.CanHide(c, Snapshot); HideField(c); @@ -122,7 +122,7 @@ public sealed partial class SchemaDomainObject : DomainObject { - GuardSchemaField.CanShow(c, Snapshot.SchemaDef); + GuardSchemaField.CanShow(c, Snapshot); ShowField(c); @@ -132,7 +132,7 @@ public sealed partial class SchemaDomainObject : DomainObject { - GuardSchemaField.CanDisable(c, Snapshot.SchemaDef); + GuardSchemaField.CanDisable(c, Snapshot); DisableField(c); @@ -142,7 +142,7 @@ public sealed partial class SchemaDomainObject : DomainObject { - GuardSchemaField.CanEnable(c, Snapshot.SchemaDef); + GuardSchemaField.CanEnable(c, Snapshot); EnableField(c); @@ -152,7 +152,7 @@ public sealed partial class SchemaDomainObject : DomainObject { - GuardSchemaField.CanUpdate(c, Snapshot.SchemaDef); + GuardSchemaField.CanUpdate(c, Snapshot); UpdateField(c); @@ -162,7 +162,7 @@ public sealed partial class SchemaDomainObject : DomainObject { - GuardSchema.CanReorder(c, Snapshot.SchemaDef); + GuardSchema.CanReorder(c, Snapshot); Reorder(c); @@ -192,7 +192,7 @@ public sealed partial class SchemaDomainObject : DomainObject { - GuardSchema.CanConfigureUIFields(c, Snapshot.SchemaDef); + GuardSchema.CanConfigureUIFields(c, Snapshot); ConfigureUIFields(c); @@ -256,7 +256,7 @@ public sealed partial class SchemaDomainObject : DomainObject Snapshot.SchemaFieldsTotal + 1, options); @@ -368,7 +368,7 @@ public sealed partial class SchemaDomainObject : DomainObject? GetFieldId(long? id) { - if (id != null && Snapshot.SchemaDef.FieldsById.TryGetValue(id.Value, out var field)) + if (id != null && Snapshot.FieldsById.TryGetValue(id.Value, out var field)) { return field.NamedId(); } @@ -380,7 +380,7 @@ public sealed partial class SchemaDomainObject : DomainObject GetStateAsync() - { - return Task.FromResult(Snapshot); - } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs deleted file mode 100644 index 00e625ca9..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs +++ /dev/null @@ -1,24 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Schemas; - -public interface ISchemaEntity : - IEntity, - IEntityWithCreatedBy, - IEntityWithLastModifiedBy, - IEntityWithVersion -{ - NamedId AppId { get; } - - bool IsDeleted { get; } - - Schema SchemaDef { get; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/ISchemasHash.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/ISchemasHash.cs index a88abee86..c120c5cde 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/ISchemasHash.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/ISchemasHash.cs @@ -6,15 +6,16 @@ // ========================================================================== using NodaTime; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Entities.Schemas; public interface ISchemasHash { - Task<(Instant Create, string Hash)> GetCurrentHashAsync(IAppEntity app, + Task<(Instant Create, string Hash)> GetCurrentHashAsync(App app, CancellationToken ct = default); - ValueTask ComputeHashAsync(IAppEntity app, IEnumerable schemas, + ValueTask ComputeHashAsync(App app, IEnumerable schemas, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/ISchemasIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/ISchemasIndex.cs index 17cd52742..d5d334a89 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/ISchemasIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/ISchemasIndex.cs @@ -5,18 +5,19 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Schemas.Indexes; public interface ISchemasIndex { - Task GetSchemaAsync(DomainId appId, DomainId id, bool canCache, + Task GetSchemaAsync(DomainId appId, DomainId id, bool canCache, CancellationToken ct = default); - Task GetSchemaAsync(DomainId appId, string name, bool canCache, + Task GetSchemaAsync(DomainId appId, string name, bool canCache, CancellationToken ct = default); - Task> GetSchemasAsync(DomainId appId, + Task> GetSchemasAsync(DomainId appId, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs index b4360deb9..acdc34fef 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Caching; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.Repositories; using Squidex.Infrastructure; @@ -31,7 +32,7 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex this.persistenceFactory = persistenceFactory; } - public async Task> GetSchemasAsync(DomainId appId, + public async Task> GetSchemasAsync(DomainId appId, CancellationToken ct = default) { using (var activity = Telemetry.Activities.StartActivity("SchemasIndex/GetSchemasAsync")) @@ -40,16 +41,11 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex var schemas = await schemaRepository.QueryAllAsync(appId, ct); - foreach (var schema in schemas.Where(IsValid)) - { - await CacheItAsync(schema); - } - - return schemas.Where(IsValid).ToList(); + return await schemas.Where(IsValid).SelectAsync(PrepareAsync); } } - public async Task GetSchemaAsync(DomainId appId, string name, bool canCache, + public async Task GetSchemaAsync(DomainId appId, string name, bool canCache, CancellationToken ct = default) { using (var activity = Telemetry.Activities.StartActivity("SchemasIndex/GetSchemaByNameAsync")) @@ -61,7 +57,7 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex if (canCache) { - if (schemaCache.TryGetValue(cacheKey, out var value) && value is ISchemaEntity cachedSchema) + if (schemaCache.TryGetValue(cacheKey, out var value) && value is Schema cachedSchema) { return cachedSchema; } @@ -69,21 +65,16 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex var schema = await schemaRepository.FindAsync(appId, name, ct); - if (!IsValid(schema)) + if (schema == null || !IsValid(schema)) { - schema = null; + return null; } - if (schema != null) - { - await CacheItAsync(schema); - } - - return schema; + return await PrepareAsync(schema); } } - public async Task GetSchemaAsync(DomainId appId, DomainId id, bool canCache, + public async Task GetSchemaAsync(DomainId appId, DomainId id, bool canCache, CancellationToken ct = default) { using (var activity = Telemetry.Activities.StartActivity("SchemasIndex/GetSchemaAsync")) @@ -95,7 +86,7 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex if (canCache) { - if (schemaCache.TryGetValue(cacheKey, out var v) && v is ISchemaEntity cachedSchema) + if (schemaCache.TryGetValue(cacheKey, out var v) && v is Schema cachedSchema) { return cachedSchema; } @@ -103,17 +94,12 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex var schema = await schemaRepository.FindAsync(appId, id, ct); - if (!IsValid(schema)) + if (schema == null || !IsValid(schema)) { - schema = null; + return null; } - if (schema != null) - { - await CacheItAsync(schema); - } - - return schema; + return await PrepareAsync(schema); } } @@ -124,9 +110,8 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex if (command is CreateSchema createSchema) { - var names = await GetNamesAsync(createSchema.AppId.Id, ct); - - var token = await CheckSchemaAsync(createSchema, names, ct); + var schemaNames = await GetNamesAsync(createSchema.AppId.Id, ct); + var schemaTokens = await CheckSchemaAsync(createSchema, schemaNames, ct); try { await next(context, ct); @@ -134,7 +119,7 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex finally { // Always remove the reservation and therefore do not pass over cancellation token. - await names.RemoveReservationAsync(token, default); + await schemaNames.RemoveReservationAsync(schemaTokens, default); } } else @@ -214,28 +199,33 @@ public sealed class SchemasIndex : ICommandMiddleware, ISchemasIndex return $"{typeof(SchemasIndex)}_Schemas_Id_{appId}_{id}"; } - private static bool IsValid(ISchemaEntity? schema) + private static bool IsValid(Schema? schema) { return schema is { Version: > EtagVersion.Empty, IsDeleted: false }; } - private Task InvalidateItAsync(DomainId appId, DomainId id, string name) + private async Task PrepareAsync(Schema schema) { + // Run some fallback migrations. + schema = FieldNames.Migrate(schema); + // Do not use cancellation here as we already so far. - return schemaCache.RemoveAsync(new[] + await schemaCache.AddAsync(new[] { - GetCacheKey(appId, id), - GetCacheKey(appId, name) - }); + new KeyValuePair(GetCacheKey(schema.AppId.Id, schema.Id), schema), + new KeyValuePair(GetCacheKey(schema.AppId.Id, schema.Name), schema), + }, CacheDuration); + + return schema; } - private Task CacheItAsync(ISchemaEntity schema) + private Task InvalidateItAsync(DomainId appId, DomainId id, string name) { // Do not use cancellation here as we already so far. - return schemaCache.AddAsync(new[] + return schemaCache.RemoveAsync(new[] { - new KeyValuePair(GetCacheKey(schema.AppId.Id, schema.Id), schema), - new KeyValuePair(GetCacheKey(schema.AppId.Id, schema.SchemaDef.Name), schema), - }, CacheDuration); + GetCacheKey(appId, id), + GetCacheKey(appId, name) + }); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/MigrateFieldNamesCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/MigrateFieldNamesCommandMiddleware.cs new file mode 100644 index 000000000..bce2a54a6 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/MigrateFieldNamesCommandMiddleware.cs @@ -0,0 +1,32 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Infrastructure.Commands; + +namespace Squidex.Domain.Apps.Entities.Schemas; + +public sealed class MigrateFieldNamesCommandMiddleware : ICommandMiddleware +{ + public Task HandleAsync(CommandContext context, NextDelegate next, + CancellationToken ct) + { + if (context.Command is IUpsertCommand upsert) + { + upsert.FieldsInLists = upsert.FieldsInLists?.Migrate(); + upsert.FieldsInReferences = upsert.FieldsInReferences?.Migrate(); + } + + if (context.Command is ConfigureUIFields configure) + { + configure.FieldsInLists = configure.FieldsInLists?.Migrate(); + configure.FieldsInReferences = configure.FieldsInReferences?.Migrate(); + } + + return next(context, ct); + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Repositories/ISchemaRepository.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Repositories/ISchemaRepository.cs index fa09944b5..e9899c874 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Repositories/ISchemaRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Repositories/ISchemaRepository.cs @@ -5,18 +5,19 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Schemas.Repositories; public interface ISchemaRepository { - Task> QueryAllAsync(DomainId appId, + Task> QueryAllAsync(DomainId appId, CancellationToken ct = default); - Task FindAsync(DomainId appId, DomainId id, + Task FindAsync(DomainId appId, DomainId id, CancellationToken ct = default); - Task FindAsync(DomainId appId, string name, + Task FindAsync(DomainId appId, string name, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs deleted file mode 100644 index 22c32357b..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Infrastructure; -using StaticNamedId = Squidex.Infrastructure.NamedId; - -namespace Squidex.Domain.Apps.Entities.Schemas; - -public static class SchemaExtensions -{ - public static NamedId NamedId(this ISchemaEntity schema) - { - return StaticNamedId.Of(schema.Id, schema.SchemaDef.Name); - } - - public static string EscapePartition(this string value) - { - return value.Replace('-', '_'); - } - - public static string TypeName(this ISchemaEntity schema) - { - return schema.SchemaDef.TypeName(); - } - - public static string DisplayName(this ISchemaEntity schema) - { - return schema.SchemaDef.DisplayName(); - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs index b4b11e479..5031761dd 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemasSearchSource.cs @@ -6,8 +6,8 @@ // ========================================================================== using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Search; using Squidex.Infrastructure; using Squidex.Infrastructure.Translations; @@ -44,13 +44,13 @@ public sealed class SchemasSearchSource : ISearchSource { var schemaId = schema.NamedId(); - var name = schema.SchemaDef.DisplayNameUnchanged(); + var name = schema.DisplayName(); if (name.Contains(query, StringComparison.OrdinalIgnoreCase)) { AddSchemaUrl(result, appId, schemaId, name); - if (schema.SchemaDef.Type != SchemaType.Component && HasPermission(context, schemaId)) + if (schema.Type != SchemaType.Component && HasPermission(context, schemaId)) { AddContentsUrl(result, appId, schema, schemaId, name); } @@ -67,9 +67,9 @@ public sealed class SchemasSearchSource : ISearchSource result.Add(T.Get("search.schemaResult", new { name }), SearchResultType.Schema, schemaUrl); } - private void AddContentsUrl(SearchResults result, NamedId appId, ISchemaEntity schema, NamedId schemaId, string name) + private void AddContentsUrl(SearchResults result, NamedId appId, Schema schema, NamedId schemaId, string name) { - if (schema.SchemaDef.Type == SchemaType.Singleton) + if (schema.Type == SchemaType.Singleton) { var contentUrl = urlGenerator.ContentUI(appId, schemaId, schemaId.Id); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Teams/Commands/_TeamCommand.cs b/backend/src/Squidex.Domain.Apps.Entities/Teams/Commands/TeamCommand.cs similarity index 69% rename from backend/src/Squidex.Domain.Apps.Entities/Teams/Commands/_TeamCommand.cs rename to backend/src/Squidex.Domain.Apps.Entities/Teams/Commands/TeamCommand.cs index 5cd29cfe0..66f12ab44 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Teams/Commands/_TeamCommand.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Teams/Commands/TeamCommand.cs @@ -6,9 +6,6 @@ // ========================================================================== using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; - -#pragma warning disable MA0048 // File name must match type name namespace Squidex.Domain.Apps.Entities.Teams.Commands; @@ -21,9 +18,3 @@ public abstract class TeamCommand : TeamCommandBase, ITeamCommand get => TeamId; } } - -// This command is needed as marker for middlewares. -public abstract class TeamCommandBase : SquidexCommand, IAggregateCommand -{ - public abstract DomainId AggregateId { get; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/IEntityWithCreatedBy.cs b/backend/src/Squidex.Domain.Apps.Entities/Teams/Commands/TeamCommandBase.cs similarity index 65% rename from backend/src/Squidex.Domain.Apps.Entities/IEntityWithCreatedBy.cs rename to backend/src/Squidex.Domain.Apps.Entities/Teams/Commands/TeamCommandBase.cs index b246de09f..0952bb0ed 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/IEntityWithCreatedBy.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Teams/Commands/TeamCommandBase.cs @@ -6,10 +6,11 @@ // ========================================================================== using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Entities; +namespace Squidex.Domain.Apps.Entities.Teams.Commands; -public interface IEntityWithCreatedBy +public abstract class TeamCommandBase : SquidexCommand, IAggregateCommand { - RefToken CreatedBy { get; } + public abstract DomainId AggregateId { get; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Teams/DomainObject/Guards/GuardTeamContributors.cs b/backend/src/Squidex.Domain.Apps.Entities/Teams/DomainObject/Guards/GuardTeamContributors.cs index ca9f66f6a..209b1689e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Teams/DomainObject/Guards/GuardTeamContributors.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Teams/DomainObject/Guards/GuardTeamContributors.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities.Teams.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Translations; @@ -16,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Teams.DomainObject.Guards; public static class GuardTeamContributors { - public static Task CanAssign(AssignContributor command, ITeamEntity team, IUserResolver users) + public static Task CanAssign(AssignContributor command, Team team, IUserResolver users) { Guard.NotNull(command); @@ -50,7 +51,7 @@ public static class GuardTeamContributors }); } - public static void CanRemove(RemoveContributor command, ITeamEntity team) + public static void CanRemove(RemoveContributor command, Team team) { Guard.NotNull(command); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Teams/DomainObject/TeamDomainObject.State.cs b/backend/src/Squidex.Domain.Apps.Entities/Teams/DomainObject/TeamDomainObject.State.cs index b059d7dc1..37e3b6ba3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Teams/DomainObject/TeamDomainObject.State.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Teams/DomainObject/TeamDomainObject.State.cs @@ -5,80 +5,52 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Text.Json.Serialization; using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Teams; +using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Teams; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Reflection; namespace Squidex.Domain.Apps.Entities.Teams.DomainObject; public partial class TeamDomainObject { - public sealed class State : DomainObjectState, ITeamEntity + protected override Team Apply(Team snapshot, Envelope @event) { - public string Name { get; set; } + var newSnapshot = snapshot; - public Contributors Contributors { get; set; } = Contributors.Empty; - - public AssignedPlan? Plan { get; set; } - - [JsonIgnore] - public DomainId UniqueId - { - get => Id; - } - - public override bool ApplyEvent(IEvent @event) + switch (@event.Payload) { - switch (@event) - { - case TeamCreated e: - { - Id = e.TeamId; - - SimpleMapper.Map(e, this); - return true; - } + case TeamCreated e: + newSnapshot = new Team { Id = e.TeamId, Name = e.Name }; + break; - case TeamUpdated e when Is.Change(Name, e.Name): - { - SimpleMapper.Map(e, this); - return true; - } + case TeamUpdated e: + newSnapshot = snapshot.Rename(e.Name); + break; - case TeamPlanChanged e when Is.Change(Plan?.PlanId, e.PlanId): - return UpdatePlan(e.ToPlan()); + case TeamPlanChanged e: + newSnapshot = snapshot.ChangePlan(new AssignedPlan(e.Actor, e.PlanId)); + break; - case TeamPlanReset e when Plan != null: - return UpdatePlan(null); + case TeamPlanReset e: + newSnapshot = snapshot.ChangePlan(null); + break; - case TeamContributorAssigned e: - return UpdateContributors(e, (e, c) => c.Assign(e.ContributorId, e.Role)); + case TeamContributorAssigned e: + newSnapshot = snapshot.UpdateContributors(e, (e, c) => c.Assign(e.ContributorId, e.Role)); + break; - case TeamContributorRemoved e: - return UpdateContributors(e, (e, c) => c.Remove(e.ContributorId)); - } - - return false; + case TeamContributorRemoved e: + newSnapshot = snapshot.UpdateContributors(e, (e, c) => c.Remove(e.ContributorId)); + break; } - private bool UpdateContributors(T @event, Func update) + if (ReferenceEquals(newSnapshot, snapshot)) { - var previous = Contributors; - - Contributors = update(@event, previous); - - return !ReferenceEquals(previous, Contributors); + return snapshot; } - private bool UpdatePlan(AssignedPlan? plan) - { - Plan = plan; - - return true; - } + return newSnapshot.Apply(@event.To()); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Teams/DomainObject/TeamDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Teams/DomainObject/TeamDomainObject.cs index dd25ce74d..9ce824934 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Teams/DomainObject/TeamDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Teams/DomainObject/TeamDomainObject.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities.Billing; using Squidex.Domain.Apps.Entities.Teams.Commands; using Squidex.Domain.Apps.Entities.Teams.DomainObject.Guards; @@ -23,18 +24,18 @@ using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.Teams.DomainObject; -public partial class TeamDomainObject : DomainObject +public partial class TeamDomainObject : DomainObject { private readonly IServiceProvider serviceProvider; - public TeamDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log, + public TeamDomainObject(DomainId id, IPersistenceFactory persistence, ILogger log, IServiceProvider serviceProvider) : base(id, persistence, log) { this.serviceProvider = serviceProvider; } - protected override bool IsDeleted(State snapshot) + protected override bool IsDeleted(Team snapshot) { return false; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Teams/ITeamEntity.cs b/backend/src/Squidex.Domain.Apps.Entities/Teams/ITeamEntity.cs deleted file mode 100644 index e7b635865..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Teams/ITeamEntity.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core; - -namespace Squidex.Domain.Apps.Entities.Teams; - -public interface ITeamEntity : - IEntity, - IEntityWithCreatedBy, - IEntityWithLastModifiedBy, - IEntityWithVersion -{ - string Name { get; } - - Contributors Contributors { get; } - - AssignedPlan? Plan { get; } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Teams/Indexes/ITeamsIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Teams/Indexes/ITeamsIndex.cs index d910ea401..95d61984a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Teams/Indexes/ITeamsIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Teams/Indexes/ITeamsIndex.cs @@ -5,15 +5,16 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Teams; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Teams.Indexes; public interface ITeamsIndex { - Task GetTeamAsync(DomainId id, + Task GetTeamAsync(DomainId id, CancellationToken ct = default); - Task> GetTeamsAsync(string userId, + Task> GetTeamsAsync(string userId, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Teams/Indexes/TeamsIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Teams/Indexes/TeamsIndex.cs index 20446b7a9..82d125f58 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Teams/Indexes/TeamsIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Teams/Indexes/TeamsIndex.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities.Teams.Repositories; using Squidex.Infrastructure; @@ -19,7 +20,7 @@ public sealed class TeamsIndex : ITeamsIndex this.teamRepository = teamRepository; } - public async Task GetTeamAsync(DomainId id, + public async Task GetTeamAsync(DomainId id, CancellationToken ct = default) { using (var activity = Telemetry.Activities.StartActivity("TeamsIndex/GetTeamAsync")) @@ -32,7 +33,7 @@ public sealed class TeamsIndex : ITeamsIndex } } - public async Task> GetTeamsAsync(string userId, + public async Task> GetTeamsAsync(string userId, CancellationToken ct = default) { using (var activity = Telemetry.Activities.StartActivity("TeamsIndex/GetTeamsAsync")) @@ -45,7 +46,7 @@ public sealed class TeamsIndex : ITeamsIndex } } - private static bool IsValid(ITeamEntity? rule) + private static bool IsValid(Team? rule) { return rule is { Version: > EtagVersion.Empty }; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Teams/Repositories/ITeamRepository.cs b/backend/src/Squidex.Domain.Apps.Entities/Teams/Repositories/ITeamRepository.cs index 6ca1d731d..1fbebe43c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Teams/Repositories/ITeamRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Teams/Repositories/ITeamRepository.cs @@ -5,15 +5,16 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Teams; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Teams.Repositories; public interface ITeamRepository { - Task> QueryAllAsync(string contributorId, + Task> QueryAllAsync(string contributorId, CancellationToken ct = default); - Task FindAsync(DomainId id, + Task FindAsync(DomainId id, CancellationToken ct = default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Teams/TeamExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Teams/TeamExtensions.cs index 10f835eb7..415013b80 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Teams/TeamExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Teams/TeamExtensions.cs @@ -6,12 +6,13 @@ // ========================================================================== using System.Diagnostics.CodeAnalysis; +using Squidex.Domain.Apps.Core.Teams; namespace Squidex.Domain.Apps.Entities.Teams; public static class TeamExtensions { - public static bool TryGetContributorRole(this ITeamEntity app, string id, [MaybeNullWhen(false)] out string role) + public static bool TryGetContributorRole(this Team app, string id, [MaybeNullWhen(false)] out string role) { return app.Contributors.TryGetValue(id, out role); } diff --git a/backend/src/Squidex.Domain.Apps.Events/Apps/AppPlanChanged.cs b/backend/src/Squidex.Domain.Apps.Events/Apps/AppPlanChanged.cs index e7286bd86..2db482427 100644 --- a/backend/src/Squidex.Domain.Apps.Events/Apps/AppPlanChanged.cs +++ b/backend/src/Squidex.Domain.Apps.Events/Apps/AppPlanChanged.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Core; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Events.Apps; @@ -14,9 +13,4 @@ namespace Squidex.Domain.Apps.Events.Apps; public sealed class AppPlanChanged : AppEvent { public string PlanId { get; set; } - - public AssignedPlan ToPlan() - { - return new AssignedPlan(Actor, PlanId); - } } diff --git a/backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreatedFieldBase.cs b/backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreatedFieldBase.cs index 241046be6..2f97fe4b2 100644 --- a/backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreatedFieldBase.cs +++ b/backend/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreatedFieldBase.cs @@ -9,7 +9,7 @@ using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Events.Schemas; -public abstract class SchemaCreatedFieldBase : IFieldSettings +public abstract class SchemaCreatedFieldBase { public string Name { get; set; } diff --git a/backend/src/Squidex.Domain.Apps.Events/Teams/TeamPlanChanged.cs b/backend/src/Squidex.Domain.Apps.Events/Teams/TeamPlanChanged.cs index fe09e111a..73efbb9af 100644 --- a/backend/src/Squidex.Domain.Apps.Events/Teams/TeamPlanChanged.cs +++ b/backend/src/Squidex.Domain.Apps.Events/Teams/TeamPlanChanged.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Core; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Events.Teams; @@ -14,9 +13,4 @@ namespace Squidex.Domain.Apps.Events.Teams; public sealed class TeamPlanChanged : TeamEvent { public string PlanId { get; set; } - - public AssignedPlan ToPlan() - { - return new AssignedPlan(Actor, PlanId); - } } diff --git a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStoreSubscription.cs b/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStoreSubscription.cs index 2e6177045..435800dde 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStoreSubscription.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStoreSubscription.cs @@ -119,7 +119,7 @@ public sealed class MongoEventStoreSubscription : IEventSubscription if (timeToNow <= Duration.FromMinutes(5)) { - cts.Cancel(); + await cts.CancelAsync(); } else { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonDomainIdSerializer.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonDomainIdSerializer.cs index 59df7d314..b3edb9561 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonDomainIdSerializer.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonDomainIdSerializer.cs @@ -13,16 +13,15 @@ namespace Squidex.Infrastructure.MongoDb; public sealed class BsonDomainIdSerializer : SerializerBase, IBsonPolymorphicSerializer, IRepresentationConfigurable { + private static readonly BsonDomainIdSerializer Instance = new BsonDomainIdSerializer(); + public static void Register() { - try - { - BsonSerializer.RegisterSerializer(new BsonDomainIdSerializer()); - } - catch (BsonSerializationException) - { - return; - } + BsonSerializer.TryRegisterSerializer(Instance); + } + + private BsonDomainIdSerializer() + { } public bool IsDiscriminatorCompatibleWithObjectSerializer diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonEscapedDictionarySerializer.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonEscapedDictionarySerializer.cs index 0511ba9f3..e234c4daf 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonEscapedDictionarySerializer.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonEscapedDictionarySerializer.cs @@ -14,16 +14,15 @@ namespace Squidex.Infrastructure.MongoDb; public sealed class BsonEscapedDictionarySerializer : ClassSerializerBase where TInstance : Dictionary, new() { + private static readonly BsonEscapedDictionarySerializer Instance = new BsonEscapedDictionarySerializer(); + public static void Register() { - try - { - BsonSerializer.RegisterSerializer(new BsonEscapedDictionarySerializer()); - } - catch (BsonSerializationException) - { - return; - } + BsonSerializer.TryRegisterSerializer(Instance); + } + + private BsonEscapedDictionarySerializer() + { } protected override TInstance DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args) diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonInstantSerializer.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonInstantSerializer.cs index 9aa0a4566..171aa0bac 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonInstantSerializer.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonInstantSerializer.cs @@ -15,16 +15,11 @@ namespace Squidex.Infrastructure.MongoDb; public sealed class BsonInstantSerializer : SerializerBase, IBsonPolymorphicSerializer, IRepresentationConfigurable { + private static readonly BsonInstantSerializer Instance = new BsonInstantSerializer(); + public static void Register() { - try - { - BsonSerializer.RegisterSerializer(new BsonInstantSerializer()); - } - catch (BsonSerializationException) - { - return; - } + BsonSerializer.TryRegisterSerializer(Instance); } public bool IsDiscriminatorCompatibleWithObjectSerializer @@ -34,7 +29,7 @@ public sealed class BsonInstantSerializer : SerializerBase, IBsonPolymo public BsonType Representation { get; } - public BsonInstantSerializer() + private BsonInstantSerializer() : this(BsonType.DateTime) { } diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonValueSerializer.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonValueSerializer.cs index f31c54714..8cecf16fc 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonValueSerializer.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonValueSerializer.cs @@ -14,16 +14,15 @@ namespace Squidex.Infrastructure.MongoDb; public sealed class BsonJsonValueSerializer : SerializerBase { + private static readonly BsonJsonValueSerializer Instance = new BsonJsonValueSerializer(); + public static void Register() { - try - { - BsonSerializer.RegisterSerializer(new BsonJsonValueSerializer()); - } - catch (BsonSerializationException) - { - return; - } + BsonSerializer.TryRegisterSerializer(Instance); + } + + private BsonJsonValueSerializer() + { } public override JsonValue Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonStringSerializer.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonStringSerializer.cs index 5b958132f..9648033fc 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonStringSerializer.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonStringSerializer.cs @@ -14,18 +14,16 @@ namespace Squidex.Infrastructure.MongoDb; public sealed class BsonStringSerializer : SerializerBase { + private static readonly BsonStringSerializer Instance = new BsonStringSerializer(); private readonly TypeConverter typeConverter = TypeDescriptor.GetConverter(typeof(T)); public static void Register() { - try - { - BsonSerializer.RegisterSerializer(new BsonStringSerializer()); - } - catch (BsonSerializationException) - { - return; - } + BsonSerializer.TryRegisterSerializer(Instance); + } + + private BsonStringSerializer() + { } public override T Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/IVersionedEntity.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/IVersionedEntity.cs index 8d5c7f724..067c06a1e 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/IVersionedEntity.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/IVersionedEntity.cs @@ -9,7 +9,7 @@ namespace Squidex.Infrastructure.MongoDb; public interface IVersionedEntity { - T DocumentId { get; set; } + T DocumentId { get; } - long Version { get; set; } + long Version { get; } } diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs index c1fb92369..b32ed225d 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs @@ -9,6 +9,7 @@ using System.Globalization; using System.Linq.Expressions; using System.Runtime.CompilerServices; using MongoDB.Bson; +using MongoDB.Bson.Serialization; using MongoDB.Driver; using Squidex.Infrastructure.States; @@ -119,22 +120,21 @@ public static class MongoExtensions } } - public static async Task UpsertVersionedAsync(this IMongoCollection collection, IClientSessionHandle session, SnapshotWriteJob job, + public static async Task UpsertVersionedAsync(this IMongoCollection collection, IClientSessionHandle session, SnapshotWriteJob job, string versionField, CancellationToken ct = default) where T : IVersionedEntity { - var field2 = Field.Of(x => nameof(x.Version)); + var filters = Builders.Filter; - var (key, snapshot, newVersion, oldVersion) = job; + var (key, snapshot, _, oldVersion) = job; try { - snapshot.DocumentId = key; - snapshot.Version = newVersion; + var filter = filters.Eq("_id", key); - Expression> filter = - oldVersion > EtagVersion.Any ? - x => x.DocumentId.Equals(key) && x.Version == oldVersion : - x => x.DocumentId.Equals(key); + if (oldVersion > EtagVersion.Any) + { + filter = filters.And(filters.Eq(versionField, oldVersion)); + } var result = await collection.ReplaceOneAsync(session, filter, job.Value, UpsertReplace, ct); @@ -143,7 +143,7 @@ public static class MongoExtensions catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey) { var existingVersion = - await collection.Find(session, x => x.DocumentId.Equals(key)).Only(x => x.DocumentId, x => x.Version) + await collection.Find(session, filters.Eq("_id", key)).Project(Builders.Projection.Include("_id").Include(versionField)) .FirstOrDefaultAsync(ct); if (existingVersion != null) @@ -159,31 +159,42 @@ public static class MongoExtensions } } - public static async Task UpsertVersionedAsync(this IMongoCollection collection, SnapshotWriteJob job, + public static async Task UpsertVersionedAsync(this IMongoCollection collection, SnapshotWriteJob job, string versionField, CancellationToken ct = default) where T : IVersionedEntity { - var field2 = Field.Of(x => nameof(x.Version)); + var filters = Builders.Filter; - var (key, snapshot, newVersion, oldVersion) = job; + var (key, snapshot, _, oldVersion) = job; try { - snapshot.DocumentId = key; - snapshot.Version = newVersion; - Expression> filter = + Expression> filter2 = oldVersion > EtagVersion.Any ? x => x.DocumentId.Equals(key) && x.Version == oldVersion : x => x.DocumentId.Equals(key); - var result = await collection.ReplaceOneAsync(filter, snapshot, UpsertReplace, ct); + var filter = filters.Eq(x => x.DocumentId, key); + + if (oldVersion > EtagVersion.Any) + { + filter = filters.And(filter, filters.Eq(versionField, oldVersion)); + } + + var rendered = + filter.Render( + BsonSerializer.SerializerRegistry.GetSerializer(), + BsonSerializer.SerializerRegistry) + .ToString(); + + var result = await collection.ReplaceOneAsync(filter, job.Value, UpsertReplace, ct); return result.IsAcknowledged && result.ModifiedCount == 1; } catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey) { var existingVersion = - await collection.Find(x => x.DocumentId.Equals(key)).Only(x => x.DocumentId, x => x.Version) + await collection.Find(filters.Eq("_id", key)).Project(Builders.Projection.Include("_id").Include(versionField)) .FirstOrDefaultAsync(ct); if (existingVersion != null) @@ -245,11 +256,10 @@ public static class MongoExtensions } var idDocuments = await find.Project(Builders.Projection.Include("_id")).ToListAsync(ct); - var idValues = idDocuments.Select(x => x["_id"]); + var idsOrdered = idDocuments.Select(x => x["_id"]); + var idsRandom = idsOrdered.TakeRandom(take); - var randomIds = idValues.TakeRandom(take); - - var documents = await collection.Find(Builders.Filter.In("_id", randomIds)).ToListAsync(ct); + var documents = await collection.Find(Builders.Filter.In("_id", idsRandom)).ToListAsync(ct); return documents.Shuffle().ToList(); } diff --git a/backend/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStoreBase.cs b/backend/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStoreBase.cs index 3eea39266..503316a41 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStoreBase.cs +++ b/backend/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStoreBase.cs @@ -57,7 +57,10 @@ public abstract class MongoSnapshotStoreBase : MongoRepositoryBase>(x => nameof(x.Version)), ct); } } diff --git a/backend/src/Squidex.Infrastructure/CollectionExtensions.cs b/backend/src/Squidex.Infrastructure/CollectionExtensions.cs index e0944972d..0f1a00d36 100644 --- a/backend/src/Squidex.Infrastructure/CollectionExtensions.cs +++ b/backend/src/Squidex.Infrastructure/CollectionExtensions.cs @@ -13,6 +13,20 @@ namespace Squidex.Infrastructure; public static class CollectionExtensions { + public static async Task> SelectAsync(this IEnumerable source, Func> selector) + { + var initialCapacity = source is IReadOnlyCollection collection ? collection.Count : 1; + + var result = new List(initialCapacity); + + foreach (var item in source) + { + result.Add(await selector(item)); + } + + return result; + } + public static bool TryAdd(this IReadOnlyDictionary source, TKey key, TValue value, [MaybeNullWhen(false)] out Dictionary result) where TKey : notnull { result = null; diff --git a/backend/src/Squidex.Infrastructure/Collections/ReadonlyList.cs b/backend/src/Squidex.Infrastructure/Collections/ReadonlyList.cs index df00a7962..07aaadbb5 100644 --- a/backend/src/Squidex.Infrastructure/Collections/ReadonlyList.cs +++ b/backend/src/Squidex.Infrastructure/Collections/ReadonlyList.cs @@ -23,7 +23,7 @@ public static class ReadonlyList public static ReadonlyList Create(params T[]? items) { - if (items == null || items.Length == 0) + if (items is not { Length: > 0 }) { return Empty(); } diff --git a/backend/src/Squidex.Infrastructure/Commands/CachingDomainObjectMiddleware.cs b/backend/src/Squidex.Infrastructure/Commands/CachingDomainObjectMiddleware.cs index a8e060d7d..3b744dd3e 100644 --- a/backend/src/Squidex.Infrastructure/Commands/CachingDomainObjectMiddleware.cs +++ b/backend/src/Squidex.Infrastructure/Commands/CachingDomainObjectMiddleware.cs @@ -8,7 +8,7 @@ namespace Squidex.Infrastructure.Commands; public class CachingDomainObjectMiddleware : AggregateCommandMiddleware - where TCommand : IAggregateCommand where T : DomainObject where TState : class, IDomainState, new() + where TCommand : IAggregateCommand where T : DomainObject where TState : Entity, new() { private readonly IDomainObjectCache domainObjectCache; diff --git a/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs b/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs index 3211777d9..e033bba41 100644 --- a/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs +++ b/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs @@ -14,7 +14,7 @@ using Squidex.Infrastructure.States; namespace Squidex.Infrastructure.Commands; -public abstract partial class DomainObject : IAggregate where T : class, IDomainState, new() +public abstract partial class DomainObject : IAggregate where T : Entity, new() { private readonly List> uncomittedEvents = []; private readonly ILogger log; @@ -75,9 +75,7 @@ public abstract partial class DomainObject : IAggregate where T : class, IDom persistence = persistenceFactory.WithSnapshotsAndEventSourcing(GetType(), UniqueId, new HandleSnapshot((newSnapshot, version) => { - newSnapshot.Version = version; - - snapshot = newSnapshot; + snapshot = newSnapshot with { Version = version }; }), @event => { @@ -356,37 +354,37 @@ public abstract partial class DomainObject : IAggregate where T : class, IDom return false; } - private (T?, bool Success) ApplyEvent(Envelope @event, T snapshot, long version, bool loading, bool update) + private (T?, bool Success) ApplyEvent(Envelope @event, T previousSnapshot, long version, bool loading, bool update) { - if (IsDeleted(snapshot)) + if (IsDeleted(previousSnapshot)) { if (!IsRecreation(@event.Payload)) { return default; } - snapshot = new T + previousSnapshot = new T { Version = Version }; } - @event = @event.Migrate(snapshot); + @event = @event.Migrate(previousSnapshot); var newVersion = version + 1; - var newSnapshot = Apply(snapshot, @event); + var newSnapshot = Apply(previousSnapshot, @event); - var isChanged = loading || !ReferenceEquals(snapshot, newSnapshot); + var isChanged = loading || !ReferenceEquals(previousSnapshot, newSnapshot); // If we are loading events at the moment, we will always update the version. if (isChanged) { - newSnapshot.Version = newVersion; + newSnapshot = newSnapshot with { Version = newVersion }; } if (update) { - this.snapshot = newSnapshot; + snapshot = newSnapshot; } return (newSnapshot, isChanged); @@ -417,10 +415,7 @@ public abstract partial class DomainObject : IAggregate where T : class, IDom await persistence.WriteSnapshotAsync(Snapshot, ct); } - protected virtual T Apply(T snapshot, Envelope @event) - { - return snapshot.Apply(@event); - } + protected abstract T Apply(T snapshot, Envelope @event); public abstract Task ExecuteAsync(IAggregateCommand command, CancellationToken ct); diff --git a/backend/src/Squidex.Infrastructure/Commands/IDomainState.cs b/backend/src/Squidex.Infrastructure/Commands/Entity.cs similarity index 51% rename from backend/src/Squidex.Infrastructure/Commands/IDomainState.cs rename to backend/src/Squidex.Infrastructure/Commands/Entity.cs index e7aa82729..c69e67cb5 100644 --- a/backend/src/Squidex.Infrastructure/Commands/IDomainState.cs +++ b/backend/src/Squidex.Infrastructure/Commands/Entity.cs @@ -5,13 +5,26 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Infrastructure.EventSourcing; +using NodaTime; namespace Squidex.Infrastructure.Commands; -public interface IDomainState +public abstract record Entity { - long Version { get; set; } + public DomainId Id { get; init; } - T Apply(Envelope @event); + public RefToken CreatedBy { get; init; } + + public RefToken LastModifiedBy { get; init; } + + public Instant Created { get; init; } + + public Instant LastModified { get; init; } + + public long Version { get; init; } = EtagVersion.Empty; + + public virtual DomainId UniqueId + { + get => Id; + } } diff --git a/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs b/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs index c5fa61ad1..1c1e62c5b 100644 --- a/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs +++ b/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs @@ -38,7 +38,7 @@ public class Rebuilder public virtual async Task RebuildStateAsync(DomainId id, CancellationToken ct = default) - where T : DomainObject where TState : class, IDomainState, new() + where T : DomainObject where TState : Entity, new() { var store = serviceProvider.GetRequiredService>(); @@ -51,14 +51,14 @@ public class Rebuilder public virtual Task RebuildAsync(StreamFilter filter, int batchSize, CancellationToken ct = default) - where T : DomainObject where TState : class, IDomainState, new() + where T : DomainObject where TState : Entity, new() { return RebuildAsync(filter, batchSize, 0, ct); } public virtual async Task RebuildAsync(StreamFilter filter, int batchSize, double errorThreshold, CancellationToken ct = default) - where T : DomainObject where TState : class, IDomainState, new() + where T : DomainObject where TState : Entity, new() { await ClearAsync(); @@ -69,14 +69,14 @@ public class Rebuilder public virtual Task InsertManyAsync(IEnumerable source, int batchSize, CancellationToken ct = default) - where T : DomainObject where TState : class, IDomainState, new() + where T : DomainObject where TState : Entity, new() { return InsertManyAsync(source, batchSize, 0, ct); } public virtual async Task InsertManyAsync(IEnumerable source, int batchSize, double errorThreshold = 0, CancellationToken ct = default) - where T : DomainObject where TState : class, IDomainState, new() + where T : DomainObject where TState : Entity, new() { Guard.NotNull(source); @@ -87,7 +87,7 @@ public class Rebuilder private async Task InsertManyAsync(IAsyncEnumerable source, int batchSize, double errorThreshold, CancellationToken ct = default) - where T : DomainObject where TState : class, IDomainState, new() + where T : DomainObject where TState : Entity, new() { var store = serviceProvider.GetRequiredService>(); @@ -135,7 +135,7 @@ public class Rebuilder } } - private async Task ClearAsync() where TState : class, IDomainState, new() + private async Task ClearAsync() where TState : Entity, new() { var store = serviceProvider.GetRequiredService>(); diff --git a/backend/src/Squidex.Infrastructure/DisposableObjectBase.cs b/backend/src/Squidex.Infrastructure/DisposableObjectBase.cs index 6c196cb71..20db1fa45 100644 --- a/backend/src/Squidex.Infrastructure/DisposableObjectBase.cs +++ b/backend/src/Squidex.Infrastructure/DisposableObjectBase.cs @@ -43,9 +43,6 @@ public abstract class DisposableObjectBase : IDisposable protected void ThrowIfDisposed() { - if (isDisposed) - { - throw new ObjectDisposedException(GetType().Name); - } + ObjectDisposedException.ThrowIf(isDisposed, this); } } diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs b/backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs index de69af9c2..0e27d82c7 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs @@ -32,7 +32,7 @@ public static class EnvelopeExtensions public static Envelope SetEventStreamNumber(this Envelope envelope, long value) where T : class, IEvent { - envelope.Headers[CommonHeaders.EventStreamNumber] = (double)value; + envelope.Headers[CommonHeaders.EventStreamNumber] = value; return envelope; } diff --git a/backend/src/Squidex.Infrastructure/IWithId.cs b/backend/src/Squidex.Infrastructure/IWithId.cs deleted file mode 100644 index ced5759f2..000000000 --- a/backend/src/Squidex.Infrastructure/IWithId.cs +++ /dev/null @@ -1,13 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure; - -public interface IWithId -{ - T Id { get; } -} diff --git a/backend/src/Squidex.Infrastructure/Json/System/JsonIgnoreReadonlyProperties.cs b/backend/src/Squidex.Infrastructure/Json/System/JsonIgnoreReadonlyProperties.cs new file mode 100644 index 000000000..87f907c76 --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Json/System/JsonIgnoreReadonlyProperties.cs @@ -0,0 +1,35 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Reflection; +using System.Text.Json.Serialization.Metadata; + +namespace Squidex.Infrastructure.Json.System; + +public static class JsonIgnoreReadonlyProperties +{ + public static Action Modifier() + { + return new Action(typeInfo => + { + if (!typeInfo.Type.IsAssignableTo(typeof(T))) + { + return; + } + + foreach (var property in typeInfo.Properties.ToList()) + { + var memberInfo = property.AttributeProvider as PropertyInfo; + + if (memberInfo?.CanWrite == false) + { + typeInfo.Properties.Remove(property); + } + } + }); + } +} diff --git a/backend/src/Squidex.Infrastructure/Json/System/JsonRenameAttribute.cs b/backend/src/Squidex.Infrastructure/Json/System/JsonRenameAttribute.cs new file mode 100644 index 000000000..db96a7566 --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Json/System/JsonRenameAttribute.cs @@ -0,0 +1,35 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Reflection; +using System.Text.Json.Serialization.Metadata; + +namespace Squidex.Infrastructure.Json.System; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public sealed class JsonRenameAttribute(string propertyName, string jsonName) : Attribute +{ + public string PropertyName { get; } = propertyName; + + public string JsonName { get; } = jsonName; + + public static void Modifier(JsonTypeInfo typeInfo) + { + var attributes = typeInfo.Type.GetCustomAttributes(); + + foreach (var property in typeInfo.Properties) + { + var memberName = (property.AttributeProvider as MemberInfo)?.Name; + + var attribute = attributes.FirstOrDefault(x => x.PropertyName == memberName); + if (attribute != null) + { + property.Name = attribute.JsonName; + } + } + } +} diff --git a/backend/src/Squidex.Infrastructure/Json/System/PolymorphicConverter.cs b/backend/src/Squidex.Infrastructure/Json/System/PolymorphicConverter.cs index 20c34f30a..82e11f9d7 100644 --- a/backend/src/Squidex.Infrastructure/Json/System/PolymorphicConverter.cs +++ b/backend/src/Squidex.Infrastructure/Json/System/PolymorphicConverter.cs @@ -7,6 +7,7 @@ using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using Squidex.Infrastructure.Reflection; namespace Squidex.Infrastructure.Json.System; @@ -27,6 +28,32 @@ public sealed class PolymorphicConverter : JsonConverter where T : class discriminatorProperty = JsonEncodedText.Encode(discriminatorName); } + public static Action Modifier(TypeRegistry typeRegistry) + { + return new Action(typeInfo => + { + var baseType = typeInfo.Type.BaseType; + + while (baseType != null) + { + if (typeRegistry.TryGetConfig(baseType, out var config) && config.TryGetName(typeInfo.Type, out var typeName)) + { + var discriminiatorName = config.DiscriminatorProperty ?? Constants.DefaultDiscriminatorProperty; + var discriminatorField = typeInfo.CreateJsonPropertyInfo(typeof(string), discriminiatorName); + + discriminatorField.Get = x => + { + return typeName; + }; + + typeInfo.Properties.Insert(0, discriminatorField); + } + + baseType = baseType.BaseType; + } + }); + } + public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // Creating a copy of the reader (The derived deserialisation has to be done from the start) @@ -78,19 +105,9 @@ public sealed class PolymorphicConverter : JsonConverter where T : class public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { - EnsureTypeResolver(options); - JsonSerializer.Serialize(writer, value!, options); } - private static void EnsureTypeResolver(JsonSerializerOptions options) - { - if (options.TypeInfoResolver is not PolymorphicTypeResolver) - { - ThrowHelper.JsonException($"TypeInfoResolver must be of type PolymorphicTypeResolver."); - } - } - private Type GetDiscriminatorType(string name) { if (!typeRegistry.TryGetType(name, out var type)) diff --git a/backend/src/Squidex.Infrastructure/Json/System/PolymorphicTypeResolver.cs b/backend/src/Squidex.Infrastructure/Json/System/PolymorphicTypeResolver.cs deleted file mode 100644 index d9c87461b..000000000 --- a/backend/src/Squidex.Infrastructure/Json/System/PolymorphicTypeResolver.cs +++ /dev/null @@ -1,51 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Text.Json; -using System.Text.Json.Serialization.Metadata; -using Squidex.Infrastructure.Reflection; - -namespace Squidex.Infrastructure.Json.System; - -public sealed class PolymorphicTypeResolver : DefaultJsonTypeInfoResolver -{ - private readonly TypeRegistry typeRegistry; - - public PolymorphicTypeResolver(TypeRegistry typeRegistry) - { - Guard.NotNull(typeRegistry); - - this.typeRegistry = typeRegistry; - } - - public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) - { - var typeInfo = base.GetTypeInfo(type, options); - - var baseType = type.BaseType; - - while (baseType != null) - { - if (typeRegistry.TryGetConfig(baseType, out var config) && config.TryGetName(type, out var typeName)) - { - var discriminiatorName = config.DiscriminatorProperty ?? Constants.DefaultDiscriminatorProperty; - var discriminatorField = typeInfo.CreateJsonPropertyInfo(typeof(string), discriminiatorName); - - discriminatorField.Get = x => - { - return typeName; - }; - - typeInfo.Properties.Insert(0, discriminatorField); - } - - baseType = baseType.BaseType; - } - - return typeInfo; - } -} diff --git a/backend/src/Squidex.Infrastructure/Queries/FilterSchema.cs b/backend/src/Squidex.Infrastructure/Queries/FilterSchema.cs index effd19dac..a1e83f227 100644 --- a/backend/src/Squidex.Infrastructure/Queries/FilterSchema.cs +++ b/backend/src/Squidex.Infrastructure/Queries/FilterSchema.cs @@ -31,7 +31,7 @@ public sealed record FilterSchema(FilterSchemaType Type) public FilterSchema Flatten(int maxDepth = 7, Predicate? predicate = null) { - if (Fields == null || Fields.Count == 0) + if (Fields is not { Count: > 0 }) { return this; } diff --git a/backend/src/Squidex.Infrastructure/Queries/PropertyPath.cs b/backend/src/Squidex.Infrastructure/Queries/PropertyPath.cs index fd630871e..9259ad78f 100644 --- a/backend/src/Squidex.Infrastructure/Queries/PropertyPath.cs +++ b/backend/src/Squidex.Infrastructure/Queries/PropertyPath.cs @@ -97,7 +97,7 @@ public sealed class PropertyPath : ReadonlyList, IEquatable { var inner = source?.ToList(); - if (inner == null || inner.Count == 0) + if (inner is not { Count: > 0 }) { ThrowHelper.ArgumentException("Path cannot be empty.", nameof(source)); return null!; diff --git a/backend/src/Squidex.Infrastructure/States/BatchContext.cs b/backend/src/Squidex.Infrastructure/States/BatchContext.cs index e47222bc7..b400ff849 100644 --- a/backend/src/Squidex.Infrastructure/States/BatchContext.cs +++ b/backend/src/Squidex.Infrastructure/States/BatchContext.cs @@ -88,7 +88,7 @@ public sealed class BatchContext : IBatchContext { var current = Interlocked.Exchange(ref snapshots, null!); - if (current == null || current.Count == 0) + if (current is not { Count: > 0 }) { return Task.CompletedTask; } diff --git a/backend/src/Squidex.Infrastructure/States/Persistence.cs b/backend/src/Squidex.Infrastructure/States/Persistence.cs index f5886ad0a..1603cd719 100644 --- a/backend/src/Squidex.Infrastructure/States/Persistence.cs +++ b/backend/src/Squidex.Infrastructure/States/Persistence.cs @@ -125,19 +125,19 @@ internal sealed class Persistence : IPersistence private async Task ReadSnapshotAsync( CancellationToken ct) { - var (_, state, version, valid) = await snapshotStore.ReadAsync(ownerKey, ct); + var (_, state, versionRead, valid) = await snapshotStore.ReadAsync(ownerKey, ct); - version = Math.Max(version, EtagVersion.Empty); - versionSnapshot = version; + versionRead = Math.Max(versionRead, EtagVersion.Empty); + versionSnapshot = versionRead; if (valid) { - versionEvents = version; + versionEvents = versionRead; } - if (applyState != null && version > EtagVersion.Empty && valid) + if (applyState != null && versionRead > EtagVersion.Empty && valid) { - applyState(state, version); + applyState(state, versionRead); } } diff --git a/backend/src/Squidex.Infrastructure/StringExtensions.cs b/backend/src/Squidex.Infrastructure/StringExtensions.cs index d50377a9f..40504f98d 100644 --- a/backend/src/Squidex.Infrastructure/StringExtensions.cs +++ b/backend/src/Squidex.Infrastructure/StringExtensions.cs @@ -49,7 +49,7 @@ public static partial class StringExtensions { Guard.NotNull(separator); - if (parts == null || parts.Length == 0) + if (parts is not { Length: > 0 }) { return string.Empty; } diff --git a/backend/src/Squidex.Infrastructure/Timers/CompletionTimer.cs b/backend/src/Squidex.Infrastructure/Timers/CompletionTimer.cs index 0867f7c2a..231a24f3c 100644 --- a/backend/src/Squidex.Infrastructure/Timers/CompletionTimer.cs +++ b/backend/src/Squidex.Infrastructure/Timers/CompletionTimer.cs @@ -30,11 +30,10 @@ public sealed class CompletionTimer runTask = RunInternalAsync(delayInMs, initialDelay, callback); } - public Task StopAsync() + public async Task StopAsync() { - stopToken.Cancel(); - - return runTask; + await stopToken.CancelAsync(); + await runTask; } public void SkipCurrentDelay() diff --git a/backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs b/backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs index 054473dea..18392d337 100644 --- a/backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs +++ b/backend/src/Squidex.Shared/Identity/SquidexClaimsExtensions.cs @@ -5,10 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Security.Claims; using System.Text.Json; using System.Text.RegularExpressions; diff --git a/backend/src/Squidex.Shared/PermissionExtensions.cs b/backend/src/Squidex.Shared/PermissionExtensions.cs index 9c2c3b6b2..1cb24806c 100644 --- a/backend/src/Squidex.Shared/PermissionExtensions.cs +++ b/backend/src/Squidex.Shared/PermissionExtensions.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Linq; using Squidex.Infrastructure.Security; namespace Squidex.Shared; diff --git a/backend/src/Squidex.Shared/PermissionIds.cs b/backend/src/Squidex.Shared/PermissionIds.cs index cb31e887c..576702f2b 100644 --- a/backend/src/Squidex.Shared/PermissionIds.cs +++ b/backend/src/Squidex.Shared/PermissionIds.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; diff --git a/backend/src/Squidex.Shared/Users/ClientUser.cs b/backend/src/Squidex.Shared/Users/ClientUser.cs index f25f12e3e..7b5aca107 100644 --- a/backend/src/Squidex.Shared/Users/ClientUser.cs +++ b/backend/src/Squidex.Shared/Users/ClientUser.cs @@ -5,8 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using System.Collections.Generic; using System.Security.Claims; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; diff --git a/backend/src/Squidex.Shared/Users/IUser.cs b/backend/src/Squidex.Shared/Users/IUser.cs index 3b7822470..6ed81e1ab 100644 --- a/backend/src/Squidex.Shared/Users/IUser.cs +++ b/backend/src/Squidex.Shared/Users/IUser.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; using System.Security.Claims; namespace Squidex.Shared.Users; diff --git a/backend/src/Squidex.Shared/Users/IUserResolver.cs b/backend/src/Squidex.Shared/Users/IUserResolver.cs index 73ba66209..191f1b565 100644 --- a/backend/src/Squidex.Shared/Users/IUserResolver.cs +++ b/backend/src/Squidex.Shared/Users/IUserResolver.cs @@ -5,10 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - namespace Squidex.Shared.Users; public interface IUserResolver diff --git a/backend/src/Squidex.Web/ApiController.cs b/backend/src/Squidex.Web/ApiController.cs index b9460989b..27a8522d1 100644 --- a/backend/src/Squidex.Web/ApiController.cs +++ b/backend/src/Squidex.Web/ApiController.cs @@ -6,10 +6,10 @@ // ========================================================================== using Microsoft.AspNetCore.Mvc; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Domain.Apps.Entities.Teams; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Security; @@ -29,11 +29,11 @@ public abstract class ApiController : Controller protected ICommandBus CommandBus { get; } - protected IAppEntity App + protected App App { get { - var app = HttpContext.Features.Get()?.App; + var app = HttpContext.Features.Get(); if (app == null) { @@ -45,11 +45,11 @@ public abstract class ApiController : Controller } } - protected ITeamEntity Team + protected Team Team { get { - var team = HttpContext.Features.Get()?.Team; + var team = HttpContext.Features.Get(); if (team == null) { @@ -61,11 +61,11 @@ public abstract class ApiController : Controller } } - protected ISchemaEntity Schema + protected Schema Schema { get { - var schema = HttpContext.Features.Get()?.Schema; + var schema = HttpContext.Features.Get(); if (schema == null) { diff --git a/backend/src/Squidex.Web/ApiPermissionAttribute.cs b/backend/src/Squidex.Web/ApiPermissionAttribute.cs index 643533ff8..18bc289d3 100644 --- a/backend/src/Squidex.Web/ApiPermissionAttribute.cs +++ b/backend/src/Squidex.Web/ApiPermissionAttribute.cs @@ -8,6 +8,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Infrastructure.Security; using Squidex.Shared; @@ -41,21 +44,21 @@ public class ApiPermissionAttribute : AuthorizeAttribute, IAsyncActionFilter { foreach (var id in permissionIds) { - var app = context.HttpContext.Features.Get()?.App.Name; + var app = context.HttpContext.Features.Get()?.Name; if (string.IsNullOrWhiteSpace(app)) { app = Permission.Any; } - var schema = context.HttpContext.Features.Get()?.Schema.SchemaDef.Name; + var schema = context.HttpContext.Features.Get()?.Name; if (string.IsNullOrWhiteSpace(schema)) { schema = Permission.Any; } - var team = context.HttpContext.Features.Get()?.Team.Id.ToString(); + var team = context.HttpContext.Features.Get()?.Id.ToString(); if (string.IsNullOrWhiteSpace(team)) { diff --git a/backend/src/Squidex.Web/CommandMiddlewares/ETagCommandMiddleware.cs b/backend/src/Squidex.Web/CommandMiddlewares/ETagCommandMiddleware.cs index e1e4386d7..9207d1e93 100644 --- a/backend/src/Squidex.Web/CommandMiddlewares/ETagCommandMiddleware.cs +++ b/backend/src/Squidex.Web/CommandMiddlewares/ETagCommandMiddleware.cs @@ -8,7 +8,6 @@ using System.Globalization; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; -using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -54,7 +53,7 @@ public class ETagCommandMiddleware : ICommandMiddleware { SetResponsEtag(httpContext, result.NewVersion); } - else if (context.PlainResult is IEntityWithVersion entity) + else if (context.PlainResult is Entity entity) { SetResponsEtag(httpContext, entity.Version); } diff --git a/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs b/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs index 6eaebe17f..379726b37 100644 --- a/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs +++ b/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; diff --git a/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs b/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs index af98e87d8..eff0e57e9 100644 --- a/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs +++ b/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs @@ -6,8 +6,8 @@ // ========================================================================== using Microsoft.AspNetCore.Http; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -42,14 +42,14 @@ public sealed class EnrichWithSchemaIdCommandMiddleware : ICommandMiddleware private NamedId GetSchemaId() { - var feature = httpContextAccessor.HttpContext?.Features.Get(); + var schema = httpContextAccessor.HttpContext?.Features.Get(); - if (feature == null) + if (schema == null) { ThrowHelper.InvalidOperationException("Cannot resolve schema."); return default!; } - return feature.Schema.NamedId(); + return schema.NamedId(); } } diff --git a/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithTeamIdCommandMiddleware.cs b/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithTeamIdCommandMiddleware.cs index 3b9f909bd..c4abc15f3 100644 --- a/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithTeamIdCommandMiddleware.cs +++ b/backend/src/Squidex.Web/CommandMiddlewares/EnrichWithTeamIdCommandMiddleware.cs @@ -6,6 +6,7 @@ // ========================================================================== using Microsoft.AspNetCore.Http; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -41,14 +42,14 @@ public sealed class EnrichWithTeamIdCommandMiddleware : ICommandMiddleware private DomainId GetTeamId() { - var feature = httpContextAccessor.HttpContext?.Features.Get(); + var team = httpContextAccessor.HttpContext?.Features.Get(); - if (feature == null) + if (team == null) { ThrowHelper.InvalidOperationException("Cannot resolve team."); return default!; } - return feature.Team.Id; + return team.Id; } } diff --git a/backend/src/Squidex.Web/ETagExtensions.cs b/backend/src/Squidex.Web/ETagExtensions.cs index 81a9b2755..6a9f178e1 100644 --- a/backend/src/Squidex.Web/ETagExtensions.cs +++ b/backend/src/Squidex.Web/ETagExtensions.cs @@ -9,14 +9,14 @@ using System.Globalization; using System.Net.Http.Headers; using System.Security.Cryptography; using Microsoft.AspNetCore.Http; -using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; namespace Squidex.Web; public static class ETagExtensions { - public static string ToEtag(this IReadOnlyList items) where T : IEntity, IEntityWithVersion + public static string ToEtag(this IReadOnlyList items) where T : Entity { using (Telemetry.Activities.StartActivity("CalculateEtag")) { @@ -26,7 +26,7 @@ public static class ETagExtensions } } - public static string ToEtag(this IResultList entities) where T : IEntity, IEntityWithVersion + public static string ToEtag(this IResultList entities) where T : Entity { using (Telemetry.Activities.StartActivity("CalculateEtag")) { @@ -36,7 +36,7 @@ public static class ETagExtensions } } - private static string Create(IReadOnlyList entities, long total) where T : IEntity, IEntityWithVersion + private static string Create(IReadOnlyList entities, long total) where T : Entity { using (var hasher = IncrementalHash.CreateHash(HashAlgorithmName.SHA256)) { @@ -52,7 +52,7 @@ public static class ETagExtensions } } - public static string ToEtag(this T entity) where T : IEntity, IEntityWithVersion + public static string ToEtag(this T entity) where T : Entity { return entity.Version.ToString(CultureInfo.InvariantCulture); } diff --git a/backend/src/Squidex.Web/FodyWeavers.xml b/backend/src/Squidex.Web/FodyWeavers.xml deleted file mode 100644 index 6ef705866..000000000 --- a/backend/src/Squidex.Web/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/backend/src/Squidex.Web/FodyWeavers.xsd b/backend/src/Squidex.Web/FodyWeavers.xsd deleted file mode 100644 index fe819e8ea..000000000 --- a/backend/src/Squidex.Web/FodyWeavers.xsd +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/backend/src/Squidex.Web/IAppFeature.cs b/backend/src/Squidex.Web/IAppFeature.cs deleted file mode 100644 index ae21db4c9..000000000 --- a/backend/src/Squidex.Web/IAppFeature.cs +++ /dev/null @@ -1,15 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Entities.Apps; - -namespace Squidex.Web; - -public interface IAppFeature -{ - IAppEntity App { get; } -} diff --git a/backend/src/Squidex.Web/ISchemaFeature.cs b/backend/src/Squidex.Web/ISchemaFeature.cs deleted file mode 100644 index 6bd785fb6..000000000 --- a/backend/src/Squidex.Web/ISchemaFeature.cs +++ /dev/null @@ -1,15 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Entities.Schemas; - -namespace Squidex.Web; - -public interface ISchemaFeature -{ - ISchemaEntity Schema { get; } -} diff --git a/backend/src/Squidex.Web/ITeamFeature.cs b/backend/src/Squidex.Web/ITeamFeature.cs deleted file mode 100644 index 49bf190ed..000000000 --- a/backend/src/Squidex.Web/ITeamFeature.cs +++ /dev/null @@ -1,15 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Entities.Teams; - -namespace Squidex.Web; - -public interface ITeamFeature -{ - ITeamEntity Team { get; } -} diff --git a/backend/src/Squidex.Web/Pipeline/AppResolver.cs b/backend/src/Squidex.Web/Pipeline/AppResolver.cs index d94b93236..9702da376 100644 --- a/backend/src/Squidex.Web/Pipeline/AppResolver.cs +++ b/backend/src/Squidex.Web/Pipeline/AppResolver.cs @@ -11,8 +11,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure.Security; using Squidex.Shared; using Squidex.Shared.Identity; @@ -113,7 +113,7 @@ public sealed class AppResolver : IAsyncActionFilter } context.HttpContext.Features.Set(requestContext); - context.HttpContext.Features.Set(new AppFeature(app)); + context.HttpContext.Features.Set(app); context.HttpContext.Response.Headers["X-AppId"] = app.Id.ToString(); } @@ -130,7 +130,7 @@ public sealed class AppResolver : IAsyncActionFilter return context.ActionDescriptor.EndpointMetadata.Any(x => x is AllowAnonymousAttribute); } - private static (string?, string?, PermissionSet?) FindByOpenIdClient(IAppEntity app, ClaimsPrincipal user, bool isFrontend) + private static (string?, string?, PermissionSet?) FindByOpenIdClient(App app, ClaimsPrincipal user, bool isFrontend) { var (appName, clientId) = user.GetClient(); @@ -147,7 +147,7 @@ public sealed class AppResolver : IAsyncActionFilter return default; } - private static (string?, string?, PermissionSet?) FindAnonymousClient(IAppEntity app, bool isFrontend) + private static (string?, string?, PermissionSet?) FindAnonymousClient(App app, bool isFrontend) { var client = app.Clients.FirstOrDefault(x => x.Value.AllowAnonymous); @@ -164,7 +164,7 @@ public sealed class AppResolver : IAsyncActionFilter return default; } - private static (string?, PermissionSet?) FindByOpenIdSubject(IAppEntity app, ClaimsPrincipal user, bool isFrontend) + private static (string?, PermissionSet?) FindByOpenIdSubject(App app, ClaimsPrincipal user, bool isFrontend) { var subjectId = user.OpenIdSubject(); diff --git a/backend/src/Squidex.Web/Pipeline/SchemaFeature.cs b/backend/src/Squidex.Web/Pipeline/SchemaFeature.cs deleted file mode 100644 index 469eec90a..000000000 --- a/backend/src/Squidex.Web/Pipeline/SchemaFeature.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Entities.Schemas; - -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter - -namespace Squidex.Web.Pipeline; - -public sealed record SchemaFeature(ISchemaEntity Schema) : ISchemaFeature; diff --git a/backend/src/Squidex.Web/Pipeline/SchemaResolver.cs b/backend/src/Squidex.Web/Pipeline/SchemaResolver.cs index af854c78a..0d2f113f8 100644 --- a/backend/src/Squidex.Web/Pipeline/SchemaResolver.cs +++ b/backend/src/Squidex.Web/Pipeline/SchemaResolver.cs @@ -8,8 +8,9 @@ using System.Security.Claims; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; using Squidex.Shared; @@ -29,9 +30,9 @@ public sealed class SchemaResolver : IAsyncActionFilter public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - var appId = context.HttpContext.Features.Get()?.App.Id ?? default; + var app = context.HttpContext.Features.Get(); - if (appId != default) + if (app != null) { if (context.RouteData.Values.TryGetValue("schema", out var schemaValue)) { @@ -43,7 +44,7 @@ public sealed class SchemaResolver : IAsyncActionFilter return; } - var schema = await GetSchemaAsync(appId, schemaIdOrName, context.HttpContext.User); + var schema = await GetSchemaAsync(app.Id, schemaIdOrName, context.HttpContext.User); if (schema == null) { @@ -51,20 +52,20 @@ public sealed class SchemaResolver : IAsyncActionFilter return; } - if (context.ActionDescriptor.EndpointMetadata.Any(x => x is SchemaMustBePublishedAttribute) && !schema.SchemaDef.IsPublished) + if (context.ActionDescriptor.EndpointMetadata.Any(x => x is SchemaMustBePublishedAttribute) && !schema.IsPublished) { context.Result = new NotFoundResult(); return; } - context.HttpContext.Features.Set(new SchemaFeature(schema)); + context.HttpContext.Features.Set(schema); } } await next(); } - private Task GetSchemaAsync(DomainId appId, string schemaIdOrName, ClaimsPrincipal user) + private Task GetSchemaAsync(DomainId appId, string schemaIdOrName, ClaimsPrincipal user) { var canCache = !user.IsInClient(DefaultClients.Frontend); diff --git a/backend/src/Squidex.Web/Pipeline/TeamFeature.cs b/backend/src/Squidex.Web/Pipeline/TeamFeature.cs deleted file mode 100644 index 3aae18d1e..000000000 --- a/backend/src/Squidex.Web/Pipeline/TeamFeature.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Entities.Teams; - -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter - -namespace Squidex.Web.Pipeline; - -public sealed record TeamFeature(ITeamEntity Team) : ITeamFeature; diff --git a/backend/src/Squidex.Web/Pipeline/TeamResolver.cs b/backend/src/Squidex.Web/Pipeline/TeamResolver.cs index 9c5528881..792a01ebf 100644 --- a/backend/src/Squidex.Web/Pipeline/TeamResolver.cs +++ b/backend/src/Squidex.Web/Pipeline/TeamResolver.cs @@ -85,7 +85,7 @@ public sealed class TeamResolver : IAsyncActionFilter } context.HttpContext.Features.Set(requestContext); - context.HttpContext.Features.Set(new TeamFeature(team)); + context.HttpContext.Features.Set(team); context.HttpContext.Response.Headers["X-TeamId"] = team.Id.ToString(); } diff --git a/backend/src/Squidex.Web/Pipeline/UsageMiddleware.cs b/backend/src/Squidex.Web/Pipeline/UsageMiddleware.cs index 5af2db810..ce433ff17 100644 --- a/backend/src/Squidex.Web/Pipeline/UsageMiddleware.cs +++ b/backend/src/Squidex.Web/Pipeline/UsageMiddleware.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using NodaTime; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Billing; using Squidex.Infrastructure; @@ -41,7 +42,7 @@ public sealed class UsageMiddleware : IMiddleware { if (context.Response.StatusCode != StatusCodes.Status429TooManyRequests) { - var app = context.Features.Get()?.App; + var app = context.Features.Get(); if (app != null) { diff --git a/backend/src/Squidex.Web/Resources.cs b/backend/src/Squidex.Web/Resources.cs index 1a5481370..85362c3f7 100644 --- a/backend/src/Squidex.Web/Resources.cs +++ b/backend/src/Squidex.Web/Resources.cs @@ -5,6 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; @@ -242,7 +244,7 @@ public sealed class Resources private string? GetSchemaName() { - return Controller.HttpContext.Features.Get()?.Schema.SchemaDef.Name; + return Controller.HttpContext.Features.Get()?.Name; } private DomainId GetAppId() @@ -252,6 +254,6 @@ public sealed class Resources private DomainId GetTeamId() { - return Controller.HttpContext.Features.Get()?.Team?.Id ?? default; + return Controller.HttpContext.Features.Get()?.Id ?? default; } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppAssetsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppAssetsController.cs index bfbc3ccc6..a23c4f8f8 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppAssetsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppAssetsController.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Squidex.Areas.Api.Controllers.Apps.Models; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure.Commands; using Squidex.Shared; using Squidex.Web; @@ -70,13 +70,13 @@ public sealed class AppAssetsController : ApiController { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - var result = context.Result(); + var result = context.Result(); var response = GetResponse(result); return response; } - private AssetScriptsDto GetResponse(IAppEntity result) + private AssetScriptsDto GetResponse(App result) { return AssetScriptsDto.FromDomain(result, Resources); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs index 6ccda9323..811346158 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Squidex.Areas.Api.Controllers.Apps.Models; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure.Commands; using Squidex.Shared; @@ -133,13 +133,13 @@ public sealed class AppClientsController : ApiController { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - var result = context.Result(); + var result = context.Result(); var response = GetResponse(result); return response; } - private ClientsDto GetResponse(IAppEntity app) + private ClientsDto GetResponse(App app) { return ClientsDto.FromApp(app, Resources); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs index 0a88d8d2d..d93091103 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs @@ -6,7 +6,7 @@ // ========================================================================== using Microsoft.AspNetCore.Mvc; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Billing; using Squidex.Domain.Apps.Entities.Invitation; @@ -119,17 +119,17 @@ public sealed class AppContributorsController : ApiController { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - if (context.PlainResult is InvitedResult invited) + if (context.PlainResult is InvitedResult invited) { return await GetResponseAsync(invited.Entity, true); } else { - return await GetResponseAsync(context.Result(), false); + return await GetResponseAsync(context.Result(), false); } } - private async Task GetResponseAsync(IAppEntity app, bool invited) + private async Task GetResponseAsync(App app, bool invited) { var (plan, _, _) = await usageGate.GetPlanForAppAsync(app, false, HttpContext.RequestAborted); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs index a033ba11a..312b9c2aa 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Squidex.Areas.Api.Controllers.Apps.Models; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -122,13 +122,13 @@ public sealed class AppLanguagesController : ApiController { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - var result = context.Result(); + var result = context.Result(); var response = GetResponse(result); return response; } - private AppLanguagesDto GetResponse(IAppEntity result) + private AppLanguagesDto GetResponse(App result) { return AppLanguagesDto.FromDomain(result, Resources); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs index 8ffb5351d..ada0c5464 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Squidex.Areas.Api.Controllers.Apps.Models; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; @@ -150,13 +151,13 @@ public sealed class AppRolesController : ApiController { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - var result = context.Result(); + var result = context.Result(); var response = GetResponse(result); return response; } - private RolesDto GetResponse(IAppEntity result) + private RolesDto GetResponse(App result) { return RolesDto.FromDomain(result, Resources); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppSettingsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppSettingsController.cs index 62f234770..f4227939f 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppSettingsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppSettingsController.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Squidex.Areas.Api.Controllers.Apps.Models; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure.Commands; using Squidex.Shared; using Squidex.Web; @@ -70,13 +70,13 @@ public sealed class AppSettingsController : ApiController { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - var result = context.Result(); + var result = context.Result(); var response = GetResponse(result); return response; } - private AppSettingsDto GetResponse(IAppEntity result) + private AppSettingsDto GetResponse(App result) { return AppSettingsDto.FromDomain(result, Resources); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppWorkflowsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppWorkflowsController.cs index 2117c3c30..be743dba7 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppWorkflowsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppWorkflowsController.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Squidex.Areas.Api.Controllers.Apps.Models; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Infrastructure; @@ -125,13 +125,13 @@ public sealed class AppWorkflowsController : ApiController { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - var result = context.Result(); + var result = context.Result(); var response = await GetResponse(result); return response; } - private async Task GetResponse(IAppEntity result) + private async Task GetResponse(App result) { return await WorkflowsDto.FromAppAsync(workflowsValidator, result, Resources); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index b6ea9ae8c..20c7567c0 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -8,8 +8,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Squidex.Areas.Api.Controllers.Apps.Models; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Security; @@ -246,11 +246,11 @@ public sealed class AppsController : ApiController }); } - private async Task InvokeCommandAsync(ICommand command, Func converter) + private async Task InvokeCommandAsync(ICommand command, Func converter) { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - var result = context.Result(); + var result = context.Result(); var response = converter(result); return response; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs index 619f1a8dd..4aa5c6572 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs @@ -12,7 +12,7 @@ using Squidex.Areas.Api.Controllers.Ping; using Squidex.Areas.Api.Controllers.Plans; using Squidex.Areas.Api.Controllers.Rules; using Squidex.Areas.Api.Controllers.Schemas; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Reflection; @@ -95,7 +95,7 @@ public sealed class AppDto : Resource /// public JsonObject RoleProperties { get; set; } - public static AppDto FromDomain(IAppEntity app, string userId, bool isFrontend, Resources resources) + public static AppDto FromDomain(App app, string userId, bool isFrontend, Resources resources) { var result = SimpleMapper.Map(app, new AppDto()); @@ -139,7 +139,7 @@ public sealed class AppDto : Resource return result.CreateLinks(app, resources, permissions, isContributor); } - private AppDto CreateLinks(IAppEntity app, Resources resources, PermissionSet permissions, bool isContributor) + private AppDto CreateLinks(App app, Resources resources, PermissionSet permissions, bool isContributor) { var values = new { app = Name }; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs index 60c59a3d8..da0438ec6 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs @@ -6,7 +6,6 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure; using Squidex.Web; @@ -53,7 +52,7 @@ public sealed class AppLanguageDto : Resource return result; } - public AppLanguageDto CreateLinks(Resources resources, IAppEntity app) + public AppLanguageDto CreateLinks(Resources resources, App app) { var values = new { app = resources.App, language = Iso2Code }; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs index 07cace53b..4ac978110 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models; @@ -17,7 +17,7 @@ public sealed class AppLanguagesDto : Resource /// public AppLanguageDto[] Items { get; set; } - public static AppLanguagesDto FromDomain(IAppEntity app, Resources resources) + public static AppLanguagesDto FromDomain(App app, Resources resources) { var config = app.Languages; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppSettingsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppSettingsDto.cs index 9a794d68b..887152bc7 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppSettingsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AppSettingsDto.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models; @@ -37,7 +37,7 @@ public sealed class AppSettingsDto : Resource /// public long Version { get; set; } - public static AppSettingsDto FromDomain(IAppEntity app, Resources resources) + public static AppSettingsDto FromDomain(App app, Resources resources) { var settings = app.Settings; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AssetScriptsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AssetScriptsDto.cs index 66f1d3917..f3a247b1b 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AssetScriptsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/AssetScriptsDto.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure.Reflection; using Squidex.Web; @@ -53,7 +53,7 @@ public sealed class AssetScriptsDto : Resource /// public long Version { get; set; } - public static AssetScriptsDto FromDomain(IAppEntity app, Resources resources) + public static AssetScriptsDto FromDomain(App app, Resources resources) { var result = SimpleMapper.Map(app.AssetScripts, new AssetScriptsDto()); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs index 51b36451f..810d05d23 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models; @@ -17,7 +17,7 @@ public sealed class ClientsDto : Resource /// public ClientDto[] Items { get; set; } - public static ClientsDto FromApp(IAppEntity app, Resources resources) + public static ClientsDto FromApp(App app, Resources resources) { var result = new ClientsDto { diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs index ab3bb3d5c..defbb9957 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs @@ -6,7 +6,6 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure.Json.Objects; using Squidex.Web; @@ -44,7 +43,7 @@ public sealed class RoleDto : Resource /// public JsonObject Properties { get; set; } - public static RoleDto FromDomain(Role role, IAppEntity app) + public static RoleDto FromDomain(Role role, App app) { var result = new RoleDto { @@ -59,12 +58,12 @@ public sealed class RoleDto : Resource return result; } - private static int GetNumContributors(Role role, IAppEntity app) + private static int GetNumContributors(Role role, App app) { return app.Contributors.Count(x => role.Equals(x.Value)); } - private static int GetNumClients(Role role, IAppEntity app) + private static int GetNumClients(Role role, App app) { return app.Clients.Count(x => role.Equals(x.Value.Role)); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs index c975d422c..c368c7a34 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models; @@ -17,7 +17,7 @@ public sealed class RolesDto : Resource /// public RoleDto[] Items { get; set; } - public static RolesDto FromDomain(IAppEntity app, Resources resources) + public static RolesDto FromDomain(App app, Resources resources) { var result = new RolesDto { diff --git a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowsDto.cs index a6f36d8f5..8fec9d53d 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowsDto.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Web; @@ -23,7 +23,7 @@ public sealed class WorkflowsDto : Resource /// public string[] Errors { get; set; } - public static async Task FromAppAsync(IWorkflowsValidator workflowsValidator, IAppEntity app, Resources resources) + public static async Task FromAppAsync(IWorkflowsValidator workflowsValidator, App app, Resources resources) { var result = new WorkflowsDto { diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs index aab2d0948..f46012baf 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs @@ -97,7 +97,7 @@ public sealed class AssetContentController : ApiController return await DeliverAssetAsync(requestContext, asset, request); } - private async Task DeliverAssetAsync(Context context, IAssetEntity? asset, AssetContentQueryDto request) + private async Task DeliverAssetAsync(Context context, Asset? asset, AssetContentQueryDto request) { request ??= new AssetContentQueryDto(); @@ -190,13 +190,13 @@ public sealed class AssetContentController : ApiController }; } - private async Task DownloadAsync(IAssetEntity asset, Stream bodyStream, string? suffix, BytesRange range, + private async Task DownloadAsync(Asset asset, Stream bodyStream, string? suffix, BytesRange range, CancellationToken ct) { await assetFileStore.DownloadAsync(asset.AppId.Id, asset.Id, asset.FileVersion, suffix, bodyStream, range, ct); } - private async Task ResizeAsync(IAssetEntity asset, string suffix, Stream target, ResizeOptions resizeOptions, bool overwrite, + private async Task ResizeAsync(Asset asset, string suffix, Stream target, ResizeOptions resizeOptions, bool overwrite, CancellationToken ct) { #pragma warning disable MA0040 // Flow the cancellation token diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetFoldersController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetFoldersController.cs index cc6db161c..8b48fc227 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetFoldersController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetFoldersController.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Squidex.Areas.Api.Controllers.Assets.Models; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure; @@ -51,21 +52,21 @@ public sealed class AssetFoldersController : ApiController [ApiCosts(1)] public async Task GetAssetFolders(string app, [FromQuery] DomainId? parentId, [FromQuery] AssetFolderScope scope = AssetFolderScope.PathAndItems) { - Task> GetAssetPathAsync() + Task> GetAssetPathAsync() { if (scope == AssetFolderScope.Items || parentId == null) { - return Task.FromResult>(ReadonlyList.Empty()); + return Task.FromResult>(ReadonlyList.Empty()); } return assetQuery.FindAssetFolderAsync(Context.App.Id, parentId.Value, HttpContext.RequestAborted); } - Task> GetAssetFoldersAsync() + Task> GetAssetFoldersAsync() { if (scope == AssetFolderScope.Path) { - return Task.FromResult(ResultList.Empty()); + return Task.FromResult(ResultList.Empty()); } return assetQuery.QueryAssetFoldersAsync(Context, parentId, HttpContext.RequestAborted); @@ -181,6 +182,6 @@ public sealed class AssetFoldersController : ApiController { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - return AssetFolderDto.FromDomain(context.Result(), Resources); + return AssetFolderDto.FromDomain(context.Result(), Resources); } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index d262bdbff..56596f837 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -436,7 +436,7 @@ public sealed class AssetsController : ApiController } else { - return AssetDto.FromDomain(context.Result(), Resources); + return AssetDto.FromDomain(context.Result(), Resources); } } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetContentQueryDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetContentQueryDto.cs index 7b5537c57..0ef487593 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetContentQueryDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetContentQueryDto.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Squidex.Assets; -using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; @@ -105,7 +105,7 @@ public sealed class AssetContentQueryDto [FromQuery(Name = "format")] public ImageFormat? Format { get; set; } - public ResizeOptions ToResizeOptions(IAssetEntity asset, IAssetThumbnailGenerator assetGenerator, HttpRequest request) + public ResizeOptions ToResizeOptions(Asset asset, IAssetThumbnailGenerator assetGenerator, HttpRequest request) { Guard.NotNull(asset); @@ -122,7 +122,7 @@ public sealed class AssetContentQueryDto return result; } - private ImageFormat? GetFormat(IAssetEntity asset, IAssetThumbnailGenerator assetGenerator, HttpRequest request) + private ImageFormat? GetFormat(Asset asset, IAssetThumbnailGenerator assetGenerator, HttpRequest request) { if (Format.HasValue || !Auto) { @@ -154,7 +154,7 @@ public sealed class AssetContentQueryDto return Format; } - private (float?, float?) GetFocusPoint(IAssetEntity asset) + private (float?, float?) GetFocusPoint(Asset asset) { if (IgnoreFocus) { diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs index ed738e4d9..677671b14 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs @@ -150,7 +150,7 @@ public sealed class AssetDto : Resource get => Metadata.GetPixelHeight(); } - public static AssetDto FromDomain(IEnrichedAssetEntity asset, Resources resources, bool isDuplicate = false) + public static AssetDto FromDomain(EnrichedAsset asset, Resources resources, bool isDuplicate = false) { var result = SimpleMapper.Map(asset, new AssetDto()); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFolderDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFolderDto.cs index dc15e8dcb..fb9078b1b 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFolderDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFolderDto.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; using Squidex.Web; @@ -34,7 +34,7 @@ public sealed class AssetFolderDto : Resource /// public long Version { get; set; } - public static AssetFolderDto FromDomain(IAssetFolderEntity asset, Resources resources) + public static AssetFolderDto FromDomain(AssetFolder asset, Resources resources) { var result = SimpleMapper.Map(asset, new AssetFolderDto()); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFoldersDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFoldersDto.cs index e33da0e5e..805b9bb59 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFoldersDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetFoldersDto.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Infrastructure; using Squidex.Web; @@ -28,7 +28,7 @@ public sealed class AssetFoldersDto : Resource /// public AssetFolderDto[] Path { get; set; } - public static AssetFoldersDto FromDomain(IResultList assetFolders, IEnumerable path, Resources resources) + public static AssetFoldersDto FromDomain(IResultList assetFolders, IEnumerable path, Resources resources) { var result = new AssetFoldersDto { diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs index f1686cfdd..70806a94e 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetsDto.cs @@ -23,7 +23,7 @@ public sealed class AssetsDto : Resource /// public AssetDto[] Items { get; set; } - public static AssetsDto FromDomain(IResultList assets, Resources resources) + public static AssetsDto FromDomain(IResultList assets, Resources resources) { var result = new AssetsDto { diff --git a/backend/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs index e61b61d0d..dbff5cba5 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs @@ -6,7 +6,7 @@ // ========================================================================== using Microsoft.AspNetCore.Mvc; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Collaboration; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index 8b83ded1b..2ade95a56 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -58,7 +58,7 @@ public sealed class ContentsController : ApiController { var contents = contentQuery.StreamAsync(Context, schema, skip, HttpContext.RequestAborted); - return new JsonStreamResult(contents); + return new JsonStreamResult(contents); } /// @@ -617,7 +617,7 @@ public sealed class ContentsController : ApiController { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - var result = context.Result(); + var result = context.Result(); var response = ContentDto.FromDomain(result, Resources); return response; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/Builder.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/Builder.cs index a2eff7df6..2ee6ffba1 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/Builder.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/Builder.cs @@ -11,9 +11,9 @@ using NSwag; using NSwag.Generation; using Squidex.Areas.Api.Controllers.Contents.Models; using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.GenerateJsonSchema; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure; namespace Squidex.Areas.Api.Controllers.Contents.Generator; @@ -34,7 +34,8 @@ internal sealed class Builder public OpenApiSchemaResolver OpenApiSchemaResolver { get; } - internal Builder(IAppEntity app, + internal Builder( + App app, OpenApiDocument document, OpenApiSchemaResolver schemaResolver, OpenApiSchemaGenerator schemaGenerator) diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasOpenApiGenerator.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasOpenApiGenerator.cs index 0e6f577d5..b4f32027b 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasOpenApiGenerator.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasOpenApiGenerator.cs @@ -10,9 +10,9 @@ using NSwag; using NSwag.Generation; using NSwag.Generation.Processors.Contexts; using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure.Caching; using Squidex.Properties; using Squidex.Shared; @@ -43,7 +43,7 @@ public sealed class SchemasOpenApiGenerator this.requestCache = requestCache; } - public async Task GenerateAsync(HttpContext httpContext, IAppEntity app, IEnumerable schemas, bool flat) + public async Task GenerateAsync(HttpContext httpContext, App app, IEnumerable schemas, bool flat) { var document = CreateApiDocument(httpContext, app); @@ -58,19 +58,16 @@ public sealed class SchemasOpenApiGenerator var builder = new Builder(app, document, schemaResolver, schemaGenerator); - var validSchemas = - schemas.Where(x => - x.SchemaDef.IsPublished && - x.SchemaDef.Type != SchemaDefType.Component && - x.SchemaDef.Fields.Count > 0); - var partitionResolver = app.PartitionResolver(); - foreach (var schema in validSchemas) + foreach (var schema in schemas) { - var components = await appProvider.GetComponentsAsync(schema, httpContext.RequestAborted); + if (schema.IsPublished && schema.Type != SchemaDefType.Component && schema.Fields.Count > 0) + { + var components = await appProvider.GetComponentsAsync(schema, httpContext.RequestAborted); - GenerateSchemaOperations(builder.Schema(schema.SchemaDef, partitionResolver, components, flat)); + GenerateSchemaOperations(builder.Schema(schema, partitionResolver, components, flat)); + } } GenerateSharedOperations(builder.Shared()); @@ -219,7 +216,7 @@ public sealed class SchemasOpenApiGenerator .Responds(204, "Content item deleted"); } - private OpenApiDocument CreateApiDocument(HttpContext context, IAppEntity app) + private OpenApiDocument CreateApiDocument(HttpContext context, App app) { var appName = app.Name; @@ -250,7 +247,7 @@ public sealed class SchemasOpenApiGenerator .Replace("[REDOC_LINK_NORMAL]", urlGenerator.BuildUrl($"api/content/{app.Name}/docs"), StringComparison.Ordinal) .Replace("[REDOC_LINK_SIMPLE]", urlGenerator.BuildUrl($"api/content/{app.Name}/docs/flat"), StringComparison.Ordinal) }, - SchemaType = SchemaType.OpenApi3 + SchemaType = NJsonSchema.SchemaType.OpenApi3 }; if (!string.IsNullOrWhiteSpace(context.Request.Host.Value)) diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs index 62efd52a9..c47175b24 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs @@ -113,7 +113,7 @@ public sealed class ContentDto : Resource /// public long Version { get; set; } - public static ContentDto FromDomain(IEnrichedContentEntity content, Resources resources) + public static ContentDto FromDomain(EnrichedContent content, Resources resources) { var response = SimpleMapper.Map(content, new ContentDto { @@ -153,7 +153,7 @@ public sealed class ContentDto : Resource return response.CreateLinksAsync(content, resources, content.SchemaId.Name); } - private ContentDto CreateLinksAsync(IEnrichedContentEntity content, Resources resources, string schema) + private ContentDto CreateLinksAsync(EnrichedContent content, Resources resources, string schema) { var app = resources.App; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs index 3f6a6207d..05ea1eadf 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Web; @@ -29,8 +29,8 @@ public sealed class ContentsDto : Resource /// public StatusInfoDto[] Statuses { get; set; } - public static async Task FromContentsAsync(IResultList contents, Resources resources, - ISchemaEntity? schema, IContentWorkflow workflow) + public static async Task FromContentsAsync(IResultList contents, Resources resources, + Schema? schema, IContentWorkflow workflow) { var result = new ContentsDto { @@ -48,16 +48,16 @@ public sealed class ContentsDto : Resource return result; } - private async Task AssignStatusesAsync(IContentWorkflow workflow, ISchemaEntity schema) + private async Task AssignStatusesAsync(IContentWorkflow workflow, Schema schema) { var allStatuses = await workflow.GetAllAsync(schema); Statuses = allStatuses.Select(StatusInfoDto.FromDomain).ToArray(); } - private async Task CreateLinksAsync(Resources resources, IContentWorkflow workflow, ISchemaEntity schema) + private async Task CreateLinksAsync(Resources resources, IContentWorkflow workflow, Schema schema) { - var values = new { app = resources.App, schema = schema.SchemaDef.Name }; + var values = new { app = resources.App, schema = schema.Name }; AddSelfLink(resources.Url(x => nameof(x.GetContents), values)); diff --git a/backend/src/Squidex/Areas/Api/Controllers/ContributorsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/ContributorsDto.cs index 3efeaa698..78cb970bf 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/ContributorsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/ContributorsDto.cs @@ -8,9 +8,9 @@ using System.Text.Json.Serialization; using Squidex.Areas.Api.Controllers.Apps; using Squidex.Areas.Api.Controllers.Teams; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities.Billing; -using Squidex.Domain.Apps.Entities.Teams; using Squidex.Shared.Users; using Squidex.Web; @@ -34,7 +34,7 @@ public sealed class ContributorsDto : Resource [JsonPropertyName("_meta")] public ContributorsMetadata? Metadata { get; set; } - public static async Task FromDomainAsync(IAppEntity app, Resources resources, IUserResolver userResolver, Plan plan, bool invited) + public static async Task FromDomainAsync(App app, Resources resources, IUserResolver userResolver, Plan plan, bool invited) { var users = await userResolver.QueryManyAsync(app.Contributors.Keys.ToArray()); @@ -54,7 +54,7 @@ public sealed class ContributorsDto : Resource return result.CreateAppLinks(resources); } - public static async Task FromDomainAsync(ITeamEntity team, Resources resources, IUserResolver userResolver, bool invited) + public static async Task FromDomainAsync(Team team, Resources resources, IUserResolver userResolver, bool invited) { var users = await userResolver.QueryManyAsync(team.Contributors.Keys.ToArray()); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/CreateRuleDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/CreateRuleDto.cs index b483026d6..159a2a8f2 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/CreateRuleDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/CreateRuleDto.cs @@ -29,7 +29,7 @@ public sealed class CreateRuleDto public Rule ToRule() { - return new Rule(Trigger.ToTrigger(), Action); + return new Rule { Trigger = Trigger.ToTrigger(), Action = Action }; } public CreateRule ToCommand() diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs index 6df6c5c67..0080c00bb 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs @@ -84,22 +84,21 @@ public sealed class RuleDto : Resource [Obsolete("Removed when migrated to new rule statistics.")] public Instant? LastExecuted { get; set; } - public static RuleDto FromDomain(IEnrichedRuleEntity rule, bool canRun, IRuleRunnerService ruleRunnerService, Resources resources) + public static RuleDto FromDomain(EnrichedRule rule, bool canRun, IRuleRunnerService ruleRunnerService, Resources resources) { var result = new RuleDto(); SimpleMapper.Map(rule, result); - SimpleMapper.Map(rule.RuleDef, result); - if (rule.RuleDef.Trigger != null) + if (rule.Trigger != null) { - result.Trigger = RuleTriggerDtoFactory.Create(rule.RuleDef.Trigger); + result.Trigger = RuleTriggerDtoFactory.Create(rule.Trigger); } return result.CreateLinks(resources, rule, canRun, ruleRunnerService); } - private RuleDto CreateLinks(Resources resources, IEnrichedRuleEntity rule, bool canRun, IRuleRunnerService ruleRunnerService) + private RuleDto CreateLinks(Resources resources, EnrichedRule rule, bool canRun, IRuleRunnerService ruleRunnerService) { var values = new { app = resources.App, id = Id }; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs index 658025d88..5ff4381eb 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs @@ -24,7 +24,7 @@ public sealed class RulesDto : Resource /// public DomainId? RunningRuleId { get; set; } - public static async Task FromRulesAsync(IEnumerable items, IRuleRunnerService ruleRunnerService, Resources resources) + public static async Task FromRulesAsync(IEnumerable items, IRuleRunnerService ruleRunnerService, Resources resources) { var runningRuleId = await ruleRunnerService.GetRunningRuleIdAsync(resources.Context.App.Id); var runningAvailable = runningRuleId != default; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs b/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs index 37df73f64..c310caab3 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs @@ -10,10 +10,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using NodaTime; using Squidex.Areas.Api.Controllers.Rules.Models; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.Repositories; @@ -483,7 +483,7 @@ public sealed class RulesController : ApiController var runningRuleId = await ruleRunnerService.GetRunningRuleIdAsync(Context.App.Id, HttpContext.RequestAborted); - var result = context.Result(); + var result = context.Result(); var response = RuleDto.FromDomain(result, runningRuleId == null, ruleRunnerService, Resources); return response; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs index d6a30477e..18eca20b2 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs @@ -8,7 +8,6 @@ using NodaTime; using Squidex.Areas.Api.Controllers.Contents; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Reflection; @@ -123,21 +122,20 @@ public class SchemaDto : Resource [LocalizedRequired] public List Fields { get; set; } = []; - public static SchemaDto FromDomain(ISchemaEntity schema, Resources resources) + public static SchemaDto FromDomain(Schema schema, Resources resources) { var result = new SchemaDto(); SimpleMapper.Map(schema, result); - SimpleMapper.Map(schema.SchemaDef, result); - SimpleMapper.Map(schema.SchemaDef.Scripts, result.Scripts); - SimpleMapper.Map(schema.SchemaDef.Properties, result.Properties); + SimpleMapper.Map(schema.Scripts, result.Scripts); + SimpleMapper.Map(schema.Properties, result.Properties); - foreach (var rule in schema.SchemaDef.FieldRules) + foreach (var rule in schema.FieldRules) { result.FieldRules.Add(FieldRuleDto.FromDomain(rule)); } - foreach (var field in schema.SchemaDef.Fields) + foreach (var field in schema.Fields) { result.Fields.Add(FieldDto.FromDomain(field)); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs index 9b5358959..13d247109 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Schemas.Models; @@ -17,7 +17,7 @@ public sealed class SchemasDto : Resource /// public SchemaDto[] Items { get; set; } - public static SchemasDto FromDomain(IList schemas, Resources resources) + public static SchemasDto FromDomain(IList schemas, Resources resources) { var result = new SchemasDto { diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs index 3e0f1f29b..3a8cd1239 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Squidex.Areas.Api.Controllers.Schemas.Models; -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure.Commands; using Squidex.Shared; @@ -510,7 +510,7 @@ public sealed class SchemaFieldsController : ApiController { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - var result = context.Result(); + var result = context.Result(); var response = SchemaDto.FromDomain(result, Resources); return response; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index bab9a523d..5df9b8404 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -8,11 +8,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Squidex.Areas.Api.Controllers.Schemas.Models; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.GenerateFilters; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -369,7 +369,7 @@ public sealed class SchemasController : ApiController { var components = await appProvider.GetComponentsAsync(Schema, HttpContext.RequestAborted); - var filters = ContentQueryModel.Build(Schema.SchemaDef, App.PartitionResolver(), components).Flatten(); + var filters = ContentQueryModel.Build(Schema, App.PartitionResolver(), components).Flatten(); return Ok(filters); } @@ -378,10 +378,10 @@ public sealed class SchemasController : ApiController { var components = await appProvider.GetComponentsAsync(Schema, HttpContext.RequestAborted); - return Schema.SchemaDef.BuildDataSchema(App.PartitionResolver(), components); + return Schema.BuildDataSchema(App.PartitionResolver(), components); } - private Task GetSchemaAsync(string schema) + private Task GetSchemaAsync(string schema) { if (Guid.TryParseExact(schema, "D", out var guid)) { @@ -399,7 +399,7 @@ public sealed class SchemasController : ApiController { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - var result = context.Result(); + var result = context.Result(); var response = SchemaDto.FromDomain(result, Resources); return response; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Teams/Models/TeamDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Teams/Models/TeamDto.cs index 909909ce9..3b7a544ac 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Teams/Models/TeamDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Teams/Models/TeamDto.cs @@ -7,7 +7,7 @@ using NodaTime; using Squidex.Areas.Api.Controllers.Plans; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities.Teams; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; @@ -49,7 +49,7 @@ public sealed class TeamDto : Resource /// public string? RoleName { get; set; } - public static TeamDto FromDomain(ITeamEntity team, string userId, Resources resources) + public static TeamDto FromDomain(Team team, string userId, Resources resources) { var result = SimpleMapper.Map(team, new TeamDto()); @@ -63,7 +63,7 @@ public sealed class TeamDto : Resource return result.CreateLinks(team, resources, permissions, true); } - private TeamDto CreateLinks(ITeamEntity team, Resources resources, PermissionSet permissions, bool isContributor) + private TeamDto CreateLinks(Team team, Resources resources, PermissionSet permissions, bool isContributor) { var values = new { team = Id.ToString() }; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Teams/TeamContributorsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Teams/TeamContributorsController.cs index 7594bd0b6..671ecd6d4 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Teams/TeamContributorsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Teams/TeamContributorsController.cs @@ -7,8 +7,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities.Invitation; -using Squidex.Domain.Apps.Entities.Teams; using Squidex.Domain.Apps.Entities.Teams.Commands; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Reflection; @@ -122,17 +122,17 @@ public sealed class TeamContributorsController : ApiController { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - if (context.PlainResult is InvitedResult invited) + if (context.PlainResult is InvitedResult invited) { return await GetResponseAsync(invited.Entity, true); } else { - return await GetResponseAsync(context.Result(), false); + return await GetResponseAsync(context.Result(), false); } } - private async Task GetResponseAsync(ITeamEntity team, bool invited) + private async Task GetResponseAsync(Team team, bool invited) { return await ContributorsDto.FromDomainAsync(team, Resources, userResolver, invited); } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Teams/TeamsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Teams/TeamsController.cs index e73f666ab..7aa5bd624 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Teams/TeamsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Teams/TeamsController.cs @@ -8,8 +8,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Squidex.Areas.Api.Controllers.Teams.Models; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities; -using Squidex.Domain.Apps.Entities.Teams; using Squidex.Infrastructure.Commands; using Squidex.Shared; using Squidex.Web; @@ -131,11 +131,11 @@ public sealed class TeamsController : ApiController }); } - private async Task InvokeCommandAsync(ICommand command, Func converter) + private async Task InvokeCommandAsync(ICommand command, Func converter) { var context = await CommandBus.PublishAsync(command, HttpContext.RequestAborted); - var result = context.Result(); + var result = context.Result(); var response = converter(result); return response; diff --git a/backend/src/Squidex/Areas/Frontend/Startup.cs b/backend/src/Squidex/Areas/Frontend/Startup.cs index b27128349..1fd1185a2 100644 --- a/backend/src/Squidex/Areas/Frontend/Startup.cs +++ b/backend/src/Squidex/Areas/Frontend/Startup.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders; using Microsoft.Net.Http.Headers; using Squidex.Areas.Frontend.Middlewares; diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/AlwaysAddScopeHandler.cs b/backend/src/Squidex/Areas/IdentityServer/Config/AlwaysAddScopeHandler.cs index 4280c8122..64f6d85ca 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Config/AlwaysAddScopeHandler.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Config/AlwaysAddScopeHandler.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Immutable; using OpenIddict.Abstractions; using OpenIddict.Server; using static OpenIddict.Server.OpenIddictServerEvents; diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs index 74f750420..c7bc86a78 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs @@ -117,7 +117,7 @@ public sealed class AccountController : IdentityServerController [HttpGet] [Route("account/logout/")] - public async Task Logout(string logoutId) + public async Task Logout() { await SignInManager.SignOutAsync(); diff --git a/backend/src/Squidex/Config/Domain/SchemasServices.cs b/backend/src/Squidex/Config/Domain/SchemasServices.cs index 4f0065461..0571f90ae 100644 --- a/backend/src/Squidex/Config/Domain/SchemasServices.cs +++ b/backend/src/Squidex/Config/Domain/SchemasServices.cs @@ -8,6 +8,7 @@ using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Search; +using Squidex.Infrastructure.Commands; namespace Squidex.Config.Domain; @@ -15,6 +16,9 @@ public static class SchemasServices { public static void AddSquidexSchemas(this IServiceCollection services) { + services.AddSingletonAs() + .As(); + services.AddTransientAs() .As(); diff --git a/backend/src/Squidex/Config/Domain/SerializationServices.cs b/backend/src/Squidex/Config/Domain/SerializationServices.cs index 242986521..50c2a2b89 100644 --- a/backend/src/Squidex/Config/Domain/SerializationServices.cs +++ b/backend/src/Squidex/Config/Domain/SerializationServices.cs @@ -8,6 +8,7 @@ using System.Security.Claims; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using Microsoft.AspNetCore.Mvc; using Migrations; using NetTopologySuite.IO.Converters; @@ -26,6 +27,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas.Json; using Squidex.Domain.Apps.Events; using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects; @@ -58,12 +60,12 @@ public static class SerializationServices options.Converters.Add(new ReadonlyDictionaryConverterFactory()); options.Converters.Add(new ReadonlyListConverterFactory()); options.Converters.Add(new SurrogateJsonConverter()); + options.Converters.Add(new SurrogateJsonConverter, FieldsSurrogate>()); options.Converters.Add(new SurrogateJsonConverter, JsonFilterSurrogate>()); options.Converters.Add(new SurrogateJsonConverter()); options.Converters.Add(new SurrogateJsonConverter()); options.Converters.Add(new SurrogateJsonConverter()); options.Converters.Add(new SurrogateJsonConverter()); - options.Converters.Add(new SurrogateJsonConverter()); options.Converters.Add(new SurrogateJsonConverter()); options.Converters.Add(new SurrogateJsonConverter()); options.Converters.Add(new StringConverter()); @@ -77,8 +79,11 @@ public static class SerializationServices options.Converters.Add(new StringConverter()); options.Converters.Add(new StringConverter()); options.Converters.Add(new JsonStringEnumConverter()); - options.TypeInfoResolver = new PolymorphicTypeResolver(typeRegistry); options.IncludeFields = true; + options.TypeInfoResolver = new DefaultJsonTypeInfoResolver() + .WithAddedModifier(PolymorphicConverter.Modifier(typeRegistry)) + .WithAddedModifier(JsonIgnoreReadonlyProperties.Modifier()) + .WithAddedModifier(JsonRenameAttribute.Modifier); return options; } diff --git a/backend/src/Squidex/Config/Domain/StoreServices.cs b/backend/src/Squidex/Config/Domain/StoreServices.cs index 22a24857f..b2f6a7987 100644 --- a/backend/src/Squidex/Config/Domain/StoreServices.cs +++ b/backend/src/Squidex/Config/Domain/StoreServices.cs @@ -13,12 +13,15 @@ using Migrations.Migrations.MongoDb; using MongoDB.Bson; using MongoDB.Driver; using MongoDB.Driver.Core.Extensions.DiagnosticSources; +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.Core.Teams; using Squidex.Domain.Apps.Entities; -using Squidex.Domain.Apps.Entities.Apps.DomainObject; using Squidex.Domain.Apps.Entities.Apps.Repositories; -using Squidex.Domain.Apps.Entities.Assets.DomainObject; using Squidex.Domain.Apps.Entities.Assets.Repositories; -using Squidex.Domain.Apps.Entities.Contents.DomainObject; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.Contents.Text.State; @@ -31,12 +34,9 @@ using Squidex.Domain.Apps.Entities.MongoDb.Rules; using Squidex.Domain.Apps.Entities.MongoDb.Schemas; using Squidex.Domain.Apps.Entities.MongoDb.Teams; using Squidex.Domain.Apps.Entities.MongoDb.Text; -using Squidex.Domain.Apps.Entities.Rules.DomainObject; using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Domain.Apps.Entities.Schemas.DomainObject; using Squidex.Domain.Apps.Entities.Schemas.Repositories; -using Squidex.Domain.Apps.Entities.Teams.DomainObject; using Squidex.Domain.Apps.Entities.Teams.Repositories; using Squidex.Domain.Users; using Squidex.Domain.Users.InMemory; @@ -100,7 +100,7 @@ public static class StoreServices .As(); services.AddSingletonAs(c => ActivatorUtilities.CreateInstance(c, GetDatabase(c, mongoContentDatabaseName))) - .As().As>().As(); + .As().As>().As(); services.AddTransientAs() .As(); @@ -139,22 +139,22 @@ public static class StoreServices .As>().As(); services.AddSingletonAs() - .As().As>().As(); + .As().As>().As(); services.AddSingletonAs() - .As().As>().As(); + .As().As>().As(); services.AddSingletonAs() - .As().As>().As(); + .As().As>().As(); services.AddSingletonAs() - .As().As>(); + .As().As>(); services.AddSingletonAs() - .As().As>().As(); + .As().As>().As(); services.AddSingletonAs() - .As().As>().As(); + .As().As>().As(); services.AddSingletonAs() .AsOptional().As().As(); diff --git a/backend/src/Squidex/Config/Web/WebServices.cs b/backend/src/Squidex/Config/Web/WebServices.cs index 3bfc11d59..36987d0f1 100644 --- a/backend/src/Squidex/Config/Web/WebServices.cs +++ b/backend/src/Squidex/Config/Web/WebServices.cs @@ -7,7 +7,6 @@ using GraphQL; using GraphQL.DI; -using GraphQL.Execution; using GraphQL.Server.Transports.AspNetCore; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -18,7 +17,6 @@ using Squidex.Config.Domain; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Contents.GraphQL; -using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Json.Objects; using Squidex.Pipeline.Plugins; diff --git a/backend/src/Squidex/Squidex.csproj b/backend/src/Squidex/Squidex.csproj index 126df3e99..568230e77 100644 --- a/backend/src/Squidex/Squidex.csproj +++ b/backend/src/Squidex/Squidex.csproj @@ -7,7 +7,7 @@ enable en enable - 1701;1702;CS1591;NETSDK1206;NU1608 + 1701;1702;CS1591;IDE0060;NETSDK1206;NU1608 diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/App.json b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/App.json new file mode 100644 index 000000000..b5ec5bd99 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/App.json @@ -0,0 +1,112 @@ +{ + "assetScripts": { + "create": "Create Script", + "update": "Update Script", + "annotate": "Annotate Script", + "move": "Move Script", + "delete": "Delete Script", + "query": "Query Script", + "queryPre": "Query Pre Script" + }, + "clients": { + "client1": { + "name": "client1", + "secret": "MySecret", + "apiCallsLimit": 13, + "apiTrafficLimit": 42, + "allowAnonymous": true, + "role": "Admin" + } + }, + "contributors": { + "me": "Owner" + }, + "created": "2022-12-05T11:00:23Z", + "createdBy": "subject:63761585e06d5466c71521b3", + "description": "My Description", + "id": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", + "image": { + "mimeType": "image/png", + "etag": "42" + }, + "isDeleted": true, + "label": "My Label", + "languages": { + "languages": { + "en": { + "fallback": [], + "isOptional": false + }, + "de": { + "fallback": [], + "isOptional": true + } + }, + "master": "en" + }, + "lastModified": "2022-12-05T11:00:23Z", + "lastModifiedBy": "subject:63761585e06d5466c71521b3", + "name": "My Team", + "plan": { + "owner": "subject:63761585e06d5466c71521b3", + "planId": "Premium" + }, + "roles": { + "Custom": { + "permissions": [ + "permission1", + "permission2" + ], + "properties": {} + } + }, + "settings": { + "patterns": [ + { + "name": "numbers", + "regex": "[0-9]+" + } + ], + "editors": [ + { + "name": "custom", + "url": "https://cloud.squidex.io" + } + ], + "hideScheduler": true, + "hideDateTimeModeButton": true + }, + "teamId": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", + "version": 0, + "workflows": { + "265aaf1e-5dff-4c40-b1e6-1149f652cd30": { + "initial": "archived", + "name": "Unnamed", + "steps": { + "published": { + "noUpdateRules": {}, + "validate": false, + "transitions": { + "archived": { + "roles": [ + "Role1" + ] + } + } + }, + "archived": { + "noUpdateRules": {}, + "validate": false, + "transitions": { + "published": { + "roles": [ + "Role1" + ] + } + } + } + }, + "schemaIds": [] + } + } +} \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppTests.cs new file mode 100644 index 000000000..069dd3bbc --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppTests.cs @@ -0,0 +1,237 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// 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.TestHelpers; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Model.Apps; + +#pragma warning disable SA1310 // Field names must not contain underscore + +public class AppTests +{ + private readonly App app_0 = new App(); + + [Fact] + public void Should_not_annotate_with_null_label() + { + var app_1 = app_0.Annotate(label: null); + + Assert.Same(app_1, app_0); + } + + [Fact] + public void Should_annotate_with_label() + { + var newLabel = "My App"; + + var app_1 = app_0.Annotate(label: newLabel); + var app_2 = app_1.Annotate(label: newLabel); + + Assert.NotSame(app_0, app_1); + Assert.Equal(newLabel, app_1.Label); + Assert.Equal(newLabel, app_2.Label); + Assert.Same(app_1, app_2); + } + + [Fact] + public void Should_not_annotate_with_null_description() + { + var app_1 = app_0.Annotate(description: null); + + Assert.Same(app_1, app_0); + } + + [Fact] + public void Should_annotate_with_description() + { + var newDescription = "My Description"; + + var app_1 = app_0.Annotate(description: newDescription); + var app_2 = app_1.Annotate(description: newDescription); + + Assert.NotSame(app_0, app_1); + Assert.Equal(newDescription, app_1.Description); + Assert.Equal(newDescription, app_2.Description); + Assert.Same(app_1, app_2); + } + + [Fact] + public void Should_transer() + { + var newTeamId = DomainId.NewGuid(); + + var app_1 = app_0.Transfer(newTeamId); + var app_2 = app_1.Transfer(newTeamId); + + Assert.NotSame(app_0, app_1); + Assert.Equal(newTeamId, app_1.TeamId); + Assert.Equal(newTeamId, app_2.TeamId); + Assert.Same(app_1, app_2); + } + + [Fact] + public void Should_update_plan() + { + var newPlan1 = new AssignedPlan(RefToken.User("me"), "Premium"); + var newPlan2 = new AssignedPlan(RefToken.User("me"), "Premium"); + + var app_1 = app_0.ChangePlan(newPlan1); + var app_2 = app_1.ChangePlan(newPlan2); + + Assert.NotSame(app_0, app_1); + Assert.Equal(newPlan1, app_1.Plan); + Assert.Equal(newPlan2, app_2.Plan); + Assert.Same(app_1, app_2); + } + + [Fact] + public void Should_update_image() + { + var newImage1 = new AppImage("image/png", "42"); + var newImage2 = new AppImage("image/png", "42"); + + var app_1 = app_0.UpdateImage(newImage1); + var app_2 = app_1.UpdateImage(newImage2); + + Assert.NotSame(app_0, app_1); + Assert.Equal(newImage1, app_1.Image); + Assert.Equal(newImage1, app_2.Image); + Assert.Same(app_1, app_2); + } + + [Fact] + public void Should_update_settings() + { + var newSettings1 = new AppSettings { HideDateTimeModeButton = true }; + var newSettings2 = new AppSettings { HideDateTimeModeButton = true }; + + var app_1 = app_0.UpdateSettings(newSettings1); + var app_2 = app_1.UpdateSettings(newSettings2); + + Assert.NotSame(app_0, app_1); + Assert.Equal(newSettings1, app_1.Settings); + Assert.Equal(newSettings1, app_2.Settings); + Assert.Same(app_1, app_2); + } + + [Fact] + public void Should_update_schema_scripts() + { + var newScripts1 = new AssetScripts { Query = "query" }; + var newScripts2 = new AssetScripts { Query = "query" }; + + var app_1 = app_0.UpdateAssetScripts(newScripts1); + var app_2 = app_1.UpdateAssetScripts(newScripts2); + + Assert.NotSame(app_0, app_1); + Assert.Equal(newScripts1, app_1.AssetScripts); + Assert.Equal(newScripts1, app_2.AssetScripts); + Assert.Same(app_1, app_2); + } + + [Fact] + public void Should_update_contributors() + { + var app_1 = app_0.UpdateContributors(true, (_, c) => c.Assign("me", Role.Owner)); + var app_2 = app_1.UpdateContributors(true, (_, c) => c.Assign("me", Role.Owner)); + + Assert.NotSame(app_0, app_1); + Assert.Single(app_1.Contributors); + Assert.Single(app_2.Contributors); + Assert.Same(app_1, app_2); + } + + [Fact] + public void Should_update_clients() + { + var newClient = "MyClient"; + + var app_1 = app_0.UpdateClients(true, (_, c) => c.Add(newClient, newClient)); + var app_2 = app_1.UpdateClients(true, (_, c) => c.Add(newClient, newClient)); + + Assert.NotSame(app_0, app_1); + Assert.True(app_1.Clients.ContainsKey(newClient)); + Assert.True(app_2.Clients.ContainsKey(newClient)); + Assert.Same(app_1, app_2); + } + + [Fact] + public void Should_update_languages() + { + var newLanguage = Language.DE; + + var app_1 = app_0.UpdateLanguages(true, (_, l) => l.Set(newLanguage)); + var app_2 = app_1.UpdateLanguages(true, (_, l) => l.Set(newLanguage)); + + Assert.NotSame(app_0, app_1); + Assert.True(app_1.Languages.Contains(newLanguage)); + Assert.True(app_2.Languages.Contains(newLanguage)); + Assert.Same(app_1, app_2); + } + + [Fact] + public void Should_update_roles() + { + var newRole = "MyRole"; + + var app_1 = app_0.UpdateRoles(true, (_, r) => r.Add(newRole)); + var app_2 = app_1.UpdateRoles(true, (_, r) => r.Add(newRole)); + + Assert.NotSame(app_0, app_1); + Assert.Contains(app_1.Roles.Custom, x => x.Name == newRole); + Assert.Contains(app_2.Roles.Custom, x => x.Name == newRole); + Assert.Same(app_1, app_2); + } + + [Fact] + public void Should_update_workflows() + { + var newWorkflowName = "MyWorkflow"; + var newWorkflowId = DomainId.NewGuid(); + + var app_1 = app_0.UpdateWorkflows(true, (_, r) => r.Add(newWorkflowId, newWorkflowName)); + var app_2 = app_1.UpdateWorkflows(true, (_, r) => r.Add(newWorkflowId, newWorkflowName)); + + Assert.NotSame(app_0, app_1); + Assert.True(app_1.Workflows.ContainsKey(newWorkflowId)); + Assert.True(app_2.Workflows.ContainsKey(newWorkflowId)); + Assert.Same(app_1, app_2); + } + + [Fact] + public void Should_deserialize_old_state() + { + var original = TestUtils.DefaultSerializer.Deserialize(File.ReadAllText("Model/Apps/App.json")); + + var deserialized = TestUtils.DefaultSerializer.Deserialize(File.ReadAllText("Model/Apps/App_Old.json")); + + deserialized.Should().BeEquivalentTo(original); + } + + [Fact] + public void Should_deserialize_state() + { + var json = File.ReadAllText("Model/Apps/App.json"); + + var deserialized = TestUtils.DefaultSerializer.Deserialize(json); + + Assert.NotNull(deserialized); + } + + [Fact] + public void Should_serialize_deserialize_state() + { + var json = File.ReadAllText("Model/Apps/App.json").CleanJson(); + + var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + + Assert.Equal(json, serialized); + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/App_Old.json b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/App_Old.json new file mode 100644 index 000000000..ea8ab466b --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/App_Old.json @@ -0,0 +1,113 @@ +{ + "assetScripts": { + "create": "Create Script", + "update": "Update Script", + "annotate": "Annotate Script", + "move": "Move Script", + "delete": "Delete Script", + "query": "Query Script", + "queryPre": "Query Pre Script" + }, + "clients": { + "client1": { + "name": "client1", + "secret": "MySecret", + "apiCallsLimit": 13, + "apiTrafficLimit": 42, + "allowAnonymous": true, + "role": "Admin" + } + }, + "contributors": { + "me": "Owner" + }, + "created": "2022-12-05T11:00:23Z", + "createdBy": "subject:63761585e06d5466c71521b3", + "description": "My Description", + "id": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", + "image": { + "mimeType": "image/png", + "etag": "42" + }, + "isDeleted": true, + "label": "My Label", + "languages": { + "languages": { + "en": { + "fallback": [], + "isOptional": false + }, + "de": { + "fallback": [], + "isOptional": true + } + }, + "master": "en" + }, + "lastModified": "2022-12-05T11:00:23Z", + "lastModifiedBy": "subject:63761585e06d5466c71521b3", + "name": "My Team", + "plan": { + "owner": "subject:63761585e06d5466c71521b3", + "planId": "Premium" + }, + "roles": { + "Custom": { + "permissions": [ + "permission1", + "permission2" + ], + "properties": {} + } + }, + "settings": { + "patterns": [ + { + "name": "numbers", + "regex": "[0-9]+", + "message": null + } + ], + "editors": [ + { + "name": "custom", + "url": "https://cloud.squidex.io" + } + ], + "hideScheduler": true, + "hideDateTimeModeButton": true + }, + "teamId": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", + "version": 0, + "workflows": { + "265aaf1e-5dff-4c40-b1e6-1149f652cd30": { + "initial": "archived", + "name": "Unnamed", + "steps": { + "published": { + "noUpdate": true, + "validate": false, + "color": null, + "transitions": { + "archived": { + "expression": null, + "role": "Role1" + } + } + }, + "archived": { + "noUpdate": true, + "validate": false, + "color": null, + "transitions": { + "published": { + "expression": null, + "role": "Role1" + } + } + } + }, + "schemaIds": [] + } + } +} \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/Asset.json b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/Asset.json new file mode 100644 index 000000000..c667d7b94 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/Asset.json @@ -0,0 +1,27 @@ +{ + "appId": "06c68527-a2ca-4d84-91eb-a1707aa5b6fb,hotels", + "created": "2022-12-05T11:00:23Z", + "createdBy": "subject:63761585e06d5466c71521b3", + "fileHash": "Hash42", + "fileName": "My Image.png", + "fileSize": 1024, + "fileVersion": 2, + "id": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", + "isDeleted": true, + "isProtected": true, + "lastModified": "2022-12-05T11:00:23Z", + "lastModifiedBy": "subject:63761585e06d5466c71521b3", + "metadata": { + "pixelWidht": 800, + "pixelHeight": 600 + }, + "mimeType": "image/png", + "parentId": "4827f8cb-ec10-4b38-930a-7c5ac722a12f", + "tags": [ + "tag1", + "tag2" + ], + "totalSize": 4000, + "type": "Image", + "version": 0 +} \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetFolder.json b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetFolder.json new file mode 100644 index 000000000..863829869 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetFolder.json @@ -0,0 +1,12 @@ +{ + "appId": "06c68527-a2ca-4d84-91eb-a1707aa5b6fb,hotels", + "created": "2022-12-05T11:00:23Z", + "createdBy": "subject:63761585e06d5466c71521b3", + "folderName": "MyFolder", + "id": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", + "isDeleted": true, + "lastModified": "2022-12-05T11:00:23Z", + "lastModifiedBy": "subject:63761585e06d5466c71521b3", + "parentId": "4827f8cb-ec10-4b38-930a-7c5ac722a12f", + "version": 0 +} \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetFolderTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetFolderTests.cs new file mode 100644 index 000000000..3c06a70de --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetFolderTests.cs @@ -0,0 +1,73 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Assets; +using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Model.Assets; + +#pragma warning disable SA1310 // Field names must not contain underscore + +public class AssetFolderTests +{ + private readonly AssetFolder assetFolder_0 = new AssetFolder(); + + [Fact] + public void Should_throw_exception_if_new_name_is_null() + { + Assert.Throws(() => assetFolder_0.Rename(null!)); + } + + [Fact] + public void Should_rename() + { + var newName = "MyFolder"; + + var assetFolder_1 = assetFolder_0.Rename(newName); + var assetFolder_2 = assetFolder_1.Rename(newName); + + Assert.NotSame(assetFolder_0, assetFolder_1); + Assert.Equal(newName, assetFolder_1.FolderName); + Assert.Equal(newName, assetFolder_2.FolderName); + Assert.Same(assetFolder_1, assetFolder_2); + } + + [Fact] + public void Should_move() + { + var newParentId = DomainId.NewGuid(); + + var assetFolder_1 = assetFolder_0.Move(newParentId); + var assetFolder_2 = assetFolder_1.Move(newParentId); + + Assert.NotSame(assetFolder_0, assetFolder_1); + Assert.Equal(newParentId, assetFolder_1.ParentId); + Assert.Equal(newParentId, assetFolder_2.ParentId); + Assert.Same(assetFolder_1, assetFolder_2); + } + + [Fact] + public void Should_deserialize_state() + { + var json = File.ReadAllText("Model/Assets/AssetFolder.json"); + + var deserialized = TestUtils.DefaultSerializer.Deserialize(json); + + Assert.NotNull(deserialized); + } + + [Fact] + public void Should_serialize_deserialize_state() + { + var json = File.ReadAllText("Model/Assets/AssetFolder.json").CleanJson(); + + var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + + Assert.Equal(json, serialized); + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetTests.cs new file mode 100644 index 000000000..6ba820c45 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetTests.cs @@ -0,0 +1,165 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Assets; +using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Model.Assets; + +#pragma warning disable SA1310 // Field names must not contain underscore + +public class AssetTests +{ + private readonly Asset asset_0 = new Asset(); + + [Fact] + public void Should_move() + { + var newParentId = DomainId.NewGuid(); + + var asset_1 = asset_0.Move(newParentId); + var asset_2 = asset_1.Move(newParentId); + + Assert.NotSame(asset_0, asset_1); + Assert.Equal(newParentId, asset_1.ParentId); + Assert.Equal(newParentId, asset_2.ParentId); + Assert.Same(asset_1, asset_2); + } + + [Fact] + public void Should_not_annotate_with_null_file_name() + { + var asset_1 = asset_0.Annotate(fileName: null); + + Assert.Same(asset_1, asset_0); + } + + [Fact] + public void Should_annotate_with_file_name() + { + var newFileName = "MyFile.png"; + + var asset_1 = asset_0.Annotate(fileName: newFileName); + var asset_2 = asset_1.Annotate(fileName: newFileName); + + Assert.NotSame(asset_0, asset_1); + Assert.Equal(newFileName, asset_1.FileName); + Assert.Equal(newFileName, asset_2.FileName); + Assert.Same(asset_1, asset_2); + } + + [Fact] + public void Should_not_annotate_with_null_slug() + { + var asset_1 = asset_0.Annotate(slug: null); + + Assert.Same(asset_1, asset_0); + } + + [Fact] + public void Should_annotate_with_slug() + { + var newSlug = "my-file.png"; + + var asset_1 = asset_0.Annotate(slug: newSlug); + var asset_2 = asset_1.Annotate(slug: newSlug); + + Assert.NotSame(asset_0, asset_1); + Assert.Equal(newSlug, asset_1.Slug); + Assert.Equal(newSlug, asset_2.Slug); + Assert.Same(asset_1, asset_2); + } + + [Fact] + public void Should_not_annotate_with_null_protected() + { + var asset_1 = asset_0.Annotate(isProtected: null); + + Assert.Same(asset_1, asset_0); + } + + [Fact] + public void Should_annotate_with_protected() + { + var newProtected = true; + + var asset_1 = asset_0.Annotate(isProtected: newProtected); + var asset_2 = asset_1.Annotate(isProtected: newProtected); + + Assert.NotSame(asset_0, asset_1); + Assert.Equal(newProtected, asset_1.IsProtected); + Assert.Equal(newProtected, asset_2.IsProtected); + Assert.Same(asset_1, asset_2); + } + + [Fact] + public void Should_not_annotate_with_null_tags() + { + var asset_1 = asset_0.Annotate(tags: null); + + Assert.Same(asset_1, asset_0); + } + + [Fact] + public void Should_annotate_with_tags() + { + var newTags1 = new HashSet { "tag1" }; + var newTags2 = new HashSet { "tag1" }; + + var asset_1 = asset_0.Annotate(tags: newTags1); + var asset_2 = asset_1.Annotate(tags: newTags2); + + Assert.NotSame(asset_0, asset_1); + Assert.Equal(newTags1, asset_1.Tags); + Assert.Equal(newTags2, asset_2.Tags); + Assert.Same(asset_1, asset_2); + } + + [Fact] + public void Should_not_annotate_with_null_metadata() + { + var asset_1 = asset_0.Annotate(metadata: null); + + Assert.Same(asset_1, asset_0); + } + + [Fact] + public void Should_annotate_with_metadata() + { + var newMetadata1 = new AssetMetadata().SetPixelWidth(1024); + var newMetadata2 = new AssetMetadata().SetPixelWidth(1024); + + var asset_1 = asset_0.Annotate(metadata: newMetadata1); + var asset_2 = asset_1.Annotate(metadata: newMetadata2); + + Assert.NotSame(asset_0, asset_1); + Assert.Equal(newMetadata1, asset_1.Metadata); + Assert.Equal(newMetadata2, asset_2.Metadata); + Assert.Same(asset_1, asset_2); + } + + [Fact] + public void Should_deserialize_state() + { + var json = File.ReadAllText("Model/Assets/Asset.json"); + + var deserialized = TestUtils.DefaultSerializer.Deserialize(json); + + Assert.NotNull(deserialized); + } + + [Fact] + public void Should_serialize_deserialize_state() + { + var json = File.ReadAllText("Model/Assets/Asset.json").CleanJson(); + + var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + + Assert.Equal(json, serialized); + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/Content.json b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/Content.json new file mode 100644 index 000000000..b11292873 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/Content.json @@ -0,0 +1,24 @@ +{ + "appId": "06c68527-a2ca-4d84-91eb-a1707aa5b6fb,hotels", + "created": "2022-12-05T11:00:23Z", + "createdBy": "subject:63761585e06d5466c71521b3", + "data": { + "location": { + "iv": "Berlin" + } + }, + "id": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", + "isDeleted": true, + "lastModified": "2022-12-05T11:00:23Z", + "lastModifiedBy": "subject:63761585e06d5466c71521b3", + "newStatus": "Draft", + "scheduleJob": { + "id": "8edd59a0-a6c2-4177-9502-99ba8aabff44", + "status": "Archived", + "scheduledBy": "subject:63761585e06d5466c71521b3", + "dueTime": "2024-12-02T12:14:33Z" + }, + "schemaId": "4827f8cb-ec10-4b38-930a-7c5ac722a12f,destinations", + "status": "Published", + "version": 0 +} \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentTests.cs new file mode 100644 index 000000000..31b41f37d --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentTests.cs @@ -0,0 +1,34 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.TestHelpers; + +namespace Squidex.Domain.Apps.Core.Model.Contents; + +public class ContentTests +{ + [Fact] + public void Should_deserialize_state() + { + var json = File.ReadAllText("Model/Contents/Content.json"); + + var deserialized = TestUtils.DefaultSerializer.Deserialize(json); + + Assert.NotNull(deserialized); + } + + [Fact] + public void Should_serialize_deserialize_state() + { + var json = File.ReadAllText("Model/Contents/Content.json").CleanJson(); + + var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + + Assert.Equal(json, serialized); + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/TranslationStatusTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/TranslationStatusTests.cs index 50be564a3..e934d1cde 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/TranslationStatusTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/TranslationStatusTests.cs @@ -19,7 +19,7 @@ public class TranslationStatusTests [Fact] public void Should_create_info_for_empty_schema() { - var schema = new Schema("my-schema"); + var schema = new Schema { Name = "my-schema" }; var actual = TranslationStatus.Create([], schema, languages); @@ -35,7 +35,7 @@ public class TranslationStatusTests public void Should_create_info_for_schema_without_localized_field() { var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddString(1, "field1", Partitioning.Invariant); var actual = TranslationStatus.Create([], schema, languages); @@ -52,7 +52,7 @@ public class TranslationStatusTests public void Should_create_info_for_schema_with_localized_field() { var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddString(1, "field1", Partitioning.Language); var actual = TranslationStatus.Create([], schema, languages); @@ -69,7 +69,7 @@ public class TranslationStatusTests public void Should_create_translation_info() { var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddString(1, "field1", Partitioning.Language) .AddString(2, "field2", Partitioning.Language) .AddString(3, "field3", Partitioning.Language) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowJsonTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowJsonTests.cs index 2645b5fe8..4bb2b4351 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowJsonTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowJsonTests.cs @@ -77,6 +77,6 @@ public class WorkflowJsonTests var actual = serialized.ToSource(); - Assert.Equal(source.Role, actual?.Roles?.Single()); + Assert.Equal("role_1", actual?.Roles?.Single()); } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WriteContent.json b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WriteContent.json new file mode 100644 index 000000000..a469139cf --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WriteContent.json @@ -0,0 +1,33 @@ +{ + "schemaId": "4827f8cb-ec10-4b38-930a-7c5ac722a12f,destinations", + "newVersion": { + "status": "Published", + "data": { + "location": { + "iv": "Berlin" + } + } + }, + "currentVersion": { + "status": "Draft", + "data": { + "location": { + "iv": "London" + } + } + }, + "scheduleJob": { + "id": "8edd59a0-a6c2-4177-9502-99ba8aabff44", + "status": "Archived", + "scheduledBy": "subject:63761585e06d5466c71521b3", + "dueTime": "2024-12-02T12:14:33Z" + }, + "appId": "06c68527-a2ca-4d84-91eb-a1707aa5b6fb,hotels", + "isDeleted": true, + "id": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", + "createdBy": "subject:63761585e06d5466c71521b3", + "lastModifiedBy": "subject:63761585e06d5466c71521b3", + "created": "2022-12-05T11:00:23Z", + "lastModified": "2022-12-05T11:00:23Z", + "version": 0 +} diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WriteContentTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WriteContentTests.cs new file mode 100644 index 000000000..298889569 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WriteContentTests.cs @@ -0,0 +1,106 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; + +namespace Squidex.Domain.Apps.Core.Model.Contents; + +public class WriteContentTests +{ + private readonly NamedId appId = NamedId.Of(DomainId.NewGuid(), "my-app"); + + [Fact] + public void Should_convert_draft_data_to_content() + { + var source = new WriteContent + { + AppId = appId, + CurrentVersion = new ContentVersion( + Status.Published, + new ContentData() + .AddField("location", + new ContentFieldData() + .AddInvariant(JsonValue.Create("Berlin")))), + NewVersion = new ContentVersion( + Status.Draft, + new ContentData() + .AddField("location", + new ContentFieldData() + .AddInvariant(JsonValue.Create("London")))), + }; + + var expected = new Content + { + AppId = appId, + Status = Status.Published, + Data = + new ContentData() + .AddField("location", + new ContentFieldData() + .AddInvariant(JsonValue.Create("London"))), + NewStatus = Status.Draft + }; + + var actual = source.ToContent(); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Should_convert_normal_data_to_content() + { + var source = new WriteContent + { + AppId = appId, + CurrentVersion = new ContentVersion( + Status.Draft, + new ContentData() + .AddField("location", + new ContentFieldData() + .AddInvariant(JsonValue.Create("Berlin")))), + }; + + var expected = new Content + { + AppId = appId, + Status = Status.Draft, + Data = + new ContentData() + .AddField("location", + new ContentFieldData() + .AddInvariant(JsonValue.Create("Berlin"))), + NewStatus = null + }; + + var actual = source.ToContent(); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Should_deserialize_state() + { + var json = File.ReadAllText("Model/Contents/WriteContent.json"); + + var deserialized = TestUtils.DefaultSerializer.Deserialize(json); + + Assert.NotNull(deserialized); + } + + [Fact] + public void Should_serialize_deserialize_state() + { + var json = File.ReadAllText("Model/Contents/WriteContent.json").CleanJson(); + + var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + + Assert.Equal(json, serialized); + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleState.json b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/Rule.json similarity index 57% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleState.json rename to backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/Rule.json index b4827b1e2..9d60493be 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleState.json +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/Rule.json @@ -1,20 +1,17 @@ { - "appId": "06c68527-a2ca-4d84-91eb-a1707aa5b6fb,hotels", - "ruleDef": { - "trigger": { - "triggerType": "ManualTrigger" - }, - "action": { - "actionType": "Webhook", - "url": "http://squidex.io" - }, - "isEnabled": true + "action": { + "actionType": "TestAction1" }, - "isDeleted": false, - "id": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", - "createdBy": "subject:63761585e06d5466c71521b3", - "lastModifiedBy": "subject:63761585e06d5466c71521b3", + "appId": "06c68527-a2ca-4d84-91eb-a1707aa5b6fb,hotels", "created": "2022-12-05T11:00:23Z", + "createdBy": "subject:63761585e06d5466c71521b3", + "id": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", + "isDeleted": true, + "isEnabled": true, "lastModified": "2022-12-05T11:00:23Z", + "lastModifiedBy": "subject:63761585e06d5466c71521b3", + "trigger": { + "triggerType": "ManualTrigger" + }, "version": 0 -} +} \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs index 08489c090..fce7e88a3 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs @@ -24,7 +24,7 @@ public class RuleTests .Select(x => new[] { x }) .ToList()!; - private readonly Rule rule_0 = new Rule(new ContentChangedTriggerV2(), new TestAction1()); + private readonly Rule rule_0 = new Rule { Action = new TestAction1(), Trigger = new ContentChangedTriggerV2() }; public sealed record OtherTrigger : RuleTrigger { @@ -60,20 +60,7 @@ public class RuleTests } [Fact] - public void Should_create_with_trigger_and_action() - { - var ruleTrigger = new ContentChangedTriggerV2(); - var ruleAction = new TestAction1(); - - var newRule = new Rule(ruleTrigger, ruleAction); - - Assert.Equal(ruleTrigger, newRule.Trigger); - Assert.Equal(ruleAction, newRule.Action); - Assert.True(newRule.IsEnabled); - } - - [Fact] - public void Should_set_enabled_to_true_if_enabling() + public void Should_enable_rule() { var rule_1 = rule_0.Disable(); var rule_2 = rule_1.Enable(); @@ -87,7 +74,7 @@ public class RuleTests } [Fact] - public void Should_set_enabled_to_false_if_disabling() + public void Should_disable_rule() { var rule_1 = rule_0.Disable(); var rule_2 = rule_1.Disable(); @@ -99,19 +86,21 @@ public class RuleTests } [Fact] - public void Should_replace_name_if_renaming() + public void Should_change_category() { - var rule_1 = rule_0.Rename("MyName"); - var rule_2 = rule_1.Rename("MyName"); + var newName = "MyName"; + + var rule_1 = rule_0.Rename(newName); + var rule_2 = rule_1.Rename(newName); Assert.NotSame(rule_0, rule_1); - Assert.Equal("MyName", rule_1.Name); - Assert.Equal("MyName", rule_2.Name); + Assert.Equal(newName, rule_1.Name); + Assert.Equal(newName, rule_2.Name); Assert.Same(rule_1, rule_2); } [Fact] - public void Should_replace_trigger_if_updating() + public void Should_replace_trigger() { var newTrigger1 = new ContentChangedTriggerV2 { HandleAll = true }; var newTrigger2 = new ContentChangedTriggerV2 { HandleAll = true }; @@ -126,6 +115,12 @@ public class RuleTests Assert.Same(rule_1, rule_2); } + [Fact] + public void Should_throw_exception_if_new_trigger_is_null() + { + Assert.Throws(() => rule_0.Update((RuleTrigger)null!)); + } + [Fact] public void Should_throw_exception_if_new_trigger_has_other_type() { @@ -133,7 +128,7 @@ public class RuleTests } [Fact] - public void Should_replace_action_if_updating() + public void Should_replace_action() { var newAction1 = new TestAction1 { Property = "NewValue" }; var newAction2 = new TestAction1 { Property = "NewValue" }; @@ -148,6 +143,12 @@ public class RuleTests Assert.Same(rule_1, rule_2); } + [Fact] + public void Should_throw_exception_if_action_trigger_is_null() + { + Assert.Throws(() => rule_0.Update((RuleAction)null!)); + } + [Fact] public void Should_throw_exception_if_new_action_has_other_type() { @@ -155,19 +156,39 @@ public class RuleTests } [Fact] - public void Should_serialize_and_deserialize() + public void Should_deserialize_old_state() + { + var original = TestUtils.DefaultSerializer.Deserialize(File.ReadAllText("Model/Rules/Rule.json")); + + var deserialized = TestUtils.DefaultSerializer.Deserialize(File.ReadAllText("Model/Rules/Rule_Old.json")); + + deserialized.Should().BeEquivalentTo(original); + } + + [Fact] + public void Should_deserialize_state() + { + var json = File.ReadAllText("Model/Rules/Rule.json"); + + var deserialized = TestUtils.DefaultSerializer.Deserialize(json); + + Assert.NotNull(deserialized); + } + + [Fact] + public void Should_serialize_deserialize_state() { - var rule_1 = rule_0.Disable().Rename("MyName"); + var json = File.ReadAllText("Model/Rules/Rule.json").CleanJson(); - var serialized = rule_1.SerializeAndDeserialize(); + var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); - serialized.Should().BeEquivalentTo(rule_1); + Assert.Equal(json, serialized); } [Fact] public void Should_serialize_and_deserialize_and_migrate_trigger() { - var rule_X = new Rule(new MigratedTrigger(), new TestAction1()); + var rule_X = new Rule { Trigger = new MigratedTrigger(), Action = new TestAction1() }; var serialized = rule_X.SerializeAndDeserialize(); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleState_Old.json b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/Rule_Old.json similarity index 82% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleState_Old.json rename to backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/Rule_Old.json index 65fa5c9b3..e3ae6aa0a 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleState_Old.json +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/Rule_Old.json @@ -1,20 +1,19 @@ { "appId": "06c68527-a2ca-4d84-91eb-a1707aa5b6fb,hotels", + "created": "2022-12-05T11:00:23Z", + "createdBy": "subject:63761585e06d5466c71521b3", + "id": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", + "isDeleted": true, + "lastModified": "2022-12-05T11:00:23Z", + "lastModifiedBy": "subject:63761585e06d5466c71521b3", "ruleDef": { "trigger": { "$type": "ManualTrigger" }, "action": { - "$type": "WebhookAction", - "url": "http://squidex.io" + "actionType": "TestAction1" }, "isEnabled": true }, - "isDeleted": false, - "id": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", - "createdBy": "subject:63761585e06d5466c71521b3", - "lastModifiedBy": "subject:63761585e06d5466c71521b3", - "created": "2022-12-05T11:00:23Z", - "lastModified": "2022-12-05T11:00:23Z", "version": 0 -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldNamesTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldNamesTests.cs new file mode 100644 index 000000000..ec865d916 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldNamesTests.cs @@ -0,0 +1,134 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Schemas; + +namespace Squidex.Domain.Apps.Core.Model.Schemas; + +public class FieldNamesTests +{ + [Theory] + [InlineData("id")] + [InlineData("lastModified")] + [InlineData("lastModifiedBy.avatar")] + public void Should_return_true_for_valid_meta_field(string fieldName) + { + Assert.True(FieldNames.IsMetaField(fieldName)); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("metaField")] + public void Should_return_false_for_invalid_meta_field(string? fieldName) + { + Assert.False(FieldNames.IsMetaField(fieldName)); + } + + [Theory] + [InlineData("data.fieldName")] + public void Should_return_true_for_valid_data_field(string fieldName) + { + Assert.True(FieldNames.IsDataField(fieldName)); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("data")] + [InlineData("data_field")] + public void Should_return_false_for_invalid_data_field(string? fieldName) + { + Assert.False(FieldNames.IsDataField(fieldName)); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("data")] + [InlineData("data_field")] + public void Should_return_false_for_invalid_data_field_with_out(string? fieldName) + { + Assert.False(FieldNames.IsDataField(fieldName, out _)); + } + + [Fact] + public void Should_extract_data_field() + { + Assert.True(FieldNames.IsDataField("data.dataField", out var dataField)); + Assert.Equal("dataField", dataField); + } + + [Fact] + public void Should_not_migrate_empty_names() + { + var source = FieldNames.Empty; + + var migrated = source.Migrate(); + + Assert.Same(source, migrated); + } + + [Fact] + public void Should_not_migrate_new_format_with_meta() + { + var source = FieldNames.Create( + "id", + "lastModifiedBy.avatar"); + + var migrated = source.Migrate(); + + Assert.Same(source, migrated); + } + + [Fact] + public void Should_not_migrate_new_format_with_data() + { + var source = FieldNames.Create( + "data.field1", + "data.field2.iv"); + + var migrated = source.Migrate(); + + Assert.Same(source, migrated); + } + + [Fact] + public void Should_not_migrate_new_format_with_mixed_fields() + { + var source = FieldNames.Create( + "id", + "data.field1", + "data.field2.iv"); + + var migrated = source.Migrate(); + + Assert.Same(source, migrated); + } + + [Fact] + public void Should_migrate_old_format() + { + var source = FieldNames.Create( + "meta.id", + "meta.lastModified", + "meta.lastModifiedBy.avatar", + "data1", + "data2.iv"); + + var migrated = source.Migrate(); + + Assert.Equal(new[] + { + "id", + "lastModified", + "lastModifiedBy.avatar", + "data.data1", + "data.data2.iv" + }, migrated.ToArray()); + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Schema.json b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Schema.json new file mode 100644 index 000000000..bbd8dcb3b --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Schema.json @@ -0,0 +1,325 @@ +{ + "appId": "7c45937e-e489-453f-81f8-a1432e778ea2,test", + "category": "My Category", + "created": "2022-07-22T17:18:04Z", + "createdBy": "subject:62cee33bcc71c0d140ad103a", + "fieldRules": [ + { + "action": "Disable", + "field": "array", + "condition": "1 == 2" + } + ], + "fields": [ + { + "id": 1, + "name": "string", + "partitioning": "language", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "StringField", + "isUnique": false, + "isEmbeddable": false, + "inlineEditable": true, + "createEnum": false, + "contentType": "Unspecified", + "editor": "Input", + "isRequired": true, + "isRequiredOnPublish": true, + "isHalfWidth": false + } + }, + { + "id": 2, + "name": "assets", + "partitioning": "language", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "AssetsField", + "previewMode": "FileName", + "expectedType": "Image", + "allowDuplicates": false, + "resolveFirst": false, + "allowedExtensions": [ + "a", + "b", + "c", + "d" + ], + "isRequired": false, + "isRequiredOnPublish": false, + "isHalfWidth": false + } + }, + { + "id": 3, + "name": "boolean", + "partitioning": "language", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "BooleanField", + "defaultValues": { + "en": true, + "de": false + }, + "inlineEditable": false, + "editor": "Checkbox", + "isRequired": false, + "isRequiredOnPublish": false, + "isHalfWidth": false + } + }, + { + "id": 4, + "name": "component", + "partitioning": "language", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "ComponentField", + "schemaId": "62a1d2a8-f08d-4870-8cb9-cb3ba286d56c", + "schemaIds": [ + "62a1d2a8-f08d-4870-8cb9-cb3ba286d56c" + ], + "isRequired": false, + "isRequiredOnPublish": false, + "isHalfWidth": false + } + }, + { + "id": 5, + "name": "components", + "partitioning": "language", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "ComponentsField", + "calculatedDefaultValue": "EmptyArray", + "schemaId": "62a1d2a8-f08d-4870-8cb9-cb3ba286d56c", + "schemaIds": [ + "62a1d2a8-f08d-4870-8cb9-cb3ba286d56c" + ], + "isRequired": false, + "isRequiredOnPublish": false, + "isHalfWidth": false + } + }, + { + "id": 6, + "name": "date", + "partitioning": "language", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "DateTimeField", + "maxValue": "2022-07-22T17:21:13Z", + "minValue": "2022-07-22T17:21:12Z", + "editor": "DateTime", + "isRequired": true, + "isRequiredOnPublish": true, + "isHalfWidth": false + } + }, + { + "id": 7, + "name": "geolocation", + "partitioning": "language", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "GeolocationField", + "editor": "Map", + "isRequired": false, + "isRequiredOnPublish": false, + "isHalfWidth": true + } + }, + { + "id": 8, + "name": "json", + "partitioning": "language", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "JsonField", + "isRequired": false, + "isRequiredOnPublish": false, + "isHalfWidth": false + } + }, + { + "id": 9, + "name": "number", + "partitioning": "language", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "NumberField", + "isUnique": false, + "inlineEditable": false, + "editor": "Input", + "isRequired": false, + "isRequiredOnPublish": false, + "isHalfWidth": false, + "tags": [ + "1", + "2", + "3" + ] + } + }, + { + "id": 10, + "name": "references", + "partitioning": "language", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "ReferencesField", + "resolveReference": false, + "allowDuplicates": false, + "mustBePublished": false, + "editor": "List", + "schemaId": "62a1d2a8-f08d-4870-8cb9-cb3ba286d56c", + "schemaIds": [ + "62a1d2a8-f08d-4870-8cb9-cb3ba286d56c" + ], + "isRequired": false, + "isRequiredOnPublish": false, + "isHalfWidth": false + } + }, + { + "id": 11, + "name": "tags", + "partitioning": "language", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "TagsField", + "defaultValues": { + "de-DE": [ + "4", + "5", + "6" + ] + }, + "defaultValue": [ + "1", + "2", + "3" + ], + "createEnum": false, + "editor": "Tags", + "normalization": "None", + "isRequired": false, + "isRequiredOnPublish": false, + "isHalfWidth": false + } + }, + { + "id": 12, + "name": "array", + "partitioning": "language", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "ArrayField", + "calculatedDefaultValue": "EmptyArray", + "isRequired": false, + "isRequiredOnPublish": false, + "isHalfWidth": false + }, + "children": [ + { + "id": 14, + "name": "nested", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "StringField", + "isUnique": false, + "isEmbeddable": false, + "inlineEditable": false, + "createEnum": false, + "contentType": "Unspecified", + "editor": "Input", + "isRequired": false, + "isRequiredOnPublish": false, + "isHalfWidth": false + } + } + ] + }, + { + "id": 13, + "name": "ui", + "partitioning": "invariant", + "isHidden": false, + "isLocked": false, + "isDisabled": false, + "properties": { + "$type": "UIField", + "editor": "Separator", + "isRequired": false, + "isRequiredOnPublish": false, + "isHalfWidth": false + } + } + ], + "fieldsInLists": [ + "string" + ], + "fieldsInReferences": [ + "boolean" + ], + "id": "62a1d2a8-f08d-4870-8cb9-cb3ba286d56c", + "isDeleted": true, + "isPublished": true, + "lastModified": "2022-07-22T17:21:54Z", + "lastModifiedBy": "subject:62cee33bcc71c0d140ad103a", + "name": "test", + "previewUrls": { + "Web": "URL" + }, + "properties": { + "tags": [ + "1", + "2", + "3" + ], + "contentsSidebarUrl": "contents-sidebar", + "contentSidebarUrl": "content-sidebar", + "contentEditorUrl": "content-editor", + "validateOnPublish": true, + "label": "label", + "hints": "hints" + }, + "schemaFieldsTotal": 14, + "scripts": { + "change": "change", + "create": "create", + "update": "update", + "delete": "delete", + "query": "query", + "queryPre": "prepare" + }, + "type": "Default", + "version": 30 +} \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs index 0f64686b8..3a9d06a43 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs @@ -28,12 +28,6 @@ public class SchemaFieldTests Assert.Equal("myField", field_0.Name); } - [Fact] - public void Should_throw_exception_if_creating_field_with_invalid_name() - { - Assert.Throws(() => Fields.Number(1, string.Empty, Partitioning.Invariant)); - } - [Fact] public void Should_hide_field() { diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs index 817d76c84..eab248358 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs @@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas; public class SchemaTests { - private readonly Schema schema_0 = new Schema("my-schema"); + private readonly Schema schema_0 = new Schema { Name = "my-schema" }; public static IEnumerable FieldProperyTypes() { @@ -38,12 +38,6 @@ public class SchemaTests Assert.Equal("my-schema", schema_0.Name); } - [Fact] - public void Should_throw_exception_if_creating_schema_with_invalid_name() - { - Assert.Throws(() => new Schema(string.Empty)); - } - [Fact] public void Should_update_schema() { @@ -255,8 +249,8 @@ public class SchemaTests var schema_1 = schema_0 .AddField(field) - .SetFieldsInLists(field.Name) - .SetFieldsInReferences(field.Name); + .SetFieldsInLists(FieldNames.Create(field.Name)) + .SetFieldsInReferences(FieldNames.Create(field.Name)); var schema_2 = schema_1.DeleteField(1); Assert.Empty(schema_2.FieldsById); @@ -350,11 +344,17 @@ public class SchemaTests Assert.Same(schema_1, schema_2); } + [Fact] + public void Should_throw_exception_if_list_fields_is_null() + { + Assert.Throws(() => schema_0.SetFieldsInLists(null!)); + } + [Fact] public void Should_set_list_fields() { - var schema_1 = schema_0.SetFieldsInLists("2"); - var schema_2 = schema_1.SetFieldsInLists("2"); + var schema_1 = schema_0.SetFieldsInLists(FieldNames.Create("2")); + var schema_2 = schema_1.SetFieldsInLists(FieldNames.Create("2")); Assert.Equal(new[] { "2" }, schema_1.FieldsInLists); Assert.Equal(new[] { "2" }, schema_2.FieldsInLists); @@ -364,19 +364,25 @@ public class SchemaTests [Fact] public void Should_also_set_list_fields_if_reordered() { - var schema_1 = schema_0.SetFieldsInLists("2", "1"); - var schema_2 = schema_1.SetFieldsInLists("1", "2"); + var schema_1 = schema_0.SetFieldsInLists(FieldNames.Create("2", "1")); + var schema_2 = schema_1.SetFieldsInLists(FieldNames.Create("1", "2")); Assert.Equal(new[] { "2", "1" }, schema_1.FieldsInLists); Assert.Equal(new[] { "1", "2" }, schema_2.FieldsInLists); Assert.NotSame(schema_1, schema_2); } + [Fact] + public void Should_throw_exception_if_reference_fields_is_null() + { + Assert.Throws(() => schema_0.SetFieldsInReferences(null!)); + } + [Fact] public void Should_set_reference_fields() { - var schema_1 = schema_0.SetFieldsInReferences("2"); - var schema_2 = schema_1.SetFieldsInReferences("2"); + var schema_1 = schema_0.SetFieldsInReferences(FieldNames.Create("2")); + var schema_2 = schema_1.SetFieldsInReferences(FieldNames.Create("2")); Assert.Equal(new[] { "2" }, schema_1.FieldsInReferences); Assert.Equal(new[] { "2" }, schema_2.FieldsInReferences); @@ -386,25 +392,37 @@ public class SchemaTests [Fact] public void Should_also_set_reference_fields_if_reordered() { - var schema_1 = schema_0.SetFieldsInReferences("2", "1"); - var schema_2 = schema_1.SetFieldsInReferences("1", "2"); + var schema_1 = schema_0.SetFieldsInReferences(FieldNames.Create("2", "1")); + var schema_2 = schema_1.SetFieldsInReferences(FieldNames.Create("1", "2")); Assert.Equal(new[] { "2", "1" }, schema_1.FieldsInReferences); Assert.Equal(new[] { "1", "2" }, schema_2.FieldsInReferences); Assert.NotSame(schema_1, schema_2); } + [Fact] + public void Should_throw_exception_if_field_rules_is_null() + { + Assert.Throws(() => schema_0.SetFieldRules(null!)); + } + [Fact] public void Should_set_field_rules() { - var schema_1 = schema_0.SetFieldRules(FieldRule.Hide("2")); - var schema_2 = schema_1.SetFieldRules(FieldRule.Hide("2")); + var schema_1 = schema_0.SetFieldRules(FieldRules.Create(FieldRule.Hide("2"))); + var schema_2 = schema_1.SetFieldRules(FieldRules.Create(FieldRule.Hide("2"))); Assert.NotEmpty(schema_1.FieldRules); Assert.NotEmpty(schema_2.FieldRules); Assert.Same(schema_1, schema_2); } + [Fact] + public void Should_throw_exception_if_scripts_is_null() + { + Assert.Throws(() => schema_0.SetScripts(null!)); + } + [Fact] public void Should_set_scripts() { @@ -427,6 +445,12 @@ public class SchemaTests Assert.Same(schema_1, schema_2); } + [Fact] + public void Should_throw_exception_preview_urls_is_null() + { + Assert.Throws(() => schema_0.SetPreviewUrls(null!)); + } + [Fact] public void Should_set_preview_urls() { @@ -449,45 +473,33 @@ public class SchemaTests } [Fact] - public void Should_serialize_and_deserialize_schema() + public void Should_deserialize_old_state() { - var schemaSource = - TestSchema.MixedSchema(SchemaType.Singleton).Schema - .ChangeCategory("Category") - .SetFieldRules(FieldRule.Hide("2")) - .SetFieldsInLists("field2") - .SetFieldsInReferences("field1") - .SetScripts(new SchemaScripts - { - Create = "" - }) - .SetPreviewUrls(new Dictionary - { - ["web"] = "Url" - }.ToReadonlyDictionary()); + var original = TestUtils.DefaultSerializer.Deserialize(File.ReadAllText("Model/Schemas/Schema.json")); - var schemaTarget = schemaSource.SerializeAndDeserialize(); + var deserialized = TestUtils.DefaultSerializer.Deserialize(File.ReadAllText("Model/Schemas/Schema_Old.json")); - schemaTarget.Should().BeEquivalentTo(schemaSource); + deserialized.Should().BeEquivalentTo(original); } [Fact] - public void Should_deserialize_obsolete_isSingleton_property() + public void Should_deserialize_state() { - var schemaSource = new - { - name = "my-schema", - isPublished = true, - isSingleton = true - }; + var json = File.ReadAllText("Model/Schemas/Schema.json"); - var expected = - new Schema("my-schema", type: SchemaType.Singleton) - .Publish(); + var deserialized = TestUtils.DefaultSerializer.Deserialize(json); + + Assert.NotNull(deserialized); + } + + [Fact] + public void Should_serialize_deserialize_state() + { + var json = File.ReadAllText("Model/Schemas/Schema.json").CleanJson(); - var schemaTarget = schemaSource.SerializeAndDeserialize(); + var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); - schemaTarget.Should().BeEquivalentTo(expected); + Assert.Equal(json, serialized); } private static RootField CreateField(int id) diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaState.json b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Schema_Old.json similarity index 99% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaState.json rename to backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Schema_Old.json index 2235e95eb..b310ee1cc 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaState.json +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Schema_Old.json @@ -1,7 +1,14 @@ { "appId": "7c45937e-e489-453f-81f8-a1432e778ea2,test", + "created": "2022-07-22T17:18:04Z", + "createdBy": "subject:62cee33bcc71c0d140ad103a", + "id": "62a1d2a8-f08d-4870-8cb9-cb3ba286d56c", + "isDeleted": true, + "lastModified": "2022-07-22T17:21:54Z", + "lastModifiedBy": "subject:62cee33bcc71c0d140ad103a", "schemaDef": { "name": "test", + "category": "My Category", "isPublished": true, "type": "Default", "properties": { @@ -316,11 +323,5 @@ } }, "schemaFieldsTotal": 14, - "isDeleted": false, - "id": "62a1d2a8-f08d-4870-8cb9-cb3ba286d56c", - "createdBy": "subject:62cee33bcc71c0d140ad103a", - "lastModifiedBy": "subject:62cee33bcc71c0d140ad103a", - "created": "2022-07-22T17:18:04Z", - "lastModified": "2022-07-22T17:21:54Z", "version": 30 } \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Teams/Team.json b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Teams/Team.json new file mode 100644 index 000000000..d782de860 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Teams/Team.json @@ -0,0 +1,16 @@ +{ + "contributors": { + "me": "Owner" + }, + "created": "2022-12-05T11:00:23Z", + "createdBy": "subject:63761585e06d5466c71521b3", + "id": "265aaf1e-5dff-4c40-b1e6-1149f652cd30", + "lastModified": "2022-12-05T11:00:23Z", + "lastModifiedBy": "subject:63761585e06d5466c71521b3", + "name": "My Team", + "plan": { + "owner": "subject:63761585e06d5466c71521b3", + "planId": "Premium" + }, + "version": 0 +} \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Teams/TeamTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Teams/TeamTests.cs new file mode 100644 index 000000000..3c6aaea96 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Teams/TeamTests.cs @@ -0,0 +1,87 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; +using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Model.Teams; + +#pragma warning disable SA1310 // Field names must not contain underscore + +public class TeamTests +{ + private readonly Team team_0 = new Team(); + + [Fact] + public void Should_throw_exception_if_new_name_is_null() + { + Assert.Throws(() => team_0.Rename(null!)); + } + + [Fact] + public void Should_rename() + { + var newName = "MyTeam"; + + var team_1 = team_0.Rename(newName); + var team_2 = team_1.Rename(newName); + + Assert.NotSame(team_0, team_1); + Assert.Equal(newName, team_1.Name); + Assert.Equal(newName, team_2.Name); + Assert.Same(team_1, team_2); + } + + [Fact] + public void Should_update_plan() + { + var plan1 = new AssignedPlan(RefToken.User("me"), "Premium"); + var plan2 = new AssignedPlan(RefToken.User("me"), "Premium"); + + var team_1 = team_0.ChangePlan(plan1); + var team_2 = team_1.ChangePlan(plan2); + + Assert.NotSame(team_0, team_1); + Assert.Equal(plan1, team_1.Plan); + Assert.Equal(plan2, team_2.Plan); + Assert.Same(team_1, team_2); + } + + [Fact] + public void Should_update_contributors() + { + var team_1 = team_0.UpdateContributors(true, (_, c) => c.Assign("me", Role.Owner)); + var team_2 = team_1.UpdateContributors(true, (_, c) => c.Assign("me", Role.Owner)); + + Assert.NotSame(team_0, team_1); + Assert.Single(team_1.Contributors); + Assert.Single(team_2.Contributors); + Assert.Same(team_1, team_2); + } + + [Fact] + public void Should_deserialize_state() + { + var json = File.ReadAllText("Model/Teams/Team.json"); + + var deserialized = TestUtils.DefaultSerializer.Deserialize(json); + + Assert.NotNull(deserialized); + } + + [Fact] + public void Should_serialize_deserialize_state() + { + var json = File.ReadAllText("Model/Teams/Team.json").CleanJson(); + + var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + + Assert.Equal(json, serialized); + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs index eaf53f740..40007ccb1 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs @@ -21,7 +21,7 @@ public class ContentConversionTests public ContentConversionTests() { schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddComponent(1, "component", Partitioning.Invariant) .AddComponents(2, "components", Partitioning.Invariant) .AddAssets(3, "assets1", Partitioning.Invariant) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/DefaultValuesTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/DefaultValuesTests.cs index f8068b469..1d72b5abe 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/DefaultValuesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/DefaultValuesTests.cs @@ -24,7 +24,7 @@ public class DefaultValuesTests public DefaultValuesTests() { schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddString(1, "myString", Partitioning.Language, new StringFieldProperties { DefaultValue = "en-string", IsRequired = true }) .AddNumber(2, "myNumber", Partitioning.Invariant, @@ -204,4 +204,37 @@ public class DefaultValuesTests Assert.Equal(expected, actual); } + + [Fact] + public void Should_enrich_fields_if_at_field_names_is_given() + { + var source = + new ContentData() + .AddField("myString", + new ContentFieldData() + .AddLocalized("de", string.Empty)) + .AddField("myNumber", + new ContentFieldData() + .AddInvariant(456)); + + var actual = + new ContentConverter(ResolvedComponents.Empty, schema) + .Add(new AddDefaultValues(languages.ToResolver()) + { + FieldNames = ["myString"] + }) + .Convert(source); + + var expected = + new ContentData() + .AddField("myString", + new ContentFieldData() + .AddLocalized("de", string.Empty) + .AddLocalized("en", "en-string")) + .AddField("myNumber", + new ContentFieldData() + .AddInvariant(456)); + + Assert.Equal(expected, actual); + } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs index c9bde14b3..ad88914b8 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs @@ -32,7 +32,7 @@ public class FieldConvertersTests var field2 = Fields.Number(2, "number2", Partitioning.Language); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1) .AddField(field2); @@ -63,7 +63,7 @@ public class FieldConvertersTests var field2 = Fields.Number(2, "number2", Partitioning.Language); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1) .AddField(field2); @@ -98,7 +98,7 @@ public class FieldConvertersTests var field2 = Fields.Number(2, "number2", Partitioning.Language).Hide(); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1) .AddField(field2); @@ -133,7 +133,7 @@ public class FieldConvertersTests var field2 = Fields.Number(2, "number2", Partitioning.Language); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1) .AddField(field2) .HideField(2); @@ -167,7 +167,7 @@ public class FieldConvertersTests var field1 = Fields.String(1, "string1", Partitioning.Language); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1); var source = @@ -199,7 +199,7 @@ public class FieldConvertersTests var field1 = Fields.String(1, "string1", Partitioning.Language); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1); var source = @@ -231,7 +231,7 @@ public class FieldConvertersTests var field1 = Fields.String(1, "string", Partitioning.Language); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1); var source = @@ -267,7 +267,7 @@ public class FieldConvertersTests var field1 = Fields.String(1, "string", Partitioning.Language); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1); var source = @@ -302,7 +302,7 @@ public class FieldConvertersTests var field1 = Fields.String(1, "string", Partitioning.Language); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1); var source = @@ -341,7 +341,7 @@ public class FieldConvertersTests var field1 = Fields.String(1, "string", Partitioning.Language); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1); var source = @@ -370,7 +370,7 @@ public class FieldConvertersTests var field1 = Fields.String(1, "string", Partitioning.Invariant); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1); var source = @@ -396,7 +396,7 @@ public class FieldConvertersTests var field1 = Fields.String(1, "string", Partitioning.Invariant); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1); var source = @@ -432,7 +432,7 @@ public class FieldConvertersTests var field1 = Fields.String(1, "string", Partitioning.Invariant); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1); var source = @@ -467,7 +467,7 @@ public class FieldConvertersTests var field1 = Fields.String(1, "string", Partitioning.Invariant); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1); var source = @@ -492,7 +492,7 @@ public class FieldConvertersTests var field1 = Fields.String(1, "string", Partitioning.Language); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1); var config = @@ -536,7 +536,7 @@ public class FieldConvertersTests var field1 = Fields.String(1, "string", Partitioning.Language); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddField(field1); var source = @@ -608,7 +608,7 @@ public class FieldConvertersTests var field = Fields.Component(1, "component", Partitioning.Invariant); var componentId = DomainId.NewGuid(); - var component = new Schema("my-component"); + var component = new Schema { Name = "my-component" }; var components = new ResolvedComponents(new Dictionary { [componentId] = component diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs index 0cb5ba6c6..95ae22706 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs @@ -107,7 +107,7 @@ public class ValueConvertersTests [InlineData("*")] public void Should_convert_nested_asset_ids_to_urls(string path) { - var field = Fields.Array(1, "parent", Partitioning.Invariant, null, null, Fields.Assets(11, "assets")); + var field = Fields.Array(1, "parent", Partitioning.Invariant, null, Fields.Assets(11, "assets")); var source = JsonValue.Array( @@ -133,7 +133,7 @@ public class ValueConvertersTests [InlineData("other.assets")] public void Should_not_convert_nested_asset_ids_if_field_name_does_not_match(string path) { - var field = Fields.Array(1, "parent", Partitioning.Invariant, null, null, Fields.Assets(11, "assets")); + var field = Fields.Array(1, "parent", Partitioning.Invariant, null, Fields.Assets(11, "assets")); var source = JsonValue.Array( @@ -155,7 +155,7 @@ public class ValueConvertersTests var field = Fields.Component(1, "component", Partitioning.Invariant); var componentId = DomainId.NewGuid(); - var component = new Schema("my-component"); + var component = new Schema { Name = "my-component" }; var components = new ResolvedComponents(new Dictionary { [componentId] = component @@ -183,7 +183,7 @@ public class ValueConvertersTests var field = Fields.Component(1, "component", Partitioning.Invariant); var componentId = DomainId.NewGuid(); - var component = new Schema("my-component"); + var component = new Schema { Name = "my-component" }; var components = new ResolvedComponents(new Dictionary { [componentId] = component @@ -209,7 +209,7 @@ public class ValueConvertersTests var field = Fields.Array(1, "component", Partitioning.Invariant); var componentId = DomainId.NewGuid(); - var component = new Schema("my-component"); + var component = new Schema { Name = "my-component" }; var components = new ResolvedComponents(new Dictionary { [componentId] = component @@ -234,7 +234,7 @@ public class ValueConvertersTests var field = Fields.Component(1, "component", Partitioning.Invariant); var componentId = DomainId.NewGuid(); - var component = new Schema("my-component"); + var component = new Schema { Name = "my-component" }; var components = new ResolvedComponents(new Dictionary { [componentId] = component diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs index 4240c3a39..4c467baa3 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs @@ -30,7 +30,7 @@ public class SchemaSynchronizerTests public void Should_create_events_if_schema_deleted() { var sourceSchema = - new Schema("source"); + new Schema { Name = "source" }; var targetSchema = (Schema?)null; @@ -46,10 +46,10 @@ public class SchemaSynchronizerTests public void Should_create_events_if_category_changed() { var sourceSchema = - new Schema("source"); + new Schema { Name = "source" }; var targetSchema = - new Schema("target") + new Schema { Name = "target" } .ChangeCategory("Category"); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -68,10 +68,11 @@ public class SchemaSynchronizerTests }; var sourceSchema = - new Schema("source"); + new Schema { Name = "source" }; var targetSchema = - new Schema("target").SetScripts(scripts); + new Schema { Name = "target" } + .SetScripts(scripts); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -89,10 +90,10 @@ public class SchemaSynchronizerTests }.ToReadonlyDictionary(); var sourceSchema = - new Schema("source"); + new Schema { Name = "source" }; var targetSchema = - new Schema("target") + new Schema { Name = "target" } .SetPreviewUrls(previewUrls); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -106,10 +107,10 @@ public class SchemaSynchronizerTests public void Should_create_events_if_schema_published() { var sourceSchema = - new Schema("source"); + new Schema { Name = "source" }; var targetSchema = - new Schema("target") + new Schema { Name = "target" } .Publish(); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -123,11 +124,11 @@ public class SchemaSynchronizerTests public void Should_create_events_if_schema_unpublished() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .Publish(); var targetSchema = - new Schema("target"); + new Schema { Name = "target" }; var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -140,12 +141,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_list_fields_changed() { var sourceSchema = - new Schema("source") - .SetFieldsInLists("1", "2"); + new Schema { Name = "source" } + .SetFieldsInLists(FieldNames.Create("1", "2")); var targetSchema = - new Schema("target") - .SetFieldsInLists("2", "1"); + new Schema { Name = "target" } + .SetFieldsInLists(FieldNames.Create("2", "1")); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -158,12 +159,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_reference_fields_changed() { var sourceSchema = - new Schema("source") - .SetFieldsInReferences("1", "2"); + new Schema { Name = "source" } + .SetFieldsInReferences(FieldNames.Create("1", "2")); var targetSchema = - new Schema("target") - .SetFieldsInReferences("2", "1"); + new Schema { Name = "target" } + .SetFieldsInReferences(FieldNames.Create("2", "1")); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -176,12 +177,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_field_rules_changed_changed() { var sourceSchema = - new Schema("source") - .SetFieldRules(FieldRule.Hide("2")); + new Schema { Name = "source" } + .SetFieldRules(FieldRules.Create(FieldRule.Hide("2"))); var targetSchema = - new Schema("target") - .SetFieldRules(FieldRule.Hide("1")); + new Schema { Name = "target" } + .SetFieldRules(FieldRules.Create(FieldRule.Hide("1"))); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -194,12 +195,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_nested_field_deleted() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -213,11 +214,11 @@ public class SchemaSynchronizerTests public void Should_create_events_if_field_deleted() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); var targetSchema = - new Schema("target"); + new Schema { Name = "target" }; var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -232,12 +233,12 @@ public class SchemaSynchronizerTests var properties = new StringFieldProperties { IsRequired = true }; var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name, properties)); @@ -254,11 +255,11 @@ public class SchemaSynchronizerTests var properties = new StringFieldProperties { Pattern = "a-z" }; var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant, properties); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -272,12 +273,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_nested_field_locked() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)) .LockField(nestedId.Id, arrayId.Id); @@ -293,11 +294,11 @@ public class SchemaSynchronizerTests public void Should_create_events_if_field_locked() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant) .LockField(stringId.Id); @@ -312,12 +313,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_nested_field_hidden() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)) .HideField(nestedId.Id, arrayId.Id); @@ -333,11 +334,11 @@ public class SchemaSynchronizerTests public void Should_create_events_if_field_hidden() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant) .HideField(stringId.Id); @@ -352,13 +353,13 @@ public class SchemaSynchronizerTests public void Should_create_events_if_nested_field_shown() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)) .HideField(nestedId.Id, arrayId.Id); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)); @@ -373,12 +374,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_field_shown() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant) .HideField(stringId.Id); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -392,12 +393,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_nested_field_disabled() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)) .DisableField(nestedId.Id, arrayId.Id); @@ -413,11 +414,11 @@ public class SchemaSynchronizerTests public void Should_create_events_if_field_disabled() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant) .DisableField(stringId.Id); @@ -432,13 +433,13 @@ public class SchemaSynchronizerTests public void Should_create_events_if_nested_field_enabled() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)) .DisableField(nestedId.Id, arrayId.Id); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)); @@ -453,12 +454,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_field_enabled() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant) .DisableField(stringId.Id); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -472,10 +473,10 @@ public class SchemaSynchronizerTests public void Should_create_events_if_field_created() { var sourceSchema = - new Schema("source"); + new Schema { Name = "source" }; var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant) .HideField(stringId.Id); @@ -493,11 +494,11 @@ public class SchemaSynchronizerTests public void Should_create_events_if_field_type_has_changed() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddTags(stringId.Id, stringId.Name, Partitioning.Invariant); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -514,11 +515,11 @@ public class SchemaSynchronizerTests public void Should_create_events_if_field_partitioning_has_changed() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddString(stringId.Id, stringId.Name, Partitioning.Language); var events = sourceSchema.Synchronize(targetSchema, idGenerator); @@ -535,10 +536,10 @@ public class SchemaSynchronizerTests public void Should_create_events_if_nested_field_created() { var sourceSchema = - new Schema("source"); + new Schema { Name = "source" }; var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(nestedId.Id, nestedId.Name)) .HideField(nestedId.Id, arrayId.Id); @@ -559,13 +560,13 @@ public class SchemaSynchronizerTests public void Should_create_events_if_nested_fields_reordered() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(10, "f1") .AddString(11, "f2")); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f .AddString(1, "f2") .AddString(2, "f1")); @@ -581,12 +582,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_fields_reordered() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(10, "f1", Partitioning.Invariant) .AddString(11, "f2", Partitioning.Invariant); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddString(1, "f2", Partitioning.Invariant) .AddString(2, "f1", Partitioning.Invariant); @@ -601,12 +602,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_fields_reordered_after_sync() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(10, "f1", Partitioning.Invariant) .AddString(11, "f2", Partitioning.Invariant); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddString(1, "f3", Partitioning.Invariant) .AddString(2, "f1", Partitioning.Invariant); @@ -623,12 +624,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_fields_reordered_after_sync2() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(10, "f1", Partitioning.Invariant) .AddString(11, "f2", Partitioning.Invariant); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddString(1, "f1", Partitioning.Invariant) .AddString(2, "f3", Partitioning.Invariant) .AddString(3, "f2", Partitioning.Invariant); @@ -645,12 +646,12 @@ public class SchemaSynchronizerTests public void Should_create_events_if_field_renamed() { var sourceSchema = - new Schema("source") + new Schema { Name = "source" } .AddString(10, "f1", Partitioning.Invariant) .AddString(11, "f2", Partitioning.Invariant); var targetSchema = - new Schema("target") + new Schema { Name = "target" } .AddString(1, "f3", Partitioning.Invariant) .AddString(2, "f2", Partitioning.Invariant); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs index 61f37ddbe..90b46baad 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs @@ -22,7 +22,7 @@ public class ReferenceExtractionTests public ReferenceExtractionTests() { schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddComponent(1, "component", Partitioning.Invariant) .AddComponents(2, "components", Partitioning.Invariant) .AddAssets(3, "assets1", Partitioning.Invariant) @@ -214,7 +214,7 @@ public class ReferenceExtractionTests var id1 = DomainId.NewGuid(); var id2 = DomainId.NewGuid(); - var arrayField = Fields.Array(1, "myArray", Partitioning.Invariant, null, null, field); + var arrayField = Fields.Array(1, "myArray", Partitioning.Invariant, null, field); var value = JsonValue.Array( diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceFormattingTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceFormattingTests.cs index 8d73a3f21..be6dc1f33 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceFormattingTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceFormattingTests.cs @@ -24,13 +24,13 @@ public class ReferenceFormattingTests var data = CreateData(); var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddString(1, "ref1", Partitioning.Invariant, new StringFieldProperties()) .AddString(2, "ref2", Partitioning.Invariant, new StringFieldProperties()) .AddString(3, "non-ref", Partitioning.Invariant) - .SetFieldsInReferences("ref1", "ref2"); + .SetFieldsInReferences(FieldNames.Create("ref1", "ref2")); var formatted = data.FormatReferences(schema, languages); @@ -78,7 +78,7 @@ public class ReferenceFormattingTests private static Schema CreateNoRefSchema() { - return new Schema("my-schema") + return new Schema { Name = "my-schema" } .AddString(1, "ref1", Partitioning.Invariant) .AddString(2, "ref2", Partitioning.Invariant) .AddString(3, "non-ref", Partitioning.Invariant); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs index 029230e02..5e8e2de5a 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs @@ -775,7 +775,7 @@ public class RuleServiceTests private RuleContext Rule(bool disable = false, bool includeStale = false, bool includeSkipped = false, RuleAction? action = null, RuleTrigger? trigger = null) { - var rule = new Rule(trigger ?? new ContentChangedTriggerV2(), action ?? new ValidAction()); + var rule = new Rule { Trigger = trigger ?? new ContentChangedTriggerV2(), Action = action ?? new ValidAction() }; if (disable) { @@ -785,10 +785,9 @@ public class RuleServiceTests return new RuleContext { AppId = appId, - Rule = rule, - RuleId = ruleId, IncludeStale = includeStale, - IncludeSkipped = includeSkipped + IncludeSkipped = includeSkipped, + Rule = rule }; } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs index 31cd9ea04..d305e411b 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs @@ -23,7 +23,7 @@ public class ScriptingCompleterTests public ScriptingCompleterTests() { var schema = - new Schema("simple") + new Schema { Name = "my-schema" } .AddString(1, "my-field", Partitioning.Invariant); dataSchema = schema.BuildDataSchema(LanguagesConfig.English.ToResolver(), ResolvedComponents.Empty); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs index 3b4d79c55..635db8a48 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs @@ -147,6 +147,6 @@ public class ArrayFieldTests : IClassFixture private static RootField Field(ArrayFieldProperties properties) { - return Fields.Array(1, "myArray", Partitioning.Invariant, properties, null, Fields.String(2, "myString")); + return Fields.Array(1, "myArray", Partitioning.Invariant, properties, Fields.String(2, "myString")); } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs index 6f40d59c6..7c951a7cb 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Core.ValidateContent; @@ -31,7 +32,7 @@ public class AssetsFieldTests : IClassFixture { var actual = ids.Select(TestAssets.Document).ToList(); - return Task.FromResult>(actual); + return Task.FromResult>(actual); }); } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentFieldTests.cs index a72c37eb4..8a8f0f167 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentFieldTests.cs @@ -172,7 +172,7 @@ public class ComponentFieldTests : IClassFixture private (DomainId, RootField, ResolvedComponents) Field(ComponentFieldProperties properties, bool isRequired = false) { var schema = - new Schema("my-component") + new Schema { Name = "my-component" } .AddNumber(1, "componentField", Partitioning.Invariant, new NumberFieldProperties { IsRequired = isRequired }); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentsFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentsFieldTests.cs index 1a93d4bba..18a26c0fe 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentsFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ComponentsFieldTests.cs @@ -220,7 +220,7 @@ public class ComponentsFieldTests : IClassFixture private (DomainId, RootField, ResolvedComponents) Field(ComponentsFieldProperties properties, bool isRequired = false) { var schema = - new Schema("my-component") + new Schema { Name = "my-component" } .AddNumber(1, "componentField", Partitioning.Invariant, new NumberFieldProperties { IsRequired = isRequired }); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs index d3f7bedca..5ea3a2923 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs @@ -20,7 +20,7 @@ public class ContentValidationTests : IClassFixture { private readonly LanguagesConfig languages = LanguagesConfig.English.Set(Language.DE); private readonly List errors = []; - private Schema schema = new Schema("my-schema"); + private Schema schema = new Schema { Name = "my-schema" }; [Fact] public async Task Should_add_error_if_value_validator_throws_exception() diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs index 6ed47399c..8f3f981bb 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/UIFieldTests.cs @@ -62,7 +62,7 @@ public class UIFieldTests : IClassFixture public async Task Should_add_error_if_field_object_is_defined() { var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddUI(1, "myUI1", Partitioning.Invariant) .AddUI(2, "myUI2", Partitioning.Invariant); @@ -88,7 +88,7 @@ public class UIFieldTests : IClassFixture public async Task Should_add_error_if_array_item_field_is_defined() { var schema = - new Schema("my-schema") + new Schema { Name = "my-schema" } .AddArray(1, "myArray", Partitioning.Invariant, array => array .AddUI(101, "myUI")); @@ -113,6 +113,6 @@ public class UIFieldTests : IClassFixture private static NestedField Field(UIFieldProperties properties) { - return new NestedField(1, "myUI", properties); + return Fields.UI(1, "myUI", properties); } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs index 881f87b47..7ed1579ca 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.TestHelpers; @@ -119,13 +120,14 @@ public static class ValidationTestExtensions ResolvedComponents? components = null, DomainId? contentId = null) { + schema ??= new Schema(); + var rootContext = new RootContext( - TestUtils.DefaultSerializer, - AppId, - SchemaId, - schema ?? new Schema(SchemaId.Name), + new App { Id = AppId.Id, Name = AppId.Name }, + schema with { Id = SchemaId.Id, Name = SchemaId.Name }, contentId ?? DomainId.NewGuid(), - components ?? ResolvedComponents.Empty); + components ?? ResolvedComponents.Empty, + TestUtils.DefaultSerializer); var context = new ValidationContext(rootContext) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AssetsValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AssetsValidatorTests.cs index 0a4e1b875..3dbb52acf 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AssetsValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/AssetsValidatorTests.cs @@ -18,16 +18,16 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators; public class AssetsValidatorTests : IClassFixture { private readonly List errors = []; - private static readonly IAssetInfo Document = TestAssets.Document(DomainId.NewGuid()); - private static readonly IAssetInfo Image1 = TestAssets.Image(DomainId.NewGuid()); - private static readonly IAssetInfo Image2 = TestAssets.Image(DomainId.NewGuid()); - private static readonly IAssetInfo ImageSvg = TestAssets.Svg(DomainId.NewGuid()); - private static readonly IAssetInfo Video = TestAssets.Video(DomainId.NewGuid()); + private static readonly Asset Document = TestAssets.Document(DomainId.NewGuid()); + private static readonly Asset Image1 = TestAssets.Image(DomainId.NewGuid()); + private static readonly Asset Image2 = TestAssets.Image(DomainId.NewGuid()); + private static readonly Asset ImageSvg = TestAssets.Svg(DomainId.NewGuid()); + private static readonly Asset Video = TestAssets.Video(DomainId.NewGuid()); public static IEnumerable AssetsWithDimensions() { - yield return new object[] { Image1.AssetId }; - yield return new object[] { Video.AssetId }; + yield return new object[] { Image1.Id }; + yield return new object[] { Video.Id }; } [Fact] @@ -35,7 +35,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties()); - await sut.ValidateAsync(CreateValue(Document.AssetId), errors); + await sut.ValidateAsync(CreateValue(Document.Id), errors); Assert.Empty(errors); } @@ -65,7 +65,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { AllowDuplicates = true }); - await sut.ValidateAsync(CreateValue(Image1.AssetId, Image1.AssetId), errors); + await sut.ValidateAsync(CreateValue(Image1.Id, Image1.Id), errors); Assert.Empty(errors); } @@ -75,7 +75,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { ExpectedType = AssetType.Image }); - await sut.ValidateAsync(CreateValue(ImageSvg.AssetId, Image1.AssetId), errors); + await sut.ValidateAsync(CreateValue(ImageSvg.Id, Image1.Id), errors); Assert.Empty(errors); } @@ -94,14 +94,14 @@ public class AssetsValidatorTests : IClassFixture [Fact] public async Task Should_add_error_if_asset_are_not_valid() { - var assetId = DomainId.NewGuid(); + var id = DomainId.NewGuid(); var sut = Validator(new AssetsFieldProperties()); - await sut.ValidateAsync(CreateValue(assetId), errors); + await sut.ValidateAsync(CreateValue(id), errors); errors.Should().BeEquivalentTo( - new[] { $"[1]: Id {assetId} not found." }); + new[] { $"[1]: Id {id} not found." }); } [Fact] @@ -109,7 +109,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { MinSize = 5 * 1024 }); - await sut.ValidateAsync(CreateValue(Document.AssetId, Image1.AssetId), errors); + await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors); errors.Should().BeEquivalentTo( new[] { "[1]: Size of 4 kB must be greater than 5 kB." }); @@ -120,7 +120,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { MaxSize = 5 * 1024 }); - await sut.ValidateAsync(CreateValue(Document.AssetId, Image1.AssetId), errors); + await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors); errors.Should().BeEquivalentTo( new[] { "[2]: Size of 8 kB must be less than 5 kB." }); @@ -131,7 +131,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { ExpectedType = AssetType.Image }); - await sut.ValidateAsync(CreateValue(Document.AssetId, Image1.AssetId), errors); + await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors); errors.Should().BeEquivalentTo( new[] { "[1]: Not of expected type: Image." }); @@ -143,7 +143,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { MinWidth = 1000 }); - await sut.ValidateAsync(CreateValue(Document.AssetId, videoOrImageId), errors); + await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); errors.Should().BeEquivalentTo( new[] { "[2]: Width 800px must be greater than 1000px." }); @@ -155,7 +155,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { MaxWidth = 700 }); - await sut.ValidateAsync(CreateValue(Document.AssetId, videoOrImageId), errors); + await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); errors.Should().BeEquivalentTo( new[] { "[2]: Width 800px must be less than 700px." }); @@ -167,7 +167,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { MinHeight = 800 }); - await sut.ValidateAsync(CreateValue(Document.AssetId, videoOrImageId), errors); + await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); errors.Should().BeEquivalentTo( new[] { "[2]: Height 600px must be greater than 800px." }); @@ -179,7 +179,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { MaxHeight = 500 }); - await sut.ValidateAsync(CreateValue(Document.AssetId, videoOrImageId), errors); + await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); errors.Should().BeEquivalentTo( new[] { "[2]: Height 600px must be less than 500px." }); @@ -191,7 +191,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { AspectWidth = 1, AspectHeight = 1 }); - await sut.ValidateAsync(CreateValue(Document.AssetId, videoOrImageId), errors); + await sut.ValidateAsync(CreateValue(Document.Id, videoOrImageId), errors); errors.Should().BeEquivalentTo( new[] { "[2]: Must have aspect ratio 1:1." }); @@ -202,7 +202,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { MinItems = 2 }); - await sut.ValidateAsync(CreateValue(Image1.AssetId), errors); + await sut.ValidateAsync(CreateValue(Image1.Id), errors); errors.Should().BeEquivalentTo( new[] { "Must have at least 2 item(s)." }); @@ -213,7 +213,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { MaxItems = 1 }); - await sut.ValidateAsync(CreateValue(Image1.AssetId, Image2.AssetId), errors); + await sut.ValidateAsync(CreateValue(Image1.Id, Image2.Id), errors); errors.Should().BeEquivalentTo( new[] { "Must not have more than 1 item(s)." }); @@ -224,7 +224,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties()); - await sut.ValidateAsync(CreateValue(Image1.AssetId, Image1.AssetId), errors); + await sut.ValidateAsync(CreateValue(Image1.Id, Image1.Id), errors); errors.Should().BeEquivalentTo( new[] { "Must not contain duplicate values." }); @@ -235,7 +235,7 @@ public class AssetsValidatorTests : IClassFixture { var sut = Validator(new AssetsFieldProperties { AllowedExtensions = ReadonlyList.Create("mp4") }); - await sut.ValidateAsync(CreateValue(Document.AssetId, Image1.AssetId), errors); + await sut.ValidateAsync(CreateValue(Document.Id, Image1.Id), errors); errors.Should().BeEquivalentTo( new[] @@ -259,9 +259,9 @@ public class AssetsValidatorTests : IClassFixture { return (ids, ct) => { - var actual = new List { Document, Image1, Image2, ImageSvg, Video }; + var actual = new List { Document, Image1, Image2, ImageSvg, Video }; - return Task.FromResult>(actual); + return Task.FromResult>(actual); }; } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/ComponentValidatorTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/ComponentValidatorTests.cs index a93e1fd4b..fb97bb9d3 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/ComponentValidatorTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/ComponentValidatorTests.cs @@ -24,7 +24,7 @@ public class ComponentValidatorTests : IClassFixture var validator = A.Fake(); var componentData = JsonValue.Object(); - var componentObject = new Component("type", componentData, new Schema("my-schema")); + var componentObject = new Component("type", componentData, new Schema { Name = "my-schema" }); var isFactoryCalled = false; diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj b/backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj index 59d554438..5b7708d6e 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj @@ -41,4 +41,40 @@ + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestAssets.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestAssets.cs index 802cd9229..847c6ffe6 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestAssets.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestAssets.cs @@ -6,54 +6,28 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Assets; -using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.TestHelpers; public static class TestAssets { - public sealed class AssetInfo : IAssetInfo + public static Asset Document(DomainId id) { - public DomainId AssetId { get; set; } - - 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 bool IsImage { get; set; } - - public int? PixelWidth { get; set; } - - public int? PixelHeight { get; set; } - - public AssetMetadata Metadata { get; set; } - - public AssetType Type { get; set; } - } - - public static AssetInfo Document(DomainId id) - { - return new AssetInfo + return new Asset { - AssetId = id, + Id = id, FileName = "MyDocument.pdf", FileSize = 1024 * 4, Type = AssetType.Unknown }; } - public static AssetInfo Image(DomainId id) + public static Asset Image(DomainId id) { - return new AssetInfo + return new Asset { - AssetId = id, + Id = id, FileName = "MyImage.png", FileSize = 1024 * 8, Type = AssetType.Image, @@ -64,11 +38,11 @@ public static class TestAssets }; } - public static AssetInfo Video(DomainId id) + public static Asset Video(DomainId id) { - return new AssetInfo + return new Asset { - AssetId = id, + Id = id, FileName = "MyImage.png", FileSize = 1024 * 8, Type = AssetType.Video, @@ -79,11 +53,11 @@ public static class TestAssets }; } - public static AssetInfo Svg(DomainId id) + public static Asset Svg(DomainId id) { - return new AssetInfo + return new Asset { - AssetId = id, + Id = id, FileName = "MyImage.png", FileSize = 1024 * 8, Type = AssetType.Unknown, diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestSchema.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestSchema.cs index c432080e9..5ed46e8f2 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestSchema.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestSchema.cs @@ -15,18 +15,40 @@ public static class TestSchema { public static (Schema Schema, ResolvedComponents) MixedSchema(SchemaType type = SchemaType.Default) { + var appId = NamedId.Of(DomainId.NewGuid(), "my-app"); + + var user = RefToken.User("me"); + var componentId1 = DomainId.NewGuid(); var componentId2 = DomainId.NewGuid(); var componentIds = ReadonlyList.Create(componentId1, componentId2); - var component1 = new Schema("component1") - .Publish() + var component1 = new Schema + { + AppId = appId, + Id = DomainId.NewGuid(), + Name = "component1", + Created = default, + CreatedBy = user, + LastModified = default, + LastModifiedBy = user, + Version = 1, + }.Publish() .AddString(1, "unique1", Partitioning.Invariant) .AddString(2, "shared1", Partitioning.Invariant) .AddBoolean(3, "shared2", Partitioning.Invariant); - var component2 = new Schema("component2") - .Publish() + var component2 = new Schema + { + AppId = appId, + Id = DomainId.NewGuid(), + Name = "component2", + Created = default, + CreatedBy = user, + LastModified = default, + LastModifiedBy = user, + Version = 1, + }.Publish() .AddNumber(1, "unique1", Partitioning.Invariant) .AddNumber(2, "shared1", Partitioning.Invariant) .AddBoolean(3, "shared2", Partitioning.Invariant); @@ -37,8 +59,18 @@ public static class TestSchema [componentId2] = component2 }); - var schema = new Schema("user", type: type) - .Publish() + var schema = new Schema + { + AppId = appId, + Id = DomainId.NewGuid(), + Name = "user", + Created = default, + CreatedBy = user, + LastModified = default, + LastModifiedBy = user, + Type = type, + Version = 1, + }.Publish() .AddArray(101, "root-array", Partitioning.Language, f => f .AddAssets(201, "nested-assets", new AssetsFieldProperties()) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs index 6dd880314..89a94c445 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs @@ -10,6 +10,7 @@ using System.Runtime.Serialization.Formatters.Binary; using System.Security.Claims; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; @@ -27,6 +28,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas.Json; using Squidex.Domain.Apps.Events; using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects; @@ -112,6 +114,7 @@ public static class TestUtils options.Converters.Add(new ReadonlyDictionaryConverterFactory()); options.Converters.Add(new ReadonlyListConverterFactory()); options.Converters.Add(new SurrogateJsonConverter()); + options.Converters.Add(new SurrogateJsonConverter, FieldsSurrogate>()); options.Converters.Add(new SurrogateJsonConverter, JsonFilterSurrogate>()); options.Converters.Add(new SurrogateJsonConverter()); options.Converters.Add(new SurrogateJsonConverter()); @@ -130,7 +133,11 @@ public static class TestUtils options.Converters.Add(new StringConverter()); options.Converters.Add(new StringConverter()); options.Converters.Add(new JsonStringEnumConverter()); - options.TypeInfoResolver = new PolymorphicTypeResolver(TypeRegistry); + options.IncludeFields = true; + options.TypeInfoResolver = new DefaultJsonTypeInfoResolver() + .WithAddedModifier(PolymorphicConverter.Modifier(TypeRegistry)) + .WithAddedModifier(JsonIgnoreReadonlyProperties.Modifier()) + .WithAddedModifier(JsonRenameAttribute.Modifier); configure?.Invoke(options); return options; @@ -191,9 +198,48 @@ public static class TestUtils return DefaultSerializer.Deserialize>(json).Value1; } + public static string SerializeWithoutNulls(this T value) + { + var options = DefaultOptions(options => + { + options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + }); + + return new SystemJsonSerializer(options).Serialize(value, true).CleanJson(); + } + public static string CleanJson(this string json) { - using var document = JsonDocument.Parse(json); + var document = System.Text.Json.Nodes.JsonNode.Parse(json); + + static void Handle(System.Text.Json.Nodes.JsonNode? node) + { + if (node is System.Text.Json.Nodes.JsonArray array) + { + foreach (var item in array) + { + Handle(item); + } + } + else if (node is System.Text.Json.Nodes.JsonObject obj) + { + var properties = obj.ToList(); + + foreach (var (key, _) in properties) + { + obj.Remove(key); + } + + foreach (var (key, value) in properties.OrderBy(x => x.Key)) + { + Handle(value); + + obj.Add(key, value); + } + } + } + + Handle(document?.Root); return DefaultSerializer.Serialize(document, true); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderExtensionsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderExtensionsTests.cs index 968142a05..61cc18106 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderExtensionsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderExtensionsTests.cs @@ -14,16 +14,13 @@ namespace Squidex.Domain.Apps.Entities; public class AppProviderExtensionsTests : GivenContext { - private readonly NamedId schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema"); private readonly NamedId componentId1 = NamedId.Of(DomainId.NewGuid(), "my-schema"); private readonly NamedId componentId2 = NamedId.Of(DomainId.NewGuid(), "my-schema"); [Fact] public async Task Should_do_nothing_if_no_component_found() { - var schema = Mocks.Schema(AppId, schemaId); - - var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken); + var components = await AppProvider.GetComponentsAsync(Schema, ct: CancellationToken); Assert.Empty(components); @@ -34,18 +31,16 @@ public class AppProviderExtensionsTests : GivenContext [Fact] public async Task Should_resolve_self_as_component() { - var schema = - Mocks.Schema(AppId, schemaId.Id, - new Schema(schemaId.Name) - .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties - { - SchemaId = schemaId.Id - })); + Schema = Schema + .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties + { + SchemaId = SchemaId.Id + }); - var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken); + var components = await AppProvider.GetComponentsAsync(Schema, ct: CancellationToken); Assert.Single(components); - Assert.Same(schema.SchemaDef, components[schemaId.Id]); + Assert.Same(Schema, components[Schema.Id]); A.CallTo(() => AppProvider.GetSchemaAsync(A._, A._, false, A._)) .MustNotHaveHappened(); @@ -54,116 +49,102 @@ public class AppProviderExtensionsTests : GivenContext [Fact] public async Task Should_resolve_from_component() { - var component = Mocks.Schema(AppId, componentId1); + var component = new Schema { Id = componentId1.Id, Name = componentId1.Name }; + + Schema = Schema + .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties + { + SchemaId = componentId1.Id + }); A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId1.Id, false, CancellationToken)) .Returns(component); - var schema = - Mocks.Schema(AppId, schemaId.Id, - new Schema(schemaId.Name) - .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties - { - SchemaId = componentId1.Id - })); - - var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken); + var components = await AppProvider.GetComponentsAsync(Schema, ct: CancellationToken); Assert.Single(components); - Assert.Same(component.SchemaDef, components[componentId1.Id]); + Assert.Same(component, components[componentId1.Id]); } [Fact] public async Task Should_resolve_from_components() { - var component = Mocks.Schema(AppId, componentId1); + var component = new Schema { Id = componentId1.Id, Name = componentId1.Name }; + + Schema = Schema + .AddComponents(1, "1", Partitioning.Invariant, new ComponentsFieldProperties + { + SchemaId = componentId1.Id + }); A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId1.Id, false, CancellationToken)) .Returns(component); - var schema = - Mocks.Schema(AppId, schemaId.Id, - new Schema(schemaId.Name) - .AddComponents(1, "1", Partitioning.Invariant, new ComponentsFieldProperties - { - SchemaId = componentId1.Id - })); - - var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken); + var components = await AppProvider.GetComponentsAsync(Schema, ct: CancellationToken); Assert.Single(components); - Assert.Same(component.SchemaDef, components[componentId1.Id]); + Assert.Same(component, components[componentId1.Id]); } [Fact] public async Task Should_resolve_from_array() { - var component = Mocks.Schema(AppId, componentId1); + var component = new Schema { Id = componentId1.Id, Name = componentId1.Name }; + + Schema = Schema + .AddArray(1, "1", Partitioning.Invariant, a => a + .AddComponent(2, "2", new ComponentFieldProperties + { + SchemaId = componentId1.Id + })); A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId1.Id, false, CancellationToken)) .Returns(component); - var schema = - Mocks.Schema(AppId, schemaId.Id, - new Schema(schemaId.Name) - .AddArray(1, "1", Partitioning.Invariant, a => a - .AddComponent(2, "2", new ComponentFieldProperties - { - SchemaId = componentId1.Id - }))); - - var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken); + var components = await AppProvider.GetComponentsAsync(Schema, ct: CancellationToken); Assert.Single(components); - Assert.Same(component.SchemaDef, components[componentId1.Id]); + Assert.Same(component, components[componentId1.Id]); } [Fact] public async Task Should_resolve_self_referencing_component() { - var component = - Mocks.Schema(AppId, componentId1.Id, - new Schema(componentId1.Name) - .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties - { - SchemaId = componentId1.Id - })); + var component = new Schema { Id = componentId1.Id, Name = componentId1.Name } + .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties + { + SchemaId = componentId1.Id + }); + + Schema = Schema + .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties + { + SchemaId = componentId1.Id + }); A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId1.Id, false, CancellationToken)) .Returns(component); - var schema = - Mocks.Schema(AppId, schemaId.Id, - new Schema(schemaId.Name) - .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties - { - SchemaId = componentId1.Id - })); - - var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken); + var components = await AppProvider.GetComponentsAsync(Schema, ct: CancellationToken); Assert.Single(components); - Assert.Same(component.SchemaDef, components[componentId1.Id]); + Assert.Same(component, components[componentId1.Id]); } [Fact] public async Task Should_resolve_component_of_component() { - var component1 = - Mocks.Schema(AppId, componentId1.Id, - new Schema(componentId1.Name) - .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties - { - SchemaId = componentId2.Id - })); - - var component2 = - Mocks.Schema(AppId, componentId2.Id, - new Schema(componentId2.Name) - .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties - { - SchemaId = componentId2.Id - })); + var component1 = new Schema { Id = componentId1.Id, Name = componentId1.Name } + .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties + { + SchemaId = componentId2.Id + }); + + var component2 = new Schema { Id = componentId2.Id, Name = componentId2.Name } + .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties + { + SchemaId = componentId1.Id + }); A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId1.Id, false, CancellationToken)) .Returns(component1); @@ -171,18 +152,16 @@ public class AppProviderExtensionsTests : GivenContext A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, componentId2.Id, false, CancellationToken)) .Returns(component2); - var schema = - Mocks.Schema(AppId, schemaId.Id, - new Schema(schemaId.Name) - .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties - { - SchemaId = componentId1.Id - })); + Schema = Schema + .AddComponent(1, "1", Partitioning.Invariant, new ComponentFieldProperties + { + SchemaId = componentId1.Id + }); - var components = await AppProvider.GetComponentsAsync(schema, ct: CancellationToken); + var components = await AppProvider.GetComponentsAsync(Schema, ct: CancellationToken); Assert.Equal(2, components.Count); - Assert.Same(component1.SchemaDef, components[componentId1.Id]); - Assert.Same(component2.SchemaDef, components[componentId2.Id]); + Assert.Same(component1, components[componentId1.Id]); + Assert.Same(component2, components[componentId2.Id]); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderTests.cs index be723234e..bcb5c0c19 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/AppProviderTests.cs @@ -6,16 +6,11 @@ // ========================================================================== using Squidex.Caching; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Indexes; -using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.Indexes; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Indexes; -using Squidex.Domain.Apps.Entities.Teams; using Squidex.Domain.Apps.Entities.Teams.Indexes; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure; using Squidex.Infrastructure.Security; namespace Squidex.Domain.Apps.Entities; @@ -151,7 +146,7 @@ public class AppProviderTests : GivenContext [Fact] public async Task Should_get_rules_from_index() { - var rule = new RuleEntity(); + var rule = CreateRule(); A.CallTo(() => indexForRules.GetRulesAsync(AppId.Id, CancellationToken)) .Returns([rule]); @@ -164,7 +159,7 @@ public class AppProviderTests : GivenContext [Fact] public async Task Should_get_rule_from_index() { - var rule = new RuleEntity { Id = DomainId.NewGuid() }; + var rule = CreateRule(); A.CallTo(() => indexForRules.GetRulesAsync(AppId.Id, CancellationToken)) .Returns([rule]); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs index 71bfb2d0b..0e03a33f4 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs @@ -98,14 +98,12 @@ public class AppPermanentDeleterTests : GivenContext [Fact] public async Task Should_call_deleters_when_app_deleted() { - var app = new AppDomainObject.State { Id = AppId.Id, Name = AppId.Name }; - var domainObject = A.Fake(); A.CallTo(() => domainObject.Snapshot) - .Returns(app); + .Returns(App); - A.CallTo(() => domainObjectFactory.Create(app.Id)) + A.CallTo(() => domainObjectFactory.Create(App.Id)) .Returns(domainObject); await sut.On(Envelope.Create(new AppDeleted @@ -113,10 +111,10 @@ public class AppPermanentDeleterTests : GivenContext AppId = AppId })); - A.CallTo(() => deleter1.DeleteAppAsync(app, default)) + A.CallTo(() => deleter1.DeleteAppAsync(App, default)) .MustHaveHappened(); - A.CallTo(() => deleter2.DeleteAppAsync(app, default)) + A.CallTo(() => deleter2.DeleteAppAsync(App, default)) .MustHaveHappened(); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsTests.cs index 5852a9740..7758d30f0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsTests.cs @@ -48,8 +48,10 @@ public sealed class AppUISettingsTests : GivenContext [Fact] public async Task Should_delete_app_and_contributors() { - A.CallTo(() => App.Contributors) - .Returns(Contributors.Empty.Assign(userId, Role.Owner)); + App = App with + { + Contributors = Contributors.Empty.Assign(userId, Role.Owner) + }; var rootState = new TestState(AppId.Id, state.PersistenceFactory); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/BackupAppsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/BackupAppsTests.cs index 68f53d812..5170ee4fb 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/BackupAppsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/BackupAppsTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Assets; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.DomainObject; using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Domain.Apps.Entities.Backup; @@ -59,14 +60,13 @@ public class BackupAppsTests : GivenContext public async Task Should_complete_reservation_with_previous_token() { var appObject = A.Fake(); - var appState = new AppDomainObject.State(); var context = CreateRestoreContext(); A.CallTo(() => appObject.Snapshot) - .Returns(appState); + .Returns(App); - A.CallTo(() => rebuilder.RebuildStateAsync(context.AppId, CancellationToken)) + A.CallTo(() => rebuilder.RebuildStateAsync(context.AppId, CancellationToken)) .Returns(appObject); A.CallTo(() => appsIndex.ReserveAsync(AppId.Id, AppId.Name, CancellationToken)) @@ -151,14 +151,13 @@ public class BackupAppsTests : GivenContext public async Task Should_register_app_to_provider() { var appObject = A.Fake(); - var appState = new AppDomainObject.State(); var context = CreateRestoreContext(); A.CallTo(() => appObject.Snapshot) - .Returns(appState); + .Returns(App); - A.CallTo(() => rebuilder.RebuildStateAsync(context.AppId, CancellationToken)) + A.CallTo(() => rebuilder.RebuildStateAsync(context.AppId, CancellationToken)) .Returns(appObject); await sut.RestoreAsync(context, CancellationToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppCommandMiddlewareTests.cs index e7f7715cb..bd6005f35 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppCommandMiddlewareTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Assets; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; @@ -14,7 +15,7 @@ using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Apps.DomainObject; -public class AppCommandMiddlewareTests : HandlerTestBase +public class AppCommandMiddlewareTests : HandlerTestBase { private readonly IDomainObjectFactory domainObjectFactory = A.Fake(); private readonly IAppImageStore appImageStore = A.Fake(); @@ -36,9 +37,9 @@ public class AppCommandMiddlewareTests : HandlerTestBase } [Fact] - public async Task Should_replace_context_app_with_domain_object_actual() + public async Task Should_replace_context_app_with_domain_object_result() { - var replaced = A.Fake(); + var replaced = new App(); await HandleAsync(new UpdateApp(), replaced); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs index 8521f8a54..bb3077c4c 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppDomainObjectTests.cs @@ -22,7 +22,7 @@ using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.Apps.DomainObject; -public class AppDomainObjectTests : HandlerTestBase +public class AppDomainObjectTests : HandlerTestBase { private readonly IBillingPlans billingPlans = A.Fake(); private readonly IBillingManager billingManager = A.Fake(); @@ -48,22 +48,24 @@ public class AppDomainObjectTests : HandlerTestBase { user = UserMocks.User(contributorId); - A.CallTo(() => Team.Contributors) - .Returns(Contributors.Empty.Assign(User.Identifier, Role.Owner)); + Team = Team with + { + Contributors = Contributors.Empty.Assign(User.Identifier, Role.Owner) + }; A.CallTo(() => userResolver.FindByIdOrEmailAsync(contributorId, CancellationToken)) .Returns(user); - A.CallTo(() => usageGate.GetPlanForAppAsync(A.That.Matches(x => x.Plan != null && x.Plan.PlanId == planIdFree), false, CancellationToken)) + A.CallTo(() => usageGate.GetPlanForAppAsync(A.That.Matches(x => x.Plan != null && x.Plan.PlanId == planIdFree), false, CancellationToken)) .Returns((new Plan { Id = planIdFree, MaxContributors = 10 }, planIdFree, null)); - A.CallTo(() => usageGate.GetPlanForAppAsync(A.That.Matches(x => x.Plan != null && x.Plan.PlanId == planIdPaid), false, CancellationToken)) + A.CallTo(() => usageGate.GetPlanForAppAsync(A.That.Matches(x => x.Plan != null && x.Plan.PlanId == planIdPaid), false, CancellationToken)) .Returns((new Plan { Id = planIdPaid, MaxContributors = 30 }, planIdPaid, null)); A.CallTo(() => billingPlans.GetFreePlan()) .Returns(new Plan { Id = planIdFree, MaxContributors = 10 }); - A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, A._, CancellationToken)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, A._, CancellationToken)) .Returns(Task.FromResult(null)); // Create a non-empty setting, otherwise the event is not raised as it does not change the domain object. @@ -161,24 +163,25 @@ public class AppDomainObjectTests : HandlerTestBase [Fact] public async Task UpdateSettings_should_create_event_and_update_settings() { - var settings = new AppSettings + var command = new UpdateAppSettings { - HideDateTimeModeButton = true + Settings = new AppSettings + { + HideDateTimeModeButton = true + } }; - var command = new UpdateAppSettings { Settings = settings }; - await ExecuteCreateAsync(); var actual = await PublishIdempotentAsync(command); actual.ShouldBeEquivalent(sut.Snapshot); - Assert.Equal(settings, sut.Snapshot.Settings); + Assert.Equal(command.Settings, sut.Snapshot.Settings); LastEvents .ShouldHaveSameEvents( - CreateEvent(new AppSettingsUpdated { Settings = settings }) + CreateEvent(new AppSettingsUpdated { Settings = command.Settings }) ); } @@ -226,7 +229,7 @@ public class AppDomainObjectTests : HandlerTestBase { var command = new ChangePlan { PlanId = planIdPaid }; - A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planIdPaid, default)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planIdPaid, default)) .Returns(Task.FromResult(null)); await ExecuteCreateAsync(); @@ -242,10 +245,10 @@ public class AppDomainObjectTests : HandlerTestBase CreateEvent(new AppPlanChanged { PlanId = planIdPaid }) ); - A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planIdPaid, CancellationToken)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planIdPaid, CancellationToken)) .MustHaveHappened(); - A.CallTo(() => billingManager.SubscribeAsync(User.Identifier, A._, planIdPaid, default)) + A.CallTo(() => billingManager.SubscribeAsync(User.Identifier, A._, planIdPaid, default)) .MustHaveHappened(); } @@ -267,10 +270,10 @@ public class AppDomainObjectTests : HandlerTestBase CreateEvent(new AppPlanChanged { PlanId = planIdPaid }) ); - A.CallTo(() => billingManager.MustRedirectToPortalAsync(A._, A._, A._, A._)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(A._, A._, A._, A._)) .MustNotHaveHappened(); - A.CallTo(() => billingManager.SubscribeAsync(A._, A._, A._, A._)) + A.CallTo(() => billingManager.SubscribeAsync(A._, A._, A._, A._)) .MustNotHaveHappened(); } @@ -293,10 +296,10 @@ public class AppDomainObjectTests : HandlerTestBase CreateEvent(new AppPlanReset()) ); - A.CallTo(() => billingManager.MustRedirectToPortalAsync(A._, A._, A._, A._)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(A._, A._, A._, A._)) .MustHaveHappenedOnceExactly(); - A.CallTo(() => billingManager.UnsubscribeAsync(A._, A._, A._)) + A.CallTo(() => billingManager.UnsubscribeAsync(A._, A._, A._)) .MustNotHaveHappened(); } @@ -319,19 +322,19 @@ public class AppDomainObjectTests : HandlerTestBase CreateEvent(new AppPlanReset()) ); - A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planIdPaid, CancellationToken)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planIdPaid, CancellationToken)) .MustHaveHappenedOnceExactly(); - A.CallTo(() => billingManager.UnsubscribeAsync(A._, A._, A._)) + A.CallTo(() => billingManager.UnsubscribeAsync(A._, A._, A._)) .MustHaveHappened(); } [Fact] - public async Task ChangePlan_should_not_make_update_for_redirect_actual() + public async Task ChangePlan_should_not_make_update_for_redirect() { var command = new ChangePlan { PlanId = planIdPaid }; - A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planIdPaid, CancellationToken)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planIdPaid, CancellationToken)) .Returns(new Uri("http://squidex.io")); await ExecuteCreateAsync(); @@ -356,10 +359,10 @@ public class AppDomainObjectTests : HandlerTestBase Assert.Equal(planIdPaid, sut.Snapshot.Plan?.PlanId); - A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planIdPaid, A._)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planIdPaid, A._)) .MustNotHaveHappened(); - A.CallTo(() => billingManager.SubscribeAsync(User.Identifier, A._, planIdPaid, A._)) + A.CallTo(() => billingManager.SubscribeAsync(User.Identifier, A._, planIdPaid, A._)) .MustNotHaveHappened(); } @@ -713,7 +716,7 @@ public class AppDomainObjectTests : HandlerTestBase CreateEvent(new AppDeleted()) ); - A.CallTo(() => billingManager.UnsubscribeAsync(command.Actor.Identifier, A._, default)) + A.CallTo(() => billingManager.UnsubscribeAsync(command.Actor.Identifier, A._, default)) .MustHaveHappened(); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppState.json b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppState.json deleted file mode 100644 index 511d67952..000000000 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppState.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "name": "test", - "label": "label", - "description": "description", - "contributors": { - "62cee33bcc71c0d140ad103a": "Owner" - }, - "roles": { - "custom": { - "permissions": [ - - ], - "properties": { - "ui_§§_schemas_§§_hide": true - } - } - }, - "clients": { - "default": { - "name": "default", - "secret": "o3yzxz8bpcpwd4zllox14nicrktgwlxxop2zfmahtiex", - "role": "Owner", - "apiCallsLimit": 0, - "apiTrafficLimit": 0, - "allowAnonymous": false - } - }, - "settings": { - "patterns": [ - { - "name": "Email", - "regex": "^[a-zA-Z0-9.!#$%&’*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$" - }, - { - "name": "Phone", - "regex": "^\\(*\\+*[1-9]{0,3}\\)*-*[1-9]{0,3}[-. /]*\\(*[2-9]\\d{2}\\)*[-. /]*\\d{3}[-. /]*\\d{4} *e*x*t*\\.* *\\d{0,4}$" - }, - { - "name": "Slug", - "regex": "^[a-z0-9]+(\\-[a-z0-9]+)*$" - }, - { - "name": "Url", - "regex": "^(?:http(s)?:\\/\\/)?[\\w.-]+(?:\\.[\\w\\.-]+)+[\\w\\-\\._~:\\/?#%[\\]@!\\$&'\\(\\)\\*\\+,;=.]+$" - } - ], - "editors": [ - - ], - "hideScheduler": false, - "hideDateTimeModeButton": false - }, - "assetScripts": { - - }, - "languages": { - "languages": { - "en": { - "fallback": [ - - ], - "isOptional": false - }, - "de": { - "fallback": [ - - ], - "isOptional": false - }, - "de-DE": { - "fallback": [ - "en" - ], - "isOptional": true - } - }, - "master": "en" - }, - "workflows": { - "274c3c3f-ad0e-4304-aa87-9d686bf082c1": { - "initial": "Draft", - "name": "test", - "steps": { - "Archived": { - "transitions": { - "Draft": { - - } - }, - "noUpdateRules": {}, - "noUpdate": false, - "validate": true, - "color": "#eb3142" - }, - "Draft": { - "transitions": { - "Archived": { - - }, - "Published": { - - } - }, - "noUpdate": false, - "validate": false, - "color": "#8091a5" - }, - "Published": { - "transitions": { - "Archived": { - - }, - "Draft": { - - } - }, - "noUpdate": false, - "validate": false, - "color": "#4bb958" - } - }, - "schemaIds": [ - - ] - } - }, - "isDeleted": false, - "id": "7c45937e-e489-453f-81f8-a1432e778ea2", - "createdBy": "subject:62cee33bcc71c0d140ad103a", - "lastModifiedBy": "subject:62cee33bcc71c0d140ad103a", - "created": "2022-07-22T17:16:05Z", - "lastModified": "2022-07-22T17:17:19Z", - "version": 10 -} \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppStateTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppStateTests.cs deleted file mode 100644 index 88c058b6b..000000000 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/AppStateTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Text.Json.Serialization; -using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Infrastructure.Json; - -namespace Squidex.Domain.Apps.Entities.Apps.DomainObject; - -public class AppStateTests -{ - private readonly IJsonSerializer serializer = TestUtils.CreateSerializer(options => - { - options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; - }); - - [Fact] - public void Should_deserialize_state() - { - var json = File.ReadAllText("Apps/DomainObject/AppState.json"); - - var deserialized = serializer.Deserialize(json); - - Assert.NotNull(deserialized); - } - - [Fact] - public void Should_serialize_deserialize_state() - { - var json = File.ReadAllText("Apps/DomainObject/AppState.json").CleanJson(); - - var serialized = serializer.Serialize(serializer.Deserialize(json), true); - - Assert.Equal(json, serialized); - } -} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppClientsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppClientsTests.cs index 061f609a1..109464cf0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppClientsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppClientsTests.cs @@ -16,17 +16,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; public class GuardAppClientsTests : GivenContext, IClassFixture { - private AppClients clients = AppClients.Empty; - - public GuardAppClientsTests() - { - A.CallTo(() => App.Roles) - .Returns(Roles.Empty); - - A.CallTo(() => App.Clients) - .ReturnsLazily(() => clients); - } - [Fact] public void CanAttach_should_throw_execption_if_client_id_is_null() { @@ -41,7 +30,10 @@ public class GuardAppClientsTests : GivenContext, IClassFixture GuardAppClients.CanAttach(command, App), new ValidationError("A client with the same id already exists.")); @@ -52,7 +44,10 @@ public class GuardAppClientsTests : GivenContext, IClassFixture GuardAppClients.CanUpdate(command, App), new ValidationError("Role is not a valid value.", "Role")); @@ -117,7 +118,10 @@ public class GuardAppClientsTests : GivenContext, IClassFixture GuardAppClients.CanUpdate(command, App), new ValidationError("ApiCallsLimit must be greater or equal to 0.", "ApiCallsLimit")); @@ -128,7 +132,10 @@ public class GuardAppClientsTests : GivenContext, IClassFixture GuardAppClients.CanUpdate(command, App), new ValidationError("ApiTrafficLimit must be greater or equal to 0.", "ApiTrafficLimit")); @@ -139,7 +146,10 @@ public class GuardAppClientsTests : GivenContext, IClassFixture(); private readonly Plan planWithoutLimit = new Plan { MaxContributors = -1 }; private readonly Plan planWithLimit = new Plan { MaxContributors = 2 }; - private Contributors contributors = Contributors.Empty; public GuardAppContributorsTests() { - A.CallTo(() => App.Roles) - .Returns(Roles.Empty); - - A.CallTo(() => App.Contributors) - .ReturnsLazily(() => contributors); - A.CallTo(() => user1.Id) .Returns("1"); @@ -80,7 +73,10 @@ public class GuardAppContributorsTests : GivenContext, IClassFixture GuardAppContributors.CanAssign(command, App, users, planWithLimit), new ValidationError("You have reached the maximum number of contributors for your plan.")); @@ -127,7 +129,10 @@ public class GuardAppContributorsTests : GivenContext, IClassFixture GuardAppContributors.CanRemove(command, App), new ValidationError("Cannot remove the only owner.")); @@ -203,7 +220,10 @@ public class GuardAppContributorsTests : GivenContext, IClassFixture { private readonly string roleName = "Role1"; - private Roles roles = Roles.Empty; public GuardAppRolesTests() { - A.CallTo(() => App.Contributors) - .Returns(Contributors.Empty.Assign(User.Identifier, "contributorRole")); + App = App with + { + Contributors = Contributors.Empty.Assign(User.Identifier, "contributorRole") + }; - A.CallTo(() => App.Clients) - .Returns(AppClients.Empty.Add(Client.Identifier, "secret", "clientRole")); - - A.CallTo(() => App.Roles) - .ReturnsLazily(() => roles); + App = App with + { + Clients = AppClients.Empty.Add(Client.Identifier, "secret", "clientRole") + }; } [Fact] @@ -44,7 +44,10 @@ public class GuardAppRolesTests : GivenContext, IClassFixture A.CallTo(() => billingPlans.GetPlan("basic")) .Returns(new Plan()); - A.CallTo(() => Team.Contributors) - .Returns(Contributors.Empty.Assign(User.Identifier, Role.Owner)); + App = App with + { + TeamId = default + }; + + Team = Team with + { + Contributors = Contributors.Empty.Assign(User.Identifier, Role.Owner) + }; } [Fact] @@ -96,8 +103,10 @@ public class GuardAppTests : GivenContext, IClassFixture { var command = new ChangePlan { PlanId = "basic", Actor = User }; - A.CallTo(() => App.Plan) - .Returns(new AssignedPlan(RefToken.User("other"), "premium")); + App = App with + { + Plan = new AssignedPlan(RefToken.User("other"), "premium") + }; ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App, billingPlans), new ValidationError("Plan can only changed from the user who configured the plan initially.")); @@ -108,8 +117,10 @@ public class GuardAppTests : GivenContext, IClassFixture { var command = new ChangePlan { PlanId = "basic", Actor = User }; - A.CallTo(() => App.TeamId) - .Returns(DomainId.NewGuid()); + App = App with + { + TeamId = Team.Id + }; ValidationAssert.Throws(() => GuardApp.CanChangePlan(command, App, billingPlans), new ValidationError("Plan is managed by the team.")); @@ -120,8 +131,10 @@ public class GuardAppTests : GivenContext, IClassFixture { var command = new ChangePlan { PlanId = "basic", Actor = User }; - A.CallTo(() => App.Plan) - .Returns(new AssignedPlan(command.Actor, "premium")); + App = App with + { + Plan = new AssignedPlan(User, "premium") + }; GuardApp.CanChangePlan(command, App, billingPlans); } @@ -131,8 +144,10 @@ public class GuardAppTests : GivenContext, IClassFixture { var command = new ChangePlan { PlanId = "basic", Actor = User }; - A.CallTo(() => App.Plan) - .Returns(new AssignedPlan(command.Actor, "premium")); + App = App with + { + Plan = new AssignedPlan(User, "premium") + }; GuardApp.CanChangePlan(command, App, billingPlans); } @@ -182,8 +197,10 @@ public class GuardAppTests : GivenContext, IClassFixture { var command = new TransferToTeam { TeamId = TeamId, Actor = User }; - A.CallTo(() => App.Plan) - .Returns(new AssignedPlan(User, "premium")); + App = App with + { + Plan = new AssignedPlan(RefToken.User("other"), "premium") + }; await ValidationAssert.ThrowsAsync(() => GuardApp.CanTransfer(command, App, AppProvider, default), new ValidationError("Subscription must be cancelled first before the app can be transfered.")); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppWorkflowTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppWorkflowTests.cs index b56f76d99..3f431d0d2 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppWorkflowTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DomainObject/Guards/GuardAppWorkflowTests.cs @@ -18,14 +18,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject.Guards; public class GuardAppWorkflowTests : GivenContext, IClassFixture { private readonly DomainId workflowId = DomainId.NewGuid(); - private readonly Workflows workflows; public GuardAppWorkflowTests() { - workflows = Workflows.Empty.Add(workflowId, "name"); - - A.CallTo(() => App.Workflows) - .Returns(workflows); + App = App with + { + Workflows = Workflows.Empty.Add(workflowId, "name") + }; } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs index 0a7499b0b..64eaccb57 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Squidex.Caching; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Repositories; @@ -144,8 +145,7 @@ public class AppsIndexTests : GivenContext [Fact] public async Task Should_return_empty_apps_if_app_not_created() { - A.CallTo(() => App.Version) - .Returns(EtagVersion.Empty); + App = App with { Version = EtagVersion.Empty }; A.CallTo(() => appRepository.QueryAllAsync(User.Identifier, A>.That.IsEmpty(), CancellationToken)) .Returns([App]); @@ -158,8 +158,7 @@ public class AppsIndexTests : GivenContext [Fact] public async Task Should_return_empty_apps_if_app_deleted() { - A.CallTo(() => App.IsDeleted) - .Returns(true); + App = App with { IsDeleted = true }; A.CallTo(() => appRepository.QueryAllAsync(User.Identifier, A>.That.IsEmpty(), CancellationToken)) .Returns([App]); @@ -173,7 +172,7 @@ public class AppsIndexTests : GivenContext public async Task Should_take_and_remove_reservation_if_created() { A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var command = Create(AppId.Name); @@ -200,7 +199,7 @@ public class AppsIndexTests : GivenContext public async Task Should_clear_reservation_if_app_creation_failed() { A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var command = Create(AppId.Name); @@ -229,7 +228,7 @@ public class AppsIndexTests : GivenContext state.Snapshot.Reservations.Add(new NameReservation(RandomHash.Simple(), AppId.Name, DomainId.NewGuid())); A.CallTo(() => appRepository.FindAsync(AppId.Name, CancellationToken)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var command = Create(AppId.Name); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs index 4d528afbb..21aa4f30b 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; @@ -28,8 +27,8 @@ public class RolePermissionsProviderTests : GivenContext A.CallTo(() => AppProvider.GetSchemasAsync(A._, default)) .Returns( [ - Mocks.Schema(AppId, NamedId.Of(DomainId.NewGuid(), "schema1")), - Mocks.Schema(AppId, NamedId.Of(DomainId.NewGuid(), "schema2")) + Schema.WithId(DomainId.NewGuid(), "my-schema1"), + Schema.WithId(DomainId.NewGuid(), "my-schema2") ]); var actual = await sut.GetPermissionsAsync(App); @@ -37,7 +36,7 @@ public class RolePermissionsProviderTests : GivenContext Assert.True(actual.Contains("*")); Assert.True(actual.Contains("clients.read")); Assert.True(actual.Contains("schemas.*.update")); - Assert.True(actual.Contains("schemas.schema1.update")); - Assert.True(actual.Contains("schemas.schema2.update")); + Assert.True(actual.Contains("schemas.my-schema1.update")); + Assert.True(actual.Contains("schemas.my-schema2.update")); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs index 763a4d59a..34426279c 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetChangedTriggerHandlerTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; @@ -16,7 +17,6 @@ using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Contents; -using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Entities.Assets; @@ -77,10 +77,10 @@ public class AssetChangedTriggerHandlerTests : GivenContext var ctx = Context(); A.CallTo(() => assetRepository.StreamAll(AppId.Id, CancellationToken)) - .Returns(new List + .Returns(new List { - new AssetEntity(), - new AssetEntity() + CreateAsset(), + CreateAsset() }.ToAsyncEnumerable()); var actual = await sut.CreateSnapshotEventsAsync(ctx, CancellationToken).ToListAsync(CancellationToken); @@ -100,7 +100,7 @@ public class AssetChangedTriggerHandlerTests : GivenContext var envelope = Envelope.Create(@event).SetEventStreamNumber(12); A.CallTo(() => assetLoader.GetAsync(AppId.Id, @event.AssetId, 12, CancellationToken)) - .Returns(new AssetEntity()); + .Returns(CreateAsset()); var actual = await sut.CreateEnrichedEventsAsync(envelope, ctx, CancellationToken).ToListAsync(CancellationToken); @@ -179,8 +179,9 @@ public class AssetChangedTriggerHandlerTests : GivenContext return new RuleContext { AppId = AppId, - Rule = new Rule(trigger, A.Fake()), - RuleId = DomainId.NewGuid() + IncludeSkipped = false, + IncludeStale = false, + Rule = CreateRule() with { Trigger = trigger } }; } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs index 275895ab5..6578adf27 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs @@ -348,13 +348,9 @@ public class AssetUsageTrackerTests : GivenContext [Fact] public async Task Should_merge_tags_with_asset_if_previous_tags_not_in_store() { - IAssetEntity asset = new AssetEntity + var asset = CreateAsset() with { - Tags = - [ - "tag1", - "tag2" - ] + Tags = ["tag1", "tag2"] }; A.CallTo(() => assetLoader.GetAsync(AppId.Id, assetId, 41, default)) diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsFluidExtensionTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsFluidExtensionTests.cs index d9829b0f7..ca3dda864 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsFluidExtensionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsFluidExtensionTests.cs @@ -306,7 +306,7 @@ public class AssetsFluidExtensionTests : GivenContext .Invokes(x => x.GetArgument(4)?.Write(bytes)); } - private (TemplateVars, IAssetEntity) SetupAssetVars(int fileSize = 100, AssetType type = AssetType.Image) + private (TemplateVars, Asset) SetupAssetVars(int fileSize = 100, AssetType type = AssetType.Image) { var assetId = DomainId.NewGuid(); var asset = CreateAsset(assetId, 1, fileSize, type); @@ -332,7 +332,7 @@ public class AssetsFluidExtensionTests : GivenContext return (vars, asset); } - private (TemplateVars, IAssetEntity[]) SetupAssetsVars(int fileSize = 100, AssetType type = AssetType.Image) + private (TemplateVars, Asset[]) SetupAssetsVars(int fileSize = 100, AssetType type = AssetType.Image) { var assetId1 = DomainId.NewGuid(); var asset1 = CreateAsset(assetId1, 1, fileSize, type); @@ -363,9 +363,9 @@ public class AssetsFluidExtensionTests : GivenContext return (vars, new[] { asset1, asset2 }); } - private IEnrichedAssetEntity CreateAsset(DomainId assetId, int index, int fileSize = 100, AssetType type = AssetType.Unknown) + private EnrichedAsset CreateAsset(DomainId assetId, int index, int fileSize = 100, AssetType type = AssetType.Unknown) { - return new AssetEntity + return new EnrichedAsset { AppId = AppId, Id = assetId, diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs index 19713054b..ab33045f2 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs @@ -398,7 +398,7 @@ public class AssetsJintExtensionTests : GivenContext, IClassFixture x.GetArgument(4)?.Write(bytes)); } - private (ScriptVars, IAssetEntity[]) SetupAssetsVars(int count, int fileSize = 100, AssetType type = AssetType.Image) + private (ScriptVars, Asset[]) SetupAssetsVars(int count, int fileSize = 100, AssetType type = AssetType.Image) { var assets = Enumerable.Range(0, count).Select(x => CreateAsset(1, fileSize, type)).ToArray(); var assetIds = assets.Select(x => x.Id); @@ -426,9 +426,9 @@ public class AssetsJintExtensionTests : GivenContext, IClassFixture urlGenerator.AssetsUI(AppId, asset1.Id.ToString())) .Returns("assets-url1"); @@ -61,9 +61,4 @@ public class AssetsSearchSourceTests : GivenContext .Add("logo1.png", SearchResultType.Asset, "assets-url1") .Add("logo2.png", SearchResultType.Asset, "assets-url2")); } - - private static IEnrichedAssetEntity CreateAsset(string fileName) - { - return new AssetEntity { FileName = fileName, Id = DomainId.NewGuid() }; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/BackupAssetsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/BackupAssetsTests.cs index 2196bf394..a92aebf46 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/BackupAssetsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/BackupAssetsTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Assets; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Entities.Assets.DomainObject; using Squidex.Domain.Apps.Entities.Backup; @@ -319,7 +320,7 @@ public class BackupAssetsTests : GivenContext var rebuildAssets = new HashSet(); - A.CallTo(() => rebuilder.InsertManyAsync(A>._, A._, CancellationToken)) + A.CallTo(() => rebuilder.InsertManyAsync(A>._, A._, CancellationToken)) .Invokes(x => rebuildAssets.AddRange(x.GetArgument>(0)!)); await sut.RestoreAsync(context, CancellationToken); @@ -356,7 +357,7 @@ public class BackupAssetsTests : GivenContext var rebuildAssetFolders = new HashSet(); - A.CallTo(() => rebuilder.InsertManyAsync(A>._, A._, CancellationToken)) + A.CallTo(() => rebuilder.InsertManyAsync(A>._, A._, CancellationToken)) .Invokes(x => rebuildAssetFolders.AddRange(x.GetArgument>(0)!)); await sut.RestoreAsync(context, CancellationToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DefaultAssetFileStoreTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DefaultAssetFileStoreTests.cs index 78802704b..bbbdc0578 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DefaultAssetFileStoreTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DefaultAssetFileStoreTests.cs @@ -233,8 +233,8 @@ public class DefaultAssetFileStoreTests : GivenContext [Fact] public async Task Should_delete_assets_invidually__on_app_deletion() { - var asset1 = new AssetEntity { Id = DomainId.NewGuid() }; - var asset2 = new AssetEntity { Id = DomainId.NewGuid() }; + var asset1 = CreateAsset(); + var asset2 = CreateAsset(); A.CallTo(() => assetRepository.StreamAll(AppId.Id, CancellationToken)) .Returns(new[] { asset1, asset2 }.ToAsyncEnumerable()); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetCommandMiddlewareTests.cs index 856ad32b5..f6c92fbe0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetCommandMiddlewareTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Assets; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Queries; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -14,7 +15,7 @@ using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject; -public class AssetCommandMiddlewareTests : HandlerTestBase +public class AssetCommandMiddlewareTests : HandlerTestBase { private readonly IDomainObjectCache domainObjectCache = A.Fake(); private readonly IDomainObjectFactory domainObjectFactory = A.Fake(); @@ -40,7 +41,7 @@ public class AssetCommandMiddlewareTests : HandlerTestBase assetQuery.FindByHashAsync(A._, A._, A._, A._, CancellationToken)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); sut = new AssetCommandMiddleware( domainObjectFactory, @@ -52,33 +53,33 @@ public class AssetCommandMiddlewareTests : HandlerTestBase assetEnricher.EnrichAsync(A._, ApiContext, A._)) + A.CallTo(() => assetEnricher.EnrichAsync(A._, ApiContext, A._)) .MustNotHaveHappened(); } [Fact] public async Task Should_not_invoke_enricher_if_already_enriched() { - var actual = new AssetEntity(); + var actual = new Asset(); var context = await HandleAsync(new AnnotateAsset(), actual); - A.CallTo(() => assetEnricher.EnrichAsync(A._, ApiContext, A._)) + A.CallTo(() => assetEnricher.EnrichAsync(A._, ApiContext, A._)) .MustNotHaveHappened(); } [Fact] - public async Task Should_enrich_asset_actual() + public async Task Should_enrich_asset_result() { - var actual = A.Fake(); + var actual = new Asset(); - var enriched = new AssetEntity(); + var enriched = CreateAsset(); A.CallTo(() => assetEnricher.EnrichAsync(actual, ApiContext, CancellationToken)) .Returns(enriched); @@ -87,19 +88,19 @@ public class AssetCommandMiddlewareTests : HandlerTestBase()); + Assert.Same(enriched, context.Result()); } [Fact] public async Task Create_should_upload_file() { - var actual = CreateAsset(); + var actual = CreateAsset().WithId(assetId); var context = await HandleAsync(new CreateAsset { File = file }, actual); - Assert.Same(actual, context.Result()); + Assert.Same(actual, context.Result()); AssertAssetHasBeenUploaded(0); AssertMetadataEnriched(); @@ -116,9 +117,9 @@ public class AssetCommandMiddlewareTests : HandlerTestBase()); + Assert.Same(actual, context.Result()); } [Fact] - public async Task Create_should_return_duplicate_actual_if_file_with_same_hash_found() + public async Task Create_should_return_duplicate_result_if_file_with_same_hash_found() { SetupSameHashAsset(file.FileName, file.FileSize, out var duplicate); @@ -144,7 +145,9 @@ public class AssetCommandMiddlewareTests : HandlerTestBase()); + Assert.Same(actual, context.Result()); } [Fact] - public async Task Upsert_should_return_duplicate_actual_if_file_with_same_hash_found() + public async Task Upsert_should_return_duplicate_result_if_file_with_same_hash_found() { SetupSameHashAsset(file.FileName, file.FileSize, out var duplicate); @@ -217,9 +222,9 @@ public class AssetCommandMiddlewareTests : HandlerTestBase +public class AssetDomainObjectTests : HandlerTestBase { private readonly IAssetQueryService assetQuery = A.Fake(); private readonly IContentRepository contentRepository = A.Fake(); @@ -38,20 +38,20 @@ public class AssetDomainObjectTests : HandlerTestBase public AssetDomainObjectTests() { - var scripts = new AssetScripts + App = App with { - Annotate = "", - Create = "", - Delete = "", - Move = "", - Update = "" + AssetScripts = new AssetScripts + { + Annotate = "", + Create = "", + Delete = "", + Move = "", + Update = "" + } }; - A.CallTo(() => App.AssetScripts) - .Returns(scripts); - A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, A._)) - .Returns(new List { A.Fake() }); + .Returns(new List { A.Fake() }); A.CallTo(() => tagService.GetTagIdsAsync(AppId.Id, TagGroups.Assets, A>._, default)) .ReturnsLazily(x => Task.FromResult(x.GetArgument>(2)?.ToDictionary(x => x) ?? [])); @@ -386,7 +386,7 @@ public class AssetDomainObjectTests : HandlerTestBase await ExecuteCreateAsync(); - A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, Id, SearchScope.All, A._)) + A.CallTo(() => contentRepository.HasReferrersAsync(App, Id, SearchScope.All, A._)) .Returns(false); var actual = await PublishAsync(command); @@ -407,7 +407,7 @@ public class AssetDomainObjectTests : HandlerTestBase await ExecuteCreateAsync(); - A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, Id, SearchScope.All, A._)) + A.CallTo(() => contentRepository.HasReferrersAsync(App, Id, SearchScope.All, A._)) .Returns(true); await Assert.ThrowsAsync(() => PublishAsync(command)); @@ -423,7 +423,7 @@ public class AssetDomainObjectTests : HandlerTestBase await ExecuteCreateAsync(); - A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, Id, SearchScope.All, A._)) + A.CallTo(() => contentRepository.HasReferrersAsync(App, Id, SearchScope.All, A._)) .Returns(true); await PublishAsync(command); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetFolderDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetFolderDomainObjectTests.cs index a038a5902..2857a4ec2 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetFolderDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/AssetFolderDomainObjectTests.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -15,7 +16,7 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Assets.DomainObject; -public class AssetFolderDomainObjectTests : HandlerTestBase +public class AssetFolderDomainObjectTests : HandlerTestBase { private readonly IAssetQueryService assetQuery = A.Fake(); private readonly IContentRepository contentRepository = A.Fake(); @@ -31,7 +32,7 @@ public class AssetFolderDomainObjectTests : HandlerTestBase assetQuery.FindAssetFolderAsync(AppId.Id, parentId, A._)) - .Returns(new List { A.Fake() }); + .Returns(new List { A.Fake() }); var log = A.Fake>(); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/GuardAssetFolderTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/GuardAssetFolderTests.cs index a2c9c2610..1a72c31bc 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/GuardAssetFolderTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/GuardAssetFolderTests.cs @@ -7,6 +7,7 @@ using System.Security.Claims; using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -45,7 +46,7 @@ public class GuardAssetFolderTests : GivenContext, IClassFixture assetQuery.FindAssetFolderAsync(AppId.Id, parentId, CancellationToken)) - .Returns(new List()); + .Returns(new List()); await ValidationAssert.ThrowsAsync(() => operation.MustMoveToValidFolder(parentId, CancellationToken), new ValidationError("Asset folder does not exist.", "ParentId")); @@ -59,7 +60,7 @@ public class GuardAssetFolderTests : GivenContext, IClassFixture assetQuery.FindAssetFolderAsync(AppId.Id, parentId, CancellationToken)) - .Returns(new List { CreateAssetFolder() }); + .Returns(new List { CreateAssetFolder() }); await operation.MustMoveToValidFolder(parentId, CancellationToken); } @@ -69,7 +70,7 @@ public class GuardAssetFolderTests : GivenContext, IClassFixture assetQuery.FindAssetFolderAsync(AppId.Id, parentId, CancellationToken)) - .Returns(new List + .Returns(new List { - CreateAssetFolder(operation.CommandId), - CreateAssetFolder(parentId, operation.CommandId) + CreateAssetFolder().WithId(operation.CommandId), + CreateAssetFolder().WithId(parentId) with { ParentId = operation.CommandId } }); await ValidationAssert.ThrowsAsync(() => operation.MustMoveToValidFolder(parentId, CancellationToken), new ValidationError("Cannot add folder to its own child.", "ParentId")); } - private AssetFolderOperation Operation(IAssetFolderEntity assetFolder) + private AssetFolderOperation Operation(AssetFolder assetFolder) { return Operation(assetFolder, Mocks.FrontendUser()); } - private AssetFolderOperation Operation(IAssetFolderEntity assetFolder, ClaimsPrincipal? currentUser) + private AssetFolderOperation Operation(AssetFolder assetFolder, ClaimsPrincipal? currentUser) { var serviceProvider = new ServiceCollection() @@ -127,28 +128,4 @@ public class GuardAssetFolderTests : GivenContext, IClassFixture(); - - A.CallTo(() => assetFolder.Id) - .Returns(OrNew(id)); - A.CallTo(() => assetFolder.AppId) - .Returns(AppId); - A.CallTo(() => assetFolder.ParentId) - .Returns(OrNew(parentId)); - - return assetFolder; - } - - private static DomainId OrNew(DomainId parentId) - { - if (parentId == default) - { - parentId = DomainId.NewGuid(); - } - - return parentId; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/GuardAssetTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/GuardAssetTests.cs index 9cb247d0d..491b30350 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/GuardAssetTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/GuardAssetTests.cs @@ -7,6 +7,7 @@ using System.Security.Claims; using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Contents; @@ -30,7 +31,7 @@ public class GuardAssetTests : GivenContext, IClassFixture var operation = Operation(CreateAsset()); A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, CancellationToken)) - .Returns(new List()); + .Returns(new List()); await ValidationAssert.ThrowsAsync(() => operation.MustMoveToValidFolder(parentId, CancellationToken), new ValidationError("Asset folder does not exist.", "ParentId")); @@ -44,7 +45,7 @@ public class GuardAssetTests : GivenContext, IClassFixture var operation = Operation(CreateAsset()); A.CallTo(() => assetQuery.FindAssetFolderAsync(AppId.Id, parentId, CancellationToken)) - .Returns(new List { CreateAssetFolder() }); + .Returns(new List { CreateAssetFolder() }); await operation.MustMoveToValidFolder(parentId, CancellationToken); } @@ -54,7 +55,7 @@ public class GuardAssetTests : GivenContext, IClassFixture { var parentId = DomainId.NewGuid(); - var operation = Operation(CreateAsset(default, parentId)); + var operation = Operation(CreateAsset() with { ParentId = parentId }); await operation.MustMoveToValidFolder(parentId, CancellationToken); @@ -67,7 +68,7 @@ public class GuardAssetTests : GivenContext, IClassFixture { var parentId = DomainId.Empty; - var operation = Operation(CreateAsset(parentId)); + var operation = Operation(CreateAsset().WithId(parentId)); await operation.MustMoveToValidFolder(parentId, CancellationToken); @@ -80,7 +81,7 @@ public class GuardAssetTests : GivenContext, IClassFixture { var operation = Operation(CreateAsset()); - A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, operation.CommandId, SearchScope.All, CancellationToken)) + A.CallTo(() => contentRepository.HasReferrersAsync(App, operation.CommandId, SearchScope.All, CancellationToken)) .Returns(true); await Assert.ThrowsAsync(() => operation.CheckReferrersAsync(CancellationToken)); @@ -91,18 +92,18 @@ public class GuardAssetTests : GivenContext, IClassFixture { var operation = Operation(CreateAsset()); - A.CallTo(() => contentRepository.HasReferrersAsync(AppId.Id, operation.CommandId, SearchScope.All, CancellationToken)) + A.CallTo(() => contentRepository.HasReferrersAsync(App, operation.CommandId, SearchScope.All, CancellationToken)) .Returns(true); await Assert.ThrowsAsync(() => operation.CheckReferrersAsync(CancellationToken)); } - private AssetOperation Operation(AssetEntity asset) + private AssetOperation Operation(Asset asset) { return Operation(asset, Mocks.FrontendUser()); } - private AssetOperation Operation(AssetEntity asset, ClaimsPrincipal? currentUser) + private AssetOperation Operation(Asset asset, ClaimsPrincipal? currentUser) { var serviceProvider = new ServiceCollection() @@ -117,40 +118,4 @@ public class GuardAssetTests : GivenContext, IClassFixture Command = new CreateAsset { User = currentUser, Actor = User } }; } - - private AssetEntity CreateAsset(DomainId id = default, DomainId parentId = default) - { - return new AssetEntity - { - Id = OrNew(id), - AppId = AppId, - Created = default, - CreatedBy = User, - ParentId = OrNew(parentId) - }; - } - - private IAssetFolderEntity CreateAssetFolder(DomainId id = default, DomainId parentId = default) - { - var assetFolder = A.Fake(); - - A.CallTo(() => assetFolder.Id) - .Returns(OrNew(id)); - A.CallTo(() => assetFolder.AppId) - .Returns(AppId); - A.CallTo(() => assetFolder.ParentId) - .Returns(OrNew(parentId)); - - return assetFolder; - } - - private static DomainId OrNew(DomainId parentId) - { - if (parentId == default) - { - parentId = DomainId.NewGuid(); - } - - return parentId; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/ScriptingExtensionsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/ScriptingExtensionsTests.cs index 39acfe626..a3d2eeba6 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/ScriptingExtensionsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/DomainObject/Guards/ScriptingExtensionsTests.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Validation; @@ -71,16 +70,16 @@ public sealed class ScriptingExtensionsTests : GivenContext await Assert.ThrowsAsync(() => operation.ExecuteAnnotateScriptAsync(command, CancellationToken)); } - private AssetOperation Operation(string script, AssetEntity asset, AnnotateAsset command) + private AssetOperation Operation(string script, Asset asset, AnnotateAsset command) { - var scripts = new AssetScripts + App = App with { - Annotate = script + AssetScripts = new AssetScripts + { + Annotate = script + } }; - A.CallTo(() => App.AssetScripts) - .Returns(scripts); - var serviceProvider = new ServiceCollection() .AddMemoryCache() @@ -99,17 +98,4 @@ public sealed class ScriptingExtensionsTests : GivenContext Command = command }; } - - private AssetEntity CreateAsset() - { - return new AssetEntity - { - Id = DomainId.NewGuid(), - AppId = AppId, - Created = default, - CreatedBy = User, - Metadata = [], - Tags = [] - }; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTagAssetMetadataSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTagAssetMetadataSourceTests.cs index acdc849a8..df51c6d5b 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTagAssetMetadataSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTagAssetMetadataSourceTests.cs @@ -8,11 +8,12 @@ using Squidex.Assets; using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Entities.Assets; -public class FileTagAssetMetadataSourceTests +public class FileTagAssetMetadataSourceTests : GivenContext { private readonly FileTagAssetMetadataSource sut = new FileTagAssetMetadataSource(); @@ -85,7 +86,7 @@ public class FileTagAssetMetadataSourceTests [Fact] public void Should_format_video() { - var source = new AssetEntity + var source = CreateAsset() with { Metadata = new AssetMetadata { @@ -104,7 +105,7 @@ public class FileTagAssetMetadataSourceTests [Fact] public void Should_format_audio() { - var source = new AssetEntity + var source = CreateAsset() with { Metadata = new AssetMetadata { @@ -121,7 +122,10 @@ public class FileTagAssetMetadataSourceTests [Fact] public void Should_not_format_image() { - var source = new AssetEntity { Type = AssetType.Image }; + var source = CreateAsset() with + { + Type = AssetType.Image + }; var formatted = sut.Format(source); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTypeAssetMetadataSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTypeAssetMetadataSourceTests.cs index ed379a34b..408d91365 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTypeAssetMetadataSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/FileTypeAssetMetadataSourceTests.cs @@ -10,7 +10,7 @@ using Squidex.Domain.Apps.Entities.TestHelpers; namespace Squidex.Domain.Apps.Entities.Assets; -public class FileTypeAssetMetadataSourceTests +public class FileTypeAssetMetadataSourceTests : GivenContext { private readonly FileTypeAssetMetadataSource sut = new FileTypeAssetMetadataSource(); @@ -53,7 +53,7 @@ public class FileTypeAssetMetadataSourceTests [Fact] public void Should_always_format_to_empty() { - var source = new AssetEntity(); + var source = CreateAsset(); var formatted = sut.Format(source); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs index 3a9b46fb2..3441341f4 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/ImageAssetMetadataSourceTests.cs @@ -156,7 +156,7 @@ public class ImageAssetMetadataSourceTests : GivenContext [Fact] public void Should_format_image() { - var source = new AssetEntity + var source = CreateAsset() with { Metadata = new AssetMetadata { @@ -174,7 +174,10 @@ public class ImageAssetMetadataSourceTests : GivenContext [Fact] public void Should_not_format_video() { - var source = new AssetEntity { Type = AssetType.Video }; + var source = CreateAsset() with + { + Type = AssetType.Video + }; var formatted = sut.Format(source); @@ -184,7 +187,10 @@ public class ImageAssetMetadataSourceTests : GivenContext [Fact] public void Should_not_format_audio() { - var source = new AssetEntity { Type = AssetType.Audio }; + var source = CreateAsset() with + { + Type = AssetType.Audio + }; var formatted = sut.Format(source); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetMappingTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetMappingTests.cs index 9a1064d49..bb220c4a0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetMappingTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetMappingTests.cs @@ -5,49 +5,21 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using NodaTime; using Squidex.Domain.Apps.Core.Assets; -using Squidex.Domain.Apps.Entities.Assets.DomainObject; using Squidex.Domain.Apps.Entities.MongoDb.Assets; -using Squidex.Infrastructure; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Assets.MongoDb; -public class AssetMappingTests +public class AssetMappingTests : GivenContext { [Fact] public void Should_map_asset() { - var user = RefToken.User("1"); + var source = CreateAsset() as Asset; - var time = SystemClock.Instance.GetCurrentInstant(); - - var source = new AssetDomainObject.State - { - Id = DomainId.NewGuid(), - AppId = NamedId.Of(DomainId.NewGuid(), "my-app"), - Created = time, - CreatedBy = user, - FileHash = "my-hash", - FileName = "my-image.png", - FileSize = 1024, - FileVersion = 13, - IsDeleted = true, - IsProtected = true, - LastModified = time, - LastModifiedBy = user, - Metadata = new AssetMetadata().SetPixelHeight(600), - MimeType = "image/png", - ParentId = DomainId.NewGuid(), - Slug = "my-image", - Tags = ["image"], - TotalSize = 1024 * 2, - Type = AssetType.Image, - Version = 42 - }; - - var snapshotJob = new SnapshotWriteJob(source.UniqueId, source, source.Version); + var snapshotJob = new SnapshotWriteJob(source.UniqueId, source, source.Version); var snapshot = MongoAssetEntity.Create(snapshotJob); var mapped = snapshot.ToState(); @@ -58,25 +30,9 @@ public class AssetMappingTests [Fact] public void Should_map_asset_folder() { - var user = RefToken.User("1"); - - var time = SystemClock.Instance.GetCurrentInstant(); - - var source = new AssetFolderDomainObject.State - { - Id = DomainId.NewGuid(), - AppId = NamedId.Of(DomainId.NewGuid(), "my-app"), - Created = time, - CreatedBy = user, - FolderName = "my-folder", - IsDeleted = true, - LastModified = time, - LastModifiedBy = user, - ParentId = DomainId.NewGuid(), - Version = 42 - }; + var source = CreateAssetFolder(); - var snapshotJob = new SnapshotWriteJob(source.UniqueId, source, source.Version); + var snapshotJob = new SnapshotWriteJob(source.UniqueId, source, source.Version); var snapshot = MongoAssetFolderEntity.Create(snapshotJob); var mapped = snapshot.ToState(); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs index 71acf1ff1..59b85c91c 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs @@ -26,6 +26,8 @@ public class AssetQueryTests static AssetQueryTests() { + MongoAssetEntity.RegisterClassMap(); + TestUtils.SetupBson(); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs index d7660d741..735790b00 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs @@ -8,10 +8,8 @@ using System.Globalization; using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; -using NodaTime; using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.Assets.DomainObject; using Squidex.Domain.Apps.Entities.MongoDb.Assets; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; @@ -21,7 +19,7 @@ using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Assets.MongoDb; -public sealed class AssetsQueryFixture : IAsyncLifetime +public sealed class AssetsQueryFixture : GivenContext, IAsyncLifetime { private readonly int numValues = 250; @@ -75,27 +73,24 @@ public sealed class AssetsQueryFixture : IAsyncLifetime return; } - var batch = new List>(); + var batch = new List>(); - async Task ExecuteBatchAsync(AssetDomainObject.State? entity) + async Task ExecuteBatchAsync(Asset? entity) { if (entity != null) { - batch.Add(new SnapshotWriteJob(entity.UniqueId, entity, 0)); + batch.Add(new SnapshotWriteJob(entity.UniqueId, entity, 0)); } if ((entity == null || batch.Count >= 1000) && batch.Count > 0) { - var store = (ISnapshotStore)AssetRepository; + var store = (ISnapshotStore)AssetRepository; await store.WriteManyAsync(batch, ct); batch.Clear(); } } - var created = SystemClock.Instance.GetCurrentInstant(); - var createdBy = RefToken.User("1"); - foreach (var appId in appIds) { for (var i = 0; i < numValues; i++) @@ -106,19 +101,10 @@ public sealed class AssetsQueryFixture : IAsyncLifetime { var tag = j.ToString(CultureInfo.InvariantCulture); - var asset = new AssetDomainObject.State + var asset = CreateAsset() with { - Id = DomainId.NewGuid(), - AppId = appId, - Created = created, - CreatedBy = createdBy, FileHash = fileName, FileName = fileName, - FileSize = 1024, - LastModified = created, - LastModifiedBy = createdBy, - IsDeleted = false, - IsProtected = false, Metadata = new AssetMetadata { ["value"] = JsonValue.Create(tag) diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryIntegrationTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryIntegrationTests.cs index 021eeb1b6..629825360 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryIntegrationTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryIntegrationTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Queries; @@ -177,7 +178,7 @@ public class AssetsQueryIntegrationTests : IClassFixture, IA yield return new object?[] { DomainId.Empty }; } - private async Task> QueryAsync(DomainId? parentId, ClrQuery clrQuery, + private async Task> QueryAsync(DomainId? parentId, ClrQuery clrQuery, int top = 1000, int skip = 100) { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs index a8c4322eb..0af565d65 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetEnricherTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Assets.Queries; @@ -15,7 +14,7 @@ public class AssetEnricherTests : GivenContext [Fact] public async Task Should_only_invoke_pre_enrich_for_empty_assets() { - var assets = Array.Empty(); + var assets = Array.Empty(); var step1 = A.Fake(); var step2 = A.Fake(); @@ -30,10 +29,10 @@ public class AssetEnricherTests : GivenContext A.CallTo(() => step2.EnrichAsync(ApiContext, CancellationToken)) .MustHaveHappened(); - A.CallTo(() => step1.EnrichAsync(ApiContext, A>._, A._)) + A.CallTo(() => step1.EnrichAsync(ApiContext, A>._, A._)) .MustNotHaveHappened(); - A.CallTo(() => step2.EnrichAsync(ApiContext, A>._, A._)) + A.CallTo(() => step2.EnrichAsync(ApiContext, A>._, A._)) .MustNotHaveHappened(); } @@ -55,15 +54,10 @@ public class AssetEnricherTests : GivenContext A.CallTo(() => step2.EnrichAsync(ApiContext, CancellationToken)) .MustHaveHappened(); - A.CallTo(() => step1.EnrichAsync(ApiContext, A>._, CancellationToken)) + A.CallTo(() => step1.EnrichAsync(ApiContext, A>._, CancellationToken)) .MustHaveHappened(); - A.CallTo(() => step2.EnrichAsync(ApiContext, A>._, CancellationToken)) + A.CallTo(() => step2.EnrichAsync(ApiContext, A>._, CancellationToken)) .MustHaveHappened(); } - - private AssetEntity CreateAsset() - { - return new AssetEntity { Id = DomainId.NewGuid(), AppId = AppId }; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetLoaderTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetLoaderTests.cs index 081375a70..120aa90e0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetLoaderTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetLoaderTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.DomainObject; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; @@ -25,8 +26,8 @@ public class AssetLoaderTests : GivenContext { uniqueId = DomainId.Combine(AppId.Id, id); - A.CallTo(() => domainObjectCache.GetAsync(A._, A._, CancellationToken)) - .Returns(Task.FromResult(null!)); + A.CallTo(() => domainObjectCache.GetAsync(A._, A._, CancellationToken)) + .Returns(Task.FromResult(null!)); A.CallTo(() => domainObjectFactory.Create(uniqueId)) .Returns(domainObject); @@ -37,7 +38,7 @@ public class AssetLoaderTests : GivenContext [Fact] public async Task Should_return_null_if_no_state_returned() { - var asset = (AssetDomainObject.State)null!; + var asset = (Asset)null!; A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken)) .Returns(asset); @@ -48,7 +49,7 @@ public class AssetLoaderTests : GivenContext [Fact] public async Task Should_return_null_if_state_empty() { - var asset = new AssetDomainObject.State { Version = EtagVersion.Empty }; + var asset = CreateAsset() with { Version = EtagVersion.Empty }; A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken)) .Returns(asset); @@ -59,7 +60,7 @@ public class AssetLoaderTests : GivenContext [Fact] public async Task Should_return_null_if_state_has_other_version() { - var asset = new AssetDomainObject.State { Version = 5 }; + var asset = CreateAsset() with { Version = 5 }; A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken)) .Returns(asset); @@ -70,7 +71,7 @@ public class AssetLoaderTests : GivenContext [Fact] public async Task Should_not_return_null_if_state_has_other_version_than_any() { - var asset = new AssetDomainObject.State { Version = 5 }; + var asset = CreateAsset() with { Version = 5 }; A.CallTo(() => domainObject.GetSnapshotAsync(EtagVersion.Any, CancellationToken)) .Returns(asset); @@ -83,7 +84,7 @@ public class AssetLoaderTests : GivenContext [Fact] public async Task Should_return_asset_from_state() { - var asset = new AssetDomainObject.State { Version = 10 }; + var asset = CreateAsset() with { Version = 10 }; A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken)) .Returns(asset); @@ -96,9 +97,9 @@ public class AssetLoaderTests : GivenContext [Fact] public async Task Should_return_content_from_cache() { - var content = new AssetDomainObject.State { Version = 10 }; + var content = CreateAsset() with { Version = 10 }; - A.CallTo(() => domainObjectCache.GetAsync(DomainId.Combine(AppId.Id, id), 10, CancellationToken)) + A.CallTo(() => domainObjectCache.GetAsync(DomainId.Combine(AppId.Id, id), 10, CancellationToken)) .Returns(content); var actual = await sut.GetAsync(AppId.Id, id, 10, CancellationToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs index 274ade7da..aaa38acf0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/AssetQueryServiceTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using Microsoft.Extensions.Options; +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; @@ -43,7 +44,7 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_find_asset_by_slug_and_enrich_it() { - var asset = CreateAsset(DomainId.NewGuid()); + var asset = CreateAsset(); A.CallTo(() => assetRepository.FindAssetBySlugAsync(AppId.Id, "slug", true, A._)) .Returns(asset); @@ -56,10 +57,10 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_return_null_if_asset_by_slug_cannot_be_found() { - var asset = CreateAsset(DomainId.NewGuid()); + var asset = CreateAsset(); A.CallTo(() => assetRepository.FindAssetBySlugAsync(AppId.Id, "slug", false, A._)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var actual = await sut.FindBySlugAsync(ApiContext, "slug", false, CancellationToken); @@ -69,7 +70,7 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_find_asset_by_id_and_enrich_it() { - var asset = CreateAsset(DomainId.NewGuid()); + var asset = CreateAsset(); A.CallTo(() => assetRepository.FindAssetAsync(AppId.Id, asset.Id, false, A._)) .Returns(asset); @@ -82,10 +83,10 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_return_null_if_asset_by_id_cannot_be_found() { - var asset = CreateAsset(DomainId.NewGuid()); + var asset = CreateAsset(); A.CallTo(() => assetRepository.FindAssetAsync(AppId.Id, asset.Id, false, A._)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var actual = await sut.FindAsync(ApiContext, asset.Id, ct: CancellationToken); @@ -95,7 +96,7 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_find_asset_by_id_and_version_and_enrich_it() { - var asset = CreateAsset(DomainId.NewGuid()); + var asset = CreateAsset(); A.CallTo(() => assetLoader.GetAsync(AppId.Id, asset.Id, 2, A._)) .Returns(asset); @@ -108,10 +109,10 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_return_null_if_asset_by_id_and_version_cannot_be_found() { - var asset = CreateAsset(DomainId.NewGuid()); + var asset = CreateAsset(); A.CallTo(() => assetLoader.GetAsync(AppId.Id, asset.Id, 2, A._)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var actual = await sut.FindAsync(ApiContext, asset.Id, false, 2, CancellationToken); @@ -121,7 +122,7 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_find_global_asset_by_id_and_enrich_it() { - var asset = CreateAsset(DomainId.NewGuid()); + var asset = CreateAsset(); A.CallTo(() => assetRepository.FindAssetAsync(asset.Id, A._)) .Returns(asset); @@ -134,10 +135,10 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_return_null_if_global_asset_by_id_cannot_be_found() { - var asset = CreateAsset(DomainId.NewGuid()); + var asset = CreateAsset(); A.CallTo(() => assetRepository.FindAssetAsync(asset.Id, A._)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var actual = await sut.FindGlobalAsync(ApiContext, asset.Id, CancellationToken); @@ -147,7 +148,7 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_find_assets_by_hash_and_and_enrich_it() { - var asset = CreateAsset(DomainId.NewGuid()); + var asset = CreateAsset(); A.CallTo(() => assetRepository.FindAssetByHashAsync(AppId.Id, "hash", "name", 123, A._)) .Returns(asset); @@ -160,10 +161,10 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_return_null_if_asset_by_hash_cannot_be_found() { - var asset = CreateAsset(DomainId.NewGuid()); + var asset = CreateAsset(); A.CallTo(() => assetRepository.FindAssetByHashAsync(AppId.Id, "hash", "name", 123, A._)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var actual = await sut.FindByHashAsync(ApiContext, "hash", "name", 123, CancellationToken); @@ -173,8 +174,8 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_query_assets_and_enrich_it() { - var asset1 = CreateAsset(DomainId.NewGuid()); - var asset2 = CreateAsset(DomainId.NewGuid()); + var asset1 = CreateAsset(); + var asset2 = CreateAsset(); var parentId = DomainId.NewGuid(); @@ -196,7 +197,7 @@ public class AssetQueryServiceTests : GivenContext { var parentId = DomainId.NewGuid(); - var assetFolders = ResultList.CreateFrom(10); + var assetFolders = ResultList.CreateFrom(10); A.CallTo(() => assetFolderRepository.QueryAsync(AppId.Id, parentId, A._)) .Returns(assetFolders); @@ -209,13 +210,12 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_find_asset_folder_with_path() { - var folderId1 = DomainId.NewGuid(); - var folder1 = CreateFolder(folderId1); + var folder1 = CreateAssetFolder(); - A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId1, A._)) + A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folder1.Id, A._)) .Returns(folder1); - var actual = await sut.FindAssetFolderAsync(AppId.Id, folderId1, CancellationToken); + var actual = await sut.FindAssetFolderAsync(AppId.Id, folder1.Id, CancellationToken); Assert.Equal(actual, new[] { folder1 }); } @@ -223,24 +223,20 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_resolve_folder_path_from_child() { - var folderId1 = DomainId.NewGuid(); - var folderId2 = DomainId.NewGuid(); - var folderId3 = DomainId.NewGuid(); - - var folder1 = CreateFolder(folderId1); - var folder2 = CreateFolder(folderId2, folderId1); - var folder3 = CreateFolder(folderId3, folderId2); + var folder1 = CreateAssetFolder(); + var folder2 = CreateAssetFolder() with { ParentId = folder1.Id }; + var folder3 = CreateAssetFolder() with { ParentId = folder2.Id }; - A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId1, A._)) + A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folder1.Id, A._)) .Returns(folder1); - A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId2, A._)) + A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folder2.Id, A._)) .Returns(folder2); - A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId3, A._)) + A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folder3.Id, A._)) .Returns(folder3); - var actual = await sut.FindAssetFolderAsync(AppId.Id, folderId3, CancellationToken); + var actual = await sut.FindAssetFolderAsync(AppId.Id, folder3.Id, CancellationToken); Assert.Equal(actual, new[] { folder1, folder2, folder3 }); } @@ -251,7 +247,7 @@ public class AssetQueryServiceTests : GivenContext var folderId1 = DomainId.NewGuid(); A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId1, A._)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var actual = await sut.FindAssetFolderAsync(AppId.Id, folderId1, CancellationToken); @@ -261,19 +257,16 @@ public class AssetQueryServiceTests : GivenContext [Fact] public async Task Should_not_resolve_folder_path_if_parent_of_child_not_found() { - var folderId1 = DomainId.NewGuid(); - var folderId2 = DomainId.NewGuid(); - - var folder1 = CreateFolder(folderId1); - var folder2 = CreateFolder(folderId2, folderId1); + var folder1 = CreateAssetFolder(); + var folder2 = CreateAssetFolder() with { ParentId = folder1.Id }; - A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId1, A._)) - .Returns(Task.FromResult(null)); + A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folder1.Id, A._)) + .Returns(Task.FromResult(null)); - A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId2, A._)) + A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folder2.Id, A._)) .Returns(folder2); - var actual = await sut.FindAssetFolderAsync(AppId.Id, folderId2, CancellationToken); + var actual = await sut.FindAssetFolderAsync(AppId.Id, folder2.Id, CancellationToken); Assert.Empty(actual); } @@ -284,11 +277,11 @@ public class AssetQueryServiceTests : GivenContext var folderId1 = DomainId.NewGuid(); var folderId2 = DomainId.NewGuid(); - var folder1 = CreateFolder(folderId1, folderId2); - var folder2 = CreateFolder(folderId2, folderId1); + var folder1 = CreateAssetFolder().WithId(folderId1) with { ParentId = folderId2 }; + var folder2 = CreateAssetFolder().WithId(folderId2) with { ParentId = folderId1 }; A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId1, A._)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); A.CallTo(() => assetFolderRepository.FindAssetFolderAsync(AppId.Id, folderId2, A._)) .Returns(folder2); @@ -298,36 +291,21 @@ public class AssetQueryServiceTests : GivenContext Assert.Empty(actual); } - private static void AssertAsset(IAssetEntity source, IEnrichedAssetEntity? actual) + private static void AssertAsset(Asset source, EnrichedAsset? actual) { Assert.NotNull(actual); Assert.NotSame(source, actual); - Assert.Equal(source.AssetId, actual?.AssetId); - } - - private static IAssetFolderEntity CreateFolder(DomainId id, DomainId parentId = default) - { - var assetFolder = A.Fake(); - - A.CallTo(() => assetFolder.Id).Returns(id); - A.CallTo(() => assetFolder.ParentId).Returns(parentId); - - return assetFolder; - } - - private static AssetEntity CreateAsset(DomainId id) - { - return new AssetEntity { Id = id }; + Assert.Equal(source.Id, actual?.Id); } private void SetupEnricher() { - A.CallTo(() => assetEnricher.EnrichAsync(A>._, A._, CancellationToken)) + A.CallTo(() => assetEnricher.EnrichAsync(A>._, A._, CancellationToken)) .ReturnsLazily(x => { - var input = x.GetArgument>(0)!; + var input = x.GetArgument>(0)!; - return Task.FromResult>(input.Select(c => SimpleMapper.Map(c, new AssetEntity())).ToList()); + return Task.FromResult>(input.Select(c => SimpleMapper.Map(c, new EnrichedAsset())).ToList()); }); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/CalculateTokensTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/CalculateTokensTests.cs index e248228d2..8003bb80a 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/CalculateTokensTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/CalculateTokensTests.cs @@ -58,9 +58,4 @@ public class CalculateTokensTests : GivenContext A.CallTo(() => urlGenerator.Root()) .MustHaveHappened(); } - - private AssetEntity CreateAsset() - { - return new AssetEntity { AppId = AppId }; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ConvertTagsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ConvertTagsTests.cs index f34f7f634..4ab2997a4 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ConvertTagsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ConvertTagsTests.cs @@ -26,7 +26,7 @@ public class ConvertTagsTests : GivenContext [Fact] public async Task Should_not_enrich_if_asset_has_null_tags() { - var asset = new AssetEntity(); + var asset = CreateAsset() with { Tags = null! }; await sut.EnrichAsync(ApiContext, Enumerable.Repeat(asset, 1), CancellationToken); @@ -36,7 +36,7 @@ public class ConvertTagsTests : GivenContext [Fact] public async Task Should_not_enrich_asset_with_tag_names_if_disabled() { - var asset = new AssetEntity(); + var asset = CreateAsset() with { TagNames = null! }; await sut.EnrichAsync(ApiContext.Clone(b => b.WithNoAssetEnrichment()), Enumerable.Repeat(asset, 1), CancellationToken); @@ -46,14 +46,9 @@ public class ConvertTagsTests : GivenContext [Fact] public async Task Should_enrich_asset_with_tag_names() { - var asset = new AssetEntity + var asset = CreateAsset() with { - Tags = - [ - "id1", - "id2" - ], - AppId = AppId + Tags = ["id1", "id2"] }; A.CallTo(() => tagService.GetTagNamesAsync(AppId.Id, TagGroups.Assets, A>.That.Is("id1", "id2"), CancellationToken)) @@ -71,24 +66,14 @@ public class ConvertTagsTests : GivenContext [Fact] public async Task Should_enrich_multiple_assets_with_tag_names() { - var asset1 = new AssetEntity + var asset1 = CreateAsset() with { - Tags = - [ - "id1", - "id2" - ], - AppId = AppId + Tags = ["id1", "id2"] }; - var asset2 = new AssetEntity + var asset2 = CreateAsset() with { - Tags = - [ - "id2", - "id3" - ], - AppId = AppId + Tags = ["id2", "id3"] }; A.CallTo(() => tagService.GetTagNamesAsync(AppId.Id, TagGroups.Assets, A>.That.Is("id1", "id2", "id3"), CancellationToken)) diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichForCachingTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichForCachingTests.cs index 20049ab37..be809e6e0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichForCachingTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichForCachingTests.cs @@ -7,7 +7,6 @@ using Squidex.Domain.Apps.Entities.Assets.Queries.Steps; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; namespace Squidex.Domain.Apps.Entities.Assets.Queries; @@ -49,7 +48,7 @@ public class EnrichForCachingTests : GivenContext [Fact] public async Task Should_add_app_version_as_dependency() { - var asset = CreateAsset(); + var asset = CreateAsset() with { Version = 13 }; await sut.EnrichAsync(ApiContext, Enumerable.Repeat(asset, 1), CancellationToken); @@ -72,16 +71,11 @@ public class EnrichForCachingTests : GivenContext [Fact] public async Task Should_not_add_cache_headers_for_assets_if_disabled() { - var asset = CreateAsset(); + var asset = CreateAsset() with { Version = 13 }; await sut.EnrichAsync(ApiContext.Clone(b => b.WithNoCacheKeys()), Enumerable.Repeat(asset, 1), CancellationToken); A.CallTo(() => requestCache.AddHeader(A._)) .MustNotHaveHappened(); } - - private AssetEntity CreateAsset() - { - return new AssetEntity { AppId = AppId, Id = DomainId.NewGuid(), Version = 13 }; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichWithMetadataTextTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichWithMetadataTextTests.cs index b38a94a97..a966bb8f3 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichWithMetadataTextTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/EnrichWithMetadataTextTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Queries.Steps; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -30,35 +31,34 @@ public class EnrichWithMetadataTextTests : GivenContext [Fact] public async Task Should_not_enrich_if_disabled() { - var asset = new AssetEntity(); + var asset = CreateAsset(); await sut.EnrichAsync(ApiContext.Clone(b => b.WithNoAssetEnrichment()), Enumerable.Repeat(asset, 1), CancellationToken); - A.CallTo(() => assetMetadataSource1.Format(A._)) + A.CallTo(() => assetMetadataSource1.Format(A._)) .MustNotHaveHappened(); - A.CallTo(() => assetMetadataSource2.Format(A._)) + A.CallTo(() => assetMetadataSource2.Format(A._)) .MustNotHaveHappened(); } [Fact] public async Task Should_enrich_asset_with_metadata() { - var asset = new AssetEntity + var asset = CreateAsset() with { FileSize = 2 * 1024, Tags = [ "id1", "id2" - ], - AppId = AppId + ] }; - A.CallTo(() => assetMetadataSource1.Format(A._)) + A.CallTo(() => assetMetadataSource1.Format(asset)) .Returns(new[] { "metadata1" }); - A.CallTo(() => assetMetadataSource2.Format(A._)) + A.CallTo(() => assetMetadataSource2.Format(asset)) .Returns(new[] { "metadata2", "metadata3" }); await sut.EnrichAsync(FrontendContext, Enumerable.Repeat(asset, 1), CancellationToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ScriptAssetTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ScriptAssetTests.cs index 3d15cb303..4e056f4ff 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ScriptAssetTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/ScriptAssetTests.cs @@ -9,7 +9,6 @@ using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Assets.Queries.Steps; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure; using Squidex.Shared; namespace Squidex.Domain.Apps.Entities.Assets.Queries; @@ -116,17 +115,14 @@ public class ScriptAssetTests : GivenContext private void SetupScript(string? query = null, string? queryPre = null) { - A.CallTo(() => App.AssetScripts) - .Returns(new AssetScripts + App = App with + { + AssetScripts = new AssetScripts { Query = query, QueryPre = queryPre - }); - } - - private static AssetEntity CreateAsset() - { - return new AssetEntity { Id = DomainId.NewGuid() }; + } + }; } private static ScriptOptions ScriptOptions() diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/NoopBillingManagerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/NoopBillingManagerTests.cs index d01aa6dfc..f7689a7a9 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/NoopBillingManagerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/NoopBillingManagerTests.cs @@ -5,43 +5,42 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Teams; +using Squidex.Domain.Apps.Entities.TestHelpers; namespace Squidex.Domain.Apps.Entities.Billing; -public class NoopBillingManagerTests +public class NoopBillingManagerTests : GivenContext { private readonly NoopBillingManager sut = new NoopBillingManager(); [Fact] public async Task Should_do_nothing_if_subscribing_to_app() { - await sut.SubscribeAsync(null!, (IAppEntity)null!, null!); + await sut.SubscribeAsync(User.Identifier, App, "free", CancellationToken); } [Fact] public async Task Should_do_nothing_if_subscribing_to_team() { - await sut.SubscribeAsync(null!, (ITeamEntity)null!, null!); + await sut.SubscribeAsync(User.Identifier, Team, "free", CancellationToken); } [Fact] public async Task Should_do_nothing_if_unsubscribing_from_app() { - await sut.UnsubscribeAsync(null!, (IAppEntity)null!); + await sut.UnsubscribeAsync(User.Identifier, App, CancellationToken); } [Fact] public async Task Should_do_nothing_if_unsubscribing_from_team() { - await sut.UnsubscribeAsync(null!, (ITeamEntity)null!); + await sut.UnsubscribeAsync(User.Identifier, Team, CancellationToken); } [Fact] public async Task Should_not_return_portal_link_for_app() { - var actual = await sut.GetPortalLinkAsync(null!, (IAppEntity)null!); + var actual = await sut.GetPortalLinkAsync(User.Identifier, App, CancellationToken); Assert.Null(actual); } @@ -49,7 +48,7 @@ public class NoopBillingManagerTests [Fact] public async Task Should_not_return_portal_link_for_team() { - var actual = await sut.GetPortalLinkAsync(null!, (ITeamEntity)null!); + var actual = await sut.GetPortalLinkAsync(User.Identifier, Team, CancellationToken); Assert.Null(actual); } @@ -57,7 +56,7 @@ public class NoopBillingManagerTests [Fact] public async Task Should_not_return_referral_code_for_app() { - var actual = await sut.GetReferralInfoAsync(null!, (IAppEntity)null!); + var actual = await sut.GetReferralInfoAsync(User.Identifier, App, CancellationToken); Assert.Null(actual); } @@ -65,7 +64,7 @@ public class NoopBillingManagerTests [Fact] public async Task Should_not_return_referral_code_for_team() { - var actual = await sut.GetReferralInfoAsync(null!, (ITeamEntity)null!); + var actual = await sut.GetReferralInfoAsync(User.Identifier, Team, CancellationToken); Assert.Null(actual); } @@ -73,7 +72,7 @@ public class NoopBillingManagerTests [Fact] public async Task Should_do_nothing_if_checking_for_redirect_for_app() { - var actual = await sut.MustRedirectToPortalAsync(null!, (IAppEntity)null!, null); + var actual = await sut.MustRedirectToPortalAsync(User.Identifier, App, "free", CancellationToken); Assert.Null(actual); } @@ -81,7 +80,7 @@ public class NoopBillingManagerTests [Fact] public async Task Should_do_nothing_if_checking_for_redirect_for_team() { - var actual = await sut.MustRedirectToPortalAsync(null!, (ITeamEntity)null!, null); + var actual = await sut.MustRedirectToPortalAsync(User.Identifier, Team, "free", CancellationToken); Assert.Null(actual); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/UsageGateTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/UsageGateTests.cs index 142350bec..27ad86b60 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/UsageGateTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/UsageGateTests.cs @@ -30,6 +30,11 @@ public class UsageGateTests : GivenContext public UsageGateTests() { + App = App with + { + TeamId = default + }; + A.CallTo(() => billingPlans.GetActualPlan(A._)) .ReturnsLazily(x => (planFree, planFree.Id)); @@ -80,8 +85,10 @@ public class UsageGateTests : GivenContext [Fact] public async Task Should_get_free_plan_for_app_with_team() { - A.CallTo(() => App.TeamId) - .Returns(TeamId); + App = App with + { + TeamId = TeamId + }; var plan = await sut.GetPlanForAppAsync(App, false, CancellationToken); @@ -91,8 +98,10 @@ public class UsageGateTests : GivenContext [Fact] public async Task Should_get_paid_plan_for_app() { - A.CallTo(() => App.Plan) - .Returns(new AssignedPlan(RefToken.User("1"), planPaid.Id)); + App = App with + { + Plan = new AssignedPlan(User, planPaid.Id) + }; var plan = await sut.GetPlanForAppAsync(App, false, CancellationToken); @@ -102,8 +111,10 @@ public class UsageGateTests : GivenContext [Fact] public async Task Should_get_paid_plan_for_app_id() { - A.CallTo(() => App.Plan) - .Returns(new AssignedPlan(RefToken.User("1"), planPaid.Id)); + App = App with + { + Plan = new AssignedPlan(User, planPaid.Id) + }; var plan = await sut.GetPlanForAppAsync(AppId.Id, false, CancellationToken); @@ -113,11 +124,10 @@ public class UsageGateTests : GivenContext [Fact] public async Task Should_get_paid_plan_for_app_with_team() { - A.CallTo(() => Team.Plan) - .Returns(new AssignedPlan(RefToken.User("1"), planPaid.Id)); - - A.CallTo(() => App.TeamId) - .Returns(TeamId); + App = App with + { + Plan = new AssignedPlan(User, planPaid.Id), TeamId = TeamId + }; var plan = await sut.GetPlanForAppAsync(App, false, CancellationToken); @@ -127,14 +137,16 @@ public class UsageGateTests : GivenContext [Fact] public async Task Should_block_with_true_if_over_client_limit() { + App = App with + { + Clients = AppClients.Empty.Add(clientId, clientId).Update(clientId, apiCallsLimit: 1000) + }; + var plan = new Plan { Id = "custom", BlockingApiCalls = 1600, MaxApiCalls = 1600 }; A.CallTo(() => billingPlans.GetActualPlan(A._)) .ReturnsLazily(x => (plan, plan.Id)); - A.CallTo(() => App.Clients) - .Returns(AppClients.Empty.Add(clientId, clientId).Update(clientId, apiCallsLimit: 1000)); - A.CallTo(() => apiUsageTracker.GetMonthCallsAsync(AppId.Id.ToString(), today, A._, CancellationToken)) .Returns(1000); @@ -272,8 +284,10 @@ public class UsageGateTests : GivenContext [Fact] public async Task Should_track_api_request_with_team() { - A.CallTo(() => App.TeamId) - .Returns(TeamId); + App = App with + { + TeamId = TeamId + }; await sut.TrackRequestAsync(App, "client", today, 42, 50, 512, CancellationToken); @@ -311,14 +325,16 @@ public class UsageGateTests : GivenContext [Fact] public async Task Should_track_rules_usage_with_team() { + App = App with + { + TeamId = TeamId + }; + Counters? countersSummary = null; Counters? countersDate = null; var ruleId = DomainId.NewGuid(); - A.CallTo(() => App.TeamId) - .Returns(TeamId); - A.CallTo(() => usageTracker.TrackAsync(default, $"{TeamId}_TeamRules", AppId.Id.ToString(), A._, CancellationToken)) .Invokes(x => countersSummary = x.GetArgument(3)); @@ -459,12 +475,14 @@ public class UsageGateTests : GivenContext [Fact] public async Task Should_track_assets_usage_with_team() { + App = App with + { + TeamId = TeamId + }; + Counters? countersSummary = null; Counters? countersDate = null; - A.CallTo(() => App.TeamId) - .Returns(TeamId); - A.CallTo(() => usageTracker.TrackAsync(default, $"{TeamId}_TeamAssets", AppId.Id.ToString(), A._, CancellationToken)) .Invokes(x => countersSummary = x.GetArgument(3)); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/UsageNotifierWorkerTest.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/UsageNotifierWorkerTest.cs index 641798df2..254a9272a 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/UsageNotifierWorkerTest.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/UsageNotifierWorkerTest.cs @@ -6,8 +6,8 @@ // ========================================================================== using NodaTime; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Collaboration; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; @@ -64,7 +64,7 @@ public class UsageNotifierWorkerTest : GivenContext await sut.HandleAsync(message, default); - A.CallTo(() => notificationSender.SendUsageAsync(A._, A._, A._, A._, A._)) + A.CallTo(() => notificationSender.SendUsageAsync(A._, A._, A._, A._, A._)) .MustNotHaveHappened(); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Collaboration/CommentTriggerHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Collaboration/CommentTriggerHandlerTests.cs index be672a18b..4e7625a22 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Collaboration/CommentTriggerHandlerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Collaboration/CommentTriggerHandlerTests.cs @@ -13,6 +13,7 @@ using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Comments; using Squidex.Domain.Apps.Events.Contents; @@ -22,7 +23,7 @@ using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.Collaboration; -public class CommentTriggerHandlerTests +public class CommentTriggerHandlerTests : GivenContext { private readonly IScriptEngine scriptEngine = A.Fake(); private readonly IUserResolver userResolver = A.Fake(); @@ -295,15 +296,16 @@ public class CommentTriggerHandlerTests } } - private static RuleContext Context(RuleTrigger? trigger = null) + private RuleContext Context(RuleTrigger? trigger = null) { trigger ??= new CommentTrigger(); return new RuleContext { - AppId = NamedId.Of(DomainId.NewGuid(), "my-app"), - Rule = new Rule(trigger, A.Fake()), - RuleId = DomainId.NewGuid() + AppId = AppId, + IncludeSkipped = false, + IncludeStale = false, + Rule = CreateRule() with { Trigger = trigger } }; } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BackupContentsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BackupContentsTests.cs index a34459792..62ab4509d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BackupContentsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/BackupContentsTests.cs @@ -187,7 +187,7 @@ public class BackupContentsTests : GivenContext var rebuildContents = new HashSet(); - A.CallTo(() => rebuilder.InsertManyAsync(A>._, A._, CancellationToken)) + A.CallTo(() => rebuilder.InsertManyAsync(A>._, A._, CancellationToken)) .Invokes(x => rebuildContents.AddRange(x.GetArgument>(0)!)); await sut.RestoreAsync(context, CancellationToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs index 56e8e6763..231d0ddb6 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs @@ -126,10 +126,10 @@ public class ContentChangedTriggerHandlerTests : GivenContext var ctx = Context(); A.CallTo(() => contentRepository.StreamAll(AppId.Id, null, SearchScope.All, CancellationToken)) - .Returns(new List + .Returns(new List { - new ContentEntity { SchemaId = schemaMatching }, - new ContentEntity { SchemaId = schemaNotMatching } + CreateContent() with { SchemaId = schemaMatching }, + CreateContent() with { SchemaId = schemaNotMatching } }.ToAsyncEnumerable()); var actual = await sut.CreateSnapshotEventsAsync(ctx, CancellationToken).ToListAsync(CancellationToken); @@ -158,10 +158,10 @@ public class ContentChangedTriggerHandlerTests : GivenContext var ctx = Context(trigger); A.CallTo(() => contentRepository.StreamAll(AppId.Id, A>.That.Is(schemaMatching.Id), SearchScope.All, CancellationToken)) - .Returns(new List + .Returns(new List { - new ContentEntity { SchemaId = schemaMatching }, - new ContentEntity { SchemaId = schemaMatching } + CreateContent() with { SchemaId = schemaMatching }, + CreateContent() with { SchemaId = schemaMatching } }.ToAsyncEnumerable()); var actual = await sut.CreateSnapshotEventsAsync(ctx, CancellationToken).ToListAsync(CancellationToken); @@ -184,7 +184,7 @@ public class ContentChangedTriggerHandlerTests : GivenContext var envelope = Envelope.Create(@event).SetEventStreamNumber(12); A.CallTo(() => contentLoader.GetAsync(AppId.Id, @event.ContentId, 12, CancellationToken)) - .Returns(SimpleMapper.Map(@event, new ContentEntity())); + .Returns(SimpleMapper.Map(@event, new Content())); var actuals = await sut.CreateEnrichedEventsAsync(envelope, ctx, CancellationToken).ToListAsync(CancellationToken); var actual = (EnrichedContentEvent)actuals.Single(); @@ -209,10 +209,10 @@ public class ContentChangedTriggerHandlerTests : GivenContext var dataOld = new ContentData(); A.CallTo(() => contentLoader.GetAsync(AppId.Id, @event.ContentId, 12, CancellationToken)) - .Returns(new ContentEntity { AppId = AppId, SchemaId = schemaMatching, Version = 12, Data = dataNow, Id = @event.ContentId }); + .Returns(new Content { AppId = AppId, SchemaId = schemaMatching, Version = 12, Data = dataNow, Id = @event.ContentId }); A.CallTo(() => contentLoader.GetAsync(AppId.Id, @event.ContentId, 11, CancellationToken)) - .Returns(new ContentEntity { AppId = AppId, SchemaId = schemaMatching, Version = 11, Data = dataOld }); + .Returns(new Content { AppId = AppId, SchemaId = schemaMatching, Version = 11, Data = dataOld }); var actuals = await sut.CreateEnrichedEventsAsync(envelope, ctx, CancellationToken).ToListAsync(CancellationToken); var actual = actuals.Single() as EnrichedContentEvent; @@ -232,10 +232,10 @@ public class ContentChangedTriggerHandlerTests : GivenContext SetupData(@event, 12); A.CallTo(() => contentRepository.StreamReferencing(AppId.Id, @event.ContentId, 100, SearchScope.All, CancellationToken)) - .Returns(new List + .Returns(new List { - new ContentEntity { SchemaId = schemaMatching }, - new ContentEntity { SchemaId = schemaMatching } + new Content { SchemaId = schemaMatching }, + new Content { SchemaId = schemaMatching } }.ToAsyncEnumerable()); var actual = await sut.CreateEnrichedEventsAsync(envelope, ctx, CancellationToken).ToListAsync(CancellationToken); @@ -493,10 +493,10 @@ public class ContentChangedTriggerHandlerTests : GivenContext var dataOld = new ContentData(); A.CallTo(() => contentLoader.GetAsync(AppId.Id, @event.ContentId, version, CancellationToken)) - .Returns(new ContentEntity { AppId = AppId, SchemaId = schemaMatching, Version = version, Data = dataNow, Id = @event.ContentId }); + .Returns(new Content { AppId = AppId, SchemaId = schemaMatching, Version = version, Data = dataNow, Id = @event.ContentId }); A.CallTo(() => contentLoader.GetAsync(AppId.Id, @event.ContentId, version, CancellationToken)) - .Returns(new ContentEntity { AppId = AppId, SchemaId = schemaMatching, Version = version - 1, Data = dataOld }); + .Returns(new Content { AppId = AppId, SchemaId = schemaMatching, Version = version - 1, Data = dataOld }); } private RulesContext ReferencingContext(int? maxEvents, bool allowExtra, NamedId? schemaId = null) @@ -522,7 +522,7 @@ public class ContentChangedTriggerHandlerTests : GivenContext IncludeStale = true, Rules = new Dictionary { - [DomainId.NewGuid()] = new Rule(trigger, A.Fake()) + [DomainId.NewGuid()] = CreateRule() with { Trigger = trigger } }.ToReadonlyDictionary(), AllowExtraEvents = allowExtra }; @@ -535,8 +535,9 @@ public class ContentChangedTriggerHandlerTests : GivenContext return new RuleContext { AppId = AppId, - Rule = new Rule(trigger, A.Fake()), - RuleId = DomainId.NewGuid() + IncludeSkipped = false, + IncludeStale = false, + Rule = CreateRule() with { Trigger = trigger } }; } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentSchedulerProcessTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentSchedulerProcessTests.cs index 1ba21bf50..5f348db7a 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentSchedulerProcessTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentSchedulerProcessTests.cs @@ -36,17 +36,13 @@ public class ContentSchedulerProcessTests : GivenContext { var now = SystemClock.Instance.GetCurrentInstant(); - var content1 = new ContentEntity + var content1 = CreateContent() with { - AppId = AppId, - Id = DomainId.NewGuid(), ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Archived, null!, now) }; - var content2 = new ContentEntity + var content2 = CreateContent() with { - AppId = AppId, - Id = DomainId.NewGuid(), ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Draft, null!, now) }; @@ -80,10 +76,8 @@ public class ContentSchedulerProcessTests : GivenContext { var now = SystemClock.Instance.GetCurrentInstant(); - var content1 = new ContentEntity + var content1 = CreateContent() with { - AppId = AppId, - Id = DomainId.NewGuid(), ScheduleJob = null }; @@ -104,10 +98,8 @@ public class ContentSchedulerProcessTests : GivenContext { var now = SystemClock.Instance.GetCurrentInstant(); - var content1 = new ContentEntity + var content1 = CreateContent() with { - AppId = AppId, - Id = DomainId.NewGuid(), ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Archived, null!, now) }; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs index 81a9344cc..9972c9963 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentsSearchSourceTests.cs @@ -9,7 +9,6 @@ using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.Text; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Search; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; @@ -32,9 +31,9 @@ public class ContentsSearchSourceTests : GivenContext A.CallTo(() => AppProvider.GetSchemasAsync(AppId.Id, CancellationToken)) .Returns( [ - Mocks.Schema(AppId, schemaId1), - Mocks.Schema(AppId, schemaId2), - Mocks.Schema(AppId, schemaId3) + Schema.WithId(schemaId1), + Schema.WithId(schemaId2), + Schema.WithId(schemaId3), ]); sut = new ContentsSearchSource(AppProvider, contentQuery, contentIndex, urlGenerator); @@ -43,7 +42,10 @@ public class ContentsSearchSourceTests : GivenContext [Fact] public async Task Should_return_content_with_default_name() { - var content = new ContentEntity { Id = DomainId.NewGuid(), SchemaId = schemaId1 }; + var content = CreateContent() with + { + SchemaId = schemaId1 + }; await TestContentAsync(content, "Content"); } @@ -51,9 +53,8 @@ public class ContentsSearchSourceTests : GivenContext [Fact] public async Task Should_return_content_with_multiple_invariant_reference_fields() { - var content = new ContentEntity + var content = CreateContent() with { - Id = DomainId.NewGuid(), Data = new ContentData() .AddField("field1", @@ -76,9 +77,8 @@ public class ContentsSearchSourceTests : GivenContext [Fact] public async Task Should_return_content_with_invariant_reference_field() { - var content = new ContentEntity + var content = CreateContent() with { - Id = DomainId.NewGuid(), Data = new ContentData() .AddField("field", @@ -97,7 +97,7 @@ public class ContentsSearchSourceTests : GivenContext [Fact] public async Task Should_return_content_with_localized_reference_field() { - var content = new ContentEntity + var content = CreateContent() with { Id = DomainId.NewGuid(), Data = @@ -118,9 +118,8 @@ public class ContentsSearchSourceTests : GivenContext [Fact] public async Task Should_return_content_with_invariant_field_and_reference_data() { - var content = new ContentEntity + var content = CreateContent() with { - Id = DomainId.NewGuid(), Data = new ContentData() .AddField("field", @@ -170,10 +169,8 @@ public class ContentsSearchSourceTests : GivenContext .MustNotHaveHappened(); } - private async Task TestContentAsync(ContentEntity content, string expectedName) + private async Task TestContentAsync(EnrichedContent content, string expectedName) { - content.AppId = AppId; - var requestContext = ContextWithPermissions(schemaId1, schemaId2); var ids = new List { content.Id }; @@ -182,7 +179,7 @@ public class ContentsSearchSourceTests : GivenContext .Returns(ids); A.CallTo(() => contentQuery.QueryAsync(requestContext, A.That.HasIds(ids), CancellationToken)) - .Returns(ResultList.CreateFrom(1, content)); + .Returns(ResultList.CreateFrom(1, content)); A.CallTo(() => urlGenerator.ContentUI(AppId, schemaId1, content.Id)) .Returns("content-url"); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs deleted file mode 100644 index be664c1a2..000000000 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs +++ /dev/null @@ -1,197 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Contents; - -public class DefaultContentWorkflowTests -{ - private readonly DefaultContentWorkflow sut = new DefaultContentWorkflow(); - - [Fact] - public async Task Should_return_info_for_valid_status() - { - var info = await sut.GetInfoAsync(null!, Status.Draft); - - Assert.Equal(new StatusInfo(Status.Draft, StatusColors.Draft), info); - } - - [Fact] - public async Task Should_return_info_as_null_for_invalid_status() - { - var info = await sut.GetInfoAsync(null!, new Status("Invalid")); - - Assert.Null(info); - } - - [Fact] - public async Task Should_return_draft_as_initial_status() - { - var actual = await sut.GetInitialStatusAsync(null!); - - Assert.Equal(Status.Draft, actual); - } - - [Fact] - public async Task Should_allow_publish_on_create() - { - var actual = await sut.CanPublishInitialAsync(null!, null); - - Assert.True(actual); - } - - [Fact] - public async Task Should_allow_if_transition_is_valid() - { - var content = new ContentEntity { Status = Status.Published }; - - var actual = await sut.CanMoveToAsync(null!, content.Status, Status.Draft, null!, null!); - - Assert.True(actual); - } - - [Fact] - public async Task Should_allow_if_transition_is_valid_for_content() - { - var content = new ContentEntity { Status = Status.Published }; - - var actual = await sut.CanMoveToAsync(content, content.Status, Status.Draft, null!); - - Assert.True(actual); - } - - [Fact] - public async Task Should_be_able_to_update_published() - { - var content = new ContentEntity { Status = Status.Published }; - - var actual = await sut.CanUpdateAsync(content, content.Status, null!); - - Assert.True(actual); - } - - [Fact] - public async Task Should_be_able_to_update_draft() - { - var content = new ContentEntity { Status = Status.Published }; - - var actual = await sut.CanUpdateAsync(content, content.Status, null!); - - Assert.True(actual); - } - - [Fact] - public async Task Should_not_be_able_to_update_archived() - { - var content = new ContentEntity { Status = Status.Archived }; - - var actual = await sut.CanUpdateAsync(content, content.Status, null!); - - Assert.False(actual); - } - - [Fact] - public async Task Should_get_next_statuses_for_draft() - { - var content = new ContentEntity { Status = Status.Draft }; - - var expected = new[] - { - new StatusInfo(Status.Archived, StatusColors.Archived), - new StatusInfo(Status.Published, StatusColors.Published) - }; - - var actual = await sut.GetNextAsync(content, content.Status, null!); - - actual.Should().BeEquivalentTo(expected); - } - - [Fact] - public async Task Should_get_next_statuses_for_archived() - { - var content = new ContentEntity { Status = Status.Archived }; - - var expected = new[] - { - new StatusInfo(Status.Draft, StatusColors.Draft) - }; - - var actual = await sut.GetNextAsync(content, content.Status, null!); - - actual.Should().BeEquivalentTo(expected); - } - - [Fact] - public async Task Should_get_next_statuses_for_published() - { - var content = new ContentEntity { Status = Status.Published }; - - var expected = new[] - { - new StatusInfo(Status.Archived, StatusColors.Archived), - new StatusInfo(Status.Draft, StatusColors.Draft) - }; - - var actual = await sut.GetNextAsync(content, content.Status, null!); - - actual.Should().BeEquivalentTo(expected); - } - - [Fact] - public async Task Should_return_all_statuses() - { - var expected = new[] - { - new StatusInfo(Status.Archived, StatusColors.Archived), - new StatusInfo(Status.Draft, StatusColors.Draft), - new StatusInfo(Status.Published, StatusColors.Published) - }; - - var actual = await sut.GetAllAsync(null!); - - actual.Should().BeEquivalentTo(expected); - } - - [Fact] - public async Task Should_not_validate_when_not_publishing() - { - var actual = await sut.ShouldValidateAsync(null!, Status.Draft); - - Assert.False(actual); - } - - [Fact] - public async Task Should_not_validate_when_publishing_but_not_enabled() - { - var actual = await sut.ShouldValidateAsync(CreateSchema(false), Status.Published); - - Assert.False(actual); - } - - [Fact] - public async Task Should_validate_when_publishing_and_enabled() - { - var actual = await sut.ShouldValidateAsync(CreateSchema(true), Status.Published); - - Assert.True(actual); - } - - private static ISchemaEntity CreateSchema(bool validateOnPublish) - { - var schema = new Schema("my-schema", new SchemaProperties - { - ValidateOnPublish = validateOnPublish - }); - - return Mocks.Schema(NamedId.Of(DomainId.NewGuid(), "my-app"), DomainId.NewGuid(), schema); - } -} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentCommandMiddlewareTests.cs index c7c89fe77..813456647 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentCommandMiddlewareTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Queries; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -13,7 +14,7 @@ using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Contents.DomainObject; -public sealed class ContentCommandMiddlewareTests : HandlerTestBase +public sealed class ContentCommandMiddlewareTests : HandlerTestBase { private readonly IDomainObjectCache domainObjectCache = A.Fake(); private readonly IDomainObjectFactory domainObjectFactory = A.Fake(); @@ -40,35 +41,35 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase contentEnricher.EnrichAsync(A._, A._, ApiContext, A._)) + A.CallTo(() => contentEnricher.EnrichAsync(A._, A._, ApiContext, A._)) .MustNotHaveHappened(); } [Fact] public async Task Should_not_invoke_enricher_if_already_enriched() { - var actual = new ContentEntity(); + var actual = CreateContent(); var context = await HandleAsync(new CreateContent(), actual); - Assert.Same(actual, context.Result()); + Assert.Same(actual, context.Result()); - A.CallTo(() => contentEnricher.EnrichAsync(A._, A._, ApiContext, A._)) + A.CallTo(() => contentEnricher.EnrichAsync(A._, A._, ApiContext, A._)) .MustNotHaveHappened(); } [Fact] - public async Task Should_enrich_content_actual() + public async Task Should_enrich_content_result() { - var actual = A.Fake(); + var actual = new Content(); - var enriched = new ContentEntity(); + var enriched = CreateContent(); A.CallTo(() => contentEnricher.EnrichAsync(actual, true, ApiContext, CancellationToken)) .Returns(enriched); @@ -77,7 +78,24 @@ public sealed class ContentCommandMiddlewareTests : HandlerTestBase()); + Assert.Same(enriched, context.Result()); + } + + [Fact] + public async Task Should_enrich_write_content_result() + { + var actual = CreateWriteContent(); + + var enriched = CreateContent(); + + A.CallTo(() => contentEnricher.EnrichAsync(A._, true, ApiContext, CancellationToken)) + .Returns(enriched); + + var context = + await HandleAsync(new CreateContent(), + actual); + + Assert.Same(enriched, context.Result()); } private Task HandleAsync(ContentCommand command, object actual) diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs index ccbb4b34b..5d0d21ca1 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentDomainObjectTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Security.Claims; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NodaTime; @@ -24,10 +25,10 @@ using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Contents.DomainObject; -public class ContentDomainObjectTests : HandlerTestBase +public class ContentDomainObjectTests : HandlerTestBase { private readonly DomainId contentId = DomainId.NewGuid(); - private readonly IContentWorkflow contentWorkflow = A.Fake(x => x.Wrapping(new DefaultContentWorkflow())); + private readonly IContentWorkflow contentWorkflow = A.Fake(); private readonly IContentRepository contentRepository = A.Fake(); private readonly IScriptEngine scriptEngine = A.Fake(); @@ -67,28 +68,41 @@ public class ContentDomainObjectTests : HandlerTestBase Schema.SchemaDef) - .Returns(schemaDef); + Schema = Schema + .AddNumber(1, "my-field1", Partitioning.Invariant, + new NumberFieldProperties { IsRequired = true }) + .AddNumber(2, "my-field2", Partitioning.Invariant, + new NumberFieldProperties { IsRequired = false }) + .SetScripts(new SchemaScripts + { + Change = "", + Create = "", + Delete = "", + Update = "" + }) + .Publish(); A.CallTo(() => scriptEngine.TransformAsync(A._, A._, ScriptOptions(), CancellationToken)) .ReturnsLazily(x => Task.FromResult(x.GetArgument(0)!.Data!)); + A.CallTo(() => contentWorkflow.GetInitialStatusAsync(Schema)) + .Returns(Status.Draft); + + A.CallTo(() => contentWorkflow.CanMoveToAsync(A._, Status.Draft, Status.Published, A._)) + .Returns(true); + + A.CallTo(() => contentWorkflow.CanMoveToAsync(A._, Status.Draft, Status.Archived, A._)) + .Returns(true); + + A.CallTo(() => contentWorkflow.CanMoveToAsync(A._, Status.Published, Status.Draft, A._)) + .Returns(true); + + A.CallTo(() => contentWorkflow.CanMoveToAsync(A._, Status.Published, Status.Archived, A._)) + .Returns(true); + + A.CallTo(() => contentWorkflow.CanUpdateAsync(A._, A._, A._)) + .Returns(true); + patched = patch.MergeInto(data); var log = A.Fake>(); @@ -175,7 +189,7 @@ public class ContentDomainObjectTests : HandlerTestBase contentWorkflow.CanMoveToAsync(A._, Status.Draft, Status.Archived, ApiContext.UserPrincipal)) + A.CallTo(() => contentWorkflow.CanMoveToAsync(A._, Status.Draft, Status.Archived, ApiContext.UserPrincipal)) .Returns(true); var actual = await PublishAsync(command); @@ -757,7 +771,7 @@ public class ContentDomainObjectTests : HandlerTestBase contentWorkflow.CanMoveToAsync(A._, Status.Draft, Status.Published, ApiContext.UserPrincipal)) + A.CallTo(() => contentWorkflow.CanMoveToAsync(A._, Status.Draft, Status.Published, ApiContext.UserPrincipal)) .Returns(false); var actual = await PublishAsync(command); @@ -783,7 +797,7 @@ public class ContentDomainObjectTests : HandlerTestBase contentRepository.HasReferrersAsync(AppId.Id, contentId, SearchScope.All, A._)) + A.CallTo(() => contentRepository.HasReferrersAsync(App, contentId, SearchScope.All, A._)) .Returns(true); await Assert.ThrowsAsync(() => PublishAsync(command)); @@ -797,7 +811,7 @@ public class ContentDomainObjectTests : HandlerTestBase contentRepository.HasReferrersAsync(AppId.Id, contentId, SearchScope.Published, A._)) + A.CallTo(() => contentRepository.HasReferrersAsync(App, contentId, SearchScope.Published, A._)) .Returns(true); await PublishAsync(command); @@ -882,7 +896,7 @@ public class ContentDomainObjectTests : HandlerTestBase contentRepository.HasReferrersAsync(AppId.Id, contentId, SearchScope.All, A._)) + A.CallTo(() => contentRepository.HasReferrersAsync(App, contentId, SearchScope.All, A._)) .Returns(true); await Assert.ThrowsAsync(() => PublishAsync(command)); @@ -895,7 +909,7 @@ public class ContentDomainObjectTests : HandlerTestBase contentRepository.HasReferrersAsync(AppId.Id, contentId, SearchScope.All, A._)) + A.CallTo(() => contentRepository.HasReferrersAsync(App, contentId, SearchScope.All, A._)) .Returns(true); await PublishAsync(command); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentsBulkUpdateCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentsBulkUpdateCommandMiddlewareTests.cs index 43c1de2bb..56263f34d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentsBulkUpdateCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/ContentsBulkUpdateCommandMiddlewareTests.cs @@ -75,7 +75,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests : GivenContext { var requestContext = SetupContext(PermissionIds.AppContentsUpdateOwn); - var (id, _, query) = CreateTestData(true); + var (_, _, query) = CreateTestData(true); A.CallTo(() => contentQuery.QueryAsync( A.That.Matches(x => @@ -84,7 +84,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests : GivenContext x.NoTotal()), schemaId.Id.ToString(), A.That.Matches(x => x.JsonQuery == query), A._)) - .Returns(ResultList.CreateFrom(2, CreateContent(id), CreateContent(id))); + .Returns(ResultList.CreateFrom(2, CreateContent(), CreateContent())); var command = BulkCommand(BulkUpdateContentType.ChangeStatus, new BulkUpdateJob { Query = query }); @@ -112,7 +112,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests : GivenContext schemaId.Id.ToString(), A.That.Matches(x => x.JsonQuery == query), A._)) - .Returns(ResultList.CreateFrom(1, CreateContent(id))); + .Returns(ResultList.CreateFrom(1, CreateContent().WithId(id))); var command = BulkCommand(BulkUpdateContentType.Upsert, new BulkUpdateJob { Query = query, Data = data }); @@ -131,10 +131,10 @@ public class ContentsBulkUpdateCommandMiddlewareTests : GivenContext { var requestContext = SetupContext(PermissionIds.AppContentsUpsert); - var (_, data, query) = CreateTestData(true); + var (id, data, query) = CreateTestData(true); - var id1 = DomainId.NewGuid(); - var id2 = DomainId.NewGuid(); + var content1 = CreateContent(); + var content2 = CreateContent(); A.CallTo(() => contentQuery.QueryAsync( A.That.Matches(x => @@ -144,9 +144,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests : GivenContext schemaId.Id.ToString(), A.That.Matches(x => x.JsonQuery == query), A._)) - .Returns(ResultList.CreateFrom(2, - CreateContent(id1), - CreateContent(id2))); + .Returns(ResultList.CreateFrom(2, content1, content2)); var command = BulkCommand(BulkUpdateContentType.Upsert, new BulkUpdateJob { Query = query, Data = data }); @@ -155,15 +153,15 @@ public class ContentsBulkUpdateCommandMiddlewareTests : GivenContext var actual = await PublishAsync(command); Assert.Equal(2, actual.Count); - Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id1 && x.Exception == null); - Assert.Single(actual, x => x.JobIndex == 0 && x.Id == id2 && x.Exception == null); + Assert.Single(actual, x => x.JobIndex == 0 && x.Id == content1.Id && x.Exception == null); + Assert.Single(actual, x => x.JobIndex == 0 && x.Id == content2.Id && x.Exception == null); A.CallTo(() => commandBus.PublishAsync( - A.That.Matches(x => x.Data == data && x.ContentId == id1), A._)) + A.That.Matches(x => x.Data == data && x.ContentId == content1.Id), A._)) .MustHaveHappenedOnceExactly(); A.CallTo(() => commandBus.PublishAsync( - A.That.Matches(x => x.Data == data && x.ContentId == id2), A._)) + A.That.Matches(x => x.Data == data && x.ContentId == content2.Id), A._)) .MustHaveHappenedOnceExactly(); } @@ -187,7 +185,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests : GivenContext } [Fact] - public async Task Should_upsert_content_with_random_id_if_query_returns_no_actual() + public async Task Should_upsert_content_with_random_id_if_query_returns_no_result() { SetupContext(PermissionIds.AppContentsUpsert); @@ -490,7 +488,7 @@ public class ContentsBulkUpdateCommandMiddlewareTests : GivenContext SetupContext(PermissionIds.AppContentsDeleteOwn); A.CallTo(() => contentQuery.GetSchemaOrThrowAsync(A._, schemaCustomId.Name, A._)) - .Returns(Mocks.Schema(AppId, schemaCustomId)); + .Returns(Schema.WithId(schemaCustomId)); var (id, _, _) = CreateTestData(false); @@ -578,9 +576,4 @@ public class ContentsBulkUpdateCommandMiddlewareTests : GivenContext return (DomainId.NewGuid(), data, query); } - - private static IEnrichedContentEntity CreateContent(DomainId id) - { - return new ContentEntity { Id = id }; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs index 52f936410..a694473c9 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DomainObject/Guards/GuardContentTests.cs @@ -12,7 +12,6 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Repositories; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Validation; @@ -26,28 +25,28 @@ public class GuardContentTests : GivenContext, IClassFixture(); private readonly IContentRepository contentRepository = A.Fake(); - private readonly ISchemaEntity normalSchema; - private readonly ISchemaEntity normalUnpublishedSchema; - private readonly ISchemaEntity singletonSchema; - private readonly ISchemaEntity singletonUnpublishedSchema; - private readonly ISchemaEntity componentSchema; + private readonly Schema normalSchema; + private readonly Schema normalUnpublishedSchema; + private readonly Schema singletonSchema; + private readonly Schema singletonUnpublishedSchema; + private readonly Schema componentSchema; public GuardContentTests() { normalUnpublishedSchema = - Mocks.Schema(AppId, SchemaId.Id, new Schema(SchemaId.Name)); + Schema.Unpublish(); normalSchema = - Mocks.Schema(AppId, SchemaId.Id, new Schema(SchemaId.Name).Publish()); + normalUnpublishedSchema.Publish(); singletonUnpublishedSchema = - Mocks.Schema(AppId, SchemaId.Id, new Schema(SchemaId.Name, type: SchemaType.Singleton)); + normalUnpublishedSchema with { Type = SchemaType.Singleton }; singletonSchema = - Mocks.Schema(AppId, SchemaId.Id, new Schema(SchemaId.Name, type: SchemaType.Singleton).Publish()); + normalSchema with { Type = SchemaType.Singleton }; componentSchema = - Mocks.Schema(AppId, SchemaId.Id, new Schema(SchemaId.Name, type: SchemaType.Component).Publish()); + normalSchema with { Type = SchemaType.Component }; } [Fact] @@ -226,7 +225,7 @@ public class GuardContentTests : GivenContext, IClassFixture contentWorkflow.CanUpdateAsync(operation.Snapshot, operation.Snapshot.EditingStatus(), operation.User)) + A.CallTo(() => contentWorkflow.CanUpdateAsync(operation.Snapshot.ToContent(), operation.Snapshot.EditingStatus, operation.User)) .Returns(false); await Assert.ThrowsAsync(() => operation.CheckUpdateAsync()); @@ -237,7 +236,7 @@ public class GuardContentTests : GivenContext, IClassFixture contentWorkflow.CanUpdateAsync(operation.Snapshot, operation.Snapshot.EditingStatus(), operation.User)) + A.CallTo(() => contentWorkflow.CanUpdateAsync(operation.Snapshot.ToContent(), operation.Snapshot.EditingStatus, operation.User)) .Returns(true); await operation.CheckUpdateAsync(); @@ -248,7 +247,7 @@ public class GuardContentTests : GivenContext, IClassFixture contentWorkflow.GetInfoAsync((ContentEntity)operation.Snapshot, Status.Archived)) + A.CallTo(() => contentWorkflow.GetInfoAsync(operation.Snapshot.ToContent(), Status.Archived)) .Returns(ValueTask.FromResult(null)); await Assert.ThrowsAsync(() => operation.CheckStatusAsync(Status.Archived)); @@ -259,7 +258,7 @@ public class GuardContentTests : GivenContext, IClassFixture contentWorkflow.GetInfoAsync((ContentEntity)operation.Snapshot, Status.Archived)) + A.CallTo(() => contentWorkflow.GetInfoAsync(operation.Snapshot.ToContent(), Status.Archived)) .Returns(new StatusInfo(Status.Archived, StatusColors.Archived)); await operation.CheckStatusAsync(Status.Archived); @@ -272,7 +271,7 @@ public class GuardContentTests : GivenContext, IClassFixture contentWorkflow.GetInfoAsync((ContentEntity)operation.Snapshot, Status.Archived)) + A.CallTo(() => contentWorkflow.GetInfoAsync(operation.Snapshot.ToContent(), Status.Archived)) .MustNotHaveHappened(); } @@ -281,7 +280,7 @@ public class GuardContentTests : GivenContext, IClassFixture contentWorkflow.CanMoveToAsync((ContentEntity)operation.Snapshot, Status.Draft, Status.Archived, operation.User)) + A.CallTo(() => contentWorkflow.CanMoveToAsync(operation.Snapshot.ToContent(), operation.Snapshot.EditingStatus, Status.Archived, operation.User)) .Returns(false); await Assert.ThrowsAsync(() => operation.CheckTransitionAsync(Status.Archived)); @@ -292,7 +291,7 @@ public class GuardContentTests : GivenContext, IClassFixture contentWorkflow.CanMoveToAsync((ContentEntity)operation.Snapshot, Status.Draft, Status.Archived, operation.User)) + A.CallTo(() => contentWorkflow.CanMoveToAsync(operation.Snapshot.ToContent(), operation.Snapshot.EditingStatus, Status.Archived, operation.User)) .Returns(true); await operation.CheckTransitionAsync(Status.Archived); @@ -305,7 +304,7 @@ public class GuardContentTests : GivenContext, IClassFixture contentWorkflow.CanMoveToAsync((ContentEntity)operation.Snapshot, A._, A._, A._)) + A.CallTo(() => contentWorkflow.CanMoveToAsync(operation.Snapshot.ToContent(), operation.Snapshot.EditingStatus, A._, A._)) .MustNotHaveHappened(); } @@ -315,9 +314,7 @@ public class GuardContentTests : GivenContext, IClassFixture(() => operation.MustHavePermission(PermissionIds.AppContentsDelete)); } @@ -357,7 +348,7 @@ public class GuardContentTests : GivenContext, IClassFixture contentRepository.HasReferrersAsync(AppId.Id, operation.CommandId, SearchScope.All, CancellationToken)) + A.CallTo(() => contentRepository.HasReferrersAsync(App, operation.CommandId, SearchScope.All, CancellationToken)) .Returns(true); await Assert.ThrowsAsync(() => operation.CheckReferrersAsync(CancellationToken)); @@ -368,18 +359,18 @@ public class GuardContentTests : GivenContext, IClassFixture contentRepository.HasReferrersAsync(AppId.Id, operation.CommandId, SearchScope.All, CancellationToken)) + A.CallTo(() => contentRepository.HasReferrersAsync(App, operation.CommandId, SearchScope.All, CancellationToken)) .Returns(true); await Assert.ThrowsAsync(() => operation.CheckReferrersAsync(CancellationToken)); } - private ContentOperation Operation(ContentEntity content, ISchemaEntity operationSchema) + private ContentOperation Operation(WriteContent content, Schema operationSchema) { return Operation(content, operationSchema, Mocks.FrontendUser()); } - private ContentOperation Operation(ContentEntity content, ISchemaEntity operationSchema, ClaimsPrincipal? currentUser) + private ContentOperation Operation(WriteContent content, Schema operationSchema, ClaimsPrincipal? currentUser) { var serviceProvider = new ServiceCollection() @@ -396,24 +387,29 @@ public class GuardContentTests : GivenContext, IClassFixture simpleSchemaId = NamedId.Of(DomainId.NewGuid(), "my-simple-schema"); + private readonly DomainId simpleSchemaId = DomainId.NewGuid(); private readonly DynamicContentWorkflow sut; private readonly Workflow workflow = new Workflow( @@ -73,12 +72,12 @@ public class DynamicContentWorkflowTests : GivenContext }.ToReadonlyDictionary(), StatusColors.Published) }.ToReadonlyDictionary(), - ReadonlyList.Create(simpleSchemaId.Id)); + ReadonlyList.Create(simpleSchemaId)); - var workflows = Workflows.Empty.Set(workflow).Set(DomainId.NewGuid(), simpleWorkflow); - - A.CallTo(() => App.Workflows) - .Returns(workflows); + App = App with + { + Workflows = Workflows.Empty.Set(workflow).Set(DomainId.NewGuid(), simpleWorkflow) + }; var scriptEngine = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())), Options.Create(new JintScriptOptions @@ -139,7 +138,7 @@ public class DynamicContentWorkflowTests : GivenContext { var content = CreateContent(Status.Draft, 2); - var actual = await sut.CanMoveToAsync(Schema, content.Status, Status.Published, content.Data, Mocks.FrontendUser(Role.Editor)); + var actual = await sut.CanMoveToAsync(content, content.Status, Status.Published, Mocks.FrontendUser(Role.Editor)); Assert.True(actual); } @@ -355,7 +354,7 @@ public class DynamicContentWorkflowTests : GivenContext new StatusInfo(Status.Published, StatusColors.Published) }; - var actual = await sut.GetAllAsync(Mocks.Schema(AppId, simpleSchemaId)); + var actual = await sut.GetAllAsync(Schema.WithId(simpleSchemaId, "simple-schema")); actual.Should().BeEquivalentTo(expected); } @@ -363,8 +362,10 @@ public class DynamicContentWorkflowTests : GivenContext [Fact] public async Task Should_return_all_statuses_for_default_workflow_if_no_workflow_configured() { - A.CallTo(() => App.Workflows) - .Returns(Workflows.Empty); + App = App with + { + Workflows = Workflows.Empty + }; var expected = new[] { @@ -373,7 +374,7 @@ public class DynamicContentWorkflowTests : GivenContext new StatusInfo(Status.Published, StatusColors.Published) }; - var actual = await sut.GetAllAsync(Mocks.Schema(AppId, simpleSchemaId)); + var actual = await sut.GetAllAsync(Schema); actual.Should().BeEquivalentTo(expected); } @@ -389,7 +390,12 @@ public class DynamicContentWorkflowTests : GivenContext [Fact] public async Task Should_not_validate_when_publishing_but_not_enabled() { - var actual = await sut.ShouldValidateAsync(CreateSchema(false), Status.Published); + var schema = Schema with + { + Properties = new SchemaProperties { ValidateOnPublish = false } + }; + + var actual = await sut.ShouldValidateAsync(schema, Status.Published); Assert.False(actual); } @@ -397,7 +403,12 @@ public class DynamicContentWorkflowTests : GivenContext [Fact] public async Task Should_validate_when_publishing_and_enabled() { - var actual = await sut.ShouldValidateAsync(CreateSchema(true), Status.Published); + var schema = Schema with + { + Properties = new SchemaProperties { ValidateOnPublish = true } + }; + + var actual = await sut.ShouldValidateAsync(schema, Status.Published); Assert.True(actual); } @@ -410,34 +421,27 @@ public class DynamicContentWorkflowTests : GivenContext Assert.True(actual); } - private ISchemaEntity CreateSchema(bool validateOnPublish) - { - var schema = new Schema("my-schema", new SchemaProperties - { - ValidateOnPublish = validateOnPublish - }); - - return Mocks.Schema(AppId, simpleSchemaId.Id, schema); - } - - private ContentEntity CreateContent(Status status, int value, bool simple = false) + private EnrichedContent CreateContent(Status status, int value, bool simple = false) { - var content = new ContentEntity { AppId = AppId, Status = status }; + var content = CreateContent(); if (simple) { - content.SchemaId = simpleSchemaId; - } - else - { - content.SchemaId = SchemaId; + content = content with + { + SchemaId = NamedId.Of(simpleSchemaId, "my-simple-schema") + }; } - content.Data = - new ContentData() - .AddField("field", - new ContentFieldData() - .AddInvariant(value)); + content = content with + { + Status = status, + Data = + new ContentData() + .AddField("field", + new ContentFieldData() + .AddInvariant(value)) + }; return content; } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs index 9606e6c33..9276d6889 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs @@ -9,7 +9,6 @@ using GraphQL.Types; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; -using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using GraphQLSchema = GraphQL.Types.Schema; using Schema = Squidex.Domain.Apps.Core.Schemas.Schema; @@ -18,6 +17,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL; public class GraphQLIntrospectionTests : GraphQLTestBase { + private Schema schema = new Schema { AppId = TestApp.DefaultId, Id = DomainId.NewGuid(), Name = "my-schema" }; + [Fact] public async Task Should_introspect() { @@ -126,12 +127,9 @@ public class GraphQLIntrospectionTests : GraphQLTestBase } [Fact] - public async Task Should_build_graphql_schema_without_schema() + public async Task Should_build_graphql_schema_without_published_schema() { - var schema = - Mocks.Schema(TestApp.DefaultId, - DomainId.NewGuid(), - new Schema("content").Publish()); + schema = schema.Unpublish(); var model = await CreateSut(schema).GetSchemaAsync(TestApp.Default); @@ -141,11 +139,7 @@ public class GraphQLIntrospectionTests : GraphQLTestBase [Fact] public async Task Should_build_graphql_schema_on_schema_with_ui_fields_only() { - var schema = - Mocks.Schema(TestApp.DefaultId, - DomainId.NewGuid(), - new Schema("content").Publish() - .AddUI(1, "ui", Partitioning.Invariant)); + schema = schema.Publish().AddUI(1, "ui", Partitioning.Invariant); var model = await CreateSut(schema).GetSchemaAsync(TestApp.Default); @@ -155,11 +149,7 @@ public class GraphQLIntrospectionTests : GraphQLTestBase [Fact] public async Task Should_build_graphql_schema_on_schema_with_invalid_fields_only() { - var schema = - Mocks.Schema(TestApp.DefaultId, - DomainId.NewGuid(), - new Schema("content").Publish() - .AddComponent(1, "component", Partitioning.Invariant)); + schema = schema.Publish().AddComponent(1, "component", Partitioning.Invariant); var model = await CreateSut(schema).GetSchemaAsync(TestApp.Default); @@ -169,11 +159,7 @@ public class GraphQLIntrospectionTests : GraphQLTestBase [Fact] public async Task Should_build_graphql_schema_on_unpublished_schema() { - var schema = - Mocks.Schema(TestApp.DefaultId, - DomainId.NewGuid(), - new Schema("content") - .AddString(1, "myField", Partitioning.Invariant)); + schema = schema.Publish().AddString(1, "myField", Partitioning.Invariant); var model = await CreateSut(schema).GetSchemaAsync(TestApp.Default); @@ -183,11 +169,8 @@ public class GraphQLIntrospectionTests : GraphQLTestBase [Fact] public async Task Should_build_graphql_schema_on_reserved_schema_name() { - var schema = - Mocks.Schema(TestApp.DefaultId, - DomainId.NewGuid(), - new Schema("content").Publish() - .AddString(1, "myField", Partitioning.Invariant)); + schema = schema with { Name = "Content" }; + schema = schema.Publish().AddString(1, "myField", Partitioning.Invariant); var graphQLSchema = await CreateSut(schema).GetSchemaAsync(TestApp.Default); @@ -197,11 +180,7 @@ public class GraphQLIntrospectionTests : GraphQLTestBase [Fact] public async Task Should_build_graphql_schema_with_reserved_field_name() { - var schema = - Mocks.Schema(TestApp.DefaultId, - DomainId.NewGuid(), - new Schema("my-schema").Publish() - .AddString(1, "content", Partitioning.Invariant)); + schema = schema.Publish().AddString(1, "content", Partitioning.Invariant); var graphQLSchema = await CreateSut(schema).GetSchemaAsync(TestApp.Default); @@ -213,11 +192,7 @@ public class GraphQLIntrospectionTests : GraphQLTestBase [Fact] public async Task Should_build_graphql_schema_on_invalid_field_name() { - var schema = - Mocks.Schema(TestApp.DefaultId, - DomainId.NewGuid(), - new Schema("my-schema").Publish() - .AddString(1, "2-field", Partitioning.Invariant)); + schema = schema.Publish().AddString(1, "2-field", Partitioning.Invariant); var graphQLSchema = await CreateSut(schema).GetSchemaAsync(TestApp.Default); @@ -229,12 +204,10 @@ public class GraphQLIntrospectionTests : GraphQLTestBase [Fact] public async Task Should_build_graphql_schema_on_duplicate_field_name() { - var schema = - Mocks.Schema(TestApp.DefaultId, - DomainId.NewGuid(), - new Schema("my-schema").Publish() - .AddString(1, "my-field", Partitioning.Invariant) - .AddString(2, "my_field", Partitioning.Invariant)); + schema = schema + .Publish() + .AddString(1, "my-field", Partitioning.Invariant) + .AddString(2, "my_field", Partitioning.Invariant); var graphQLSchema = await CreateSut(schema).GetSchemaAsync(TestApp.Default); @@ -247,13 +220,11 @@ public class GraphQLIntrospectionTests : GraphQLTestBase [Fact] public async Task Should_not_build_grapqhl_schema_on_invalid_component() { - var schema = - Mocks.Schema(TestApp.DefaultId, - DomainId.NewGuid(), - new Schema("my-schema").Publish() - .AddComponent(1, "my-component", Partitioning.Invariant, - new ComponentFieldProperties()) - .AddString(2, "my-string", Partitioning.Invariant)); + schema = schema + .Publish() + .AddComponent(1, "my-component", Partitioning.Invariant, + new ComponentFieldProperties()) + .AddString(2, "my-string", Partitioning.Invariant); var graphQLSchema = await CreateSut(schema).GetSchemaAsync(TestApp.Default); @@ -265,13 +236,11 @@ public class GraphQLIntrospectionTests : GraphQLTestBase [Fact] public async Task Should_not_build_grapqhl_schema_on_invalid_components() { - var schema = - Mocks.Schema(TestApp.DefaultId, - DomainId.NewGuid(), - new Schema("my-schema").Publish() - .AddComponents(1, "my-components", Partitioning.Invariant, - new ComponentsFieldProperties()) - .AddString(2, "my-string", Partitioning.Invariant)); + schema = schema + .Publish() + .AddComponents(1, "my-components", Partitioning.Invariant, + new ComponentsFieldProperties()) + .AddString(2, "my-string", Partitioning.Invariant); var graphQLSchema = await CreateSut(schema).GetSchemaAsync(TestApp.Default); @@ -283,13 +252,11 @@ public class GraphQLIntrospectionTests : GraphQLTestBase [Fact] public async Task Should_not_build_grapqhl_schema_on_invalid_references() { - var schema = - Mocks.Schema(TestApp.DefaultId, - DomainId.NewGuid(), - new Schema("my-schema").Publish() - .AddReferences(1, "my-references", Partitioning.Invariant, - new ReferencesFieldProperties { SchemaId = DomainId.NewGuid() }) - .AddString(2, "my-string", Partitioning.Invariant)); + schema = schema + .Publish() + .AddReferences(1, "my-references", Partitioning.Invariant, + new ReferencesFieldProperties { SchemaId = DomainId.NewGuid() }) + .AddString(2, "my-string", Partitioning.Invariant); var graphQLSchema = await CreateSut(schema).GetSchemaAsync(TestApp.Default); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs index c68ea30ae..febfbcaad 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs @@ -7,8 +7,8 @@ using NodaTime.Text; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.Commands; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Shared; @@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL; public class GraphQLMutationTests : GraphQLTestBase { private readonly DomainId contentId = DomainId.NewGuid(); - private readonly IEnrichedContentEntity content; + private readonly EnrichedContent content; private readonly CommandContext commandContext = new CommandContext(new PatchContent(), A.Dummy()); public GraphQLMutationTests() diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs index 17c3901c4..17b8c83ec 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs @@ -5,9 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Assets; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; @@ -274,7 +274,7 @@ public class GraphQLQueriesTests : GraphQLTestBase A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null, A.That.HasIdsWithoutTotal(assetId), A._)) - .Returns(ResultList.CreateFrom(1)); + .Returns(ResultList.CreateFrom(1)); var actual = await ExecuteAsync(new TestQuery { @@ -471,7 +471,7 @@ public class GraphQLQueriesTests : GraphQLTestBase A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), A.That.HasIdsWithoutTotal(contentId), A._)) - .Returns(ResultList.CreateFrom(1)); + .Returns(ResultList.CreateFrom(1)); var actual = await ExecuteAsync(new TestQuery { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index 508bd5553..b1c402935 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -15,6 +15,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.ExtractReferenceIds; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Contents.TestData; @@ -108,7 +109,7 @@ public abstract class GraphQLTestBase : IClassFixture return actual; } - protected CachingGraphQLResolver CreateSut(params ISchemaEntity[] schemas) + protected CachingGraphQLResolver CreateSut(params Schema[] schemas) { var appProvider = A.Fake(); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestApp.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestApp.cs index 472ead8e7..cf4c33392 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestApp.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestApp.cs @@ -5,8 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.TestHelpers; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL; @@ -15,10 +14,15 @@ public static class TestApp { public static readonly NamedId DefaultId = NamedId.Of(DomainId.NewGuid(), "my-app"); - public static readonly IAppEntity Default; - - static TestApp() - { - Default = Mocks.App(DefaultId, Language.DE, Language.GermanGermany); - } + public static readonly App Default = + new App + { + Id = DefaultId.Id, + Created = default, + CreatedBy = RefToken.User("42"), + Languages = LanguagesConfig.English.Set(Language.GermanGermany), + LastModified = default, + LastModifiedBy = RefToken.User("42"), + Name = DefaultId.Name, + }; } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestAsset.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestAsset.cs index 0f0aa8350..384b42b30 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestAsset.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestAsset.cs @@ -52,11 +52,11 @@ public static class TestAsset metadata slug"; - public static IEnrichedAssetEntity Create(DomainId id) + public static EnrichedAsset Create(DomainId id) { var now = SystemClock.Instance.GetCurrentInstant(); - var asset = new AssetEntity + var asset = new EnrichedAsset { Id = id, AppId = TestApp.DefaultId, @@ -88,7 +88,7 @@ public static class TestAsset return asset; } - public static object Response(IEnrichedAssetEntity asset) + public static object Response(EnrichedAsset asset) { return new { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs index c35923490..47d8edeeb 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs @@ -7,7 +7,7 @@ using NodaTime; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; @@ -211,7 +211,7 @@ public static class TestContent } }"; - public static IEnrichedContentEntity Create(DomainId id, DomainId refId = default, DomainId assetId = default, ContentData? data = null) + public static EnrichedContent Create(DomainId id, DomainId refId = default, DomainId assetId = default, ContentData? data = null) { var now = SystemClock.Instance.GetCurrentInstant(); @@ -261,7 +261,7 @@ public static class TestContent .AddInvariant( JsonValue.Object() .Add(Component.Discriminator, TestSchemas.Component.Id) - .Add(Component.Descriptor, TestSchemas.Component.SchemaDef.Name) + .Add(Component.Descriptor, TestSchemas.Component.Name) .Add("component-field", "Component1"))) .AddField("my-components", new ContentFieldData() @@ -269,15 +269,15 @@ public static class TestContent JsonValue.Array( JsonValue.Object() .Add(Component.Discriminator, TestSchemas.Reference1.Id) - .Add(Component.Descriptor, TestSchemas.Reference1.SchemaDef.Name) + .Add(Component.Descriptor, TestSchemas.Reference1.Name) .Add("reference1-field", "Component1"), JsonValue.Object() .Add(Component.Discriminator, TestSchemas.Reference2.Id) - .Add(Component.Descriptor, TestSchemas.Reference2.SchemaDef.Name) + .Add(Component.Descriptor, TestSchemas.Reference2.Name) .Add("reference2-field", "Component2"), JsonValue.Object() .Add(Component.Discriminator, TestSchemas.Component.Id) - .Add(Component.Descriptor, TestSchemas.Component.SchemaDef.Name) + .Add(Component.Descriptor, TestSchemas.Component.Name) .Add("component-field", "Component3")))) .AddField("my-json", new ContentFieldData() @@ -325,7 +325,7 @@ public static class TestContent .AddInvariant(JsonValue.Create($"assets:{assetId}, contents:{refId}"))); } - var content = new ContentEntity + var content = new EnrichedContent { Id = id, AppId = TestApp.DefaultId, @@ -346,7 +346,7 @@ public static class TestContent return content; } - public static IEnrichedContentEntity CreateSimple(NamedId schemaId, DomainId id, string field, string value) + public static EnrichedContent CreateSimple(NamedId schemaId, DomainId id, string field, string value) { var now = SystemClock.Instance.GetCurrentInstant(); @@ -356,7 +356,7 @@ public static class TestContent new ContentFieldData() .AddInvariant(value)); - var content = new ContentEntity + var content = new EnrichedContent { Id = id, AppId = TestApp.DefaultId, @@ -377,7 +377,7 @@ public static class TestContent return content; } - public static object Response(IEnrichedContentEntity content) + public static object Response(EnrichedContent content) { return new { @@ -409,7 +409,7 @@ public static class TestContent }; } - public static object FlatResponse(IEnrichedContentEntity content) + public static object FlatResponse(EnrichedContent content) { return new { @@ -441,7 +441,7 @@ public static class TestContent }; } - public static object Input(IContentEntity content, DomainId refId = default, DomainId assetId = default) + public static object Input(EnrichedContent content, DomainId refId = default, DomainId assetId = default) { var actual = new Dictionary { @@ -508,7 +508,7 @@ public static class TestContent iv = new Dictionary { ["schemaId"] = TestSchemas.Component.Id.ToString(), - ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["schemaName"] = TestSchemas.Component.Name, ["componentField"] = "Component1" } }, @@ -519,19 +519,19 @@ public static class TestContent new Dictionary { ["schemaId"] = TestSchemas.Reference1.Id.ToString(), - ["schemaName"] = TestSchemas.Reference1.SchemaDef.Name, + ["schemaName"] = TestSchemas.Reference1.Name, ["reference1Field"] = "Component1" }, new Dictionary { ["schemaId"] = TestSchemas.Reference2.Id.ToString(), - ["schemaName"] = TestSchemas.Reference2.SchemaDef.Name, + ["schemaName"] = TestSchemas.Reference2.Name, ["reference2Field"] = "Component2" }, new Dictionary { ["schemaId"] = TestSchemas.Component.Id.ToString(), - ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["schemaName"] = TestSchemas.Component.Name, ["componentField"] = "Component3" } } @@ -611,7 +611,7 @@ public static class TestContent return actual; } - private static object Data(IContentEntity content) + private static object Data(EnrichedContent content) { var actual = new Dictionary { @@ -681,7 +681,7 @@ public static class TestContent iv = new Dictionary { ["schemaId"] = TestSchemas.Component.Id.ToString(), - ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["schemaName"] = TestSchemas.Component.Name, ["component-field"] = "Component1" } }, @@ -690,7 +690,7 @@ public static class TestContent iv = new Dictionary { ["schemaId"] = TestSchemas.Component.Id.ToString(), - ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["schemaName"] = TestSchemas.Component.Name, ["componentField"] = "Component1" } }, @@ -701,19 +701,19 @@ public static class TestContent new Dictionary { ["schemaId"] = TestSchemas.Reference1.Id.ToString(), - ["schemaName"] = TestSchemas.Reference1.SchemaDef.Name, + ["schemaName"] = TestSchemas.Reference1.Name, ["reference1-field"] = "Component1" }, new Dictionary { ["schemaId"] = TestSchemas.Reference2.Id.ToString(), - ["schemaName"] = TestSchemas.Reference2.SchemaDef.Name, + ["schemaName"] = TestSchemas.Reference2.Name, ["reference2-field"] = "Component2" }, new Dictionary { ["schemaId"] = TestSchemas.Component.Id.ToString(), - ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["schemaName"] = TestSchemas.Component.Name, ["component-field"] = "Component3" } } @@ -726,21 +726,21 @@ public static class TestContent { ["__typename"] = "MyReference1Component", ["schemaId"] = TestSchemas.Reference1.Id.ToString(), - ["schemaName"] = TestSchemas.Reference1.SchemaDef.Name, + ["schemaName"] = TestSchemas.Reference1.Name, ["reference1Field"] = "Component1" }, new Dictionary { ["__typename"] = "MyReference2Component", ["schemaId"] = TestSchemas.Reference2.Id.ToString(), - ["schemaName"] = TestSchemas.Reference2.SchemaDef.Name, + ["schemaName"] = TestSchemas.Reference2.Name, ["reference2Field"] = "Component2" }, new Dictionary { ["__typename"] = "MyComponentComponent", ["schemaId"] = TestSchemas.Component.Id.ToString(), - ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["schemaName"] = TestSchemas.Component.Name, ["componentField"] = "Component3" } } @@ -782,7 +782,7 @@ public static class TestContent return actual; } - private static object FlatData(IContentEntity content) + private static object FlatData(EnrichedContent content) { var actual = new Dictionary { @@ -823,13 +823,13 @@ public static class TestContent ["myComponent__Dynamic"] = new Dictionary { ["schemaId"] = TestSchemas.Component.Id.ToString(), - ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["schemaName"] = TestSchemas.Component.Name, ["component-field"] = "Component1" }, ["myComponent"] = new Dictionary { ["schemaId"] = TestSchemas.Component.Id.ToString(), - ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["schemaName"] = TestSchemas.Component.Name, ["componentField"] = "Component1" }, ["myComponents__Dynamic"] = new[] @@ -837,19 +837,19 @@ public static class TestContent new Dictionary { ["schemaId"] = TestSchemas.Reference1.Id.ToString(), - ["schemaName"] = TestSchemas.Reference1.SchemaDef.Name, + ["schemaName"] = TestSchemas.Reference1.Name, ["reference1-field"] = "Component1" }, new Dictionary { ["schemaId"] = TestSchemas.Reference2.Id.ToString(), - ["schemaName"] = TestSchemas.Reference2.SchemaDef.Name, + ["schemaName"] = TestSchemas.Reference2.Name, ["reference2-field"] = "Component2" }, new Dictionary { ["schemaId"] = TestSchemas.Component.Id.ToString(), - ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["schemaName"] = TestSchemas.Component.Name, ["component-field"] = "Component3" } }, @@ -859,21 +859,21 @@ public static class TestContent { ["__typename"] = "MyReference1Component", ["schemaId"] = TestSchemas.Reference1.Id.ToString(), - ["schemaName"] = TestSchemas.Reference1.SchemaDef.Name, + ["schemaName"] = TestSchemas.Reference1.Name, ["reference1Field"] = "Component1" }, new Dictionary { ["__typename"] = "MyReference2Component", ["schemaId"] = TestSchemas.Reference2.Id.ToString(), - ["schemaName"] = TestSchemas.Reference2.SchemaDef.Name, + ["schemaName"] = TestSchemas.Reference2.Name, ["reference2Field"] = "Component2" }, new Dictionary { ["__typename"] = "MyComponentComponent", ["schemaId"] = TestSchemas.Component.Id.ToString(), - ["schemaName"] = TestSchemas.Component.SchemaDef.Name, + ["schemaName"] = TestSchemas.Component.Name, ["componentField"] = "Component3" } }, diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestQuery.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestQuery.cs index 3dbcad65f..6867837d0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestQuery.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestQuery.cs @@ -73,7 +73,7 @@ public sealed class TestQuery return new Context(Mocks.FrontendUser(), TestApp.Default); } - var permission = PermissionIds.ForApp(permissionId, TestApp.Default.Name, TestSchemas.Default.SchemaDef.Name).Id; + var permission = PermissionIds.ForApp(permissionId, TestApp.Default.Name, TestSchemas.Default.Name).Id; return new Context(Mocks.FrontendUser(permission: permission), TestApp.Default); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs index cb6501228..dd0725703 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs @@ -7,8 +7,6 @@ using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Collections; @@ -16,11 +14,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL; public static class TestSchemas { - public static readonly ISchemaEntity Default; - public static readonly ISchemaEntity Reference1; - public static readonly ISchemaEntity Reference2; - public static readonly ISchemaEntity Singleton; - public static readonly ISchemaEntity Component; + public static readonly Schema Default; + public static readonly Schema Reference1; + public static readonly Schema Reference2; + public static readonly Schema Singleton; + public static readonly Schema Component; static TestSchemas() { @@ -44,67 +42,103 @@ public static class TestSchemas nestedArray: [String] }"; - Component = Mocks.Schema(TestApp.DefaultId, DomainId.NewGuid(), - new Schema("my-component", type: SchemaType.Component) - .AddString(1, "component-field", Partitioning.Invariant)); + Component = + new Schema + { + AppId = TestApp.DefaultId, + Id = DomainId.NewGuid(), + IsPublished = true, + IsDeleted = false, + Name = "my-component", + Type = SchemaType.Default, + } + .AddString(1, "component-field", Partitioning.Invariant); - Reference1 = Mocks.Schema(TestApp.DefaultId, DomainId.NewGuid(), - new Schema("my-reference1") - .Publish() - .AddString(1, "reference1-field", Partitioning.Invariant)); + Reference1 = + new Schema + { + AppId = TestApp.DefaultId, + Id = DomainId.NewGuid(), + IsPublished = true, + IsDeleted = false, + Name = "my-reference1", + Type = SchemaType.Default, + } + .AddString(1, "reference1-field", Partitioning.Invariant); - Reference2 = Mocks.Schema(TestApp.DefaultId, DomainId.NewGuid(), - new Schema("my-reference2") - .Publish() - .AddString(1, "reference2-field", Partitioning.Invariant)); + Reference2 = + new Schema + { + AppId = TestApp.DefaultId, + Id = DomainId.NewGuid(), + IsPublished = true, + IsDeleted = false, + Name = "my-reference2", + Type = SchemaType.Default, + } + .AddString(1, "reference2-field", Partitioning.Invariant); - Singleton = Mocks.Schema(TestApp.DefaultId, DomainId.NewGuid(), - new Schema("my-singleton", type: SchemaType.Singleton) - .Publish() - .AddString(1, "singleton-field", Partitioning.Invariant)); + Singleton = + new Schema + { + AppId = TestApp.DefaultId, + Id = DomainId.NewGuid(), + IsPublished = true, + IsDeleted = false, + Name = "my-singleton", + Type = SchemaType.Singleton, + } + .AddString(1, "singleton-field", Partitioning.Invariant); - Default = Mocks.Schema(TestApp.DefaultId, DomainId.NewGuid(), - new Schema("my-schema") - .Publish() - .AddJson(1, "my-json", Partitioning.Invariant, - new JsonFieldProperties()) - .AddJson(2, "my-json2", Partitioning.Invariant, - new JsonFieldProperties { GraphQLSchema = jsonSchema }) - .AddString(3, "my-string", Partitioning.Invariant, - new StringFieldProperties()) - .AddString(4, "my-string-enum", Partitioning.Invariant, - new StringFieldProperties { AllowedValues = enums, CreateEnum = true }) - .AddString(5, "my-localized-string", Partitioning.Language, - new StringFieldProperties()) - .AddNumber(6, "my-number", Partitioning.Invariant, - new NumberFieldProperties()) - .AddAssets(7, "my-assets", Partitioning.Invariant, - new AssetsFieldProperties()) - .AddBoolean(8, "my-boolean", Partitioning.Invariant, + Default = + new Schema + { + AppId = TestApp.DefaultId, + Id = DomainId.NewGuid(), + IsPublished = true, + IsDeleted = false, + Name = "my-schema", + Type = SchemaType.Default, + } + .AddJson(1, "my-json", Partitioning.Invariant, + new JsonFieldProperties()) + .AddJson(2, "my-json2", Partitioning.Invariant, + new JsonFieldProperties { GraphQLSchema = jsonSchema }) + .AddString(3, "my-string", Partitioning.Invariant, + new StringFieldProperties()) + .AddString(4, "my-string-enum", Partitioning.Invariant, + new StringFieldProperties { AllowedValues = enums, CreateEnum = true }) + .AddString(5, "my-localized-string", Partitioning.Language, + new StringFieldProperties()) + .AddNumber(6, "my-number", Partitioning.Invariant, + new NumberFieldProperties()) + .AddAssets(7, "my-assets", Partitioning.Invariant, + new AssetsFieldProperties()) + .AddBoolean(8, "my-boolean", Partitioning.Invariant, + new BooleanFieldProperties()) + .AddDateTime(9, "my-datetime", Partitioning.Invariant, + new DateTimeFieldProperties()) + .AddReferences(10, "my-references", Partitioning.Invariant, + new ReferencesFieldProperties { SchemaId = Reference1.Id }) + .AddReferences(11, "my-union", Partitioning.Invariant, + new ReferencesFieldProperties()) + .AddGeolocation(12, "my-geolocation", Partitioning.Invariant, + new GeolocationFieldProperties()) + .AddComponent(13, "my-component", Partitioning.Invariant, + new ComponentFieldProperties { SchemaId = Component.Id }) + .AddComponents(14, "my-components", Partitioning.Invariant, + new ComponentsFieldProperties { SchemaIds = ReadonlyList.Create(Reference1.Id, Reference2.Id, Component.Id) }) + .AddTags(15, "my-tags", Partitioning.Invariant, + new TagsFieldProperties()) + .AddTags(16, "my-tags-enum", Partitioning.Invariant, + new TagsFieldProperties { AllowedValues = enums, CreateEnum = true }) + .AddArray(100, "my-array", Partitioning.Invariant, f => f + .AddBoolean(121, "nested-boolean", new BooleanFieldProperties()) - .AddDateTime(9, "my-datetime", Partitioning.Invariant, - new DateTimeFieldProperties()) - .AddReferences(10, "my-references", Partitioning.Invariant, - new ReferencesFieldProperties { SchemaId = Reference1.Id }) - .AddReferences(11, "my-union", Partitioning.Invariant, - new ReferencesFieldProperties()) - .AddGeolocation(12, "my-geolocation", Partitioning.Invariant, - new GeolocationFieldProperties()) - .AddComponent(13, "my-component", Partitioning.Invariant, - new ComponentFieldProperties { SchemaId = Component.Id }) - .AddComponents(14, "my-components", Partitioning.Invariant, - new ComponentsFieldProperties { SchemaIds = ReadonlyList.Create(Reference1.Id, Reference2.Id, Component.Id) }) - .AddTags(15, "my-tags", Partitioning.Invariant, - new TagsFieldProperties()) - .AddTags(16, "my-tags-enum", Partitioning.Invariant, - new TagsFieldProperties { AllowedValues = enums, CreateEnum = true }) - .AddArray(100, "my-array", Partitioning.Invariant, f => f - .AddBoolean(121, "nested-boolean", - new BooleanFieldProperties()) - .AddNumber(122, "nested-number", - new NumberFieldProperties())) - .AddString(17, "my-embeds", Partitioning.Invariant, - new StringFieldProperties { IsEmbeddable = true, SchemaIds = ReadonlyList.Create(Reference1.Id, Reference2.Id) }) - .SetScripts(new SchemaScripts { Query = "" })); + .AddNumber(122, "nested-number", + new NumberFieldProperties())) + .AddString(17, "my-embeds", Partitioning.Invariant, + new StringFieldProperties { IsEmbeddable = true, SchemaIds = ReadonlyList.Create(Reference1.Id, Reference2.Id) }) + .SetScripts(new SchemaScripts { Query = "" }); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentMappingTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentMappingTests.cs index e48ce9c3b..6c1aaf911 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentMappingTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentMappingTests.cs @@ -5,12 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using NodaTime; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Entities.Contents.DomainObject; using Squidex.Domain.Apps.Entities.MongoDb.Contents; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure; using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; @@ -20,13 +17,13 @@ public class ContentMappingTests : GivenContext [Fact] public async Task Should_map_content_without_new_version_to_draft() { - var source = CreateContentWithoutNewVersion(); + var source = CreateWriteContent(); - var snapshotJob = new SnapshotWriteJob(source.UniqueId, source, source.Version); + var snapshotJob = new SnapshotWriteJob(source.UniqueId, source, source.Version); var snapshot = await MongoContentEntity.CreateCompleteAsync(snapshotJob, AppProvider, default); Assert.Equal(source.CurrentVersion.Data, snapshot.Data); - Assert.Null(snapshot.DraftData); + Assert.Null(snapshot.NewData); Assert.Null(snapshot.NewStatus); Assert.NotNull(snapshot.ScheduleJob); Assert.True(snapshot.IsSnapshot); @@ -39,13 +36,13 @@ public class ContentMappingTests : GivenContext [Fact] public async Task Should_map_content_without_new_version_to_published() { - var source = CreateContentWithoutNewVersion(); + var source = CreateWriteContent(); - var snapshotJob = new SnapshotWriteJob(source.UniqueId, source, source.Version); + var snapshotJob = new SnapshotWriteJob(source.UniqueId, source, source.Version); var snapshot = await MongoContentEntity.CreatePublishedAsync(snapshotJob, AppProvider, default); Assert.Equal(source.CurrentVersion.Data, snapshot.Data); - Assert.Null(snapshot.DraftData); + Assert.Null(snapshot.NewData); Assert.Null(snapshot.NewStatus); Assert.Null(snapshot.ScheduleJob); Assert.False(snapshot.IsSnapshot); @@ -56,11 +53,11 @@ public class ContentMappingTests : GivenContext { var source = CreateContentWithNewVersion(); - var snapshotJob = new SnapshotWriteJob(source.UniqueId, source, source.Version); + var snapshotJob = new SnapshotWriteJob(source.UniqueId, source, source.Version); var snapshot = await MongoContentEntity.CreateCompleteAsync(snapshotJob, AppProvider, default); Assert.Equal(source.NewVersion?.Data, snapshot.Data); - Assert.Equal(source.CurrentVersion.Data, snapshot.DraftData); + Assert.Equal(source.CurrentVersion.Data, snapshot.NewData); Assert.NotNull(snapshot.NewStatus); Assert.NotNull(snapshot.ScheduleJob); Assert.True(snapshot.IsSnapshot); @@ -75,76 +72,27 @@ public class ContentMappingTests : GivenContext { var source = CreateContentWithNewVersion(); - var snapshotJob = new SnapshotWriteJob(source.UniqueId, source, source.Version); + var snapshotJob = new SnapshotWriteJob(source.UniqueId, source, source.Version); var snapshot = await MongoContentEntity.CreatePublishedAsync(snapshotJob, AppProvider, default); Assert.Equal(source.CurrentVersion?.Data, snapshot.Data); - Assert.Null(snapshot.DraftData); + Assert.Null(snapshot.NewData); Assert.Null(snapshot.NewStatus); Assert.Null(snapshot.ScheduleJob); Assert.False(snapshot.IsSnapshot); } - private ContentDomainObject.State CreateContentWithoutNewVersion() + private WriteContent CreateContentWithNewVersion() { - var data = - new ContentData() - .AddField("my-field", - new ContentFieldData() - .AddInvariant(42)); - - var time = SystemClock.Instance.GetCurrentInstant(); - - var state = new ContentDomainObject.State - { - Id = DomainId.NewGuid(), - AppId = AppId, - Created = time, - CreatedBy = User, - CurrentVersion = new ContentVersion(Status.Archived, data), - IsDeleted = true, - LastModified = time, - LastModifiedBy = User, - ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Published, User, time), - SchemaId = SchemaId, - Version = 42 - }; - - return state; - } - - private ContentDomainObject.State CreateContentWithNewVersion() - { - var data = - new ContentData() - .AddField("my-field", - new ContentFieldData() - .AddInvariant(42)); - - var newData = - new ContentData() - .AddField("my-field", - new ContentFieldData() - .AddInvariant(13)); - - var time = SystemClock.Instance.GetCurrentInstant(); - - var state = new ContentDomainObject.State + return CreateWriteContent() with { - Id = DomainId.NewGuid(), - AppId = AppId, - Created = time, - CreatedBy = User, - CurrentVersion = new ContentVersion(Status.Archived, data), - IsDeleted = true, - LastModified = time, - LastModifiedBy = User, - NewVersion = new ContentVersion(Status.Published, newData), - ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Published, User, time), - SchemaId = SchemaId, - Version = 42 + NewVersion = + new ContentVersion( + Status.Draft, + new ContentData() + .AddField("my-field", + new ContentFieldData() + .AddInvariant(13))), }; - - return state; } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentQueryTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentQueryTests.cs index 8ecb3ae2e..3cbb87616 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentQueryTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentQueryTests.cs @@ -9,13 +9,11 @@ using MongoDB.Bson.Serialization; using MongoDB.Driver; using NodaTime.Text; using Squidex.Domain.Apps.Core; -using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.MongoDb.Contents; using Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; @@ -24,52 +22,39 @@ using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; -public class ContentQueryTests +public class ContentQueryTests : GivenContext { - private readonly DomainId appId = DomainId.NewGuid(); - private readonly Schema schemaDef; - private readonly LanguagesConfig languages = LanguagesConfig.English.Set(Language.DE); - static ContentQueryTests() { + MongoContentEntity.RegisterClassMap(); + TestUtils.SetupBson(); } public ContentQueryTests() { - schemaDef = - new Schema("user") - .AddString(1, "firstName", Partitioning.Language, - new StringFieldProperties()) - .AddString(2, "lastName", Partitioning.Language, - new StringFieldProperties()) - .AddBoolean(3, "isAdmin", Partitioning.Invariant, - new BooleanFieldProperties()) - .AddNumber(4, "age", Partitioning.Invariant, - new NumberFieldProperties()) - .AddDateTime(5, "birthday", Partitioning.Invariant, - new DateTimeFieldProperties()) - .AddAssets(6, "pictures", Partitioning.Invariant, - new AssetsFieldProperties()) - .AddReferences(7, "friends", Partitioning.Invariant, - new ReferencesFieldProperties()) - .AddString(8, "dashed-field", Partitioning.Invariant, - new StringFieldProperties()) - .AddJson(9, "json", Partitioning.Invariant, - new JsonFieldProperties()) - .AddArray(10, "hobbies", Partitioning.Invariant, a => a - .AddString(101, "name")) - .Update(new SchemaProperties()); - - var schema = A.Dummy(); - A.CallTo(() => schema.Id).Returns(DomainId.NewGuid()); - A.CallTo(() => schema.Version).Returns(3); - A.CallTo(() => schema.SchemaDef).Returns(schemaDef); - - var app = A.Dummy(); - A.CallTo(() => app.Id).Returns(DomainId.NewGuid()); - A.CallTo(() => app.Version).Returns(3); - A.CallTo(() => app.Languages).Returns(languages); + Schema = Schema with { Version = 3 }; + Schema = Schema + .AddString(1, "firstName", Partitioning.Language, + new StringFieldProperties()) + .AddString(2, "lastName", Partitioning.Language, + new StringFieldProperties()) + .AddBoolean(3, "isAdmin", Partitioning.Invariant, + new BooleanFieldProperties()) + .AddNumber(4, "age", Partitioning.Invariant, + new NumberFieldProperties()) + .AddDateTime(5, "birthday", Partitioning.Invariant, + new DateTimeFieldProperties()) + .AddAssets(6, "pictures", Partitioning.Invariant, + new AssetsFieldProperties()) + .AddReferences(7, "friends", Partitioning.Invariant, + new ReferencesFieldProperties()) + .AddString(8, "dashed-field", Partitioning.Invariant, + new StringFieldProperties()) + .AddJson(9, "json", Partitioning.Invariant, + new JsonFieldProperties()) + .AddArray(10, "hobbies", Partitioning.Invariant, a => a + .AddString(101, "name")); } [Fact] @@ -79,7 +64,7 @@ public class ContentQueryTests var filter = ClrFilter.Eq("id", id); - AssertQuery($"{{ '_id' : '{appId}--{id}' }}", filter); + AssertQuery($"{{ '_id' : '{AppId.Id}--{id}' }}", filter); } [Fact] @@ -89,7 +74,7 @@ public class ContentQueryTests var filter = ClrFilter.Eq("id", id); - AssertQuery($"{{ '_id' : '{appId}--{id}' }}", filter); + AssertQuery($"{{ '_id' : '{AppId.Id}--{id}' }}", filter); } [Fact] @@ -99,7 +84,7 @@ public class ContentQueryTests var filter = ClrFilter.In("id", new List { id }); - AssertQuery($"{{ '_id' : {{ '$in' : ['{appId}--{id}'] }} }}", filter); + AssertQuery($"{{ '_id' : {{ '$in' : ['{AppId.Id}--{id}'] }} }}", filter); } [Fact] @@ -109,7 +94,7 @@ public class ContentQueryTests var filter = ClrFilter.In("id", new List { id }); - AssertQuery($"{{ '_id' : {{ '$in' : ['{appId}--{id}'] }} }}", filter); + AssertQuery($"{{ '_id' : {{ '$in' : ['{AppId.Id}--{id}'] }} }}", filter); } [Fact] @@ -238,7 +223,7 @@ public class ContentQueryTests private void AssertQuery(ClrQuery query, string expected, object? arg = null) { - var filter = query.AdjustToModel(appId).BuildFilter(false).Filter!; + var filter = query.AdjustToModel(AppId.Id).BuildFilter(false).Filter!; var rendered = filter.Render( @@ -265,7 +250,7 @@ public class ContentQueryTests .ToString(); }); - cursor.QuerySort(new ClrQuery { Sort = sort.ToList() }.AdjustToModel(appId)); + cursor.QuerySort(new ClrQuery { Sort = sort.ToList() }.AdjustToModel(AppId.Id)); Assert.Equal(Cleanup(expected), rendered); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs index 1eb7d19fd..4ad379396 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs @@ -10,15 +10,11 @@ using LoremNET; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using MongoDB.Driver; -using NodaTime; -using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Contents.DomainObject; using Squidex.Domain.Apps.Entities.MongoDb.Contents; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; @@ -45,7 +41,7 @@ public sealed class ContentsQueryFixture_Dedicated : ContentsQueryFixture } } -public abstract class ContentsQueryFixture : IAsyncLifetime +public abstract class ContentsQueryFixture : GivenContext, IAsyncLifetime { private readonly int numValues = 10000; @@ -110,18 +106,18 @@ public abstract class ContentsQueryFixture : IAsyncLifetime return; } - var batch = new List>(); + var batch = new List>(); - async Task ExecuteBatchAsync(ContentDomainObject.State? state) + async Task ExecuteBatchAsync(WriteContent? state) { if (state != null) { - batch.Add(new SnapshotWriteJob(state.UniqueId, state, 0)); + batch.Add(new SnapshotWriteJob(state.UniqueId, state, 0)); } if ((state == null || batch.Count >= 1000) && batch.Count > 0) { - var store = (ISnapshotStore)ContentRepository; + var store = (ISnapshotStore)ContentRepository; await store.WriteManyAsync(batch, ct); @@ -129,34 +125,24 @@ public abstract class ContentsQueryFixture : IAsyncLifetime } } - var created = SystemClock.Instance.GetCurrentInstant(); - var createdBy = RefToken.User("1"); - foreach (var appId in AppIds) { foreach (var schemaId in SchemaIds) { for (var i = 0; i < numValues; i++) { - var data = - new ContentData() - .AddField("field1", - new ContentFieldData() - .AddInvariant(JsonValue.Create(i))) - .AddField("field2", - new ContentFieldData() - .AddInvariant(JsonValue.Create(Lorem.Paragraph(200, 20)))); - - var content = new ContentDomainObject.State + var content = CreateWriteContent() with { - Id = DomainId.NewGuid(), AppId = appId, - Created = created, - CreatedBy = createdBy, - CurrentVersion = new ContentVersion(Status.Published, data), - IsDeleted = false, - LastModified = created, - LastModifiedBy = createdBy, + CurrentVersion = new ContentVersion( + Status.Published, + new ContentData() + .AddField("field1", + new ContentFieldData() + .AddInvariant(JsonValue.Create(i))) + .AddField("field2", + new ContentFieldData() + .AddInvariant(JsonValue.Create(Lorem.Paragraph(200, 20))))), SchemaId = schemaId }; @@ -168,7 +154,7 @@ public abstract class ContentsQueryFixture : IAsyncLifetime await ExecuteBatchAsync(null); } - private static IAppProvider CreateAppProvider() + private IAppProvider CreateAppProvider() { var appProvider = A.Fake(); @@ -177,7 +163,7 @@ public abstract class ContentsQueryFixture : IAsyncLifetime { var appId = x.GetArgument(0)!; - return Task.FromResult<(IAppEntity?, ISchemaEntity?)>(( + return Task.FromResult<(App?, Schema?)>(( CreateApp(appId), CreateSchema(appId, x.GetArgument(1)!))); }); @@ -190,17 +176,17 @@ public abstract class ContentsQueryFixture : IAsyncLifetime return AppIds[Random.Shared.Next(AppIds.Length)].Id; } - public IAppEntity RandomApp() + public DomainId RandomSchemaId() { - return CreateApp(RandomAppId()); + return SchemaIds[Random.Shared.Next(SchemaIds.Length)].Id; } - public DomainId RandomSchemaId() + public App RandomApp() { - return SchemaIds[Random.Shared.Next(SchemaIds.Length)].Id; + return CreateApp(RandomAppId()); } - public ISchemaEntity RandomSchema() + public Schema RandomSchema() { return CreateSchema(RandomAppId(), RandomSchemaId()); } @@ -210,21 +196,13 @@ public abstract class ContentsQueryFixture : IAsyncLifetime return Random.Shared.Next(numValues).ToString(CultureInfo.InvariantCulture); } - private static IAppEntity CreateApp(DomainId appId) + private App CreateApp(DomainId appId) { - return Mocks.App(NamedId.Of(appId, "my-app")); + return App with { Id = appId }; } - private static ISchemaEntity CreateSchema(DomainId appId, DomainId schemaId) + private Schema CreateSchema(DomainId appId, DomainId schemaId) { - var schemaDef = - new Schema("my-schema") - .AddField(Fields.Number(1, "value", Partitioning.Invariant)); - - return - Mocks.Schema( - NamedId.Of(appId, "my-app"), - schemaId, - schemaDef); + return Schema with { Id = schemaId, AppId = NamedId.Of(appId, "my-app") }; } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryTestsBase.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryTestsBase.cs index 550d45026..bebd8b3de 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryTestsBase.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryTestsBase.cs @@ -6,8 +6,8 @@ // ========================================================================== using NodaTime; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Repositories; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Queries; using F = Squidex.Infrastructure.Queries.ClrFilter; @@ -30,7 +30,7 @@ public abstract class ContentsQueryTestsBase { var ids = Enumerable.Repeat(0, 50).Select(_ => DomainId.NewGuid()).ToHashSet(); - var contents = await _.ContentRepository.QueryIdsAsync(_.RandomAppId(), ids, SearchScope.Published); + var contents = await _.ContentRepository.QueryIdsAsync(_.RandomApp(), ids, SearchScope.Published); // The IDs are random here, as it does not really matter. Assert.NotNull(contents); @@ -41,12 +41,7 @@ public abstract class ContentsQueryTestsBase { var ids = Enumerable.Repeat(0, 50).Select(_ => DomainId.NewGuid()).ToHashSet(); - var schemas = new List - { - _.RandomSchema() - }; - - var contents = await _.ContentRepository.QueryAsync(_.RandomApp(), schemas, Q.Empty.WithIds(ids), SearchScope.All); + var contents = await _.ContentRepository.QueryAsync(_.RandomApp(), [_.RandomSchema()], Q.Empty.WithIds(ids), SearchScope.All); // The IDs are random here, as it does not really matter. Assert.NotNull(contents); @@ -68,7 +63,7 @@ public abstract class ContentsQueryTestsBase { var filter = F.Eq("data.field1.iv", 12); - var contents = await _.ContentRepository.QueryIdsAsync(_.RandomAppId(), _.RandomSchemaId(), filter, SearchScope.All); + var contents = await _.ContentRepository.QueryIdsAsync(_.RandomApp(), _.RandomSchema(), filter, SearchScope.All); // We have a concrete query, so we expect an actual. Assert.NotEmpty(contents); @@ -194,7 +189,7 @@ public abstract class ContentsQueryTestsBase Assert.Equal(40, contents.Count); } - private async Task> QueryAsync(IContentRepository contentRepository, + private async Task> QueryAsync(IContentRepository contentRepository, ClrQuery clrQuery, int top = 1000, int skip = 100, diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/CalculateTokensTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/CalculateTokensTests.cs index cabc27e57..793908874 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/CalculateTokensTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/CalculateTokensTests.cs @@ -50,11 +50,6 @@ public class CalculateTokensTests : GivenContext .MustHaveHappened(); } - private ContentEntity CreateContent() - { - return new ContentEntity { AppId = AppId, SchemaId = SchemaId }; - } - private ProvideSchema SchemaProvider() { return x => Task.FromResult((Schema, ResolvedComponents.Empty)); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs index 48f054e97..ecc2d78bd 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentEnricherTests.cs @@ -6,7 +6,7 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; namespace Squidex.Domain.Apps.Entities.Contents.Queries; @@ -15,9 +15,9 @@ public class ContentEnricherTests : GivenContext { private sealed class ResolveSchema : IContentEnricherStep { - public ISchemaEntity Schema { get; private set; } + public Schema Schema { get; private set; } - public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, + public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) @@ -30,7 +30,7 @@ public class ContentEnricherTests : GivenContext [Fact] public async Task Should_only_invoke_pre_enrich_for_empty_contents() { - var source = Array.Empty(); + var source = Array.Empty(); var step1 = A.Fake(); var step2 = A.Fake(); @@ -45,10 +45,10 @@ public class ContentEnricherTests : GivenContext A.CallTo(() => step2.EnrichAsync(ApiContext, CancellationToken)) .MustHaveHappened(); - A.CallTo(() => step1.EnrichAsync(ApiContext, A>._, A._, A._)) + A.CallTo(() => step1.EnrichAsync(ApiContext, A>._, A._, A._)) .MustNotHaveHappened(); - A.CallTo(() => step2.EnrichAsync(ApiContext, A>._, A._, A._)) + A.CallTo(() => step2.EnrichAsync(ApiContext, A>._, A._, A._)) .MustNotHaveHappened(); } @@ -70,10 +70,10 @@ public class ContentEnricherTests : GivenContext A.CallTo(() => step2.EnrichAsync(ApiContext, CancellationToken)) .MustHaveHappened(); - A.CallTo(() => step1.EnrichAsync(ApiContext, A>._, A._, CancellationToken)) + A.CallTo(() => step1.EnrichAsync(ApiContext, A>._, A._, CancellationToken)) .MustHaveHappened(); - A.CallTo(() => step2.EnrichAsync(ApiContext, A>._, A._, CancellationToken)) + A.CallTo(() => step2.EnrichAsync(ApiContext, A>._, A._, CancellationToken)) .MustHaveHappened(); } @@ -99,7 +99,7 @@ public class ContentEnricherTests : GivenContext [Fact] public async Task Should_clone_data_if_requested() { - var source = CreateContent([]); + var source = CreateContent(); var sut = new ContentEnricher(Enumerable.Empty(), AppProvider); @@ -111,7 +111,7 @@ public class ContentEnricherTests : GivenContext [Fact] public async Task Should_not_clone_data_if_not_requested() { - var source = CreateContent([]); + var source = CreateContent(); var sut = new ContentEnricher(Enumerable.Empty(), AppProvider); @@ -119,9 +119,4 @@ public class ContentEnricherTests : GivenContext Assert.Same(source.Data, actual.Data); } - - private ContentEntity CreateContent(ContentData? data = null) - { - return new ContentEntity { SchemaId = SchemaId, Data = data! }; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentLoaderTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentLoaderTests.cs index d0de8045c..d1347d2cc 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentLoaderTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentLoaderTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.DomainObject; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; @@ -25,8 +26,8 @@ public class ContentLoaderTests : GivenContext { unqiueId = DomainId.Combine(AppId.Id, id); - A.CallTo(() => domainObjectCache.GetAsync(A._, A._, CancellationToken)) - .Returns(Task.FromResult(null!)); + A.CallTo(() => domainObjectCache.GetAsync(A._, A._, CancellationToken)) + .Returns(Task.FromResult(null!)); A.CallTo(() => domainObjectFactory.Create(unqiueId)) .Returns(domainObject); @@ -37,7 +38,7 @@ public class ContentLoaderTests : GivenContext [Fact] public async Task Should_return_null_if_no_state_returned() { - var content = (ContentDomainObject.State)null!; + var content = (WriteContent)null!; A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken)) .Returns(content); @@ -48,7 +49,7 @@ public class ContentLoaderTests : GivenContext [Fact] public async Task Should_return_null_if_state_empty() { - var content = new ContentDomainObject.State { Version = EtagVersion.Empty }; + var content = CreateWriteContent() with { Version = EtagVersion.Empty }; A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken)) .Returns(content); @@ -59,7 +60,7 @@ public class ContentLoaderTests : GivenContext [Fact] public async Task Should_return_null_if_state_has_other_version() { - var content = new ContentDomainObject.State { Version = 5 }; + var content = CreateWriteContent() with { Version = 5 }; A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken)) .Returns(content); @@ -70,40 +71,40 @@ public class ContentLoaderTests : GivenContext [Fact] public async Task Should_not_return_null_if_state_has_other_version_than_any() { - var content = new ContentDomainObject.State { Version = 5 }; + var content = CreateWriteContent() with { Version = 5 }; A.CallTo(() => domainObject.GetSnapshotAsync(EtagVersion.Any, CancellationToken)) .Returns(content); var actual = await sut.GetAsync(AppId.Id, id, EtagVersion.Any, CancellationToken); - Assert.Same(content, actual); + Assert.Equal(content.Version, actual?.Version); } [Fact] public async Task Should_return_content_from_state() { - var content = new ContentDomainObject.State { Version = 10 }; + var content = CreateWriteContent() with { Version = 10 }; A.CallTo(() => domainObject.GetSnapshotAsync(10, CancellationToken)) .Returns(content); var actual = await sut.GetAsync(AppId.Id, id, 10, CancellationToken); - Assert.Same(content, actual); + Assert.Equal(content.Version, actual?.Version); } [Fact] public async Task Should_return_content_from_cache() { - var content = new ContentDomainObject.State { Version = 10 }; + var content = CreateWriteContent() with { Version = 10 }; - A.CallTo(() => domainObjectCache.GetAsync(DomainId.Combine(AppId.Id, id), 10, CancellationToken)) + A.CallTo(() => domainObjectCache.GetAsync(DomainId.Combine(AppId.Id, id), 10, CancellationToken)) .Returns(content); var actual = await sut.GetAsync(AppId.Id, id, 10, CancellationToken); - Assert.Same(content, actual); + Assert.Equal(content.Version, actual?.Version); A.CallTo(() => domainObjectFactory.Create(unqiueId)) .MustNotHaveHappened(); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs index 5b24c9e85..184bd4247 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs @@ -28,13 +28,9 @@ public class ContentQueryParserTests : GivenContext { var options = Options.Create(new ContentOptions { DefaultPageSize = 30 }); - var schemaDef = - new Schema(SchemaId.Name) - .AddString(1, "firstName", Partitioning.Invariant) - .AddGeolocation(2, "geo", Partitioning.Invariant); - - A.CallTo(() => Schema.SchemaDef) - .Returns(schemaDef); + Schema = Schema + .AddString(1, "firstName", Partitioning.Invariant) + .AddGeolocation(2, "geo", Partitioning.Invariant); var cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs index 50093369d..25ecc6e00 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.Repositories; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; @@ -29,20 +28,21 @@ public class ContentQueryServiceTests : GivenContext public ContentQueryServiceTests() { - var schemaDef = - new Schema(SchemaId.Name) - .Publish() - .SetScripts(new SchemaScripts { Query = "" }); - - A.CallTo(() => Schema.SchemaDef) - .Returns(schemaDef); + Schema = Schema with + { + Scripts = new SchemaScripts + { + Query = "" + }, + IsPublished = true + }; SetupEnricher(); A.CallTo(() => AppProvider.GetSchemasAsync(AppId.Id, CancellationToken)) .Returns([Schema]); - A.CallTo(() => queryParser.ParseAsync(A._, A._, A._, CancellationToken)) + A.CallTo(() => queryParser.ParseAsync(A._, A._, A._, CancellationToken)) .ReturnsLazily(c => Task.FromResult(c.GetArgument(1)!)); var options = Options.Create(new ContentOptions()); @@ -95,10 +95,10 @@ public class ContentQueryServiceTests : GivenContext { var requestContext = SetupContext(allowSchema: false); - var content = CreateContent(DomainId.NewGuid()); + var content = CreateContent() as Content; A.CallTo(() => contentRepository.FindContentAsync(App, Schema, content.Id, A._, A._)) - .Returns(CreateContent(DomainId.NewGuid())); + .Returns(content); await Assert.ThrowsAsync(() => sut.FindAsync(requestContext, SchemaId.Name, content.Id, ct: CancellationToken)); } @@ -108,10 +108,10 @@ public class ContentQueryServiceTests : GivenContext { var requestContext = SetupContext(); - var content = CreateContent(DomainId.NewGuid()); + var content = CreateContent(); A.CallTo(() => contentRepository.FindContentAsync(App, Schema, content.Id, A._, A._)) - .Returns(null); + .Returns(null); var actual = await sut.FindAsync(requestContext, SchemaId.Name, content.Id, ct: CancellationToken); @@ -123,7 +123,7 @@ public class ContentQueryServiceTests : GivenContext { var requestContext = SetupContext(); - var content = CreateContent(DomainId.NewGuid()); + var content = CreateContent(); A.CallTo(() => contentRepository.FindContentAsync(App, Schema, SchemaId.Id, SearchScope.Published, A._)) .Returns(content); @@ -142,7 +142,7 @@ public class ContentQueryServiceTests : GivenContext { var requestContext = SetupContext(isFrontend, isUnpublished: unpublished); - var content = CreateContent(DomainId.NewGuid()); + var content = CreateContent(); A.CallTo(() => contentRepository.FindContentAsync(App, Schema, content.Id, scope, A._)) .Returns(content); @@ -157,7 +157,7 @@ public class ContentQueryServiceTests : GivenContext { var requestContext = SetupContext(); - var content = CreateContent(DomainId.NewGuid()); + var content = CreateContent(); A.CallTo(() => contentVersionLoader.GetAsync(AppId.Id, content.Id, 13, A._)) .Returns(content); @@ -184,8 +184,8 @@ public class ContentQueryServiceTests : GivenContext { var requestContext = SetupContext(isFrontend, isUnpublished: unpublished); - var content1 = CreateContent(DomainId.NewGuid()); - var content2 = CreateContent(DomainId.NewGuid()); + var content1 = CreateContent(); + var content2 = CreateContent(); var q = Q.Empty.WithReference(DomainId.NewGuid()); @@ -210,12 +210,12 @@ public class ContentQueryServiceTests : GivenContext var requestContext = SetupContext(isFrontend, isUnpublished: unpublished); var contentIds = Enumerable.Range(0, 5).Select(x => DomainId.NewGuid()).ToList(); - var contents = contentIds.Select(CreateContent).ToList(); + var contents = contentIds.Select(x => CreateContent().WithId(x)).ToList(); var q = Q.Empty.WithIds(contentIds); A.CallTo(() => contentRepository.QueryAsync(App, - A>.That.Matches(x => x.Count == 1), q, scope, + A>.That.Matches(x => x.Count == 1), q, scope, A._)) .Returns(ResultList.Create(5, contents)); @@ -235,12 +235,12 @@ public class ContentQueryServiceTests : GivenContext var requestContext = SetupContext(allowSchema: false); var contentIds = Enumerable.Range(0, 5).Select(x => DomainId.NewGuid()).ToList(); - var contents = contentIds.Select(CreateContent).ToList(); + var contents = contentIds.Select(x => CreateContent().WithId(x)).ToList(); var q = Q.Empty.WithIds(contentIds); A.CallTo(() => contentRepository.QueryAsync(App, - A>.That.Matches(x => x.Count == 0), q, SearchScope.All, + A>.That.Matches(x => x.Count == 0), q, SearchScope.All, A._)) .Returns(ResultList.Create(0, contents)); @@ -277,12 +277,12 @@ public class ContentQueryServiceTests : GivenContext private void SetupEnricher() { - A.CallTo(() => contentEnricher.EnrichAsync(A>._, A._, CancellationToken)) + A.CallTo(() => contentEnricher.EnrichAsync(A>._, A._, CancellationToken)) .ReturnsLazily(x => { - var input = x.GetArgument>(0)!; + var input = x.GetArgument>(0)!; - return Task.FromResult>(input.Select(c => SimpleMapper.Map(c, new ContentEntity())).ToList()); + return Task.FromResult>(input.Select(c => SimpleMapper.Map(c, new EnrichedContent())).ToList()); }); } @@ -304,24 +304,11 @@ public class ContentQueryServiceTests : GivenContext return CreateContext(isFrontend == 1, permissions.ToArray()).Clone(b => b.WithUnpublished(isUnpublished == 1)); } - private static void AssertContent(IContentEntity source, IEnrichedContentEntity? actual) + private static void AssertContent(EnrichedContent source, EnrichedContent? actual) { Assert.NotNull(actual); Assert.NotSame(source, actual); Assert.Same(source.Data, actual?.Data); Assert.Equal(source.Id, actual?.Id); } - - private IContentEntity CreateContent(DomainId id) - { - var content = new ContentEntity - { - Id = id, - Data = contentData, - SchemaId = SchemaId, - Status = Status.Published - }; - - return content; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ConvertDataTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ConvertDataTests.cs index ec93a01e8..4c2bd6cf3 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ConvertDataTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ConvertDataTests.cs @@ -29,18 +29,14 @@ public class ConvertDataTests : GivenContext public ConvertDataTests() { - var schemaDef = - new Schema(SchemaId.Name) - .AddReferences(1, "references", Partitioning.Invariant, - new ReferencesFieldProperties { DefaultValue = ReadonlyList.Create("default1") }) - .AddAssets(2, "assets", Partitioning.Invariant, - new AssetsFieldProperties { DefaultValue = ReadonlyList.Create("default2") }) - .AddArray(3, "array", Partitioning.Invariant, a => a - .AddAssets(31, "nested", - new AssetsFieldProperties { DefaultValue = ReadonlyList.Create("default3") })); - - A.CallTo(() => Schema.SchemaDef) - .Returns(schemaDef); + Schema = Schema + .AddReferences(1, "references", Partitioning.Invariant, + new ReferencesFieldProperties { DefaultValue = ReadonlyList.Create("default1") }) + .AddAssets(2, "assets", Partitioning.Invariant, + new AssetsFieldProperties { DefaultValue = ReadonlyList.Create("default2") }) + .AddArray(3, "array", Partitioning.Invariant, a => a + .AddAssets(31, "nested", + new AssetsFieldProperties { DefaultValue = ReadonlyList.Create("default3") })); sut = new ConvertData(urlGenerator, TestUtils.DefaultSerializer, assetRepository, contentRepository); } @@ -48,7 +44,7 @@ public class ConvertDataTests : GivenContext [Fact] public async Task Should_convert_data_and_data_draft_if_frontend_user() { - var content = CreateContent([]); + var content = CreateContent(); await sut.EnrichAsync(FrontendContext, new[] { content }, SchemaProvider(), CancellationToken); @@ -66,7 +62,10 @@ public class ConvertDataTests : GivenContext JsonValue.Array( JsonValue.Object()))); - var content = CreateContent(source); + var content = CreateContent() with + { + Data = source + }; var expected = new ContentData() @@ -96,7 +95,10 @@ public class ConvertDataTests : GivenContext var source = BuildTestData(id1, id2); - var content = CreateContent(source); + var content = CreateContent() with + { + Data = source + }; var expected = new ContentData() @@ -116,7 +118,7 @@ public class ConvertDataTests : GivenContext A.CallTo(() => assetRepository.QueryIdsAsync(AppId.Id, A>.That.Is(id1, id2), CancellationToken)) .Returns(new List { id2 }); - A.CallTo(() => contentRepository.QueryIdsAsync(AppId.Id, A>.That.Is(id1, id2), SearchScope.All, CancellationToken)) + A.CallTo(() => contentRepository.QueryIdsAsync(App, A>.That.Is(id1, id2), SearchScope.All, CancellationToken)) .Returns(new List { new ContentIdStatus(id2, id2, Status.Published) }); await sut.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken); @@ -132,7 +134,10 @@ public class ConvertDataTests : GivenContext var source = BuildTestData(id1, id2); - var content = CreateContent(source); + var content = CreateContent() with + { + Data = source + }; var expected = new ContentData() @@ -152,7 +157,7 @@ public class ConvertDataTests : GivenContext A.CallTo(() => assetRepository.QueryIdsAsync(AppId.Id, A>.That.Is(id1, id2), CancellationToken)) .Returns(new List()); - A.CallTo(() => contentRepository.QueryIdsAsync(AppId.Id, A>.That.Is(id1, id2), SearchScope.All, CancellationToken)) + A.CallTo(() => contentRepository.QueryIdsAsync(App, A>.That.Is(id1, id2), SearchScope.All, CancellationToken)) .Returns(new List()); await sut.EnrichAsync(ApiContext, new[] { content }, SchemaProvider(), CancellationToken); @@ -177,19 +182,6 @@ public class ConvertDataTests : GivenContext .Add("nested", JsonValue.Array(id1, id2))))); } - private ContentEntity CreateContent(ContentData data) - { - return new ContentEntity - { - AppId = AppId, - Created = default, - CreatedBy = User, - Data = data, - SchemaId = SchemaId, - Status = Status.Published - }; - } - private ProvideSchema SchemaProvider() { return x => Task.FromResult((Schema, ResolvedComponents.Empty)); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichForCachingTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichForCachingTests.cs index 819151915..fda5c4105 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichForCachingTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichForCachingTests.cs @@ -8,7 +8,6 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.Queries.Steps; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; namespace Squidex.Domain.Apps.Entities.Contents.Queries; @@ -50,7 +49,7 @@ public class EnrichForCachingTests : GivenContext [Fact] public async Task Should_add_app_version_and_schema_as_dependency() { - var content = CreateContent(); + var content = CreateContent() with { Version = 13 }; await sut.EnrichAsync(ApiContext, Enumerable.Repeat(content, 1), SchemaProvider(), CancellationToken); @@ -76,7 +75,7 @@ public class EnrichForCachingTests : GivenContext [Fact] public async Task Should_not_add_cache_headers_for_contents_if_disabled() { - var content = CreateContent(); + var content = CreateContent() with { Version = 13 }; await sut.EnrichAsync(ApiContext.Clone(b => b.WithNoCacheKeys()), Enumerable.Repeat(content, 1), SchemaProvider(), CancellationToken); @@ -84,11 +83,6 @@ public class EnrichForCachingTests : GivenContext .MustNotHaveHappened(); } - private ContentEntity CreateContent() - { - return new ContentEntity { AppId = AppId, Id = DomainId.NewGuid(), SchemaId = SchemaId, Version = 13 }; - } - private ProvideSchema SchemaProvider() { return x => Task.FromResult((Schema, ResolvedComponents.Empty)); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs index bdea2cdda..494cbb697 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithSchemaTests.cs @@ -50,11 +50,6 @@ public class EnrichWithSchemaTests : GivenContext Assert.Equal("my-schema", content.SchemaDisplayName); } - private ContentEntity CreateContent() - { - return new ContentEntity { SchemaId = SchemaId }; - } - private ProvideSchema SchemaProvider() { return x => Task.FromResult((Schema, ResolvedComponents.Empty)); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithWorkflowsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithWorkflowsTests.cs index 16543c96a..bc0f1b30d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithWorkflowsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/EnrichWithWorkflowsTests.cs @@ -26,7 +26,7 @@ public class EnrichWithWorkflowsTests : GivenContext [Fact] public async Task Should_enrich_content_with_next_statuses() { - var content = new ContentEntity { SchemaId = SchemaId }; + var content = CreateContent(); var nexts = new[] { @@ -44,7 +44,7 @@ public class EnrichWithWorkflowsTests : GivenContext [Fact] public async Task Should_enrich_content_with_next_statuses_if_draft_singleton() { - var content = new ContentEntity { SchemaId = SchemaId, IsSingleton = true, Status = Status.Draft }; + var content = CreateContent() with { IsSingleton = true, Status = Status.Draft }; await sut.EnrichAsync(FrontendContext, new[] { content }, null!, default); @@ -57,7 +57,7 @@ public class EnrichWithWorkflowsTests : GivenContext [Fact] public async Task Should_enrich_content_with_next_statuses_if_published_singleton() { - var content = new ContentEntity { SchemaId = SchemaId, IsSingleton = true, Status = Status.Published }; + var content = CreateContent() with { IsSingleton = true }; await sut.EnrichAsync(FrontendContext, new[] { content }, null!, CancellationToken); @@ -70,7 +70,7 @@ public class EnrichWithWorkflowsTests : GivenContext [Fact] public async Task Should_enrich_content_with_status_color() { - var content = new ContentEntity { SchemaId = SchemaId }; + var content = CreateContent(); A.CallTo(() => workflow.GetInfoAsync(content, content.Status)) .Returns(new StatusInfo(Status.Published, StatusColors.Published)); @@ -83,9 +83,9 @@ public class EnrichWithWorkflowsTests : GivenContext [Fact] public async Task Should_enrich_content_with_new_status_color() { - var content = new ContentEntity { SchemaId = SchemaId, NewStatus = Status.Archived }; + var content = CreateContent() with { NewStatus = Status.Archived }; - A.CallTo(() => workflow.GetInfoAsync(content, content.NewStatus.Value)) + A.CallTo(() => workflow.GetInfoAsync(content, content.NewStatus!.Value)) .Returns(new StatusInfo(Status.Published, StatusColors.Archived)); await sut.EnrichAsync(FrontendContext, new[] { content }, null!, CancellationToken); @@ -96,7 +96,7 @@ public class EnrichWithWorkflowsTests : GivenContext [Fact] public async Task Should_enrich_content_with_scheduled_status_color() { - var content = new ContentEntity { SchemaId = SchemaId, ScheduleJob = ScheduleJob.Build(Status.Archived, User, default) }; + var content = CreateContent() with { ScheduleJob = ScheduleJob.Build(Status.Archived, User, Timestamp()) }; A.CallTo(() => workflow.GetInfoAsync(content, content.ScheduleJob.Status)) .Returns(new StatusInfo(Status.Published, StatusColors.Archived)); @@ -109,7 +109,7 @@ public class EnrichWithWorkflowsTests : GivenContext [Fact] public async Task Should_enrich_content_with_default_color_if_not_found() { - var content = new ContentEntity { SchemaId = SchemaId }; + var content = CreateContent(); A.CallTo(() => workflow.GetInfoAsync(content, content.Status)) .Returns(ValueTask.FromResult(null!)); @@ -122,7 +122,7 @@ public class EnrichWithWorkflowsTests : GivenContext [Fact] public async Task Should_enrich_content_with_can_update() { - var content = new ContentEntity { SchemaId = SchemaId }; + var content = CreateContent(); A.CallTo(() => workflow.CanUpdateAsync(content, content.Status, FrontendContext.UserPrincipal)) .Returns(true); @@ -135,7 +135,7 @@ public class EnrichWithWorkflowsTests : GivenContext [Fact] public async Task Should_not_enrich_content_with_can_update_if_disabled_in_context() { - var content = new ContentEntity { SchemaId = SchemaId }; + var content = CreateContent(); await sut.EnrichAsync(ApiContext.Clone(b => b.WithResolveFlow(false)), new[] { content }, null!, CancellationToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveAssetsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveAssetsTests.cs index e28e7c311..b2742a9d3 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveAssetsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveAssetsTests.cs @@ -28,24 +28,20 @@ public class ResolveAssetsTests : GivenContext public ResolveAssetsTests() { - var schemaDef = - new Schema(SchemaId.Name) - .AddAssets(1, "asset1", Partitioning.Invariant, new AssetsFieldProperties - { - ResolveFirst = true, - MinItems = 2, - MaxItems = 3 - }) - .AddAssets(2, "asset2", Partitioning.Language, new AssetsFieldProperties - { - ResolveFirst = true, - MinItems = 1, - MaxItems = 1 - }) - .SetFieldsInLists("asset1", "asset2"); - - A.CallTo(() => Schema.SchemaDef) - .Returns(schemaDef); + Schema = Schema + .AddAssets(1, "asset1", Partitioning.Invariant, new AssetsFieldProperties + { + ResolveFirst = true, + MinItems = 2, + MaxItems = 3 + }) + .AddAssets(2, "asset2", Partitioning.Language, new AssetsFieldProperties + { + ResolveFirst = true, + MinItems = 1, + MaxItems = 1 + }) + .SetFieldsInLists(FieldNames.Create("asset1", "asset2")); A.CallTo(() => urlGenerator.AssetContent(AppId, A._)) .ReturnsLazily(ctx => $"url/to/{ctx.GetArgument(1)}"); @@ -68,8 +64,8 @@ public class ResolveAssetsTests : GivenContext [Fact] public async Task Should_add_assets_id_and_versions_as_dependency() { - var doc1 = CreateAsset(DomainId.NewGuid(), 3, AssetType.Unknown, "Document1.docx"); - var doc2 = CreateAsset(DomainId.NewGuid(), 4, AssetType.Unknown, "Document2.docx"); + var doc1 = CreateAsset() with { Version = 3, Type = AssetType.Unknown, FileName = "Document1.docx" }; + var doc2 = CreateAsset() with { Version = 4, Type = AssetType.Unknown, FileName = "Document2.docx" }; var contents = new[] { @@ -97,11 +93,11 @@ public class ResolveAssetsTests : GivenContext [Fact] public async Task Should_enrich_with_asset_urls() { - var img1 = CreateAsset(DomainId.NewGuid(), 1, AssetType.Image, "Image1.png"); - var img2 = CreateAsset(DomainId.NewGuid(), 2, AssetType.Unknown, "Image2.png", "image/svg+xml"); + var img1 = CreateAsset() with { Version = 1, Type = AssetType.Image, FileName = "Image1.png" }; + var img2 = CreateAsset() with { Version = 2, Type = AssetType.Unknown, FileName = "Image2.png", MimeType = "image/svg+xml" }; - var doc1 = CreateAsset(DomainId.NewGuid(), 3, AssetType.Unknown, "Document1.png"); - var doc2 = CreateAsset(DomainId.NewGuid(), 4, AssetType.Unknown, "Document2.png", "image/svg+xml", 20_000); + var doc1 = CreateAsset() with { Version = 3, Type = AssetType.Unknown, FileName = "Document1.png" }; + var doc2 = CreateAsset() with { Version = 4, Type = AssetType.Unknown, FileName = "Document2.png", MimeType = "image/svg+xml", FileSize = 20_000 }; var contents = new[] { @@ -208,9 +204,9 @@ public class ResolveAssetsTests : GivenContext .MustHaveHappened(); } - private ContentEntity CreateContent(DomainId[] assets1, DomainId[] assets2) + private EnrichedContent CreateContent(DomainId[] assets1, DomainId[] assets2) { - return new ContentEntity + return CreateContent() with { Data = new ContentData() @@ -219,22 +215,7 @@ public class ResolveAssetsTests : GivenContext .AddLocalized("iv", JsonValue.Array(assets1.Select(x => x.ToString())))) .AddField("asset2", new ContentFieldData() - .AddLocalized("en", JsonValue.Array(assets2.Select(x => x.ToString())))), - SchemaId = SchemaId - }; - } - - private AssetEntity CreateAsset(DomainId id, int version, AssetType type, string fileName, string? fileType = null, int fileSize = 100) - { - return new AssetEntity - { - AppId = AppId, - Id = id, - Type = type, - FileName = fileName, - FileSize = fileSize, - MimeType = fileType!, - Version = version + .AddLocalized("en", JsonValue.Array(assets2.Select(x => x.ToString())))) }; } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveReferencesTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveReferencesTests.cs index 75ababc54..2dfbfc3b6 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveReferencesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveReferencesTests.cs @@ -28,34 +28,38 @@ public class ResolveReferencesTests : GivenContext, IClassFixture Schema.SchemaDef) - .Returns(schemaDef); + .SetFieldsInReferences(FieldNames.Create("name", "number")); + + var refSchema2 = + Schema.WithId(referenceSchemaId2) + .AddString(1, "name", Partitioning.Invariant, + new StringFieldProperties()) + .AddNumber(2, "number", Partitioning.Invariant, + new NumberFieldProperties()) + .SetFieldsInReferences(FieldNames.Create("name", "number")); + + Schema = Schema + .AddReferences(1, "ref1", Partitioning.Invariant, new ReferencesFieldProperties + { + ResolveReference = true, + MinItems = 1, + MaxItems = 1, + SchemaId = referenceSchemaId1.Id + }) + .AddReferences(2, "ref2", Partitioning.Invariant, new ReferencesFieldProperties + { + ResolveReference = true, + MinItems = 1, + MaxItems = 1, + SchemaId = referenceSchemaId2.Id + }) + .SetFieldsInLists(FieldNames.Create("ref1", "ref2")); schemaProvider = x => { @@ -65,11 +69,11 @@ public class ResolveReferencesTests : GivenContext, IClassFixture requestCache.AddDependency(DomainId.Combine(AppId, referenceSchemaId1.Id), 0)) + A.CallTo(() => requestCache.AddDependency(DomainId.Combine(AppId, referenceSchemaId1.Id), 1)) .MustHaveHappened(); - A.CallTo(() => requestCache.AddDependency(DomainId.Combine(AppId, referenceSchemaId2.Id), 0)) + A.CallTo(() => requestCache.AddDependency(DomainId.Combine(AppId, referenceSchemaId2.Id), 1)) .MustHaveHappened(); A.CallTo(() => requestCache.AddDependency(ref1_1.UniqueId, ref1_1.Version)) @@ -122,10 +126,10 @@ public class ResolveReferencesTests : GivenContext, IClassFixture x.ToString())))), - AppId = AppId, - SchemaId = SchemaId, - Status = Status.Draft, - StatusColor = null!, - Version = 0 }; } - private IEnrichedContentEntity CreateRefContent(DomainId id, int version, string name, int number, NamedId refSchemaId) + private EnrichedContent CreateRefContent(int version, string name, int number, NamedId refSchemaId) { - return new ContentEntity + return CreateContent() with { - Id = id, + SchemaId = refSchemaId, Data = new ContentData() .AddField("name", @@ -307,11 +305,7 @@ public class ResolveReferencesTests : GivenContext, IClassFixture Schema.SchemaDef) - .Returns( - new Schema(SchemaId.Name) - .SetScripts(new SchemaScripts - { - Query = query, - QueryPre = queryPre - })); - } - - private ContentEntity CreateContent() - { - return new ContentEntity { Data = [], SchemaId = SchemaId }; - } - private ProvideSchema SchemaProvider() { return x => Task.FromResult((Schema, ResolvedComponents.Empty)); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesFluidExtensionTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesFluidExtensionTests.cs index 8c99d4e07..1825d5486 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesFluidExtensionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesFluidExtensionTests.cs @@ -115,9 +115,9 @@ public class ReferencesFluidExtensionTests : GivenContext Assert.Equal(Cleanup(expected), Cleanup(actual)); } - private static IEnrichedContentEntity CreateReference(DomainId referenceId, int index) + private EnrichedContent CreateReference(DomainId referenceId, int index) { - return new ContentEntity + return CreateContent() with { Data = new ContentData() diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesJintExtensionTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesJintExtensionTests.cs index ccbe4e5b9..ffb929820 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesJintExtensionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesJintExtensionTests.cs @@ -110,7 +110,7 @@ public class ReferencesJintExtensionTests : GivenContext, IClassFixture CreateReference(i + 1)).ToArray(); var referenceIds = references.Select(x => x.Id); @@ -139,9 +139,9 @@ public class ReferencesJintExtensionTests : GivenContext, IClassFixture id) + { + return schema.WithId(id.Id, id.Name); + } + + public static Schema WithId(this Schema schema, DomainId id, string name) + { + return schema with { Id = id, Name = name }; + } + + public static Asset WithId(this T asset, DomainId id) where T : Asset + { + return asset with { Id = id }; + } + + public static EnrichedAsset WithId(this EnrichedAsset content, DomainId id) + { + return content with { Id = id }; + } + + public static AssetFolder WithId(this AssetFolder folder, DomainId id) + { + return folder with { Id = id }; + } + + public static Content WithId(this Content content, DomainId id) + { + return content with { Id = id }; + } + + public static EnrichedContent WithId(this EnrichedContent content, DomainId id) + { + return content with { Id = id }; + } + + public static WriteContent WithId(this WriteContent content, DomainId id) + { + return content with { Id = id }; + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InvitationEventConsumerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InvitationEventConsumerTests.cs index 67ee6f659..12c2c5533 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InvitationEventConsumerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InvitationEventConsumerTests.cs @@ -7,10 +7,10 @@ using Microsoft.Extensions.Logging; using NodaTime; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Collaboration; -using Squidex.Domain.Apps.Entities.Teams; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Teams; @@ -276,7 +276,7 @@ public class InvitationEventConsumerTests : GivenContext var @event = CreateTeamEvent(true); A.CallTo(() => AppProvider.GetTeamAsync(A._, default)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); await sut.On(@event); @@ -297,10 +297,10 @@ public class InvitationEventConsumerTests : GivenContext private void MustNotSendEmail() { - A.CallTo(() => userNotifications.SendInviteAsync(A._, A._, A._, default)) + A.CallTo(() => userNotifications.SendInviteAsync(A._, A._, A._, default)) .MustNotHaveHappened(); - A.CallTo(() => userNotifications.SendInviteAsync(A._, A._, A._, default)) + A.CallTo(() => userNotifications.SendInviteAsync(A._, A._, A._, default)) .MustNotHaveHappened(); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InviteUserCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InviteUserCommandMiddlewareTests.cs index 5ce670fbd..a1c51b965 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InviteUserCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InviteUserCommandMiddlewareTests.cs @@ -5,9 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Teams; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure.Commands; using Squidex.Shared.Users; @@ -43,7 +43,7 @@ public class InviteUserCommandMiddlewareTests : GivenContext await sut.HandleAsync(context, CancellationToken); - Assert.Same(context.Result>().Entity, App); + Assert.Same(context.Result>().Entity, App); Assert.Equal(user.Id, command.ContributorId); A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(user.Email, true, CancellationToken)) @@ -66,7 +66,7 @@ public class InviteUserCommandMiddlewareTests : GivenContext await sut.HandleAsync(context, CancellationToken); - Assert.Same(context.Result>().Entity, Team); + Assert.Same(context.Result>().Entity, Team); Assert.Equal(user.Id, command.ContributorId); A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(user.Email, true, CancellationToken)) @@ -89,7 +89,7 @@ public class InviteUserCommandMiddlewareTests : GivenContext await sut.HandleAsync(context, CancellationToken); - Assert.Same(context.Result(), App); + Assert.Same(context.Result(), App); Assert.Equal(user.Id, command.ContributorId); A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(user.Email, true, CancellationToken)) @@ -112,7 +112,7 @@ public class InviteUserCommandMiddlewareTests : GivenContext await sut.HandleAsync(context, CancellationToken); - Assert.Same(context.Result(), Team); + Assert.Same(context.Result(), Team); Assert.Equal(user.Id, command.ContributorId); A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(user.Email, true, CancellationToken)) diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/BackupRulesTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/BackupRulesTests.cs index c4f51c750..ec7e1f397 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/BackupRulesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/BackupRulesTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Backup; using Squidex.Domain.Apps.Entities.Rules.DomainObject; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -62,7 +63,7 @@ public class BackupRulesTests : GivenContext var rebuildAssets = new HashSet(); - A.CallTo(() => rebuilder.InsertManyAsync(A>._, A._, CancellationToken)) + A.CallTo(() => rebuilder.InsertManyAsync(A>._, A._, CancellationToken)) .Invokes(x => rebuildAssets.AddRange(x.GetArgument>(0)!)); await sut.RestoreAsync(context, CancellationToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/Guards/GuardRuleTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/Guards/GuardRuleTests.cs index 884e93f78..d535a8483 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/Guards/GuardRuleTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/Guards/GuardRuleTests.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Rules.DomainObject.Guards; @@ -81,7 +80,7 @@ public class GuardRuleTests : GivenContext, IClassFixture { var command = new UpdateRule(); - await GuardRule.CanUpdate(command, Rule(), AppProvider); + await GuardRule.CanUpdate(command, CreateRule(), AppProvider); } [Fact] @@ -89,11 +88,11 @@ public class GuardRuleTests : GivenContext, IClassFixture { var command = new UpdateRule { Name = "MyName" }; - await GuardRule.CanUpdate(command, Rule(), AppProvider); + await GuardRule.CanUpdate(command, CreateRule(), AppProvider); } [Fact] - public async Task CanUpdate_should_not_throw_exception_if_trigger_action__and_name_are_valid() + public async Task CanUpdate_should_not_throw_exception_if_trigger_action_and_name_are_valid() { var command = new UpdateRule { @@ -108,7 +107,7 @@ public class GuardRuleTests : GivenContext, IClassFixture Name = "NewName" }; - await GuardRule.CanUpdate(command, Rule(), AppProvider); + await GuardRule.CanUpdate(command, CreateRule(), AppProvider); } private CreateRule CreateCommand(CreateRule command) @@ -117,13 +116,4 @@ public class GuardRuleTests : GivenContext, IClassFixture return command; } - - private IRuleEntity Rule() - { - var rule = A.Fake(); - - A.CallTo(() => rule.AppId).Returns(AppId); - - return rule; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/Guards/Triggers/ContentChangedTriggerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/Guards/Triggers/ContentChangedTriggerTests.cs index 6f7e96015..3dc9c32f0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/Guards/Triggers/ContentChangedTriggerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/Guards/Triggers/ContentChangedTriggerTests.cs @@ -6,8 +6,8 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Collections; @@ -41,7 +41,7 @@ public class ContentChangedTriggerTests : GivenContext, IClassFixture AppProvider.GetSchemaAsync(AppId.Id, SchemaId.Id, false, default)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var trigger = new ContentChangedTriggerV2 { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleCommandMiddlewareTests.cs index c185c0907..a86e0899e 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleCommandMiddlewareTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; @@ -12,7 +13,7 @@ using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Rules.DomainObject; -public sealed class RuleCommandMiddlewareTests : HandlerTestBase +public sealed class RuleCommandMiddlewareTests : HandlerTestBase { private readonly IDomainObjectFactory domainObjectFactory = A.Fake(); private readonly IRuleEnricher ruleEnricher = A.Fake(); @@ -34,35 +35,35 @@ public sealed class RuleCommandMiddlewareTests : HandlerTestBase ruleEnricher.EnrichAsync(A._, ApiContext, A._)) + A.CallTo(() => ruleEnricher.EnrichAsync(A._, ApiContext, A._)) .MustNotHaveHappened(); } [Fact] public async Task Should_not_invoke_enricher_if_already_enriched() { - var rule = new RuleEntity(); + var rule = CreateRule(); var context = await HandleAsync(new EnableRule(), rule); - Assert.Same(rule, context.Result()); + Assert.Same(rule, context.Result()); - A.CallTo(() => ruleEnricher.EnrichAsync(A._, ApiContext, A._)) + A.CallTo(() => ruleEnricher.EnrichAsync(A._, ApiContext, A._)) .MustNotHaveHappened(); } [Fact] public async Task Should_enrich_rule() { - var rule = A.Fake(); + var rule = new Rule(); - var enriched = new RuleEntity(); + var enriched = CreateRule(); A.CallTo(() => ruleEnricher.EnrichAsync(rule, ApiContext, CancellationToken)) .Returns(enriched); @@ -71,7 +72,7 @@ public sealed class RuleCommandMiddlewareTests : HandlerTestBase()); + Assert.Same(enriched, context.Result()); } private Task HandleAsync(RuleCommand command, object actual) diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleDomainObjectTests.cs index 79a6fc8af..664c7af18 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleDomainObjectTests.cs @@ -18,7 +18,7 @@ using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Entities.Rules.DomainObject; -public class RuleDomainObjectTests : HandlerTestBase +public class RuleDomainObjectTests : HandlerTestBase { private readonly IRuleEnqueuer ruleEnqueuer = A.Fake(); private readonly DomainId ruleId = DomainId.NewGuid(); @@ -70,8 +70,8 @@ public class RuleDomainObjectTests : HandlerTestBase Assert.Equal(AppId, sut.Snapshot.AppId); - Assert.Same(command.Trigger, sut.Snapshot.RuleDef.Trigger); - Assert.Same(command.Action, sut.Snapshot.RuleDef.Action); + Assert.Same(command.Trigger, sut.Snapshot.Trigger); + Assert.Same(command.Action, sut.Snapshot.Action); LastEvents .ShouldHaveSameEvents( @@ -90,12 +90,12 @@ public class RuleDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.True(sut.Snapshot.RuleDef.IsEnabled); + Assert.True(sut.Snapshot.IsEnabled); - Assert.Same(command.Trigger, sut.Snapshot.RuleDef.Trigger); - Assert.Same(command.Action, sut.Snapshot.RuleDef.Action); + Assert.Same(command.Trigger, sut.Snapshot.Trigger); + Assert.Same(command.Action, sut.Snapshot.Action); - Assert.Equal(command.Name, sut.Snapshot.RuleDef.Name); + Assert.Equal(command.Name, sut.Snapshot.Name); LastEvents .ShouldHaveSameEvents( @@ -115,7 +115,7 @@ public class RuleDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.True(sut.Snapshot.RuleDef.IsEnabled); + Assert.True(sut.Snapshot.IsEnabled); LastEvents .ShouldHaveSameEvents( @@ -138,7 +138,7 @@ public class RuleDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.True(sut.Snapshot.RuleDef.IsEnabled); + Assert.True(sut.Snapshot.IsEnabled); LastEvents .ShouldHaveSameEvents( @@ -157,7 +157,7 @@ public class RuleDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.False(sut.Snapshot.RuleDef.IsEnabled); + Assert.False(sut.Snapshot.IsEnabled); LastEvents .ShouldHaveSameEvents( @@ -179,7 +179,7 @@ public class RuleDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.False(sut.Snapshot.RuleDef.IsEnabled); + Assert.False(sut.Snapshot.IsEnabled); LastEvents .ShouldHaveSameEvents( @@ -217,7 +217,7 @@ public class RuleDomainObjectTests : HandlerTestBase Assert.Equal(0, sut.Version); - A.CallTo(() => ruleEnqueuer.EnqueueAsync(sut.Snapshot.Id, sut.Snapshot.RuleDef, + A.CallTo(() => ruleEnqueuer.EnqueueAsync(sut.Snapshot.Id, sut.Snapshot, A>.That.Matches(x => x.Payload is RuleManuallyTriggered))) .MustHaveHappened(); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleStateTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleStateTests.cs deleted file mode 100644 index faa88be8e..000000000 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/DomainObject/RuleStateTests.cs +++ /dev/null @@ -1,63 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Text.Json.Serialization; -using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Infrastructure.Json; - -namespace Squidex.Domain.Apps.Entities.Rules.DomainObject; - -public class RuleStateTests -{ - private readonly IJsonSerializer serializer = TestUtils.CreateSerializer(options => - { - options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; - }); - - [RuleAction] - public sealed record WebhookAction : RuleAction - { - public Uri Url { get; set; } - } - - static RuleStateTests() - { - TestUtils.TypeRegistry.Map(new RuleTypeProvider().Add()); - } - - [Fact] - public void Should_deserialize_state() - { - var json = File.ReadAllText("Rules/DomainObject/RuleState.json"); - - var deserialized = serializer.Deserialize(json); - - Assert.NotNull(deserialized); - } - - [Fact] - public void Should_deserialize_state_from_old_representation() - { - var json = File.ReadAllText("Rules/DomainObject/RuleState_Old.json"); - - var deserialized = serializer.Deserialize(json); - - Assert.NotNull(deserialized); - } - - [Fact] - public void Should_serialize_deserialize_state() - { - var json = File.ReadAllText("Rules/DomainObject/RuleState.json").CleanJson(); - - var serialized = serializer.Serialize(serializer.Deserialize(json), true); - - Assert.Equal(json, serialized); - } -} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesIndexTests.cs index 893c5b3fb..08f430d6e 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesIndexTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Indexes/RulesIndexTests.cs @@ -23,7 +23,7 @@ public class RulesIndexTests : GivenContext [Fact] public async Task Should_resolve_rules_by_id() { - var rule = SetupRule(0); + var rule = CreateRule(); A.CallTo(() => ruleRepository.QueryAllAsync(AppId.Id, CancellationToken)) .Returns([rule]); @@ -36,7 +36,7 @@ public class RulesIndexTests : GivenContext [Fact] public async Task Should_return_empty_rules_if_rule_not_created() { - var rule = SetupRule(-1); + var rule = CreateRule() with { Version = -1 }; A.CallTo(() => ruleRepository.QueryAllAsync(AppId.Id, CancellationToken)) .Returns([rule]); @@ -49,7 +49,7 @@ public class RulesIndexTests : GivenContext [Fact] public async Task Should_return_empty_rules_if_rule_deleted() { - var rule = SetupRule(0, true); + var rule = CreateRule() with { IsDeleted = true }; A.CallTo(() => ruleRepository.QueryAllAsync(AppId.Id, CancellationToken)) .Returns([rule]); @@ -58,9 +58,4 @@ public class RulesIndexTests : GivenContext Assert.Empty(actual); } - - private IRuleEntity SetupRule(long version, bool isDeleted = false) - { - return new RuleEntity { AppId = AppId, Version = version, IsDeleted = isDeleted }; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleEnricherTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleEnricherTests.cs index c52bcd2d8..5116a0d9c 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleEnricherTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleEnricherTests.cs @@ -26,7 +26,7 @@ public class RuleEnricherTests : GivenContext [Fact] public async Task Should_not_enrich_if_statistics_not_found() { - var source = CreateRule(); + var source = CreateRule() with { Version = 13 }; var actual = await sut.EnrichAsync(source, ApiContext, CancellationToken); @@ -43,7 +43,7 @@ public class RuleEnricherTests : GivenContext [Fact] public async Task Should_enrich_rules_with_found_statistics() { - var source = CreateRule(); + var source = CreateRule() with { Version = 13 }; var stats = new Dictionary { @@ -67,9 +67,4 @@ public class RuleEnricherTests : GivenContext A.CallTo(() => requestCache.AddDependency(12L)) .MustHaveHappened(); } - - private IRuleEntity CreateRule() - { - return new RuleEntity { AppId = AppId, Id = DomainId.NewGuid(), Version = 13 }; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleQueryServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleQueryServiceTests.cs index 834a7707e..a7f835789 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleQueryServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Queries/RuleQueryServiceTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Rules.Indexes; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -24,14 +25,14 @@ public class RuleQueryServiceTests : GivenContext [Fact] public async Task Should_get_rules_from_index_and_enrich() { - var original = new List + var original = new List { - new RuleEntity() + new Rule() }; - var enriched = new List + var enriched = new List { - new RuleEntity() + CreateRule() }; A.CallTo(() => rulesIndex.GetRulesAsync(AppId.Id, CancellationToken)) diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs index 1048b5364..abd8d8c91 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs @@ -12,7 +12,6 @@ using NodaTime; using Squidex.Caching; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Contents; @@ -83,7 +82,7 @@ public class RuleEnqueuerTests : GivenContext A.CallTo(() => ruleService.CreateJobsAsync(@event, MatchingContext(rule), default)) .Returns(Enumerable.Repeat(new JobResult(), 1).ToAsyncEnumerable()); - await sut.EnqueueAsync(rule.Id, rule.RuleDef, @event); + await sut.EnqueueAsync(rule.Id, rule, @event); A.CallTo(() => ruleEventRepository.EnqueueAsync(A>._, default)) .MustNotHaveHappened(); @@ -97,14 +96,9 @@ public class RuleEnqueuerTests : GivenContext var rule = CreateRule(); A.CallTo(() => ruleService.CreateJobsAsync(@event, MatchingContext(rule), default)) - .Returns(Enumerable.Repeat(new JobResult - { - Rule = rule.RuleDef, - RuleId = rule.Id, - SkipReason = SkipReason.WrongEvent - }, 1).ToAsyncEnumerable()); + .Returns(Enumerable.Repeat(new JobResult { Rule = rule, SkipReason = SkipReason.WrongEvent }, 1).ToAsyncEnumerable()); - await sut.EnqueueAsync(rule.Id, rule.RuleDef, @event); + await sut.EnqueueAsync(rule.Id, rule, @event); A.CallTo(() => ruleEventRepository.EnqueueAsync(A>._, default)) .MustNotHaveHappened(); @@ -131,14 +125,9 @@ public class RuleEnqueuerTests : GivenContext .Invokes(x => writes = x.GetArgument>(0)?.ToArray()); A.CallTo(() => ruleService.CreateJobsAsync(@event, MatchingContext(rule), default)) - .Returns(Enumerable.Repeat(new JobResult - { - Job = job, - Rule = rule.RuleDef, - RuleId = rule.Id - }, 1).ToAsyncEnumerable()); + .Returns(Enumerable.Repeat(new JobResult { Job = job, Rule = rule }, 1).ToAsyncEnumerable()); - await sut.EnqueueAsync(rule.Id, rule.RuleDef, @event); + await sut.EnqueueAsync(rule.Id, rule, @event); Assert.Equal(new[] { new RuleEventWrite(job, job.Created) }, writes); @@ -167,15 +156,9 @@ public class RuleEnqueuerTests : GivenContext .Invokes(x => writes = x.GetArgument>(0)?.ToArray()); A.CallTo(() => ruleService.CreateJobsAsync(@event, MatchingContext(rule), default)) - .Returns(Enumerable.Repeat(new JobResult - { - Job = job, - Rule = rule.RuleDef, - RuleId = rule.Id, - SkipReason = SkipReason.Failed - }, 1).ToAsyncEnumerable()); + .Returns(Enumerable.Repeat(new JobResult { Job = job, Rule = rule, SkipReason = SkipReason.Failed }, 1).ToAsyncEnumerable()); - await sut.EnqueueAsync(rule.Id, rule.RuleDef, @event); + await sut.EnqueueAsync(rule.Id, rule, @event); Assert.Equal(new[] { new RuleEventWrite(job) }, writes); @@ -292,27 +275,14 @@ public class RuleEnqueuerTests : GivenContext .Returns([rule]); A.CallTo(() => ruleService.CreateJobsAsync(@event, MatchingContext(rule), default)) - .Returns(Enumerable.Repeat(new JobResult - { - Job = job, - Rule = rule.RuleDef, - RuleId = rule.Id, - SkipReason = skipReason - }, 1).ToAsyncEnumerable()); + .Returns(Enumerable.Repeat(new JobResult { Job = job, Rule = rule, SkipReason = skipReason }, 1).ToAsyncEnumerable()); } - private static RuleEntity CreateRule() - { - var rule = new Rule(new ContentChangedTriggerV2(), new TestAction { Url = new Uri("https://squidex.io") }); - - return new RuleEntity { RuleDef = rule, Id = DomainId.NewGuid() }; - } - - private static RulesContext MatchingContext(RuleEntity rule) + private static RulesContext MatchingContext(Rule rule) { // These two properties must not be set to true for performance reasons. return A.That.Matches(x => - x.Rules.Values.Contains(rule.RuleDef) && + x.Rules.Values.Contains(rule) && !x.IncludeSkipped && !x.IncludeStale); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/UsageTracking/UsageTriggerHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/UsageTracking/UsageTriggerHandlerTests.cs index 104302b7d..e2ea4dc21 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/UsageTracking/UsageTriggerHandlerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/UsageTracking/UsageTriggerHandlerTests.cs @@ -9,14 +9,14 @@ using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Contents; -using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking; -public class UsageTriggerHandlerTests +public class UsageTriggerHandlerTests : GivenContext { private readonly IRuleTriggerHandler sut = new UsageTriggerHandler(); @@ -70,22 +70,23 @@ public class UsageTriggerHandlerTests { var ctx = Context(); - var @event = new AppUsageExceeded { RuleId = ctx.RuleId }; + var @event = new AppUsageExceeded { RuleId = ctx.Rule.Id }; var actual = sut.Trigger(Envelope.Create(@event), ctx.Rule.Trigger); Assert.True(actual); } - private static RuleContext Context(RuleTrigger? trigger = null) + private RuleContext Context(RuleTrigger? trigger = null) { trigger ??= new UsageTrigger(); return new RuleContext { - AppId = NamedId.Of(DomainId.NewGuid(), "my-app"), - Rule = new Rule(trigger, A.Fake()), - RuleId = DomainId.NewGuid() + AppId = AppId, + IncludeSkipped = false, + IncludeStale = false, + Rule = CreateRule() with { Trigger = trigger } }; } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/BackupSchemasTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/BackupSchemasTests.cs index 32ec63b1b..32e5b47f0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/BackupSchemasTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/BackupSchemasTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Backup; using Squidex.Domain.Apps.Entities.Schemas.DomainObject; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -63,7 +64,7 @@ public class BackupSchemasTests : GivenContext var rebuildContents = new HashSet(); - A.CallTo(() => rebuilder.InsertManyAsync(A>._, A._, CancellationToken)) + A.CallTo(() => rebuilder.InsertManyAsync(A>._, A._, CancellationToken)) .Invokes(x => rebuildContents.AddRange(x.GetArgument>(0)!)); await sut.RestoreAsync(context, CancellationToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/Guards/GuardSchemaFieldTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/Guards/GuardSchemaFieldTests.cs index 85d8ce039..dcd437ccf 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/Guards/GuardSchemaFieldTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/Guards/GuardSchemaFieldTests.cs @@ -17,21 +17,19 @@ using Squidex.Infrastructure.Validation; namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject.Guards; -public class GuardSchemaFieldTests : IClassFixture +public class GuardSchemaFieldTests : GivenContext, IClassFixture { - private readonly Schema schema_0; private readonly StringFieldProperties validProperties = new StringFieldProperties(); private readonly StringFieldProperties invalidProperties = new StringFieldProperties { MinLength = 10, MaxLength = 5 }; public GuardSchemaFieldTests() { - schema_0 = - new Schema("my-schema") - .AddString(1, "field1", Partitioning.Invariant) - .AddString(2, "field2", Partitioning.Invariant) - .AddArray(3, "field3", Partitioning.Invariant, f => f - .AddNumber(301, "field301")) - .AddUI(4, "field4", Partitioning.Invariant); + Schema = Schema + .AddString(1, "field1", Partitioning.Invariant) + .AddString(2, "field2", Partitioning.Invariant) + .AddArray(3, "field3", Partitioning.Invariant, f => f + .AddNumber(301, "field301")) + .AddUI(4, "field4", Partitioning.Invariant); } private static Action A(Action method) where T : FieldCommand @@ -56,7 +54,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new T { FieldId = 5 }; - Assert.Throws(() => action(command, schema_0)); + Assert.Throws(() => action(command, Schema)); } [Theory] @@ -65,7 +63,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new T { ParentFieldId = 4, FieldId = 401 }; - Assert.Throws(() => action(command, schema_0)); + Assert.Throws(() => action(command, Schema)); } [Theory] @@ -74,7 +72,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new T { ParentFieldId = 3, FieldId = 302 }; - Assert.Throws(() => action(command, schema_0)); + Assert.Throws(() => action(command, Schema)); } [Fact] @@ -82,9 +80,9 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new DisableField { FieldId = 1 }; - var schema_1 = schema_0.UpdateField(1, f => f.Disable()); + Schema = Schema.UpdateField(1, f => f.Disable()); - GuardSchemaField.CanDisable(command, schema_1); + GuardSchemaField.CanDisable(command, Schema); } [Fact] @@ -92,9 +90,9 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new DisableField { FieldId = 1 }; - var schema_1 = schema_0.UpdateField(1, f => f.Lock()); + Schema = Schema.UpdateField(1, f => f.Lock()); - Assert.Throws(() => GuardSchemaField.CanDisable(command, schema_1)); + Assert.Throws(() => GuardSchemaField.CanDisable(command, Schema)); } [Fact] @@ -102,7 +100,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new DisableField { FieldId = 4 }; - Assert.Throws(() => GuardSchemaField.CanDisable(command, schema_0)); + Assert.Throws(() => GuardSchemaField.CanDisable(command, Schema)); } [Fact] @@ -110,9 +108,9 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new EnableField { FieldId = 1 }; - var schema_1 = schema_0.UpdateField(1, f => f.Enable()); + Schema = Schema.UpdateField(1, f => f.Enable()); - GuardSchemaField.CanEnable(command, schema_1); + GuardSchemaField.CanEnable(command, Schema); } [Fact] @@ -120,9 +118,9 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new EnableField { FieldId = 1 }; - var schema_1 = schema_0.UpdateField(1, f => f.Lock()); + Schema = Schema.UpdateField(1, f => f.Lock()); - Assert.Throws(() => GuardSchemaField.CanEnable(command, schema_1)); + Assert.Throws(() => GuardSchemaField.CanEnable(command, Schema)); } [Fact] @@ -130,7 +128,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new EnableField { FieldId = 4 }; - Assert.Throws(() => GuardSchemaField.CanEnable(command, schema_0)); + Assert.Throws(() => GuardSchemaField.CanEnable(command, Schema)); } [Fact] @@ -138,9 +136,9 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new EnableField { FieldId = 1 }; - var schema_1 = schema_0.UpdateField(1, f => f.Hide()); + Schema = Schema.UpdateField(1, f => f.Hide()); - GuardSchemaField.CanEnable(command, schema_1); + GuardSchemaField.CanEnable(command, Schema); } [Fact] @@ -148,9 +146,9 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new HideField { FieldId = 1 }; - var schema_1 = schema_0.UpdateField(1, f => f.Lock()); + Schema = Schema.UpdateField(1, f => f.Lock()); - Assert.Throws(() => GuardSchemaField.CanHide(command, schema_1)); + Assert.Throws(() => GuardSchemaField.CanHide(command, Schema)); } [Fact] @@ -158,7 +156,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new HideField { FieldId = 4 }; - Assert.Throws(() => GuardSchemaField.CanHide(command, schema_0)); + Assert.Throws(() => GuardSchemaField.CanHide(command, Schema)); } [Fact] @@ -166,9 +164,9 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new EnableField { FieldId = 1 }; - var schema_1 = schema_0.UpdateField(1, f => f.Show()); + Schema = Schema.UpdateField(1, f => f.Show()); - GuardSchemaField.CanEnable(command, schema_1); + GuardSchemaField.CanEnable(command, Schema); } [Fact] @@ -176,9 +174,9 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new ShowField { FieldId = 1 }; - var schema_1 = schema_0.UpdateField(1, f => f.Lock()); + Schema = Schema.UpdateField(1, f => f.Lock()); - Assert.Throws(() => GuardSchemaField.CanShow(command, schema_1)); + Assert.Throws(() => GuardSchemaField.CanShow(command, Schema)); } [Fact] @@ -186,7 +184,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new ShowField { FieldId = 4 }; - Assert.Throws(() => GuardSchemaField.CanShow(command, schema_0)); + Assert.Throws(() => GuardSchemaField.CanShow(command, Schema)); } [Fact] @@ -194,9 +192,9 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new DeleteField { FieldId = 1 }; - var schema_1 = schema_0.UpdateField(1, f => f.Lock()); + Schema = Schema.UpdateField(1, f => f.Lock()); - Assert.Throws(() => GuardSchemaField.CanDelete(command, schema_1)); + Assert.Throws(() => GuardSchemaField.CanDelete(command, Schema)); } [Fact] @@ -204,7 +202,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new DeleteField { FieldId = 1 }; - GuardSchemaField.CanDelete(command, schema_0); + GuardSchemaField.CanDelete(command, Schema); } [Fact] @@ -212,9 +210,9 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new UpdateField { FieldId = 1, Properties = validProperties }; - var schema_1 = schema_0.UpdateField(1, f => f.Lock()); + Schema = Schema.UpdateField(1, f => f.Lock()); - Assert.Throws(() => GuardSchemaField.CanUpdate(command, schema_1)); + Assert.Throws(() => GuardSchemaField.CanUpdate(command, Schema)); } [Fact] @@ -222,7 +220,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new UpdateField { FieldId = 1, Properties = validProperties }; - GuardSchemaField.CanUpdate(command, schema_0); + GuardSchemaField.CanUpdate(command, Schema); } [Fact] @@ -230,7 +228,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new UpdateField { FieldId = 2, Properties = null! }; - ValidationAssert.Throws(() => GuardSchemaField.CanUpdate(command, schema_0), + ValidationAssert.Throws(() => GuardSchemaField.CanUpdate(command, Schema), new ValidationError("Properties is required.", "Properties")); } @@ -239,7 +237,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new UpdateField { FieldId = 2, Properties = new StringFieldProperties { MinLength = 10, MaxLength = 5 } }; - ValidationAssert.Throws(() => GuardSchemaField.CanUpdate(command, schema_0), + ValidationAssert.Throws(() => GuardSchemaField.CanUpdate(command, Schema), new ValidationError("Max length must be greater or equal to min length.", "Properties.MinLength", "Properties.MaxLength")); } @@ -248,7 +246,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new AddField { Name = "field1", Properties = validProperties }; - ValidationAssert.Throws(() => GuardSchemaField.CanAdd(command, schema_0), + ValidationAssert.Throws(() => GuardSchemaField.CanAdd(command, Schema), new ValidationError("A field with the same name already exists.")); } @@ -257,7 +255,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new AddField { Name = "field301", Properties = validProperties, ParentFieldId = 3 }; - ValidationAssert.Throws(() => GuardSchemaField.CanAdd(command, schema_0), + ValidationAssert.Throws(() => GuardSchemaField.CanAdd(command, Schema), new ValidationError("A field with the same name already exists.")); } @@ -266,7 +264,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new AddField { Name = "INVALID_NAME", Properties = validProperties }; - ValidationAssert.Throws(() => GuardSchemaField.CanAdd(command, schema_0), + ValidationAssert.Throws(() => GuardSchemaField.CanAdd(command, Schema), new ValidationError("Name is not a Javascript property name.", "Name")); } @@ -275,7 +273,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new AddField { Name = "field5", Properties = invalidProperties }; - ValidationAssert.Throws(() => GuardSchemaField.CanAdd(command, schema_0), + ValidationAssert.Throws(() => GuardSchemaField.CanAdd(command, Schema), new ValidationError("Max length must be greater or equal to min length.", "Properties.MinLength", "Properties.MaxLength")); } @@ -284,7 +282,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new AddField { Name = "field5", Properties = null! }; - ValidationAssert.Throws(() => GuardSchemaField.CanAdd(command, schema_0), + ValidationAssert.Throws(() => GuardSchemaField.CanAdd(command, Schema), new ValidationError("Properties is required.", "Properties")); } @@ -293,7 +291,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new AddField { Name = "field5", Partitioning = "INVALID_PARTITIONING", Properties = validProperties }; - ValidationAssert.Throws(() => GuardSchemaField.CanAdd(command, schema_0), + ValidationAssert.Throws(() => GuardSchemaField.CanAdd(command, Schema), new ValidationError("Partitioning is not a valid value.", "Partitioning")); } @@ -302,7 +300,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new AddField { Name = "field302", Properties = validProperties, ParentFieldId = 99 }; - Assert.Throws(() => GuardSchemaField.CanAdd(command, schema_0)); + Assert.Throws(() => GuardSchemaField.CanAdd(command, Schema)); } [Fact] @@ -310,7 +308,7 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new AddField { Name = "field5", Properties = validProperties }; - GuardSchemaField.CanAdd(command, schema_0); + GuardSchemaField.CanAdd(command, Schema); } [Fact] @@ -318,6 +316,6 @@ public class GuardSchemaFieldTests : IClassFixture { var command = new AddField { Name = "field1", Properties = validProperties, ParentFieldId = 3 }; - GuardSchemaField.CanAdd(command, schema_0); + GuardSchemaField.CanAdd(command, Schema); } -} \ No newline at end of file +} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/Guards/GuardSchemaTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/Guards/GuardSchemaTests.cs index 42bca4bad..79d87136a 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/Guards/GuardSchemaTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/Guards/GuardSchemaTests.cs @@ -20,15 +20,12 @@ namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject.Guards; public class GuardSchemaTests : GivenContext, IClassFixture { - private readonly Schema schema_0; - public GuardSchemaTests() { - schema_0 = - new Schema("my-schema") - .AddString(1, "field1", Partitioning.Invariant) - .AddString(2, "field2", Partitioning.Invariant) - .AddUI(4, "field4", Partitioning.Invariant); + Schema = Schema + .AddString(1, "field1", Partitioning.Invariant) + .AddString(2, "field2", Partitioning.Invariant) + .AddUI(4, "field4", Partitioning.Invariant); } [Fact] @@ -329,8 +326,8 @@ public class GuardSchemaTests : GivenContext, IClassFixture Partitioning = Partitioning.Invariant.Key }, ], - FieldsInLists = FieldNames.Create("field1"), - FieldsInReferences = FieldNames.Create("field1"), + FieldsInLists = FieldNames.Create("data.field1"), + FieldsInReferences = FieldNames.Create("data.field1"), Name = "new-schema" }); @@ -365,7 +362,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture Partitioning = Partitioning.Invariant.Key }, ], - FieldsInLists = FieldNames.Create(null!, null!, "field3", "field1", "field1", "field4"), + FieldsInLists = FieldNames.Create(null!, null!, "data.field3", "data.field1", "data.field1", "data.field4"), FieldsInReferences = null, Name = "new-schema" }); @@ -379,7 +376,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture "FieldsInLists[2]"), new ValidationError("Field cannot be an UI field.", "FieldsInLists[5]"), - new ValidationError("Field 'field1' has been added twice.", + new ValidationError("Field 'data.field1' has been added twice.", "FieldsInLists")); } @@ -404,7 +401,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture }, ], FieldsInLists = null, - FieldsInReferences = FieldNames.Create(null!, null!, "field3", "field1", "field1", "field4"), + FieldsInReferences = FieldNames.Create(null!, null!, "data.field3", "data.field1", "data.field1", "data.field4"), Name = "new-schema" }); @@ -417,7 +414,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture "FieldsInReferences[2]"), new ValidationError("Field cannot be an UI field.", "FieldsInReferences[5]"), - new ValidationError("Field 'field1' has been added twice.", + new ValidationError("Field 'data.field1' has been added twice.", "FieldsInReferences")); } @@ -427,7 +424,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture var command = CreateCommand(new CreateSchema { FieldsInLists = null, - FieldsInReferences = FieldNames.Create("meta.id"), + FieldsInReferences = FieldNames.Create("id"), Name = "new-schema" }); @@ -477,8 +474,8 @@ public class GuardSchemaTests : GivenContext, IClassFixture ] }, ], - FieldsInLists = FieldNames.Create("field1", "meta.id"), - FieldsInReferences = FieldNames.Create("field1"), + FieldsInLists = FieldNames.Create("data.field1", "id"), + FieldsInReferences = FieldNames.Create("data.field1"), Name = "new-schema" }); @@ -490,11 +487,11 @@ public class GuardSchemaTests : GivenContext, IClassFixture { var command = new ConfigureUIFields { - FieldsInLists = FieldNames.Create(null!, null!, "field3", "field1", "field1", "field4"), + FieldsInLists = FieldNames.Create(null!, null!, "data.field3", "data.field1", "data.field1", "data.field4"), FieldsInReferences = null }; - ValidationAssert.Throws(() => GuardSchema.CanConfigureUIFields(command, schema_0), + ValidationAssert.Throws(() => GuardSchema.CanConfigureUIFields(command, Schema), new ValidationError("Field is required.", "FieldsInLists[0]"), new ValidationError("Field is required.", @@ -503,7 +500,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture "FieldsInLists[2]"), new ValidationError("Field cannot be an UI field.", "FieldsInLists[5]"), - new ValidationError("Field 'field1' has been added twice.", + new ValidationError("Field 'data.field1' has been added twice.", "FieldsInLists")); } @@ -513,10 +510,10 @@ public class GuardSchemaTests : GivenContext, IClassFixture var command = new ConfigureUIFields { FieldsInLists = null, - FieldsInReferences = FieldNames.Create(null!, null!, "field3", "field1", "field1", "field4") + FieldsInReferences = FieldNames.Create(null!, null!, "data.field3", "data.field1", "data.field1", "data.field4") }; - ValidationAssert.Throws(() => GuardSchema.CanConfigureUIFields(command, schema_0), + ValidationAssert.Throws(() => GuardSchema.CanConfigureUIFields(command, Schema), new ValidationError("Field is required.", "FieldsInReferences[0]"), new ValidationError("Field is required.", @@ -525,7 +522,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture "FieldsInReferences[2]"), new ValidationError("Field cannot be an UI field.", "FieldsInReferences[5]"), - new ValidationError("Field 'field1' has been added twice.", + new ValidationError("Field 'data.field1' has been added twice.", "FieldsInReferences")); } @@ -538,7 +535,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture FieldsInReferences = FieldNames.Create("meta.id") }; - ValidationAssert.Throws(() => GuardSchema.CanConfigureUIFields(command, schema_0), + ValidationAssert.Throws(() => GuardSchema.CanConfigureUIFields(command, Schema), new ValidationError("Field is not part of the schema.", "FieldsInReferences[0]")); } @@ -548,11 +545,11 @@ public class GuardSchemaTests : GivenContext, IClassFixture { var command = new ConfigureUIFields { - FieldsInLists = FieldNames.Create("field1", "meta.id"), - FieldsInReferences = FieldNames.Create("field2") + FieldsInLists = FieldNames.Create("data.field1", "id"), + FieldsInReferences = FieldNames.Create("data.field2") }; - GuardSchema.CanConfigureUIFields(command, schema_0); + GuardSchema.CanConfigureUIFields(command, Schema); } [Fact] @@ -605,7 +602,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture { var command = new ReorderFields { FieldIds = [1L, 3L] }; - ValidationAssert.Throws(() => GuardSchema.CanReorder(command, schema_0), + ValidationAssert.Throws(() => GuardSchema.CanReorder(command, Schema), new ValidationError("Field ids do not cover all fields.", "FieldIds")); } @@ -614,7 +611,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture { var command = new ReorderFields { FieldIds = [1L] }; - ValidationAssert.Throws(() => GuardSchema.CanReorder(command, schema_0), + ValidationAssert.Throws(() => GuardSchema.CanReorder(command, Schema), new ValidationError("Field ids do not cover all fields.", "FieldIds")); } @@ -623,7 +620,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture { var command = new ReorderFields { FieldIds = null! }; - ValidationAssert.Throws(() => GuardSchema.CanReorder(command, schema_0), + ValidationAssert.Throws(() => GuardSchema.CanReorder(command, Schema), new ValidationError("Field IDs is required.", "FieldIds")); } @@ -632,7 +629,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture { var command = new ReorderFields { FieldIds = [1L, 2L], ParentFieldId = 99 }; - Assert.Throws(() => GuardSchema.CanReorder(command, schema_0)); + Assert.Throws(() => GuardSchema.CanReorder(command, Schema)); } [Fact] @@ -640,7 +637,7 @@ public class GuardSchemaTests : GivenContext, IClassFixture { var command = new ReorderFields { FieldIds = [1L, 2L, 4L] }; - GuardSchema.CanReorder(command, schema_0); + GuardSchema.CanReorder(command, Schema); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaDomainObjectTests.cs index c8fba1ad7..71813b157 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaDomainObjectTests.cs @@ -17,7 +17,7 @@ using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject; -public class SchemaDomainObjectTests : HandlerTestBase +public class SchemaDomainObjectTests : HandlerTestBase { private readonly string fieldName = "age"; private readonly string arrayName = "array"; @@ -52,49 +52,57 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task Create_should_create_events_and_set_intitial_state() { - var properties = new SchemaProperties(); - - var command = new CreateSchema { Name = SchemaId.Name, SchemaId = SchemaId.Id, Properties = properties, Type = SchemaType.Singleton }; + var command = new CreateSchema + { + Name = SchemaId.Name, + SchemaId = SchemaId.Id, + Scripts = null!, + Properties = new SchemaProperties() + }; var actual = await PublishAsync(command); actual.ShouldBeEquivalent(sut.Snapshot); Assert.Equal(AppId, sut.Snapshot.AppId); + Assert.Equal(SchemaId.Id, sut.Snapshot.Id); + Assert.Equal(SchemaId.Name, sut.Snapshot.Name); + Assert.Equal(SchemaType.Default, sut.Snapshot.Type); - Assert.Equal(SchemaId.Name, sut.Snapshot.SchemaDef.Name); - Assert.Equal(SchemaType.Singleton, sut.Snapshot.SchemaDef.Type); + var schema = new Schema { Name = command.Name, Properties = command.Properties }; LastEvents .ShouldHaveSameEvents( - CreateEvent(new SchemaCreated { Schema = new Schema(command.Name, command.Properties, SchemaType.Singleton) }) + CreateEvent(new SchemaCreated { Schema = schema }) ); } [Fact] public async Task Create_should_create_events_and_schema_with_initial_fields() { - var properties = new SchemaProperties(); - - var fields = new[] + var command = new CreateSchema { - new UpsertSchemaField { Name = "field1", Properties = ValidProperties() }, - new UpsertSchemaField { Name = "field2", Properties = ValidProperties() }, - new UpsertSchemaField - { - Name = "field3", - Partitioning = Partitioning.Language.Key, - Properties = new ArrayFieldProperties(), - Nested = - [ - new UpsertSchemaNestedField { Name = "nested1", Properties = ValidProperties() }, - new UpsertSchemaNestedField { Name = "nested2", Properties = ValidProperties() } - ] - } + Name = SchemaId.Name, + SchemaId = SchemaId.Id, + Properties = new SchemaProperties(), + Fields = + [ + new UpsertSchemaField { Name = "field1", Properties = ValidProperties() }, + new UpsertSchemaField { Name = "field2", Properties = ValidProperties() }, + new UpsertSchemaField + { + Name = "field3", + Partitioning = Partitioning.Language.Key, + Properties = new ArrayFieldProperties(), + Nested = + [ + new UpsertSchemaNestedField { Name = "nested1", Properties = ValidProperties() }, + new UpsertSchemaNestedField { Name = "nested2", Properties = ValidProperties() } + ] + }, + ] }; - var command = new CreateSchema { Name = SchemaId.Name, SchemaId = SchemaId.Id, Properties = properties, Fields = fields }; - var actual = await PublishAsync(command); actual.ShouldBeEquivalent(sut.Snapshot); @@ -102,16 +110,19 @@ public class SchemaDomainObjectTests : HandlerTestBase var @event = (SchemaCreated)LastEvents.Single().Payload; Assert.Equal(AppId, sut.Snapshot.AppId); - Assert.Equal(SchemaId.Name, sut.Snapshot.SchemaDef.Name); - Assert.Equal(SchemaType.Default, sut.Snapshot.SchemaDef.Type); - + Assert.Equal(SchemaId.Id, sut.Snapshot.Id); + Assert.Equal(SchemaId.Name, sut.Snapshot.Name); + Assert.Equal(SchemaType.Default, sut.Snapshot.Type); Assert.Equal(3, @event.Schema.Fields.Count); } [Fact] public async Task Update_should_create_events_and_update_schema_properties() { - var command = new UpdateSchema { Properties = new SchemaProperties { Label = "My Properties" } }; + var command = new UpdateSchema + { + Properties = new SchemaProperties { Label = "My Properties" } + }; await ExecuteCreateAsync(); @@ -119,7 +130,7 @@ public class SchemaDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.Equal(command.Properties, sut.Snapshot.SchemaDef.Properties); + Assert.Equal(command.Properties, sut.Snapshot.Properties); LastEvents .ShouldHaveSameEvents( @@ -144,7 +155,7 @@ public class SchemaDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.Equal("", sut.Snapshot.SchemaDef.Scripts.Query); + Assert.Equal("", sut.Snapshot.Scripts.Query); LastEvents .ShouldHaveSameEvents( @@ -169,7 +180,7 @@ public class SchemaDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.NotEmpty(sut.Snapshot.SchemaDef.FieldRules); + Assert.NotEmpty(sut.Snapshot.FieldRules); LastEvents .ShouldHaveSameEvents( @@ -182,7 +193,7 @@ public class SchemaDomainObjectTests : HandlerTestBase { var command = new ConfigureUIFields { - FieldsInLists = FieldNames.Create(fieldName) + FieldsInLists = FieldNames.Create($"data.{fieldName}") }; await ExecuteCreateAsync(); @@ -192,7 +203,7 @@ public class SchemaDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.Equal(command.FieldsInLists, sut.Snapshot.SchemaDef.FieldsInLists); + Assert.Equal(command.FieldsInLists, sut.Snapshot.FieldsInLists); LastEvents .ShouldHaveSameEvents( @@ -205,7 +216,7 @@ public class SchemaDomainObjectTests : HandlerTestBase { var command = new ConfigureUIFields { - FieldsInReferences = FieldNames.Create(fieldName) + FieldsInReferences = FieldNames.Create($"data.{fieldName}") }; await ExecuteCreateAsync(); @@ -215,7 +226,7 @@ public class SchemaDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.Equal(command.FieldsInReferences, sut.Snapshot.SchemaDef.FieldsInReferences); + Assert.Equal(command.FieldsInReferences, sut.Snapshot.FieldsInReferences); LastEvents .ShouldHaveSameEvents( @@ -234,7 +245,7 @@ public class SchemaDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.True(sut.Snapshot.SchemaDef.IsPublished); + Assert.True(sut.Snapshot.IsPublished); LastEvents .ShouldHaveSameEvents( @@ -254,7 +265,7 @@ public class SchemaDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.False(sut.Snapshot.SchemaDef.IsPublished); + Assert.False(sut.Snapshot.IsPublished); LastEvents .ShouldHaveSameEvents( @@ -273,7 +284,7 @@ public class SchemaDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.Equal(command.Name, sut.Snapshot.SchemaDef.Category); + Assert.Equal(command.Name, sut.Snapshot.Category); LastEvents .ShouldHaveSameEvents( @@ -298,7 +309,7 @@ public class SchemaDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.Equal(command.PreviewUrls, sut.Snapshot.SchemaDef.PreviewUrls); + Assert.Equal(command.PreviewUrls, sut.Snapshot.PreviewUrls); LastEvents .ShouldHaveSameEvents( @@ -328,7 +339,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task Reorder_should_create_events_and_reorder_fields() { - var command = new ReorderFields { FieldIds = [2L, 1L] }; + var command = new ReorderFields + { + ParentFieldId = null, + FieldIds = [2L, 1L] + }; await ExecuteCreateAsync(); await ExecuteAddFieldAsync("field1"); @@ -347,7 +362,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task Reorder_should_create_events_and_reorder_nestedy_fields() { - var command = new ReorderFields { ParentFieldId = 1, FieldIds = [3L, 2L] }; + var command = new ReorderFields + { + ParentFieldId = 1, + FieldIds = [3L, 2L] + }; await ExecuteCreateAsync(); await ExecuteAddArrayFieldAsync(); @@ -367,7 +386,12 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task Add_should_create_events_and_add_field() { - var command = new AddField { Name = fieldName, Properties = ValidProperties() }; + var command = new AddField + { + ParentFieldId = null, + Name = fieldName, + Properties = ValidProperties() + }; await ExecuteCreateAsync(); @@ -386,7 +410,12 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task Add_should_create_events_and_add_field_to_array() { - var command = new AddField { ParentFieldId = 1, Name = fieldName, Properties = ValidProperties() }; + var command = new AddField + { + ParentFieldId = 1, + Name = fieldName, + Properties = ValidProperties() + }; await ExecuteCreateAsync(); await ExecuteAddArrayFieldAsync(); @@ -406,7 +435,12 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task UpdateField_should_create_events_and_update_field_properties() { - var command = new UpdateField { FieldId = 1, Properties = new StringFieldProperties() }; + var command = new UpdateField + { + ParentFieldId = null, + FieldId = 1, + Properties = new StringFieldProperties() + }; await ExecuteCreateAsync(); await ExecuteAddFieldAsync(fieldName); @@ -426,7 +460,12 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task UpdateField_should_create_events_and_update_nested_field_properties() { - var command = new UpdateField { ParentFieldId = 1, FieldId = 2, Properties = new StringFieldProperties() }; + var command = new UpdateField + { + ParentFieldId = 1, + FieldId = 2, + Properties = new StringFieldProperties() + }; await ExecuteCreateAsync(); await ExecuteAddArrayFieldAsync(); @@ -447,7 +486,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task LockField_should_create_events_and_update_field_locked_flag() { - var command = new LockField { FieldId = 1 }; + var command = new LockField + { + ParentFieldId = null, + FieldId = 1 + }; await ExecuteCreateAsync(); await ExecuteAddFieldAsync(fieldName); @@ -467,7 +510,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task LockField_should_create_events_and_update_nested_field_locked_flag() { - var command = new LockField { ParentFieldId = 1, FieldId = 2 }; + var command = new LockField + { + ParentFieldId = 1, + FieldId = 2 + }; await ExecuteCreateAsync(); await ExecuteAddArrayFieldAsync(); @@ -488,7 +535,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task HideField_should_create_events_and_update_field_hidden_flag() { - var command = new HideField { FieldId = 1 }; + var command = new HideField + { + ParentFieldId = null, + FieldId = 1 + }; await ExecuteCreateAsync(); await ExecuteAddFieldAsync(fieldName); @@ -508,7 +559,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task HideField_should_create_events_and_update_nested_field_hidden_flag() { - var command = new HideField { ParentFieldId = 1, FieldId = 2 }; + var command = new HideField + { + ParentFieldId = 1, + FieldId = 2 + }; await ExecuteCreateAsync(); await ExecuteAddArrayFieldAsync(); @@ -529,7 +584,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task ShowField_should_create_events_and_update_field_hidden_flag() { - var command = new ShowField { FieldId = 1 }; + var command = new ShowField + { + ParentFieldId = null, + FieldId = 1 + }; await ExecuteCreateAsync(); await ExecuteAddFieldAsync(fieldName); @@ -550,7 +609,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task ShowField_should_create_events_and_update_nested_field_hidden_flag() { - var command = new ShowField { ParentFieldId = 1, FieldId = 2 }; + var command = new ShowField + { + ParentFieldId = 1, + FieldId = 2 + }; await ExecuteCreateAsync(); await ExecuteAddArrayFieldAsync(); @@ -572,7 +635,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task DisableField_should_create_events_and_update_field_disabled_flag() { - var command = new DisableField { FieldId = 1 }; + var command = new DisableField + { + ParentFieldId = null, + FieldId = 1 + }; await ExecuteCreateAsync(); await ExecuteAddFieldAsync(fieldName); @@ -592,7 +659,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task DisableField_should_create_events_and_update_nested_field_disabled_flag() { - var command = new DisableField { ParentFieldId = 1, FieldId = 2 }; + var command = new DisableField + { + ParentFieldId = 1, + FieldId = 2 + }; await ExecuteCreateAsync(); await ExecuteAddArrayFieldAsync(); @@ -613,7 +684,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task EnableField_should_create_events_and_update_field_disabled_flag() { - var command = new EnableField { FieldId = 1 }; + var command = new EnableField + { + ParentFieldId = null, + FieldId = 1 + }; await ExecuteCreateAsync(); await ExecuteAddFieldAsync(fieldName); @@ -634,7 +709,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task EnableField_should_create_events_and_update_nested_field_disabled_flag() { - var command = new EnableField { ParentFieldId = 1, FieldId = 2 }; + var command = new EnableField + { + ParentFieldId = 1, + FieldId = 2 + }; await ExecuteCreateAsync(); await ExecuteAddArrayFieldAsync(); @@ -656,7 +735,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task DeleteField_should_create_events_and_delete_field() { - var command = new DeleteField { FieldId = 1 }; + var command = new DeleteField + { + ParentFieldId = null, + FieldId = 1 + }; await ExecuteCreateAsync(); await ExecuteAddFieldAsync(fieldName); @@ -676,7 +759,11 @@ public class SchemaDomainObjectTests : HandlerTestBase [Fact] public async Task DeleteField_should_create_events_and_delete_nested_field() { - var command = new DeleteField { ParentFieldId = 1, FieldId = 2 }; + var command = new DeleteField + { + ParentFieldId = 1, + FieldId = 2 + }; await ExecuteCreateAsync(); await ExecuteAddArrayFieldAsync(); @@ -708,7 +795,7 @@ public class SchemaDomainObjectTests : HandlerTestBase actual.ShouldBeEquivalent(sut.Snapshot); - Assert.Equal(command.Category, sut.Snapshot.SchemaDef.Category); + Assert.Equal(command.Category, sut.Snapshot.Category); LastEvents .ShouldHaveSameEvents( @@ -753,12 +840,12 @@ public class SchemaDomainObjectTests : HandlerTestBase private IField GetField(int id) { - return sut.Snapshot.SchemaDef.FieldsById.GetValueOrDefault(id)!; + return sut.Snapshot.FieldsById.GetValueOrDefault(id)!; } private IField GetNestedField(int parentId, int childId) { - return ((IArrayField)sut.Snapshot.SchemaDef.FieldsById[parentId]).FieldsById.GetValueOrDefault(childId)!; + return ((IArrayField)sut.Snapshot.FieldsById[parentId]).FieldsById.GetValueOrDefault(childId)!; } private static StringFieldProperties ValidProperties() diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaStateTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaStateTests.cs deleted file mode 100644 index d9365943a..000000000 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/DomainObject/SchemaStateTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Text.Json.Serialization; -using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Infrastructure.Json; - -namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject; - -public class SchemaStateTests -{ - private readonly IJsonSerializer serializer = TestUtils.CreateSerializer(options => - { - options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; - }); - - [Fact] - public void Should_deserialize_state() - { - var json = File.ReadAllText("Schemas/DomainObject/SchemaState.json"); - - var deserialized = serializer.Deserialize(json); - - Assert.NotNull(deserialized); - } - - [Fact] - public void Should_serialize_deserialize_state() - { - var json = File.ReadAllText("Schemas/DomainObject/SchemaState.json").CleanJson(); - - var serialized = serializer.Serialize(serializer.Deserialize(json), true); - - Assert.Equal(json, serialized); - } -} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs index c38836fce..27027ad4a 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Squidex.Caching; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.Repositories; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -120,8 +121,7 @@ public class SchemasIndexTests : GivenContext [Fact] public async Task Should_return_empty_schemas_if_schema_not_created() { - A.CallTo(() => Schema.Version) - .Returns(EtagVersion.Empty); + Schema = Schema with { Version = EtagVersion.Empty }; A.CallTo(() => schemaRepository.QueryAllAsync(AppId.Id, CancellationToken)) .Returns([Schema]); @@ -134,8 +134,7 @@ public class SchemasIndexTests : GivenContext [Fact] public async Task Should_return_empty_schemas_if_schema_deleted() { - A.CallTo(() => Schema.IsDeleted) - .Returns(true); + Schema = Schema with { IsDeleted = true }; A.CallTo(() => schemaRepository.QueryAllAsync(AppId.Id, CancellationToken)) .Returns([Schema]); @@ -149,7 +148,7 @@ public class SchemasIndexTests : GivenContext public async Task Should_take_and_remove_reservation_if_created() { A.CallTo(() => schemaRepository.FindAsync(AppId.Id, SchemaId.Name, CancellationToken)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var command = Create(SchemaId.Name); @@ -176,7 +175,7 @@ public class SchemasIndexTests : GivenContext public async Task Should_clear_reservation_if_schema_creation_failed() { A.CallTo(() => schemaRepository.FindAsync(AppId.Id, SchemaId.Name, CancellationToken)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var command = Create(SchemaId.Name); @@ -205,7 +204,7 @@ public class SchemasIndexTests : GivenContext state.Snapshot.Reservations.Add(new NameReservation(RandomHash.Simple(), SchemaId.Name, DomainId.NewGuid())); A.CallTo(() => schemaRepository.FindAsync(AppId.Id, SchemaId.Name, CancellationToken)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); var command = Create(SchemaId.Name); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MigrateFieldNamesCommandMiddlewareTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MigrateFieldNamesCommandMiddlewareTests.cs new file mode 100644 index 000000000..87c19cc2d --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MigrateFieldNamesCommandMiddlewareTests.cs @@ -0,0 +1,106 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Domain.Apps.Entities.TestHelpers; +using Squidex.Infrastructure.Commands; + +namespace Squidex.Domain.Apps.Entities.Schemas; + +public class MigrateFieldNamesCommandMiddlewareTests : GivenContext +{ + private readonly ICommandBus commandBus = A.Fake(); + private readonly MigrateFieldNamesCommandMiddleware sut; + + public MigrateFieldNamesCommandMiddlewareTests() + { + sut = new MigrateFieldNamesCommandMiddleware(); + } + + [Fact] + public async void Should_migrate_synchronize_command() + { + var command = new SynchronizeSchema + { + FieldsInLists = FieldNames.Create( + "meta.id", + "meta.lastModified", + "dataField1" + ), + FieldsInReferences = FieldNames.Create( + "meta.version", + "meta.lastModifiedBy", + "dataField2" + ), + }; + + var commandContext = new CommandContext(command, commandBus); + + await sut.HandleAsync(commandContext, CancellationToken); + + Assert.Equal(FieldNames.Create("id", "lastModified", "data.dataField1"), + command.FieldsInLists); + + Assert.Equal(FieldNames.Create("version", "lastModifiedBy", "data.dataField2"), + command.FieldsInReferences); + } + + [Fact] + public async void Should_migrate_synchronize_command_with_null_fields() + { + var command = new SynchronizeSchema(); + + var commandContext = new CommandContext(command, commandBus); + + await sut.HandleAsync(commandContext, CancellationToken); + + Assert.Null(command.FieldsInLists); + Assert.Null(command.FieldsInReferences); + } + + [Fact] + public async void Should_migrate_configure_command() + { + var command = new ConfigureUIFields + { + FieldsInLists = FieldNames.Create( + "meta.id", + "meta.lastModified", + "dataField1" + ), + FieldsInReferences = FieldNames.Create( + "meta.version", + "meta.lastModifiedBy", + "dataField2" + ), + }; + + var commandContext = new CommandContext(command, commandBus); + + await sut.HandleAsync(commandContext, CancellationToken); + + Assert.Equal(FieldNames.Create("id", "lastModified", "data.dataField1"), + command.FieldsInLists); + + Assert.Equal(FieldNames.Create("version", "lastModifiedBy", "data.dataField2"), + command.FieldsInReferences); + } + + [Fact] + public async void Should_migrate_configure_command_with_null_fields() + { + var command = new ConfigureUIFields(); + + var commandContext = new CommandContext(command, commandBus); + + await sut.HandleAsync(commandContext, CancellationToken); + + Assert.Null(command.FieldsInLists); + Assert.Null(command.FieldsInReferences); + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashTests.cs index 91f4766fe..9839b908d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashTests.cs @@ -6,7 +6,9 @@ // ========================================================================== using NodaTime; -using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; @@ -16,7 +18,7 @@ using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Entities.Schemas.MongoDb; [Trait("Category", "Dependencies")] -public class SchemasHashTests : IClassFixture +public class SchemasHashTests : GivenContext, IClassFixture { public SchemasHashFixture _ { get; } @@ -28,13 +30,11 @@ public class SchemasHashTests : IClassFixture [Fact] public async Task Should_compute_cache_independent_from_order() { - var app = CreateApp(DomainId.NewGuid(), 1); + var schema1 = Schema.WithId(DomainId.NewGuid(), "my-schema"); + var schema2 = Schema.WithId(DomainId.NewGuid(), "my-schema") with { Version = 3 }; - var schema1 = CreateSchema(DomainId.NewGuid(), 2); - var schema2 = CreateSchema(DomainId.NewGuid(), 3); - - var hash1 = await _.SchemasHash.ComputeHashAsync(app, new[] { schema1, schema2 }); - var hash2 = await _.SchemasHash.ComputeHashAsync(app, new[] { schema2, schema1 }); + var hash1 = await _.SchemasHash.ComputeHashAsync(App, new[] { schema1, schema2 }, CancellationToken); + var hash2 = await _.SchemasHash.ComputeHashAsync(App, new[] { schema2, schema1 }, CancellationToken); Assert.NotNull(hash1); Assert.NotNull(hash2); @@ -44,53 +44,31 @@ public class SchemasHashTests : IClassFixture [Fact] public async Task Should_compute_cache_independent_from_db() { - var app = CreateApp(DomainId.NewGuid(), 1); - - var schema1 = CreateSchema(DomainId.NewGuid(), 2); - var schema2 = CreateSchema(DomainId.NewGuid(), 3); + var schema1 = Schema.WithId(DomainId.NewGuid(), "my-schema"); + var schema2 = Schema.WithId(DomainId.NewGuid(), "my-schema") with { Version = 3 }; var timestamp = SystemClock.Instance.GetCurrentInstant().WithoutMs(); - var computedHash = await _.SchemasHash.ComputeHashAsync(app, new[] { schema1, schema2 }); + var computedHash = await _.SchemasHash.ComputeHashAsync(App, new[] { schema1, schema2 }, CancellationToken); await _.SchemasHash.On(new[] { Envelope.Create(new SchemaCreated { - AppId = NamedId.Of(app.Id, "my-app"), - SchemaId = NamedId.Of(schema1.Id, "my-schema") + AppId = AppId, + SchemaId = schema1.NamedId() }).SetEventStreamNumber(schema1.Version).SetTimestamp(timestamp), Envelope.Create(new SchemaCreated { - AppId = NamedId.Of(app.Id, "my-app"), - SchemaId = NamedId.Of(schema2.Id, "my-schema") + AppId = AppId, + SchemaId = schema2.NamedId() }).SetEventStreamNumber(schema2.Version).SetTimestamp(timestamp) }); - var (dbTime, dbHash) = await _.SchemasHash.GetCurrentHashAsync(app); + var (dbTime, dbHash) = await _.SchemasHash.GetCurrentHashAsync(App, CancellationToken); Assert.Equal(dbHash, computedHash); Assert.Equal(dbTime, timestamp); } - - private static IAppEntity CreateApp(DomainId id, long version) - { - var app = A.Fake(); - - A.CallTo(() => app.Id).Returns(id); - A.CallTo(() => app.Version).Returns(version); - - return app; - } - - private static ISchemaEntity CreateSchema(DomainId id, long version) - { - var schema = A.Fake(); - - A.CallTo(() => schema.Id).Returns(id); - A.CallTo(() => schema.Version).Returns(version); - - return schema; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaChangedTriggerHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaChangedTriggerHandlerTests.cs index 3a5e0c488..ba6d2961f 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaChangedTriggerHandlerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaChangedTriggerHandlerTests.cs @@ -11,15 +11,15 @@ using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Schemas; -using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Entities.Schemas; -public class SchemaChangedTriggerHandlerTests +public class SchemaChangedTriggerHandlerTests : GivenContext { private readonly IScriptEngine scriptEngine = A.Fake(); private readonly IRuleTriggerHandler sut; @@ -66,7 +66,7 @@ public class SchemaChangedTriggerHandlerTests [MemberData(nameof(TestEvents))] public async Task Should_create_enriched_events(SchemaEvent @event, EnrichedSchemaEventType type) { - var ctx = Context(appId: @event.AppId).ToRulesContext(); + var ctx = Context().ToRulesContext(); var envelope = Envelope.Create(@event).SetEventStreamNumber(12); @@ -154,15 +154,16 @@ public class SchemaChangedTriggerHandlerTests } } - private static RuleContext Context(RuleTrigger? trigger = null, NamedId? appId = null) + private RuleContext Context(RuleTrigger? trigger = null) { trigger ??= new SchemaChangedTrigger(); return new RuleContext { - AppId = appId ?? NamedId.Of(DomainId.NewGuid(), "my-app"), - Rule = new Rule(trigger, A.Fake()), - RuleId = DomainId.NewGuid() + AppId = AppId, + IncludeSkipped = true, + IncludeStale = true, + Rule = CreateRule() with { Trigger = trigger } }; } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaCommandsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaCommandsTests.cs index 91db79ec5..e3d5d1d48 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaCommandsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaCommandsTests.cs @@ -14,13 +14,67 @@ namespace Squidex.Domain.Apps.Entities.Schemas; public class SchemaCommandsTests { + [Fact] + public void Should_convert_upsert_command_with_defaults() + { + var command = new SynchronizeSchema + { + Fields = + [ + new UpsertSchemaField + { + Name = "myString", + IsDisabled = true, + IsHidden = true, + IsLocked = true, + Properties = new StringFieldProperties + { + IsRequired = true + }, + Partitioning = "language" + }, + ], + Category = "myCategory" + }; + + var field = Fields.String(1, "myString", Partitioning.Language, new StringFieldProperties + { + IsRequired = true + }) with + { + IsDisabled = true, + IsHidden = true, + IsLocked = true + }; + + var expected = new Schema + { + Name = "my-schema", + Properties = new SchemaProperties(), + FieldsInLists = FieldNames.Create(), + FieldsInReferences = FieldNames.Create(), + Scripts = new SchemaScripts(), + PreviewUrls = ReadonlyDictionary.Empty(), + Category = "myCategory" + }; + + expected = expected.AddField(field); + + var actual = command.BuildSchema("my-schema", SchemaType.Default); + + actual.Should().BeEquivalentTo(expected, opts => opts.Excluding(x => x.AppId).Excluding(x => x.UniqueId)); + } + [Fact] public void Should_convert_upsert_command() { var command = new SynchronizeSchema { + Properties = new SchemaProperties + { + Hints = "MyHints" + }, IsPublished = true, - Properties = new SchemaProperties { Hints = "MyHints" }, Fields = [ new UpsertSchemaField @@ -36,8 +90,14 @@ public class SchemaCommandsTests Partitioning = "language" }, ], - FieldsInLists = FieldNames.Create("meta.id", "myString"), - FieldsInReferences = FieldNames.Create("myString"), + FieldsInLists = FieldNames.Create( + "lastModified", + "lastModifiedBy", + "data.myString" + ), + FieldsInReferences = FieldNames.Create( + "data.myString" + ), Scripts = new SchemaScripts { Change = "change-script" @@ -49,29 +109,47 @@ public class SchemaCommandsTests Category = "myCategory" }; - var expected = - new Schema("my-schema") - .Update(new SchemaProperties { Hints = "MyHints" }) - .AddString(1, "myString", Partitioning.Language, new StringFieldProperties - { - IsRequired = true - }) - .HideField(1).DisableField(1).LockField(1) - .ChangeCategory("myCategory") - .SetFieldsInLists(FieldNames.Create("meta.id", "myString")) - .SetFieldsInReferences(FieldNames.Create("myString")) - .SetScripts(new SchemaScripts - { - Change = "change-script" - }) - .SetPreviewUrls(new Dictionary - { - ["mobile"] = "http://mobile" - }.ToReadonlyDictionary()) - .Publish(); + var field = Fields.String(1, "myString", Partitioning.Language, new StringFieldProperties + { + IsRequired = true + }) with + { + IsDisabled = true, + IsHidden = true, + IsLocked = true + }; + + var expected = new Schema + { + Name = "my-schema", + Properties = new SchemaProperties + { + Hints = "MyHints" + }, + IsPublished = true, + FieldsInLists = FieldNames.Create( + "lastModified", + "lastModifiedBy", + "data.myString" + ), + FieldsInReferences = FieldNames.Create( + "data.myString" + ), + Scripts = new SchemaScripts + { + Change = "change-script" + }, + PreviewUrls = new Dictionary + { + ["mobile"] = "http://mobile" + }.ToReadonlyDictionary(), + Category = "myCategory" + }; + + expected = expected.AddField(field); var actual = command.BuildSchema("my-schema", SchemaType.Default); - actual.Should().BeEquivalentTo(expected); + actual.Should().BeEquivalentTo(expected, opts => opts.Excluding(x => x.AppId).Excluding(x => x.UniqueId)); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemasSearchSourceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemasSearchSourceTests.cs index 084125f2f..0ede70ed3 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemasSearchSourceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemasSearchSourceTests.cs @@ -10,7 +10,6 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Search; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure; using Squidex.Shared; namespace Squidex.Domain.Apps.Entities.Schemas; @@ -26,9 +25,9 @@ public class SchemasSearchSourceTests : GivenContext, IClassFixture AppProvider.GetSchemasAsync(AppId.Id, CancellationToken)) .Returns([schema1]); @@ -40,15 +39,15 @@ public class SchemasSearchSourceTests : GivenContext, IClassFixture AppProvider.GetSchemasAsync(AppId.Id, CancellationToken)) .Returns([schema1]); @@ -60,17 +59,17 @@ public class SchemasSearchSourceTests : GivenContext, IClassFixture AppProvider.GetSchemasAsync(AppId.Id, CancellationToken)) .Returns([schema1, schema2, schema3]); @@ -88,17 +87,17 @@ public class SchemasSearchSourceTests : GivenContext, IClassFixture AppProvider.GetSchemasAsync(AppId.Id, CancellationToken)) .Returns([schema1]); @@ -113,12 +112,7 @@ public class SchemasSearchSourceTests : GivenContext, IClassFixture - - PreserveNewest - PreserveNewest @@ -79,14 +76,5 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - \ No newline at end of file diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Teams/DomainObject/Guards/GuardTeamContributorsTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Teams/DomainObject/Guards/GuardTeamContributorsTests.cs index 013fcbce8..f49360bbe 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Teams/DomainObject/Guards/GuardTeamContributorsTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Teams/DomainObject/Guards/GuardTeamContributorsTests.cs @@ -22,13 +22,9 @@ public class GuardTeamContributorsTests : GivenContext, IClassFixture(); - private Contributors contributors = Contributors.Empty; public GuardTeamContributorsTests() { - A.CallTo(() => Team.Contributors) - .ReturnsLazily(() => contributors); - A.CallTo(() => user1.Id) .Returns("1"); @@ -74,7 +70,10 @@ public class GuardTeamContributorsTests : GivenContext, IClassFixture GuardTeamContributors.CanRemove(command, Team), new ValidationError("Cannot remove the only owner.")); @@ -176,7 +190,10 @@ public class GuardTeamContributorsTests : GivenContext, IClassFixture +public class TeamDomainObjectTests : HandlerTestBase { private readonly IBillingPlans billingPlans = A.Fake(); private readonly IBillingManager billingManager = A.Fake(); @@ -51,7 +52,7 @@ public class TeamDomainObjectTests : HandlerTestBase A.CallTo(() => billingPlans.GetPlan(planPaid.Id)) .Returns(planPaid); - A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, A._, CancellationToken)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, A._, CancellationToken)) .Returns(Task.FromResult(null)); var serviceProvider = @@ -127,7 +128,7 @@ public class TeamDomainObjectTests : HandlerTestBase { var command = new ChangePlan { PlanId = planPaid.Id }; - A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planPaid.Id, CancellationToken)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planPaid.Id, CancellationToken)) .Returns(Task.FromResult(null)); await ExecuteCreateAsync(); @@ -143,10 +144,10 @@ public class TeamDomainObjectTests : HandlerTestBase CreateEvent(new TeamPlanChanged { PlanId = planPaid.Id }) ); - A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planPaid.Id, CancellationToken)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planPaid.Id, CancellationToken)) .MustHaveHappened(); - A.CallTo(() => billingManager.SubscribeAsync(User.Identifier, A._, planPaid.Id, default)) + A.CallTo(() => billingManager.SubscribeAsync(User.Identifier, A._, planPaid.Id, default)) .MustHaveHappened(); } @@ -168,10 +169,10 @@ public class TeamDomainObjectTests : HandlerTestBase CreateEvent(new TeamPlanChanged { PlanId = planPaid.Id }) ); - A.CallTo(() => billingManager.MustRedirectToPortalAsync(A._, A._, A._, A._)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(A._, A._, A._, A._)) .MustNotHaveHappened(); - A.CallTo(() => billingManager.SubscribeAsync(A._, A._, A._, A._)) + A.CallTo(() => billingManager.SubscribeAsync(A._, A._, A._, A._)) .MustNotHaveHappened(); } @@ -194,10 +195,10 @@ public class TeamDomainObjectTests : HandlerTestBase CreateEvent(new TeamPlanReset()) ); - A.CallTo(() => billingManager.MustRedirectToPortalAsync(A._, A._, A._, CancellationToken)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(A._, A._, A._, CancellationToken)) .MustHaveHappenedOnceExactly(); - A.CallTo(() => billingManager.UnsubscribeAsync(A._, A._, A._)) + A.CallTo(() => billingManager.UnsubscribeAsync(A._, A._, A._)) .MustNotHaveHappened(); } @@ -220,19 +221,19 @@ public class TeamDomainObjectTests : HandlerTestBase CreateEvent(new TeamPlanReset()) ); - A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planPaid.Id, CancellationToken)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planPaid.Id, CancellationToken)) .MustHaveHappenedOnceExactly(); - A.CallTo(() => billingManager.UnsubscribeAsync(A._, A._, A._)) + A.CallTo(() => billingManager.UnsubscribeAsync(A._, A._, A._)) .MustHaveHappened(); } [Fact] - public async Task ChangePlan_should_not_make_update_for_redirect_actual() + public async Task ChangePlan_should_not_make_update_for_redirect_result() { var command = new ChangePlan { PlanId = planPaid.Id }; - A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planPaid.Id, CancellationToken)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planPaid.Id, CancellationToken)) .Returns(new Uri("http://squidex.io")); await ExecuteCreateAsync(); @@ -257,10 +258,10 @@ public class TeamDomainObjectTests : HandlerTestBase Assert.Equal(planPaid.Id, sut.Snapshot.Plan?.PlanId); - A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planPaid.Id, A._)) + A.CallTo(() => billingManager.MustRedirectToPortalAsync(User.Identifier, A._, planPaid.Id, A._)) .MustNotHaveHappened(); - A.CallTo(() => billingManager.SubscribeAsync(User.Identifier, A._, planPaid.Id, A._)) + A.CallTo(() => billingManager.SubscribeAsync(User.Identifier, A._, planPaid.Id, A._)) .MustNotHaveHappened(); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Teams/Indexes/TeamsIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Teams/Indexes/TeamsIndexTests.cs index 44aea8920..ef901ff47 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Teams/Indexes/TeamsIndexTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Teams/Indexes/TeamsIndexTests.cs @@ -5,9 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities.Teams.Repositories; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Teams.Indexes; @@ -24,23 +24,21 @@ public class TeamsIndexTests : GivenContext [Fact] public async Task Should_resolve_teams_by_id() { - var team = SetupTeam(0); - A.CallTo(() => teamRepository.QueryAllAsync("user1", CancellationToken)) - .Returns([team]); + .Returns([Team]); var actual = await sut.GetTeamsAsync("user1", CancellationToken); - Assert.Same(actual[0], team); + Assert.Same(actual[0], Team); } [Fact] public async Task Should_return_empty_teams_if_team_not_created() { - var team = SetupTeam(-1); + Team = Team with { Version = -1 }; A.CallTo(() => teamRepository.QueryAllAsync("user1", CancellationToken)) - .Returns([team]); + .Returns([Team]); var actual = await sut.GetTeamsAsync("user1", CancellationToken); @@ -50,36 +48,22 @@ public class TeamsIndexTests : GivenContext [Fact] public async Task Should_resolve_team_by_id() { - var team = SetupTeam(0); - - A.CallTo(() => teamRepository.FindAsync(team.Id, CancellationToken)) - .Returns(team); + A.CallTo(() => teamRepository.FindAsync(Team.Id, CancellationToken)) + .Returns(Team); - var actual = await sut.GetTeamAsync(team.Id, CancellationToken); + var actual = await sut.GetTeamAsync(Team.Id, CancellationToken); - Assert.Same(actual, team); + Assert.Same(actual, Team); } [Fact] public async Task Should_return_null_team_if_team_not_created() { - var team = SetupTeam(0); + A.CallTo(() => teamRepository.FindAsync(Team.Id, CancellationToken)) + .Returns(Task.FromResult(null)); - A.CallTo(() => teamRepository.FindAsync(team.Id, CancellationToken)) - .Returns(Task.FromResult(null)); - - var actual = await sut.GetTeamAsync(team.Id, CancellationToken); + var actual = await sut.GetTeamAsync(Team.Id, CancellationToken); Assert.Null(actual); } - - private static ITeamEntity SetupTeam(long version) - { - var team = Mocks.Team(DomainId.NewGuid(), "my-team"); - - A.CallTo(() => team.Version) - .Returns(version); - - return team; - } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/GivenContext.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/GivenContext.cs index bf29112fd..ca1d99475 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/GivenContext.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/GivenContext.cs @@ -5,10 +5,19 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Domain.Apps.Entities.Teams; +using NodaTime; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Assets; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.Teams; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Rules; +using Squidex.Extensions.Actions.Webhook; using Squidex.Infrastructure; +using ContentFieldData = Squidex.Domain.Apps.Core.Contents.ContentFieldData; namespace Squidex.Domain.Apps.Entities.TestHelpers; @@ -29,11 +38,11 @@ public abstract class GivenContext public NamedId SchemaId { get; } = NamedId.Of(DomainId.NewGuid(), "my-schema"); - public ITeamEntity Team { get; set; } + public App App { get; set; } - public IAppEntity App { get; set; } + public Team Team { get; set; } - public ISchemaEntity Schema { get; set; } + public Schema Schema { get; set; } public RefToken User { get; } = RefToken.User("me"); @@ -68,13 +77,47 @@ public abstract class GivenContext protected GivenContext() { - Team = Mocks.Team(TeamId, TeamName); - - App = Mocks.App(AppId, - Language.EN, - Language.DE); + Team = new Team + { + Id = TeamId, + Name = TeamName, + Created = default, + CreatedBy = User, + LastModified = default, + LastModifiedBy = User, + Version = 1, + }; + + App = new App + { + Id = AppId.Id, + Name = AppId.Name, + Created = default, + CreatedBy = User, + Languages = LanguagesConfig.English.Set(Language.DE, false), + LastModified = default, + LastModifiedBy = User, + TeamId = TeamId, + Version = 1, + }; + + Schema = new Schema + { + Id = SchemaId.Id, + Name = SchemaId.Name, + AppId = AppId, + Created = default, + CreatedBy = User, + IsPublished = true, + LastModified = default, + LastModifiedBy = User, + Version = 1, + }; + } - Schema = Mocks.Schema(AppId, SchemaId); + public Instant Timestamp() + { + return SystemClock.Instance.GetCurrentInstant(); } public Context CreateContext(params string[] permissions) @@ -122,4 +165,119 @@ public abstract class GivenContext return result; } + + public EnrichedAsset CreateAsset() + { + var id = DomainId.NewGuid(); + + return new EnrichedAsset + { + Id = id, + AppId = AppId, + Created = Timestamp(), + CreatedBy = User, + FileName = "My File.png", + FileHash = "my-hash-42", + FileSize = 1024, + FileVersion = 0, + TotalSize = 1024 * 2, + LastModified = Timestamp(), + LastModifiedBy = User, + Metadata = [], + MetadataText = string.Empty, + MimeType = "image/png", + Tags = [], + TagNames = [], + Slug = "my-file", + Version = 1, + }; + } + + public AssetFolder CreateAssetFolder() + { + var id = DomainId.NewGuid(); + + return new AssetFolder + { + Id = id, + AppId = AppId, + Created = Timestamp(), + CreatedBy = User, + FolderName = "My Folder", + LastModified = Timestamp(), + LastModifiedBy = User, + Version = 1, + }; + } + + public EnrichedRule CreateRule() + { + var id = DomainId.NewGuid(); + + return new EnrichedRule + { + Id = id, + AppId = AppId, + Action = new WebhookAction(), + Created = Timestamp(), + CreatedBy = User, + Name = "My Rule", + LastModified = Timestamp(), + LastModifiedBy = User, + Trigger = new ContentChangedTriggerV2(), + Version = 1, + }; + } + + public EnrichedContent CreateContent() + { + var id = DomainId.NewGuid(); + + var data = + new ContentData() + .AddField("my-field", + new ContentFieldData() + .AddInvariant(42)); + + return new EnrichedContent + { + Id = id, + AppId = AppId, + Created = Timestamp(), + CreatedBy = User, + Data = data, + LastModified = Timestamp(), + LastModifiedBy = User, + ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Archived, User, Timestamp()), + SchemaId = SchemaId, + Status = Status.Published, + Version = 1, + }; + } + + public WriteContent CreateWriteContent() + { + var id = DomainId.NewGuid(); + + var data = + new ContentData() + .AddField("my-field", + new ContentFieldData() + .AddInvariant(42)); + + return new WriteContent + { + Id = id, + AppId = AppId, + Created = Timestamp(), + CreatedBy = User, + CurrentVersion = new ContentVersion(Status.Published, data), + NewVersion = null, + LastModified = Timestamp(), + LastModifiedBy = User, + ScheduleJob = new ScheduleJob(DomainId.NewGuid(), Status.Archived, User, Timestamp()), + SchemaId = SchemaId, + Version = 1, + }; + } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs index f68d3dc17..e0067ace4 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs @@ -60,7 +60,7 @@ public abstract class HandlerTestBase : GivenContext return context; } - protected async Task PublishIdempotentAsync(DomainObject domainObject, IAggregateCommand command) where T : class, IDomainState, new() + protected async Task PublishIdempotentAsync(DomainObject domainObject, IAggregateCommand command) where T : Entity, new() { var actual = await domainObject.ExecuteAsync(command, CancellationToken); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs index f282f06e4..6bdaf996b 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/Mocks.cs @@ -6,12 +6,6 @@ // ========================================================================== using System.Security.Claims; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Domain.Apps.Entities.Teams; -using Squidex.Infrastructure; using Squidex.Infrastructure.Security; using Squidex.Shared; using Squidex.Shared.Identity; @@ -20,61 +14,6 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers; public static class Mocks { - public static IAppEntity App(NamedId appId, params Language[] languages) - { - var config = LanguagesConfig.English; - - foreach (var language in languages) - { - config = config.Set(language); - } - - var app = A.Fake(); - - A.CallTo(() => app.Id).Returns(appId.Id); - A.CallTo(() => app.Name).Returns(appId.Name); - A.CallTo(() => app.Languages).Returns(config); - A.CallTo(() => app.UniqueId).Returns(appId.Id); - - return app; - } - - public static ISchemaEntity Schema(NamedId appId, NamedId schemaId) - { - var schemaEntity = A.Fake(); - var schemaDef = new Schema(schemaId.Name).Publish(); - - A.CallTo(() => schemaEntity.Id).Returns(schemaId.Id); - A.CallTo(() => schemaEntity.AppId).Returns(appId); - A.CallTo(() => schemaEntity.SchemaDef).Returns(schemaDef); - A.CallTo(() => schemaEntity.UniqueId).Returns(DomainId.Combine(appId, schemaId.Id)); - - return schemaEntity; - } - - public static ISchemaEntity Schema(NamedId appId, DomainId schemaId, Schema schemaDef) - { - var schemaEntity = A.Fake(); - - A.CallTo(() => schemaEntity.Id).Returns(schemaId); - A.CallTo(() => schemaEntity.AppId).Returns(appId); - A.CallTo(() => schemaEntity.SchemaDef).Returns(schemaDef); - A.CallTo(() => schemaEntity.UniqueId).Returns(DomainId.Combine(appId, schemaId)); - - return schemaEntity; - } - - public static ITeamEntity Team(DomainId teamId, string teamName) - { - var team = A.Fake(); - - A.CallTo(() => team.Id).Returns(teamId); - A.CallTo(() => team.UniqueId).Returns(teamId); - A.CallTo(() => team.Name).Returns(teamName); - - return team; - } - public static ClaimsPrincipal ApiUser(string? role = null, params string[] permissions) { return CreateUser(false, role, permissions); diff --git a/backend/tests/Squidex.Infrastructure.Tests/Commands/CommandContextTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Commands/CommandContextTests.cs index 676fb2754..ee2e81dd4 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Commands/CommandContextTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Commands/CommandContextTests.cs @@ -41,7 +41,7 @@ public class CommandContextTests } [Fact] - public void Should_provide_actual_if_succeeded_with_value() + public void Should_provide_result_if_succeeded_with_value() { sut.Complete("RESULT"); @@ -49,7 +49,7 @@ public class CommandContextTests } [Fact] - public void Should_provide_plain_actual_if_succeeded_with_value() + public void Should_provide_plain_result_if_succeeded_with_value() { sut.Complete("RESULT"); diff --git a/backend/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectTests.cs index 4acb61bf5..ca6ecf162 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectTests.cs @@ -357,7 +357,7 @@ public class DomainObjectTests } [Fact] - public async Task Should_return_custom_actual_on_create() + public async Task Should_return_custom_result_on_create() { var actual = await sut.ExecuteAsync(new CreateCustom(), ct); @@ -379,7 +379,7 @@ public class DomainObjectTests } [Fact] - public async Task Should_return_custom_actual_on_update() + public async Task Should_return_custom_result_on_update() { SetupCreated(4); diff --git a/backend/tests/Squidex.Infrastructure.Tests/Json/System/PolymorphicConverterTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Json/System/PolymorphicConverterTests.cs index a39194b5c..c5acc6efa 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Json/System/PolymorphicConverterTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Json/System/PolymorphicConverterTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Text.Json.Serialization.Metadata; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.TestHelpers; @@ -100,7 +101,8 @@ public class PolymorphicConverterTests .Add("A") .Add("B"); - options.TypeInfoResolver = new PolymorphicTypeResolver(typeRegistry); + options.TypeInfoResolver = new DefaultJsonTypeInfoResolver() + .WithAddedModifier(PolymorphicConverter.Modifier(typeRegistry)); options.Converters.Add(new PolymorphicConverter(typeRegistry)); }); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Log/BackgroundRequestLogStoreTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Log/BackgroundRequestLogStoreTests.cs index 1e1858214..da47f7c1a 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Log/BackgroundRequestLogStoreTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Log/BackgroundRequestLogStoreTests.cs @@ -62,7 +62,7 @@ public class BackgroundRequestLogStoreTests } [Fact] - public async Task Should_provide_actuals_from_repository() + public async Task Should_provide_results_from_repository() { var key = "my-key"; @@ -78,7 +78,7 @@ public class BackgroundRequestLogStoreTests } [Fact] - public async Task Should_not_provide_actuals_from_repository_if_disabled() + public async Task Should_not_provide_results_from_repository_if_disabled() { options.StoreEnabled = false; diff --git a/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainObject.cs b/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainObject.cs index 22cd6a2d7..9b38d4a5f 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainObject.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainObject.cs @@ -137,6 +137,11 @@ public sealed class MyDomainObject : DomainObject throw new NotSupportedException(); } } + + protected override MyDomainState Apply(MyDomainState snapshot, Envelope @event) + { + return snapshot.Apply(@event); + } } public sealed class Delete : MyCommand diff --git a/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainState.cs b/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainState.cs index 6afb49cd0..57111b1c1 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainState.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainState.cs @@ -12,21 +12,14 @@ using Squidex.Infrastructure.EventSourcing; namespace Squidex.Infrastructure.TestHelpers; -public sealed record MyDomainState : IDomainState +public sealed record MyDomainState : Entity { public const long Unchanged = 13; public bool IsDeleted { get; set; } - public long Version { get; set; } - public long Value { get; set; } - public MyDomainState Copy() - { - return (MyDomainState)MemberwiseClone(); - } - public MyDomainState Apply(Envelope @event) { switch (@event.Payload) diff --git a/backend/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs b/backend/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs index 965cc5d96..18d8d6332 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs @@ -134,7 +134,7 @@ public class BackgroundUsageTrackerTests } [Fact] - public async Task Should_create_empty_actuals_with_default_category_is_actual_is_empty() + public async Task Should_create_empty_results_with_default_category_is_result_is_empty() { var dateFrom = date; var dateTo = dateFrom.AddDays(4); @@ -160,7 +160,7 @@ public class BackgroundUsageTrackerTests } [Fact] - public async Task Should_create_actuals_with_filled_days() + public async Task Should_create_results_with_filled_days() { var dateFrom = date; var dateTo = dateFrom.AddDays(4); diff --git a/backend/tests/Squidex.Infrastructure.Tests/ValidationExceptionTests.cs b/backend/tests/Squidex.Infrastructure.Tests/ValidationExceptionTests.cs index 2647ced35..615473647 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/ValidationExceptionTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/ValidationExceptionTests.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Infrastructure.TestHelpers; using Squidex.Infrastructure.Validation; namespace Squidex.Infrastructure; diff --git a/backend/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs b/backend/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs index 9902620a0..e1176d6f0 100644 --- a/backend/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs +++ b/backend/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs @@ -15,7 +15,6 @@ using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Shared; using Squidex.Shared.Identity; -using Squidex.Web.Pipeline; #pragma warning disable IDE0017 // Simplify object initialization @@ -59,7 +58,7 @@ public class ApiPermissionAttributeTests : GivenContext [Fact] public async Task Should_make_permission_check_with_app_feature() { - actionExecutingContext.HttpContext.Features.Set(new AppFeature(App)); + actionExecutingContext.HttpContext.Features.Set(App); user.AddClaim(new Claim(SquidexClaimTypes.Permissions, "squidex.apps.my-app")); @@ -76,8 +75,8 @@ public class ApiPermissionAttributeTests : GivenContext [Fact] public async Task Should_make_permission_check_with_schema_feature() { - actionExecutingContext.HttpContext.Features.Set(new AppFeature(App)); - actionExecutingContext.HttpContext.Features.Set(new SchemaFeature(Schema)); + actionExecutingContext.HttpContext.Features.Set(App); + actionExecutingContext.HttpContext.Features.Set(Schema); user.AddClaim(new Claim(SquidexClaimTypes.Permissions, "squidex.apps.my-app.schemas.my-schema")); @@ -94,7 +93,7 @@ public class ApiPermissionAttributeTests : GivenContext [Fact] public async Task Should_return_forbidden_if_user_has_wrong_permission() { - actionExecutingContext.HttpContext.Features.Set(new AppFeature(App)); + actionExecutingContext.HttpContext.Features.Set(App); user.AddClaim(new Claim(SquidexClaimTypes.Permissions, "squidex.apps.other-app")); diff --git a/backend/tests/Squidex.Web.Tests/CommandMiddlewares/ETagCommandMiddlewareTests.cs b/backend/tests/Squidex.Web.Tests/CommandMiddlewares/ETagCommandMiddlewareTests.cs index 5555f39d8..6e6d88b03 100644 --- a/backend/tests/Squidex.Web.Tests/CommandMiddlewares/ETagCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Web.Tests/CommandMiddlewares/ETagCommandMiddlewareTests.cs @@ -8,14 +8,14 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; -using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; namespace Squidex.Web.CommandMiddlewares; -public class ETagCommandMiddlewareTests +public class ETagCommandMiddlewareTests : GivenContext { private readonly IHttpContextAccessor httpContextAccessor = A.Fake(); private readonly HttpContext httpContext = new DefaultHttpContext(); @@ -69,7 +69,7 @@ public class ETagCommandMiddlewareTests } [Fact] - public async Task Should_add_version_from_actual_as_etag_to_response() + public async Task Should_add_version_from_result_as_etag_to_response() { var actual = CommandResult.Empty(DomainId.Empty, 17, 16); @@ -81,7 +81,7 @@ public class ETagCommandMiddlewareTests [Fact] public async Task Should_add_version_from_entity_as_etag_to_response() { - var actual = new ContentEntity { Version = 17 }; + var actual = CreateContent() with { Version = 17 }; await HandleAsync(new CreateContent(), actual); diff --git a/backend/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs b/backend/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs index bd0a57af4..4617b435a 100644 --- a/backend/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs @@ -6,12 +6,12 @@ // ========================================================================== using Microsoft.AspNetCore.Http; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -using Squidex.Web.Pipeline; namespace Squidex.Web.CommandMiddlewares; @@ -23,8 +23,8 @@ public class EnrichWithSchemaIdCommandMiddlewareTests : GivenContext public EnrichWithSchemaIdCommandMiddlewareTests() { - httpContext.Features.Set(new AppFeature(App)); - httpContext.Features.Set(new SchemaFeature(Schema)); + httpContext.Features.Set(App); + httpContext.Features.Set(Schema); A.CallTo(() => httpContextAccessor.HttpContext) .Returns(httpContext); @@ -35,7 +35,7 @@ public class EnrichWithSchemaIdCommandMiddlewareTests : GivenContext [Fact] public async Task Should_throw_exception_if_schema_not_found() { - httpContext.Features.Set(null); + httpContext.Features.Set(null); await Assert.ThrowsAsync(() => HandleAsync(new CreateContent())); } diff --git a/backend/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithTeamIdCommandMiddlewareTests.cs b/backend/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithTeamIdCommandMiddlewareTests.cs index 98c880e2b..6f7402d24 100644 --- a/backend/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithTeamIdCommandMiddlewareTests.cs +++ b/backend/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithTeamIdCommandMiddlewareTests.cs @@ -6,12 +6,12 @@ // ========================================================================== using Microsoft.AspNetCore.Http; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Teams.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -using Squidex.Web.Pipeline; namespace Squidex.Web.CommandMiddlewares; @@ -23,7 +23,7 @@ public class EnrichWithTeamIdCommandMiddlewareTests : GivenContext public EnrichWithTeamIdCommandMiddlewareTests() { - httpContext.Features.Set(new TeamFeature(Team)); + httpContext.Features.Set(Team); A.CallTo(() => httpContextAccessor.HttpContext) .Returns(httpContext); @@ -34,7 +34,7 @@ public class EnrichWithTeamIdCommandMiddlewareTests : GivenContext [Fact] public async Task Should_throw_exception_if_team_not_found() { - httpContext.Features.Set(null); + httpContext.Features.Set(null); await Assert.ThrowsAsync(() => HandleAsync(new UpdateTeam())); } diff --git a/backend/tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs b/backend/tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs index d813e548d..df216c7a5 100644 --- a/backend/tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs +++ b/backend/tests/Squidex.Web.Tests/Pipeline/AppResolverTests.cs @@ -14,7 +14,6 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Routing; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure.Security; using Squidex.Shared; @@ -79,7 +78,7 @@ public class AppResolverTests : GivenContext SetupUser(); A.CallTo(() => AppProvider.GetAppAsync(AppId.Name, false, httpContext.RequestAborted)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); await sut.OnActionExecutionAsync(actionExecutingContext, next); @@ -130,8 +129,10 @@ public class AppResolverTests : GivenContext user.AddClaim(new Claim(OpenIdClaims.Subject, User.Identifier)); - A.CallTo(() => App.Contributors) - .Returns(Contributors.Empty.Assign(User.Identifier, Role.Reader)); + App = App with + { + Contributors = Contributors.Empty.Assign(User.Identifier, Role.Reader) + }; A.CallTo(() => AppProvider.GetAppAsync(AppId.Name, true, httpContext.RequestAborted)) .Returns(App); @@ -155,8 +156,10 @@ public class AppResolverTests : GivenContext user.AddClaim(new Claim(OpenIdClaims.Subject, User.Identifier)); user.AddClaim(new Claim(OpenIdClaims.ClientId, DefaultClients.Frontend)); - A.CallTo(() => App.Contributors) - .Returns(Contributors.Empty.Assign(User.Identifier, Role.Reader)); + App = App with + { + Contributors = Contributors.Empty.Assign(User.Identifier, Role.Reader) + }; A.CallTo(() => AppProvider.GetAppAsync(AppId.Name, false, httpContext.RequestAborted)) .Returns(App); @@ -179,8 +182,10 @@ public class AppResolverTests : GivenContext user.AddClaim(new Claim(OpenIdClaims.ClientId, $"{AppId.Name}:{Client.Identifier}")); - A.CallTo(() => App.Clients) - .Returns(AppClients.Empty.Add(Client.Identifier, Role.Reader)); + App = App with + { + Clients = AppClients.Empty.Add(Client.Identifier, Role.Reader) + }; A.CallTo(() => AppProvider.GetAppAsync(AppId.Name, true, httpContext.RequestAborted)) .Returns(App); @@ -197,8 +202,10 @@ public class AppResolverTests : GivenContext { var user = SetupUser(); - A.CallTo(() => App.Clients) - .Returns(AppClients.Empty.Add(Client.Identifier, Role.Reader).Update(Client.Identifier, allowAnonymous: true)); + App = App with + { + Clients = AppClients.Empty.Add(Client.Identifier, Role.Reader).Update(Client.Identifier, allowAnonymous: true) + }; A.CallTo(() => AppProvider.GetAppAsync(AppId.Name, true, httpContext.RequestAborted)) .Returns(App); @@ -255,8 +262,10 @@ public class AppResolverTests : GivenContext user.AddClaim(new Claim(OpenIdClaims.ClientId, $"other:{Client.Identifier}")); - A.CallTo(() => App.Clients) - .Returns(AppClients.Empty.Add(Client.Identifier, Role.Reader)); + App = App with + { + Contributors = Contributors.Empty.Assign(User.Identifier, Role.Reader) + }; A.CallTo(() => AppProvider.GetAppAsync(AppId.Name, false, httpContext.RequestAborted)) .Returns(App); diff --git a/backend/tests/Squidex.Web.Tests/Pipeline/SchemaResolverTests.cs b/backend/tests/Squidex.Web.Tests/Pipeline/SchemaResolverTests.cs index 0cbe2c617..05a9d2284 100644 --- a/backend/tests/Squidex.Web.Tests/Pipeline/SchemaResolverTests.cs +++ b/backend/tests/Squidex.Web.Tests/Pipeline/SchemaResolverTests.cs @@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Routing; -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; @@ -41,7 +41,7 @@ public class SchemaResolverTests : GivenContext actionExecutingContext = new ActionExecutingContext(actionContext, new List(), new Dictionary(), this); actionExecutingContext.HttpContext = httpContext; actionExecutingContext.HttpContext.User = new ClaimsPrincipal(user); - actionExecutingContext.HttpContext.Features.Set(new AppFeature(App)); + actionExecutingContext.HttpContext.Features.Set(App); next = () => { @@ -75,8 +75,7 @@ public class SchemaResolverTests : GivenContext actionContext.ActionDescriptor.EndpointMetadata.Add(new SchemaMustBePublishedAttribute()); actionContext.RouteData.Values["schema"] = SchemaId.Id.ToString(); - A.CallTo(() => Schema.SchemaDef) - .Returns(Schema.SchemaDef.Unpublish()); + Schema = Schema.Unpublish(); A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, A._, true, httpContext.RequestAborted)) .Returns(Schema); @@ -91,8 +90,7 @@ public class SchemaResolverTests : GivenContext { actionContext.RouteData.Values["schema"] = SchemaId.Id.ToString(); - A.CallTo(() => Schema.SchemaDef) - .Returns(Schema.SchemaDef.Unpublish()); + Schema = Schema.Unpublish(); A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, A._, true, httpContext.RequestAborted)) .Returns(Schema); @@ -108,7 +106,7 @@ public class SchemaResolverTests : GivenContext actionContext.RouteData.Values["schema"] = SchemaId.Id.ToString(); A.CallTo(() => AppProvider.GetSchemaAsync(AppId.Id, A._, true, httpContext.RequestAborted)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); await sut.OnActionExecutionAsync(actionExecutingContext, next); @@ -174,7 +172,6 @@ public class SchemaResolverTests : GivenContext [Fact] public async Task Should_do_nothing_if_app_feature_not_set() { - actionExecutingContext.HttpContext.Features.Set(null!); actionExecutingContext.RouteData.Values["schema"] = SchemaId.Name; await sut.OnActionExecutionAsync(actionExecutingContext, next); @@ -204,7 +201,7 @@ public class SchemaResolverTests : GivenContext private void AssertSchema() { - Assert.Equal(Schema, actionContext.HttpContext.Features.Get()!.Schema); + Assert.Equal(Schema, actionContext.HttpContext.Features.Get()); Assert.True(isNextCalled); } } diff --git a/backend/tests/Squidex.Web.Tests/Pipeline/TeamResolverTests.cs b/backend/tests/Squidex.Web.Tests/Pipeline/TeamResolverTests.cs index 25319d746..87a088945 100644 --- a/backend/tests/Squidex.Web.Tests/Pipeline/TeamResolverTests.cs +++ b/backend/tests/Squidex.Web.Tests/Pipeline/TeamResolverTests.cs @@ -14,7 +14,7 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Routing; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Entities.Teams; +using Squidex.Domain.Apps.Core.Teams; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; @@ -79,7 +79,7 @@ public class TeamResolverTests : GivenContext SetupUser(); A.CallTo(() => AppProvider.GetTeamAsync(TeamId, httpContext.RequestAborted)) - .Returns(Task.FromResult(null)); + .Returns(Task.FromResult(null)); await sut.OnActionExecutionAsync(actionExecutingContext, next); @@ -116,7 +116,7 @@ public class TeamResolverTests : GivenContext var permissions = user.Claims.Where(x => x.Type == SquidexClaimTypes.Permissions).ToList(); - Assert.Same(Team, httpContext.Features.Get()!.Team); + Assert.Same(Team, httpContext.Features.Get()); Assert.True(user.Claims.Any()); Assert.True(permissions.Count < 3); Assert.True(permissions.TrueForAll(x => x.Value.StartsWith($"squidex.teams.{TeamId}", StringComparison.OrdinalIgnoreCase))); @@ -130,8 +130,10 @@ public class TeamResolverTests : GivenContext user.AddClaim(new Claim(OpenIdClaims.Subject, User.Identifier)); - A.CallTo(() => Team.Contributors) - .Returns(Contributors.Empty.Assign(User.Identifier, Role.Owner)); + Team = Team with + { + Contributors = Contributors.Empty.Assign(User.Identifier, Role.Reader) + }; A.CallTo(() => AppProvider.GetTeamAsync(TeamId, httpContext.RequestAborted)) .Returns(Team); @@ -140,7 +142,7 @@ public class TeamResolverTests : GivenContext var permissions = user.Claims.Where(x => x.Type == SquidexClaimTypes.Permissions).ToList(); - Assert.Same(Team, httpContext.Features.Get()!.Team); + Assert.Same(Team, httpContext.Features.Get()); Assert.True(user.Claims.Count() > 2); Assert.True(permissions.Count < 3); Assert.True(permissions.TrueForAll(x => x.Value.StartsWith($"squidex.teams.{TeamId}", StringComparison.OrdinalIgnoreCase))); @@ -162,7 +164,7 @@ public class TeamResolverTests : GivenContext await sut.OnActionExecutionAsync(actionExecutingContext, next); - Assert.Same(Team, httpContext.Features.Get()!.Team); + Assert.Same(Team, httpContext.Features.Get()); Assert.Equal(2, user.Claims.Count()); Assert.True(isNextCalled); } diff --git a/backend/tests/Squidex.Web.Tests/Pipeline/UsageMiddlewareTests.cs b/backend/tests/Squidex.Web.Tests/Pipeline/UsageMiddlewareTests.cs index 796fbe28d..ee41830c0 100644 --- a/backend/tests/Squidex.Web.Tests/Pipeline/UsageMiddlewareTests.cs +++ b/backend/tests/Squidex.Web.Tests/Pipeline/UsageMiddlewareTests.cs @@ -8,6 +8,7 @@ using System.Text; using Microsoft.AspNetCore.Http; using NodaTime; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Billing; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -53,14 +54,14 @@ public class UsageMiddlewareTests : GivenContext var date = instant.ToDateOnly(); - A.CallTo(() => usageGate.TrackRequestAsync(A._, A._, A._, A._, A._, A._, default)) + A.CallTo(() => usageGate.TrackRequestAsync(A._, A._, A._, A._, A._, A._, default)) .MustNotHaveHappened(); } [Fact] public async Task Should_not_track_if_call_blocked() { - httpContext.Features.Set(new AppFeature(App)); + httpContext.Features.Set(App); httpContext.Features.Set(new ApiCostsAttribute(13)); httpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests; @@ -78,7 +79,7 @@ public class UsageMiddlewareTests : GivenContext [Fact] public async Task Should_track_if_calls_left() { - httpContext.Features.Set(new AppFeature(App)); + httpContext.Features.Set(App); httpContext.Features.Set(new ApiCostsAttribute(13)); await sut.InvokeAsync(httpContext, next); @@ -94,7 +95,7 @@ public class UsageMiddlewareTests : GivenContext [Fact] public async Task Should_track_request_bytes() { - httpContext.Features.Set(new AppFeature(App)); + httpContext.Features.Set(App); httpContext.Features.Set(new ApiCostsAttribute(13)); httpContext.Request.ContentLength = 1024; @@ -111,7 +112,7 @@ public class UsageMiddlewareTests : GivenContext [Fact] public async Task Should_track_response_bytes_with_writer() { - httpContext.Features.Set(new AppFeature(App)); + httpContext.Features.Set(App); httpContext.Features.Set(new ApiCostsAttribute(13)); await sut.InvokeAsync(httpContext, async x => @@ -132,7 +133,7 @@ public class UsageMiddlewareTests : GivenContext [Fact] public async Task Should_track_response_bytes_with_stream() { - httpContext.Features.Set(new AppFeature(App)); + httpContext.Features.Set(App); httpContext.Features.Set(new ApiCostsAttribute(13)); await sut.InvokeAsync(httpContext, async x => @@ -153,7 +154,7 @@ public class UsageMiddlewareTests : GivenContext [Fact] public async Task Should_track_response_bytes_with_file() { - httpContext.Features.Set(new AppFeature(App)); + httpContext.Features.Set(App); httpContext.Features.Set(new ApiCostsAttribute(13)); var tempFileName = Path.GetTempFileName(); @@ -184,7 +185,7 @@ public class UsageMiddlewareTests : GivenContext [Fact] public async Task Should_not_track_if_costs_are_zero() { - httpContext.Features.Set(new AppFeature(App)); + httpContext.Features.Set(App); httpContext.Features.Set(new ApiCostsAttribute(0)); await sut.InvokeAsync(httpContext, next); @@ -200,7 +201,7 @@ public class UsageMiddlewareTests : GivenContext [Fact] public async Task Should_log_request_even_if_costs_are_zero() { - httpContext.Features.Set(new AppFeature(App)); + httpContext.Features.Set(App); httpContext.Features.Set(new ApiCostsAttribute(0)); httpContext.Request.Method = "GET"; diff --git a/frontend/src/app/features/content/pages/content/inspecting/content-inspection.component.ts b/frontend/src/app/features/content/pages/content/inspecting/content-inspection.component.ts index 29ca71de9..845d0ed67 100644 --- a/frontend/src/app/features/content/pages/content/inspecting/content-inspection.component.ts +++ b/frontend/src/app/features/content/pages/content/inspecting/content-inspection.component.ts @@ -54,12 +54,14 @@ export class ContentInspectionComponent implements OnDestroy { this.mode, ]).pipe( switchMap(([language, mode]) => { - if (mode === 'Content') { - return of(this.content); - } else if (mode === 'Data') { + if (mode === 'Data') { return of(this.content.data); + } else if (mode === 'Content') { + return this.contentsService.getRawContent(this.appName, + this.content.schemaName, + this.content.id); } else { - return this.contentsService.getContent(this.appName, + return this.contentsService.getRawContent(this.appName, this.content.schemaName, this.content.id, language?.iso2Code).pipe( diff --git a/frontend/src/app/shared/components/contents/content-list-header.component.ts b/frontend/src/app/shared/components/contents/content-list-header.component.ts index 8abbe2f04..51696104d 100644 --- a/frontend/src/app/shared/components/contents/content-list-header.component.ts +++ b/frontend/src/app/shared/components/contents/content-list-header.component.ts @@ -47,9 +47,9 @@ export class ContentListHeaderComponent { } else if (field.rootField?.properties.isSortable !== true) { this.sortPath = undefined; } else if (field.rootField.isLocalizable && language) { - this.sortPath = `data.${field.name}.${language.iso2Code}`; + this.sortPath = `${field.name}.${language.iso2Code}`; } else { - this.sortPath = `data.${field.name}.iv`; + this.sortPath = `${field.name}.iv`; } if (field === META_FIELDS.lastModified) { diff --git a/frontend/src/app/shared/services/contents.service.spec.ts b/frontend/src/app/shared/services/contents.service.spec.ts index 74056e8cf..2644ff575 100644 --- a/frontend/src/app/shared/services/contents.service.spec.ts +++ b/frontend/src/app/shared/services/contents.service.spec.ts @@ -170,6 +170,46 @@ describe('ContentsService', () => { expect(content!).toEqual(createContent(12)); })); + it('should make get request to get raw content', + inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { + let content: any; + + contentsService.getRawContent('my-app', 'my-schema', '1').subscribe(result => { + content = result; + }); + + const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/1'); + + expect(req.request.method).toEqual('GET'); + expect(req.request.headers.get('If-Match')).toBeNull(); + expect(req.request.headers.get('X-Flatten')).toBeNull(); + expect(req.request.headers.get('X-Languages')).toBeNull(); + + req.flush({ id: '1' }); + + expect(content!).toEqual({ id: '1' }); + })); + + it('should make get request to get raw content with language', + inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { + let content: any; + + contentsService.getRawContent('my-app', 'my-schema', '1', 'en').subscribe(result => { + content = result; + }); + + const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/1'); + + expect(req.request.method).toEqual('GET'); + expect(req.request.headers.get('If-Match')).toBeNull(); + expect(req.request.headers.get('X-Flatten')).toEqual('1'); + expect(req.request.headers.get('X-Languages')).toEqual('en'); + + req.flush({ id: '1' }); + + expect(content!).toEqual({ id: '1' }); + })); + it('should make post request to create content', inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { const dto = {}; diff --git a/frontend/src/app/shared/services/contents.service.ts b/frontend/src/app/shared/services/contents.service.ts index 3ae68866e..47e7bf88e 100644 --- a/frontend/src/app/shared/services/contents.service.ts +++ b/frontend/src/app/shared/services/contents.service.ts @@ -262,7 +262,17 @@ export class ContentsService { pretifyError('i18n:contents.loadFailed')); } - public getContent(appName: string, schemaName: string, id: string, language?: string): Observable { + public getContent(appName: string, schemaName: string, id: string): Observable { + const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`); + + return HTTP.getVersioned(this.http, url).pipe( + map(({ payload }) => { + return parseContent(payload.body); + }), + pretifyError('i18n:contents.loadContentFailed')); + } + + public getRawContent(appName: string, schemaName: string, id: string, language?: string): Observable { const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`); let headers = new HttpHeaders(); @@ -274,7 +284,7 @@ export class ContentsService { return HTTP.getVersioned(this.http, url, undefined, headers).pipe( map(({ payload }) => { - return parseContent(payload.body); + return payload.body; }), pretifyError('i18n:contents.loadContentFailed')); } diff --git a/frontend/src/app/shared/services/schemas.service.ts b/frontend/src/app/shared/services/schemas.service.ts index c393c8d5d..e8985592f 100644 --- a/frontend/src/app/shared/services/schemas.service.ts +++ b/frontend/src/app/shared/services/schemas.service.ts @@ -25,67 +25,67 @@ export const META_FIELDS = { title: '', }, id: { - name: 'meta.id', + name: 'id', label: 'i18n:schemas.tableHeaders.id', title: 'i18n:schemas.tableHeaders.id_title', }, created: { - name: 'meta.created', + name: 'created', label: 'i18n:schemas.tableHeaders.created', title: 'i18n:schemas.tableHeaders.created_title', }, createdByAvatar: { - name: 'meta.createdBy.avatar', + name: 'createdBy.avatar', label: 'i18n:schemas.tableHeaders.createdByShort', title: 'i18n:schemas.tableHeaders.createdByShort_title', }, createdByName: { - name: 'meta.createdBy.name', + name: 'createdBy.name', label: 'i18n:schemas.tableHeaders.createdBy', title: 'i18n:schemas.tableHeaders.createdBy_title', }, lastModified: { - name: 'meta.lastModified', + name: 'lastModified', label: 'i18n:schemas.tableHeaders.lastModified', title: 'i18n:schemas.tableHeaders.lastModified_title', }, lastModifiedByAvatar: { - name: 'meta.lastModifiedBy.avatar', + name: 'lastModifiedBy.avatar', label: 'i18n:schemas.tableHeaders.lastModifiedByShort', title: 'i18n:schemas.tableHeaders.lastModifiedByShort_title', }, lastModifiedByName: { - name: 'meta.lastModifiedBy.name', + name: 'lastModifiedBy.name', label: 'i18n:schemas.tableHeaders.lastModifiedBy', title: 'i18n:schemas.tableHeaders.lastModifiedBy_title', }, status: { - name: 'meta.status', + name: 'status', label: 'i18n:schemas.tableHeaders.status', title: 'i18n:schemas.tableHeaders.status_title', }, statusColor: { - name: 'meta.status.color', + name: 'status.color', label: 'i18n:schemas.tableHeaders.status', title: 'i18n:schemas.tableHeaders.status_title', }, statusNext: { - name: 'meta.status.next', + name: 'status.next', label: 'i18n:schemas.tableHeaders.nextStatus', title: 'i18n:schemas.tableHeaders.nextStatus_title', }, version: { - name: 'meta.version', + name: 'version', label: 'i18n:schemas.tableHeaders.version', title: 'i18n:schemas.tableHeaders.version_title', }, translationStatus: { - name: 'meta.translationStatus', + name: 'translationStatus', label: 'i18n:schemas.tableHeaders.translationStatus', title: 'i18n:schemas.tableHeaders.translationStatus_title', }, translationStatusAverage: { - name: 'meta.translationStatusAverage', + name: 'translationStatusAverage', label: 'i18n:schemas.tableHeaders.translationStatusAverage', title: 'i18n:schemas.tableHeaders.translationStatusAverage_title', }, @@ -95,7 +95,7 @@ export function getTableFields(fields: ReadonlyArray) { const result: string[] = []; for (const field of fields) { - if (field.name && field.name.indexOf('meta') < 0) { + if (field.name?.startsWith('data.')) { result.push(field.name); } } @@ -181,13 +181,13 @@ export class SchemaDto { function tableField(rootField: RootFieldDto) { const label = rootField.displayName; - return { name: rootField.name, label, rootField }; + return { name: `data.${rootField.name}`, label, rootField }; } if (fields) { this.contentFields = fields.filter(x => x.properties.isContentField).map(tableField); - function tableFields(names: ReadonlyArray, fields: ReadonlyArray): TableField[] { + function tableFields(names: ReadonlyArray, fields: ReadonlyArray): TableField[] { const result: TableField[] = []; for (const name of names) { @@ -196,10 +196,10 @@ export class SchemaDto { if (metaField) { result.push(metaField); } else { - const field = fields.find(x => x.name === name && x.properties.isContentField); + const field = fields.find(x => x.name === name); if (field) { - result.push(tableField(field)); + result.push(field); } } } @@ -207,7 +207,7 @@ export class SchemaDto { return result; } - const listFields = tableFields(fieldsInLists, fields); + const listFields = tableFields(fieldsInLists, this.contentFields); if (listFields.length === 0) { listFields.push(META_FIELDS.lastModifiedByAvatar); @@ -224,7 +224,7 @@ export class SchemaDto { this.defaultListFields = listFields; - const referenceFields = tableFields(fieldsInReferences, fields); + const referenceFields = tableFields(fieldsInReferences, this.contentFields); if (referenceFields.length === 0) { if (fields.length > 0) { diff --git a/frontend/src/app/shared/services/schemas.spec.ts b/frontend/src/app/shared/services/schemas.spec.ts index 995b46af4..7372c64cc 100644 --- a/frontend/src/app/shared/services/schemas.spec.ts +++ b/frontend/src/app/shared/services/schemas.spec.ts @@ -37,21 +37,37 @@ describe('SchemaDto', () => { }); it('should return configured fields as list fields if fields are declared', () => { - const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3], fieldsInLists: ['field1', 'field3', META_FIELDS.status.name] }); + const schema = createSchema({ + properties: new SchemaPropertiesDto(''), + fieldsInLists: [ + 'data.field1', + 'data.field3', + META_FIELDS.status.name, + ], + fields: [field1, field2, field3], + }); expect(schema.defaultListFields).toEqual([ - { name: field1.name, rootField: field1, label: field1.displayName }, - { name: field3.name, rootField: field3, label: field3.displayName }, + { name: 'data.field1', rootField: field1, label: field1.displayName }, + { name: 'data.field3', rootField: field3, label: field3.displayName }, META_FIELDS.status, ]); }); it('should return configured fields as references fields if fields are declared', () => { - const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3], fieldsInReferences: ['field1', 'field3', META_FIELDS.status.name] }); + const schema = createSchema({ + properties: new SchemaPropertiesDto(''), + fieldsInReferences: [ + 'data.field1', + 'data.field3', + META_FIELDS.status.name, + ], + fields: [field1, field2, field3], + }); expect(schema.defaultReferenceFields).toEqual([ - { name: field1.name, rootField: field1, label: field1.displayName }, - { name: field3.name, rootField: field3, label: field3.displayName }, + { name: 'data.field1', rootField: field1, label: field1.displayName }, + { name: 'data.field3', rootField: field3, label: field3.displayName }, META_FIELDS.status, ]); }); @@ -61,7 +77,7 @@ describe('SchemaDto', () => { expect(schema.defaultListFields).toEqual([ META_FIELDS.lastModifiedByAvatar, - { name: field1.name, rootField: field1, label: field1.displayName }, + { name: 'data.field1', rootField: field1, label: field1.displayName }, META_FIELDS.statusColor, META_FIELDS.lastModified, ]); @@ -82,7 +98,7 @@ describe('SchemaDto', () => { const schema = createSchema({ properties: new SchemaPropertiesDto(''), fields: [field1, field2, field3] }); expect(schema.defaultReferenceFields).toEqual([ - { name: field1.name, rootField: field1, label: field1.displayName }, + { name: 'data.field1', rootField: field1, label: field1.displayName }, ]); }); diff --git a/frontend/src/app/shared/state/table-settings.spec.ts b/frontend/src/app/shared/state/table-settings.spec.ts index e814e62d9..de75e94a0 100644 --- a/frontend/src/app/shared/state/table-settings.spec.ts +++ b/frontend/src/app/shared/state/table-settings.spec.ts @@ -35,8 +35,14 @@ describe('TableSettings', () => { const INVALID_FIELD = { name: 'invalid', label: 'invalid' }; const INVALID_CONFIGS = [ - { case: 'blank', fields: [] }, - { case: 'broken', fields: [{ name: 'invalid', label: 'invalid' }] }, + { + case: 'blank', + fields: [], + }, + { + case: 'broken', + fields: [{ name: 'invalid', label: 'invalid' }], + }, ]; const EMPTY = { fields: [], sizes: {}, wrappings: {} }; @@ -78,7 +84,7 @@ describe('TableSettings', () => { expect(listFields!).toEqual([ META_FIELDS.lastModifiedByAvatar.name, - schema.fields[0].name, + `data.${schema.fields[0].name}`, META_FIELDS.statusColor.name, META_FIELDS.lastModified.name, ]); @@ -256,12 +262,12 @@ describe('TableSettings', () => { META_FIELDS.version.name, ], sizes: { - field1: 100, - field2: 200, + 'data.field1': 100, + 'data.field2': 200, }, wrappings: { - field3: true, - field4: false, + 'data.field3': true, + 'data.field4': false, }, }; @@ -286,7 +292,7 @@ describe('TableSettings', () => { expect(listFields!).toEqual([ META_FIELDS.lastModifiedByAvatar.name, - schema.fields[0].name, + `data.${schema.fields[0].name}`, META_FIELDS.statusColor.name, META_FIELDS.lastModified.name, ]); diff --git a/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs b/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs index 345d0dc1a..8ba6b4bb2 100644 --- a/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs +++ b/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs @@ -24,6 +24,30 @@ public class ContentQueryTests : IClassFixture _ = fixture; } + [Fact] + public async Task Should_query_with_old_fields() + { + var context = + QueryContext.Default + .WithFields(TestEntityData.StringField); + + var items_0 = await _.Client.DynamicContents(_.SchemaName).GetAsync(context: context); + + Assert.True(items_0.Items.All(x => x.Data.Count == 1)); + } + + [Fact] + public async Task Should_query_with_new_fields() + { + var context = + QueryContext.Default + .WithFields($"data.{TestEntityData.StringField}"); + + var items_0 = await _.Client.DynamicContents(_.SchemaName).GetAsync(context: context); + + Assert.True(items_0.Items.All(x => x.Data.Count == 1)); + } + [Fact] public async Task Should_query_newly_created_schema() { @@ -81,11 +105,11 @@ public class ContentQueryTests : IClassFixture [Fact] public async Task Should_query_by_ids_filter() { - var q0 = new ContentQuery { Filter = "data/number/iv gt 3 and data/number/iv lt 7", OrderBy = "data/number/iv asc" }; + var q_0 = new ContentQuery { Filter = "data/number/iv gt 3 and data/number/iv lt 7", OrderBy = "data/number/iv asc" }; - var items_0 = await _.Contents.GetAsync(q0); + var items_0 = await _.Contents.GetAsync(q_0); - var q1 = new ContentQuery + var q_1 = new ContentQuery { JsonQuery = new { @@ -108,7 +132,7 @@ public class ContentQueryTests : IClassFixture } }; - var items_1 = await _.Contents.GetAsync(q1); + var items_1 = await _.Contents.GetAsync(q_1); AssertItems(items_0, 3, [4, 5, 6]); AssertItems(items_1, 3, [4, 5, 6]); diff --git a/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs b/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs index 3ad15ea87..d0eb405f1 100644 --- a/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs +++ b/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs @@ -345,7 +345,7 @@ public sealed class GraphQLTests : IClassFixture var httpClient = _.Client.CreateHttpClient(); // Create the request manually to check the content type. - var response = await httpClient.PostAsync(_.Client.GenerateUrl($"api/content/{_.AppName}/graphql/batch"), query.ToContent()); + var response = await httpClient.PostAsync(_.Client.GenerateUrl($"api/content/{_.AppName}/graphql/batch"), query.ToContent(_.Client.Options)); Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType); } diff --git a/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj b/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj index ae37f2e32..4e5a48a98 100644 --- a/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj +++ b/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj @@ -9,18 +9,18 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all diff --git a/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj b/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj index 4e6da4c7b..446d8d7a3 100644 --- a/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj +++ b/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj @@ -7,7 +7,7 @@ enable - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj b/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj index fc0a3aa02..f7b7614cd 100644 --- a/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj +++ b/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj @@ -7,7 +7,7 @@ enable - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -18,10 +18,10 @@ - - + + - +