From 4edafac32806c4377eb8fc8e539a7e60a9b62cb0 Mon Sep 17 00:00:00 2001 From: Tobias Klika Date: Wed, 24 Jul 2024 10:45:14 +0200 Subject: [PATCH] remove legacy project --- zero.Core/ApplicationBuilderExtensions.cs | 8 - zero.Core/Applications/Application.cs | 43 --- zero.Core/Applications/ApplicationOptions.cs | 12 - .../Applications/ApplicationRegistration.cs | 35 -- zero.Core/Applications/ApplicationRegistry.cs | 28 -- zero.Core/Applications/ApplicationResolver.cs | 260 ------------- .../IApplicationResolverHandler.cs | 8 - .../IBackofficeApplicationResolverHandler.cs | 8 - zero.Core/Applications/SystemApplication.cs | 5 - .../Applications/ZeroApplicationModule.cs | 14 - .../AssemblyDiscovery/AssemblyDiscovery.cs | 161 -------- .../AssemblyDiscoveryContext.cs | 26 -- .../IAssemblyDiscoveryRule.cs | 12 - .../ZeroAssemblyDiscoveryRule.cs | 16 - .../Architecture/Blueprints/Blueprint.cs | 216 ----------- .../Blueprints/BlueprintChildInterceptor.cs | 32 -- .../Blueprints/BlueprintConfiguration.cs | 27 -- .../Architecture/Blueprints/BlueprintField.cs | 54 --- .../Blueprints/BlueprintInterceptor.cs | 137 ------- .../Blueprints/BlueprintOptions.cs | 21 -- .../Blueprints/BlueprintService.cs | 76 ---- .../Blueprints/BlueprintStatus.cs | 17 - .../Blueprints/IBlueprintInterceptor.cs | 6 - .../Plugins/IZeroBuiltInPlugin.cs | 6 - zero.Core/Architecture/Plugins/IZeroPlugin.cs | 25 -- .../Architecture/Plugins/ZeroPluginOptions.cs | 30 -- .../Architecture/ZeroArchitectureModule.cs | 15 - zero.Core/Architecture/ZeroModule.cs | 50 --- .../Architecture/ZeroModuleCollection.cs | 70 ---- zero.Core/Communication/Handlers/IHandler.cs | 5 - .../Communication/Handlers/IHandlerHolder.cs | 32 -- .../Communication/Interceptors/Interceptor.cs | 207 ----------- .../Interceptors/InterceptorInstruction.cs | 148 -------- .../Interceptors/InterceptorParameters.cs | 44 --- .../Interceptors/InterceptorResult.cs | 19 - .../Interceptors/InterceptorRunType.cs | 8 - .../Interceptors/Interceptors.cs | 49 --- zero.Core/Communication/LazilyResolved.cs | 11 - zero.Core/Communication/Messages/IMessage.cs | 6 - .../Communication/Messages/IMessageHandler.cs | 26 -- .../Messages/MessageAggregator.cs | 52 --- .../Messages/MessageSubscription.cs | 41 -- .../Communication/ZeroCommunicationModule.cs | 15 - zero.Core/Configuration/Constants.cs | 102 ----- zero.Core/Configuration/Features/Feature.cs | 22 -- .../Configuration/Features/FeatureOptions.cs | 19 - .../Configuration/IZeroCollectionOptions.cs | 6 - .../Integrations/FlavorOptionsExtensions.cs | 21 -- .../Configuration/Integrations/Integration.cs | 12 - .../Integrations/IntegrationStore.cs | 210 ----------- .../Integrations/IntegrationType.cs | 34 -- .../Integrations/IntegrationTypeService.cs | 70 ---- .../Integrations/IntegrationTypeWithStatus.cs | 12 - zero.Core/Configuration/OptionsType.cs | 26 -- .../Configuration/ZeroConfigurationModule.cs | 35 -- zero.Core/Configuration/ZeroOptions.cs | 79 ---- zero.Core/Configuration/ZeroStartupOptions.cs | 23 -- zero.Core/Context/ZeroContext.cs | 327 ---------------- zero.Core/Context/ZeroContextMiddleware.cs | 20 - zero.Core/Context/ZeroContextModule.cs | 13 - zero.Core/Extensions/CharExtensions.cs | 57 --- zero.Core/Extensions/ColorExtensions.cs | 58 --- zero.Core/Extensions/DictionaryExtensions.cs | 36 -- zero.Core/Extensions/EnumerableExtensions.cs | 36 -- zero.Core/Extensions/ExpressionExtensions.cs | 48 --- zero.Core/Extensions/HttpContextExtensions.cs | 54 --- zero.Core/Extensions/NumberExtensions.cs | 47 --- zero.Core/Extensions/ObjectExtensions.cs | 30 -- zero.Core/Extensions/ResultExtensions.cs | 48 --- .../Extensions/ServiceCollectionExtensions.cs | 109 ------ zero.Core/Extensions/StringExtensions.cs | 196 ---------- zero.Core/FileStorage/FileResult.cs | 10 - zero.Core/FileStorage/FileSizeNotation.cs | 18 - zero.Core/FileStorage/FileSystemException.cs | 10 - zero.Core/FileStorage/FileSystemOptions.cs | 6 - zero.Core/FileStorage/IFileMeta.cs | 44 --- zero.Core/FileStorage/IFileSystem.cs | 71 ---- zero.Core/FileStorage/Paths.cs | 217 ----------- zero.Core/FileStorage/PhysicalFileMeta.cs | 43 --- zero.Core/FileStorage/PhysicalFileSystem.cs | 305 --------------- zero.Core/FileStorage/WebRootFileSystem.cs | 28 -- .../FileStorage/ZeroFileStorageModule.cs | 25 -- .../AuthenticationBuilderExtensions.cs | 95 ----- .../Identity/BackofficeUserExtensions.cs | 21 -- zero.Core/Identity/Base32.cs | 116 ------ zero.Core/Identity/Models/LoginResult.cs | 16 - zero.Core/Identity/Models/UserClaim.cs | 41 -- .../Identity/Models/UserClaimComparer.cs | 14 - zero.Core/Identity/Models/ZeroIdentityRole.cs | 9 - zero.Core/Identity/Models/ZeroIdentityUser.cs | 73 ---- zero.Core/Identity/Models/ZeroUser.cs | 28 -- zero.Core/Identity/Models/ZeroUserRole.cs | 15 - zero.Core/Identity/PasswordGenerator.cs | 21 -- .../Identity/Permissions/EntityPermission.cs | 39 -- zero.Core/Identity/Permissions/Permission.cs | 85 ----- .../Identity/Permissions/PermissionContext.cs | 80 ---- .../Identity/Permissions/PermissionGroup.cs | 63 ---- .../Permissions/PermissionProvider.cs | 21 -- zero.Core/Identity/Permissions/Permissions.cs | 67 ---- zero.Core/Identity/RavenRoleStore(TRole).cs | 150 -------- zero.Core/Identity/RavenScopedStores.cs | 27 -- zero.Core/Identity/RavenUserStore(TUser).cs | 349 ----------------- .../Identity/RavenUserStore(TUser,TRole).cs | 53 --- .../Security/ContextualCookieManager.cs | 24 -- .../Identity/Security/SchemedSignInManager.cs | 102 ----- .../Identity/Security/ZeroAuthOptions.cs | 8 - .../Security/ZeroAuthorizeAttribute.cs | 136 ------- .../ZeroBackofficeClaimsPrincipalFactory.cs | 74 ---- .../Security/ZeroClaimsPrinicipalFactory.cs | 119 ------ .../Services/AuthenticationService.cs | 135 ------- .../Identity/Services/AuthorizationService.cs | 135 ------- .../Identity/Services/UserRolesService.cs | 117 ------ zero.Core/Identity/Services/UserService.cs | 350 ------------------ zero.Core/Identity/UserIdentity.cs | 108 ------ .../Validation/BackofficeUserRoleValidator.cs | 35 -- .../Validation/BackofficeUserValidator.cs | 20 - .../ZeroAuthenticationSchemeProvider.cs | 55 --- zero.Core/Identity/ZeroIdentityExtensions.cs | 91 ----- zero.Core/Identity/ZeroIdentityModule.cs | 44 --- zero.Core/Localization/CountryStore.cs | 9 - zero.Core/Localization/CountryValidator.cs | 12 - zero.Core/Localization/CultureResolver.cs | 109 ------ zero.Core/Localization/CultureService.cs | 31 -- zero.Core/Localization/LanguageStore.cs | 9 - zero.Core/Localization/LanguageValidator.cs | 18 - zero.Core/Localization/LocalizeAttribute.cs | 12 - zero.Core/Localization/Localizer.cs | 116 ------ zero.Core/Localization/LocalizerExtensions.cs | 25 -- zero.Core/Localization/Models/Country.cs | 15 - zero.Core/Localization/Models/Culture.cs | 8 - zero.Core/Localization/Models/Language.cs | 25 -- zero.Core/Localization/Models/Translation.cs | 27 -- zero.Core/Localization/TranslationStore.cs | 22 -- .../Localization/TranslationValidator.cs | 12 - .../Localization/ZeroLocalizationModule.cs | 39 -- zero.Core/Mails/FileMailDispatcher.cs | 89 ----- zero.Core/Mails/IMailDispatcher.cs | 19 - zero.Core/Mails/LoggerMailDispatcher.cs | 45 --- zero.Core/Mails/Mail.cs | 27 -- zero.Core/Mails/MailMetadata.cs | 6 - zero.Core/Mails/MailOptions.cs | 6 - zero.Core/Mails/MailPlaceholders.cs | 6 - zero.Core/Mails/MailProvider.cs | 208 ----------- zero.Core/Mails/MailSendResult.cs | 10 - zero.Core/Mails/MailTemplateValidator.cs | 29 -- zero.Core/Mails/MailTemplatesStore.cs | 24 -- zero.Core/Mails/Models/MailTemplate.cs | 45 --- zero.Core/Mails/ZeroMailModule.cs | 28 -- zero.Core/Mapper/MapperExtensions.cs | 61 --- zero.Core/Mapper/ZeroMapper.cs | 196 ---------- zero.Core/Mapper/ZeroMapperContext.cs | 22 -- zero.Core/Mapper/ZeroMapperModule.cs | 12 - zero.Core/Mapper/ZeroMapperProfile.cs | 16 - zero.Core/Media/Indexes/Media_ByChildren.cs | 38 -- zero.Core/Media/Indexes/Media_ByHierarchy.cs | 45 --- zero.Core/Media/MediaCreator.cs | 150 -------- zero.Core/Media/MediaFileSystem.cs | 39 -- zero.Core/Media/MediaLinkProvider.cs | 87 ----- zero.Core/Media/MediaManagement.cs | 205 ---------- zero.Core/Media/MediaManagementExtensions.cs | 120 ------ zero.Core/Media/MediaOptions.cs | 16 - zero.Core/Media/MediaStore.cs | 42 --- zero.Core/Media/Models/Media.cs | 48 --- zero.Core/Media/Models/MediaFocalPoint.cs | 13 - zero.Core/Media/Models/MediaImageMetadata.cs | 58 --- zero.Core/Media/Models/MediaListItem.cs | 24 -- zero.Core/Media/Models/Video.cs | 24 -- zero.Core/Media/ZeroMediaModule.cs | 51 --- zero.Core/Models/IAlwaysActive.cs | 12 - zero.Core/Models/ISupportsFlavors.cs | 14 - zero.Core/Models/ISupportsRouting.cs | 14 - zero.Core/Models/ISupportsSorting.cs | 9 - zero.Core/Models/ISupportsTrees.cs | 19 - zero.Core/Models/Results/Paged.cs | 66 ---- zero.Core/Models/Results/Result.cs | 114 ------ zero.Core/Models/Results/UrlsResult.cs | 56 --- zero.Core/Models/ZeroEntity.cs | 86 ----- zero.Core/Models/ZeroEntityPreview.cs | 15 - zero.Core/Models/ZeroIdEntity.cs | 9 - zero.Core/Models/ZeroReference.cs | 30 -- zero.Core/Pages/FlavorOptionsExtensions.cs | 17 - zero.Core/Pages/IPageTypeHandler.cs | 6 - .../Pages/Indexes/zero_Pages_AsHistory.cs | 25 -- .../Pages/Indexes/zero_Pages_ByHierarchy.cs | 45 --- zero.Core/Pages/Indexes/zero_Pages_ByType.cs | 16 - .../Pages/Indexes/zero_Pages_WithChildren.cs | 38 -- zero.Core/Pages/Models/Page.cs | 27 -- zero.Core/Pages/Models/PageFolder.cs | 17 - .../Modules/ConfigureModuleTypeJsonOptions.cs | 19 - zero.Core/Pages/Modules/IModuleTypeHandler.cs | 6 - .../Pages/Modules/JsonModuleTypeConverter.cs | 33 -- zero.Core/Pages/Modules/ModulePermissions.cs | 17 - zero.Core/Pages/Modules/PageModule.cs | 23 -- zero.Core/Pages/Modules/PageModuleOptions.cs | 19 - zero.Core/Pages/Modules/PageModuleType.cs | 26 -- .../Pages/Modules/PageModuleTypeService.cs | 106 ------ zero.Core/Pages/PageOptions.cs | 6 - zero.Core/Pages/PageTypeService.cs | 89 ----- zero.Core/Pages/PagesStore.cs | 62 ---- zero.Core/Pages/ZeroPageModule.cs | 50 --- zero.Core/Persistence/GenerateIdAttribute.cs | 17 - .../Persistence/ISupportsDbConventions.cs | 6 - zero.Core/Persistence/IdGenerator.cs | 112 ------ zero.Core/Persistence/Indexes/ZeroIndex.cs | 288 -------------- .../Indexes/ZeroIndexExtensions.cs | 15 - .../Indexes/ZeroTreeHierachyIndex.cs | 28 -- .../Persistence/RavenCollectionAttribute.cs | 15 - zero.Core/Persistence/RavenConstants.cs | 9 - zero.Core/Persistence/RavenIndexExtensions.cs | 11 - zero.Core/Persistence/RavenOptions.cs | 122 ------ .../Persistence/RavenQueryableExtensions.cs | 118 ------ zero.Core/Persistence/Revisions/Revision.cs | 26 -- .../Persistence/Revisions/RevisionService.cs | 96 ----- zero.Core/Persistence/Safenames.cs | 143 ------- .../Tokens/Rfc6238AuthenticationService.cs | 104 ------ zero.Core/Persistence/Tokens/Token.cs | 13 - .../Persistence/Tokens/ZeroTokenProvider.cs | 295 --------------- .../ZeroDocumentConventionsBuilder.cs | 142 ------- zero.Core/Persistence/ZeroDocumentSession.cs | 51 --- .../ZeroDocumentSessionExtensions.cs | 14 - zero.Core/Persistence/ZeroDocumentStore.cs | 147 -------- .../Persistence/ZeroPersistenceModule.cs | 58 --- zero.Core/Persistence/ZeroStore.cs | 103 ------ zero.Core/Preview/PreviewOptions.cs | 17 - zero.Core/Preview/PreviewService.cs | 87 ----- zero.Core/Preview/Routing/PreviewRoute.cs | 8 - .../Preview/Routing/PreviewRouteModel.cs | 8 - .../Preview/Routing/PreviewRouteProvider.cs | 43 --- zero.Core/Preview/ZeroPreviewModule.cs | 13 - zero.Core/RecycleBin/RecycleBinApi.cs | 258 ------------- zero.Core/RecycleBin/RecycleBinListQuery.cs | 9 - zero.Core/RecycleBin/RecycledEntity.cs | 28 -- zero.Core/Rendering/RazorRenderer.cs | 345 ----------------- zero.Core/Rendering/ZeroRenderingModule.cs | 12 - .../Routing/ApplicationBuilderExtensions.cs | 13 - .../Configuration/RoutingEndpointOptions.cs | 50 --- .../Routing/Configuration/RoutingOptions.cs | 38 -- .../RoutingPageResolverOptions.cs | 52 --- .../Routing/Indexes/RouteRedirects_ByUrl.cs | 15 - .../Routing/Indexes/Routes_ByDependencies.cs | 13 - .../Routing/Indexes/Routes_ForResolver.cs | 14 - zero.Core/Routing/Links/ILinkProvider.cs | 21 -- .../Routing/Links/LinkTargetExtensions.cs | 13 - zero.Core/Routing/Links/Links.cs | 177 --------- zero.Core/Routing/Links/RawLinkProvider.cs | 60 --- zero.Core/Routing/Models/IRouteModel.cs | 11 - zero.Core/Routing/Models/Link.cs | 25 -- zero.Core/Routing/Models/LinkPreview.cs | 16 - zero.Core/Routing/Models/LinkTarget.cs | 8 - zero.Core/Routing/Models/Route.cs | 72 ---- zero.Core/Routing/Models/RouteEndpoint.cs | 47 --- zero.Core/Routing/Models/RouteRedirect.cs | 38 -- zero.Core/Routing/Models/RouteResponse.cs | 14 - zero.Core/Routing/Models/RoutingContext.cs | 28 -- zero.Core/Routing/NotFoundMatcherPolicy.cs | 84 ----- .../PageRouteProvider/BasePageRoute.cs | 15 - .../PageRouteProvider/PageLinkProvider.cs | 89 ----- .../Routing/PageRouteProvider/PageRoute.cs | 11 - .../PageRouteProvider/PageRouteIdBuilder.cs | 19 - .../PageRouteProvider/PageRouteProvider.cs | 117 ------ .../PageRouteResolverHelper.cs | 181 --------- .../PageRouteProvider/PageUrlBuilder.cs | 107 ------ zero.Core/Routing/RedirectAutomation.cs | 89 ----- zero.Core/Routing/RequestUrlResolver.cs | 148 -------- zero.Core/Routing/RouteBulkRefresher.cs | 56 --- zero.Core/Routing/RouteExtensions.cs | 38 -- zero.Core/Routing/RouteResolver.cs | 156 -------- zero.Core/Routing/Routes.cs | 199 ---------- zero.Core/Routing/ZeroEndpointRouteBuilder.cs | 26 -- .../ZeroEnpointRouteBuilderExtensions.cs | 30 -- .../Routing/ZeroEntityRouteInterceptor.cs | 199 ---------- zero.Core/Routing/ZeroRouteProvider.cs | 257 ------------- zero.Core/Routing/ZeroRoutesTransformer.cs | 114 ------ zero.Core/Routing/ZeroRoutingModule.cs | 35 -- zero.Core/Search/Indexes/zero_Search.cs | 20 - zero.Core/Search/Models/SearchIndexMap.cs | 120 ------ zero.Core/Search/Models/SearchResult.cs | 34 -- zero.Core/Search/SearchService.cs | 77 ---- zero.Core/Search/ZeroSearchModule.cs | 19 - zero.Core/Search/ZeroSearchOptions.cs | 13 - zero.Core/ServiceCollectionExtensions.cs | 17 - zero.Core/Setup/SetupApi.cs | 339 ----------------- zero.Core/Setup/SetupModel.cs | 29 -- zero.Core/Setup/SetupModelValidator.cs | 19 - zero.Core/Spaces/FlavorOptionsExtensions.cs | 53 --- zero.Core/Spaces/Models/Space.cs | 10 - zero.Core/Spaces/Models/SpaceType.cs | 14 - zero.Core/Spaces/Models/SpaceView.cs | 8 - zero.Core/Spaces/SpaceService.cs | 104 ------ zero.Core/Spaces/SpaceStore.cs | 15 - zero.Core/Spaces/SpaceTypeService.cs | 58 --- zero.Core/Spaces/ZeroSpaceModule.cs | 22 -- zero.Core/Stores/EntityStore.cs | 190 ---------- zero.Core/Stores/EntityStoreExtensions.cs | 46 --- .../Flavors/ConfigureFlavorJsonOptions.cs | 20 - zero.Core/Stores/Flavors/FlavorConfig.cs | 45 --- zero.Core/Stores/Flavors/FlavorOptions.cs | 190 ---------- zero.Core/Stores/Flavors/FlavorProvider.cs | 30 -- .../Stores/Flavors/FlavorProviderOptions.cs | 82 ---- .../Flavors/JsonFlavorVariantConverter.cs | 33 -- .../JsonFlavorVariantConverterFactory.cs | 35 -- zero.Core/Stores/StoreCache.cs | 39 -- zero.Core/Stores/StoreConfig.cs | 14 - zero.Core/Stores/StoreContext.cs | 48 --- zero.Core/Stores/StoreOperations.Delete.cs | 30 -- zero.Core/Stores/StoreOperations.Empty.cs | 16 - zero.Core/Stores/StoreOperations.Read.cs | 121 ------ zero.Core/Stores/StoreOperations.Tree.cs | 192 ---------- zero.Core/Stores/StoreOperations.Write.cs | 112 ------ zero.Core/Stores/StoreOperations.cs | 301 --------------- zero.Core/Stores/StoreOperationsExtensions.cs | 45 --- zero.Core/Stores/TreeEntityStore.cs | 110 ------ zero.Core/Stores/ZeroStoreModule.cs | 22 -- zero.Core/Usings.cs | 31 -- zero.Core/Utils/DateRange.cs | 40 -- zero.Core/Utils/Gender.cs | 9 - zero.Core/Utils/JsonDiscriminatorConverter.cs | 67 ---- zero.Core/Utils/ObjectCopycat.cs | 86 ----- zero.Core/Utils/ObjectTraverser.cs | 273 -------------- zero.Core/Utils/PriceRange.cs | 8 - zero.Core/Utils/PrimitiveTypeCollection.cs | 48 --- zero.Core/Utils/TableBuilder/CsvCreator.cs | 184 --------- zero.Core/Utils/TableBuilder/ExcelCreator.cs | 261 ------------- zero.Core/Utils/TableBuilder/ITableCreator.cs | 17 - zero.Core/Utils/TableBuilder/TableBuilder.cs | 162 -------- zero.Core/Utils/TableBuilder/TableColumn.cs | 65 ---- zero.Core/Utils/TableBuilder/TableExport.cs | 51 --- zero.Core/Utils/TableBuilder/TableFormat.cs | 7 - zero.Core/Utils/TableBuilder/TableResult.cs | 10 - zero.Core/Utils/TokenReplacement.cs | 53 --- .../ValidatorCamelCasePropertyResolver.cs | 29 -- zero.Core/Validation/ValidatorExtensions.cs | 177 --------- zero.Core/Validation/ZeroMergedValidator.cs | 98 ----- zero.Core/Validation/ZeroValidationModule.cs | 12 - zero.Core/Validation/ZeroValidator.cs | 29 -- zero.Core/ZeroApplicationBuilder.cs | 57 --- zero.Core/ZeroBuilder.cs | 91 ----- zero.Core/zero.Core.csproj | 33 -- zero.sln | 11 - 339 files changed, 20990 deletions(-) delete mode 100644 zero.Core/ApplicationBuilderExtensions.cs delete mode 100644 zero.Core/Applications/Application.cs delete mode 100644 zero.Core/Applications/ApplicationOptions.cs delete mode 100644 zero.Core/Applications/ApplicationRegistration.cs delete mode 100644 zero.Core/Applications/ApplicationRegistry.cs delete mode 100644 zero.Core/Applications/ApplicationResolver.cs delete mode 100644 zero.Core/Applications/IApplicationResolverHandler.cs delete mode 100644 zero.Core/Applications/IBackofficeApplicationResolverHandler.cs delete mode 100644 zero.Core/Applications/SystemApplication.cs delete mode 100644 zero.Core/Applications/ZeroApplicationModule.cs delete mode 100644 zero.Core/Architecture/AssemblyDiscovery/AssemblyDiscovery.cs delete mode 100644 zero.Core/Architecture/AssemblyDiscovery/AssemblyDiscoveryContext.cs delete mode 100644 zero.Core/Architecture/AssemblyDiscovery/IAssemblyDiscoveryRule.cs delete mode 100644 zero.Core/Architecture/AssemblyDiscovery/ZeroAssemblyDiscoveryRule.cs delete mode 100644 zero.Core/Architecture/Blueprints/Blueprint.cs delete mode 100644 zero.Core/Architecture/Blueprints/BlueprintChildInterceptor.cs delete mode 100644 zero.Core/Architecture/Blueprints/BlueprintConfiguration.cs delete mode 100644 zero.Core/Architecture/Blueprints/BlueprintField.cs delete mode 100644 zero.Core/Architecture/Blueprints/BlueprintInterceptor.cs delete mode 100644 zero.Core/Architecture/Blueprints/BlueprintOptions.cs delete mode 100644 zero.Core/Architecture/Blueprints/BlueprintService.cs delete mode 100644 zero.Core/Architecture/Blueprints/BlueprintStatus.cs delete mode 100644 zero.Core/Architecture/Blueprints/IBlueprintInterceptor.cs delete mode 100644 zero.Core/Architecture/Plugins/IZeroBuiltInPlugin.cs delete mode 100644 zero.Core/Architecture/Plugins/IZeroPlugin.cs delete mode 100644 zero.Core/Architecture/Plugins/ZeroPluginOptions.cs delete mode 100644 zero.Core/Architecture/ZeroArchitectureModule.cs delete mode 100644 zero.Core/Architecture/ZeroModule.cs delete mode 100644 zero.Core/Architecture/ZeroModuleCollection.cs delete mode 100644 zero.Core/Communication/Handlers/IHandler.cs delete mode 100644 zero.Core/Communication/Handlers/IHandlerHolder.cs delete mode 100644 zero.Core/Communication/Interceptors/Interceptor.cs delete mode 100644 zero.Core/Communication/Interceptors/InterceptorInstruction.cs delete mode 100644 zero.Core/Communication/Interceptors/InterceptorParameters.cs delete mode 100644 zero.Core/Communication/Interceptors/InterceptorResult.cs delete mode 100644 zero.Core/Communication/Interceptors/InterceptorRunType.cs delete mode 100644 zero.Core/Communication/Interceptors/Interceptors.cs delete mode 100644 zero.Core/Communication/LazilyResolved.cs delete mode 100644 zero.Core/Communication/Messages/IMessage.cs delete mode 100644 zero.Core/Communication/Messages/IMessageHandler.cs delete mode 100644 zero.Core/Communication/Messages/MessageAggregator.cs delete mode 100644 zero.Core/Communication/Messages/MessageSubscription.cs delete mode 100644 zero.Core/Communication/ZeroCommunicationModule.cs delete mode 100644 zero.Core/Configuration/Constants.cs delete mode 100644 zero.Core/Configuration/Features/Feature.cs delete mode 100644 zero.Core/Configuration/Features/FeatureOptions.cs delete mode 100644 zero.Core/Configuration/IZeroCollectionOptions.cs delete mode 100644 zero.Core/Configuration/Integrations/FlavorOptionsExtensions.cs delete mode 100644 zero.Core/Configuration/Integrations/Integration.cs delete mode 100644 zero.Core/Configuration/Integrations/IntegrationStore.cs delete mode 100644 zero.Core/Configuration/Integrations/IntegrationType.cs delete mode 100644 zero.Core/Configuration/Integrations/IntegrationTypeService.cs delete mode 100644 zero.Core/Configuration/Integrations/IntegrationTypeWithStatus.cs delete mode 100644 zero.Core/Configuration/OptionsType.cs delete mode 100644 zero.Core/Configuration/ZeroConfigurationModule.cs delete mode 100644 zero.Core/Configuration/ZeroOptions.cs delete mode 100644 zero.Core/Configuration/ZeroStartupOptions.cs delete mode 100644 zero.Core/Context/ZeroContext.cs delete mode 100644 zero.Core/Context/ZeroContextMiddleware.cs delete mode 100644 zero.Core/Context/ZeroContextModule.cs delete mode 100644 zero.Core/Extensions/CharExtensions.cs delete mode 100644 zero.Core/Extensions/ColorExtensions.cs delete mode 100644 zero.Core/Extensions/DictionaryExtensions.cs delete mode 100644 zero.Core/Extensions/EnumerableExtensions.cs delete mode 100644 zero.Core/Extensions/ExpressionExtensions.cs delete mode 100644 zero.Core/Extensions/HttpContextExtensions.cs delete mode 100644 zero.Core/Extensions/NumberExtensions.cs delete mode 100644 zero.Core/Extensions/ObjectExtensions.cs delete mode 100644 zero.Core/Extensions/ResultExtensions.cs delete mode 100644 zero.Core/Extensions/ServiceCollectionExtensions.cs delete mode 100644 zero.Core/Extensions/StringExtensions.cs delete mode 100644 zero.Core/FileStorage/FileResult.cs delete mode 100644 zero.Core/FileStorage/FileSizeNotation.cs delete mode 100644 zero.Core/FileStorage/FileSystemException.cs delete mode 100644 zero.Core/FileStorage/FileSystemOptions.cs delete mode 100644 zero.Core/FileStorage/IFileMeta.cs delete mode 100644 zero.Core/FileStorage/IFileSystem.cs delete mode 100644 zero.Core/FileStorage/Paths.cs delete mode 100644 zero.Core/FileStorage/PhysicalFileMeta.cs delete mode 100644 zero.Core/FileStorage/PhysicalFileSystem.cs delete mode 100644 zero.Core/FileStorage/WebRootFileSystem.cs delete mode 100644 zero.Core/FileStorage/ZeroFileStorageModule.cs delete mode 100644 zero.Core/Identity/AuthenticationBuilderExtensions.cs delete mode 100644 zero.Core/Identity/BackofficeUserExtensions.cs delete mode 100644 zero.Core/Identity/Base32.cs delete mode 100644 zero.Core/Identity/Models/LoginResult.cs delete mode 100644 zero.Core/Identity/Models/UserClaim.cs delete mode 100644 zero.Core/Identity/Models/UserClaimComparer.cs delete mode 100644 zero.Core/Identity/Models/ZeroIdentityRole.cs delete mode 100644 zero.Core/Identity/Models/ZeroIdentityUser.cs delete mode 100644 zero.Core/Identity/Models/ZeroUser.cs delete mode 100644 zero.Core/Identity/Models/ZeroUserRole.cs delete mode 100644 zero.Core/Identity/PasswordGenerator.cs delete mode 100644 zero.Core/Identity/Permissions/EntityPermission.cs delete mode 100644 zero.Core/Identity/Permissions/Permission.cs delete mode 100644 zero.Core/Identity/Permissions/PermissionContext.cs delete mode 100644 zero.Core/Identity/Permissions/PermissionGroup.cs delete mode 100644 zero.Core/Identity/Permissions/PermissionProvider.cs delete mode 100644 zero.Core/Identity/Permissions/Permissions.cs delete mode 100644 zero.Core/Identity/RavenRoleStore(TRole).cs delete mode 100644 zero.Core/Identity/RavenScopedStores.cs delete mode 100644 zero.Core/Identity/RavenUserStore(TUser).cs delete mode 100644 zero.Core/Identity/RavenUserStore(TUser,TRole).cs delete mode 100644 zero.Core/Identity/Security/ContextualCookieManager.cs delete mode 100644 zero.Core/Identity/Security/SchemedSignInManager.cs delete mode 100644 zero.Core/Identity/Security/ZeroAuthOptions.cs delete mode 100644 zero.Core/Identity/Security/ZeroAuthorizeAttribute.cs delete mode 100644 zero.Core/Identity/Security/ZeroBackofficeClaimsPrincipalFactory.cs delete mode 100644 zero.Core/Identity/Security/ZeroClaimsPrinicipalFactory.cs delete mode 100644 zero.Core/Identity/Services/AuthenticationService.cs delete mode 100644 zero.Core/Identity/Services/AuthorizationService.cs delete mode 100644 zero.Core/Identity/Services/UserRolesService.cs delete mode 100644 zero.Core/Identity/Services/UserService.cs delete mode 100644 zero.Core/Identity/UserIdentity.cs delete mode 100644 zero.Core/Identity/Validation/BackofficeUserRoleValidator.cs delete mode 100644 zero.Core/Identity/Validation/BackofficeUserValidator.cs delete mode 100644 zero.Core/Identity/ZeroAuthenticationSchemeProvider.cs delete mode 100644 zero.Core/Identity/ZeroIdentityExtensions.cs delete mode 100644 zero.Core/Identity/ZeroIdentityModule.cs delete mode 100644 zero.Core/Localization/CountryStore.cs delete mode 100644 zero.Core/Localization/CountryValidator.cs delete mode 100644 zero.Core/Localization/CultureResolver.cs delete mode 100644 zero.Core/Localization/CultureService.cs delete mode 100644 zero.Core/Localization/LanguageStore.cs delete mode 100644 zero.Core/Localization/LanguageValidator.cs delete mode 100644 zero.Core/Localization/LocalizeAttribute.cs delete mode 100644 zero.Core/Localization/Localizer.cs delete mode 100644 zero.Core/Localization/LocalizerExtensions.cs delete mode 100644 zero.Core/Localization/Models/Country.cs delete mode 100644 zero.Core/Localization/Models/Culture.cs delete mode 100644 zero.Core/Localization/Models/Language.cs delete mode 100644 zero.Core/Localization/Models/Translation.cs delete mode 100644 zero.Core/Localization/TranslationStore.cs delete mode 100644 zero.Core/Localization/TranslationValidator.cs delete mode 100644 zero.Core/Localization/ZeroLocalizationModule.cs delete mode 100644 zero.Core/Mails/FileMailDispatcher.cs delete mode 100644 zero.Core/Mails/IMailDispatcher.cs delete mode 100644 zero.Core/Mails/LoggerMailDispatcher.cs delete mode 100644 zero.Core/Mails/Mail.cs delete mode 100644 zero.Core/Mails/MailMetadata.cs delete mode 100644 zero.Core/Mails/MailOptions.cs delete mode 100644 zero.Core/Mails/MailPlaceholders.cs delete mode 100644 zero.Core/Mails/MailProvider.cs delete mode 100644 zero.Core/Mails/MailSendResult.cs delete mode 100644 zero.Core/Mails/MailTemplateValidator.cs delete mode 100644 zero.Core/Mails/MailTemplatesStore.cs delete mode 100644 zero.Core/Mails/Models/MailTemplate.cs delete mode 100644 zero.Core/Mails/ZeroMailModule.cs delete mode 100644 zero.Core/Mapper/MapperExtensions.cs delete mode 100644 zero.Core/Mapper/ZeroMapper.cs delete mode 100644 zero.Core/Mapper/ZeroMapperContext.cs delete mode 100644 zero.Core/Mapper/ZeroMapperModule.cs delete mode 100644 zero.Core/Mapper/ZeroMapperProfile.cs delete mode 100644 zero.Core/Media/Indexes/Media_ByChildren.cs delete mode 100644 zero.Core/Media/Indexes/Media_ByHierarchy.cs delete mode 100644 zero.Core/Media/MediaCreator.cs delete mode 100644 zero.Core/Media/MediaFileSystem.cs delete mode 100644 zero.Core/Media/MediaLinkProvider.cs delete mode 100644 zero.Core/Media/MediaManagement.cs delete mode 100644 zero.Core/Media/MediaManagementExtensions.cs delete mode 100644 zero.Core/Media/MediaOptions.cs delete mode 100644 zero.Core/Media/MediaStore.cs delete mode 100644 zero.Core/Media/Models/Media.cs delete mode 100644 zero.Core/Media/Models/MediaFocalPoint.cs delete mode 100644 zero.Core/Media/Models/MediaImageMetadata.cs delete mode 100644 zero.Core/Media/Models/MediaListItem.cs delete mode 100644 zero.Core/Media/Models/Video.cs delete mode 100644 zero.Core/Media/ZeroMediaModule.cs delete mode 100644 zero.Core/Models/IAlwaysActive.cs delete mode 100644 zero.Core/Models/ISupportsFlavors.cs delete mode 100644 zero.Core/Models/ISupportsRouting.cs delete mode 100644 zero.Core/Models/ISupportsSorting.cs delete mode 100644 zero.Core/Models/ISupportsTrees.cs delete mode 100644 zero.Core/Models/Results/Paged.cs delete mode 100644 zero.Core/Models/Results/Result.cs delete mode 100644 zero.Core/Models/Results/UrlsResult.cs delete mode 100644 zero.Core/Models/ZeroEntity.cs delete mode 100644 zero.Core/Models/ZeroEntityPreview.cs delete mode 100644 zero.Core/Models/ZeroIdEntity.cs delete mode 100644 zero.Core/Models/ZeroReference.cs delete mode 100644 zero.Core/Pages/FlavorOptionsExtensions.cs delete mode 100644 zero.Core/Pages/IPageTypeHandler.cs delete mode 100644 zero.Core/Pages/Indexes/zero_Pages_AsHistory.cs delete mode 100644 zero.Core/Pages/Indexes/zero_Pages_ByHierarchy.cs delete mode 100644 zero.Core/Pages/Indexes/zero_Pages_ByType.cs delete mode 100644 zero.Core/Pages/Indexes/zero_Pages_WithChildren.cs delete mode 100644 zero.Core/Pages/Models/Page.cs delete mode 100644 zero.Core/Pages/Models/PageFolder.cs delete mode 100644 zero.Core/Pages/Modules/ConfigureModuleTypeJsonOptions.cs delete mode 100644 zero.Core/Pages/Modules/IModuleTypeHandler.cs delete mode 100644 zero.Core/Pages/Modules/JsonModuleTypeConverter.cs delete mode 100644 zero.Core/Pages/Modules/ModulePermissions.cs delete mode 100644 zero.Core/Pages/Modules/PageModule.cs delete mode 100644 zero.Core/Pages/Modules/PageModuleOptions.cs delete mode 100644 zero.Core/Pages/Modules/PageModuleType.cs delete mode 100644 zero.Core/Pages/Modules/PageModuleTypeService.cs delete mode 100644 zero.Core/Pages/PageOptions.cs delete mode 100644 zero.Core/Pages/PageTypeService.cs delete mode 100644 zero.Core/Pages/PagesStore.cs delete mode 100644 zero.Core/Pages/ZeroPageModule.cs delete mode 100644 zero.Core/Persistence/GenerateIdAttribute.cs delete mode 100644 zero.Core/Persistence/ISupportsDbConventions.cs delete mode 100644 zero.Core/Persistence/IdGenerator.cs delete mode 100644 zero.Core/Persistence/Indexes/ZeroIndex.cs delete mode 100644 zero.Core/Persistence/Indexes/ZeroIndexExtensions.cs delete mode 100644 zero.Core/Persistence/Indexes/ZeroTreeHierachyIndex.cs delete mode 100644 zero.Core/Persistence/RavenCollectionAttribute.cs delete mode 100644 zero.Core/Persistence/RavenConstants.cs delete mode 100644 zero.Core/Persistence/RavenIndexExtensions.cs delete mode 100644 zero.Core/Persistence/RavenOptions.cs delete mode 100644 zero.Core/Persistence/RavenQueryableExtensions.cs delete mode 100644 zero.Core/Persistence/Revisions/Revision.cs delete mode 100644 zero.Core/Persistence/Revisions/RevisionService.cs delete mode 100644 zero.Core/Persistence/Safenames.cs delete mode 100644 zero.Core/Persistence/Tokens/Rfc6238AuthenticationService.cs delete mode 100644 zero.Core/Persistence/Tokens/Token.cs delete mode 100644 zero.Core/Persistence/Tokens/ZeroTokenProvider.cs delete mode 100644 zero.Core/Persistence/ZeroDocumentConventionsBuilder.cs delete mode 100644 zero.Core/Persistence/ZeroDocumentSession.cs delete mode 100644 zero.Core/Persistence/ZeroDocumentSessionExtensions.cs delete mode 100644 zero.Core/Persistence/ZeroDocumentStore.cs delete mode 100644 zero.Core/Persistence/ZeroPersistenceModule.cs delete mode 100644 zero.Core/Persistence/ZeroStore.cs delete mode 100644 zero.Core/Preview/PreviewOptions.cs delete mode 100644 zero.Core/Preview/PreviewService.cs delete mode 100644 zero.Core/Preview/Routing/PreviewRoute.cs delete mode 100644 zero.Core/Preview/Routing/PreviewRouteModel.cs delete mode 100644 zero.Core/Preview/Routing/PreviewRouteProvider.cs delete mode 100644 zero.Core/Preview/ZeroPreviewModule.cs delete mode 100644 zero.Core/RecycleBin/RecycleBinApi.cs delete mode 100644 zero.Core/RecycleBin/RecycleBinListQuery.cs delete mode 100644 zero.Core/RecycleBin/RecycledEntity.cs delete mode 100644 zero.Core/Rendering/RazorRenderer.cs delete mode 100644 zero.Core/Rendering/ZeroRenderingModule.cs delete mode 100644 zero.Core/Routing/ApplicationBuilderExtensions.cs delete mode 100644 zero.Core/Routing/Configuration/RoutingEndpointOptions.cs delete mode 100644 zero.Core/Routing/Configuration/RoutingOptions.cs delete mode 100644 zero.Core/Routing/Configuration/RoutingPageResolverOptions.cs delete mode 100644 zero.Core/Routing/Indexes/RouteRedirects_ByUrl.cs delete mode 100644 zero.Core/Routing/Indexes/Routes_ByDependencies.cs delete mode 100644 zero.Core/Routing/Indexes/Routes_ForResolver.cs delete mode 100644 zero.Core/Routing/Links/ILinkProvider.cs delete mode 100644 zero.Core/Routing/Links/LinkTargetExtensions.cs delete mode 100644 zero.Core/Routing/Links/Links.cs delete mode 100644 zero.Core/Routing/Links/RawLinkProvider.cs delete mode 100644 zero.Core/Routing/Models/IRouteModel.cs delete mode 100644 zero.Core/Routing/Models/Link.cs delete mode 100644 zero.Core/Routing/Models/LinkPreview.cs delete mode 100644 zero.Core/Routing/Models/LinkTarget.cs delete mode 100644 zero.Core/Routing/Models/Route.cs delete mode 100644 zero.Core/Routing/Models/RouteEndpoint.cs delete mode 100644 zero.Core/Routing/Models/RouteRedirect.cs delete mode 100644 zero.Core/Routing/Models/RouteResponse.cs delete mode 100644 zero.Core/Routing/Models/RoutingContext.cs delete mode 100644 zero.Core/Routing/NotFoundMatcherPolicy.cs delete mode 100644 zero.Core/Routing/PageRouteProvider/BasePageRoute.cs delete mode 100644 zero.Core/Routing/PageRouteProvider/PageLinkProvider.cs delete mode 100644 zero.Core/Routing/PageRouteProvider/PageRoute.cs delete mode 100644 zero.Core/Routing/PageRouteProvider/PageRouteIdBuilder.cs delete mode 100644 zero.Core/Routing/PageRouteProvider/PageRouteProvider.cs delete mode 100644 zero.Core/Routing/PageRouteProvider/PageRouteResolverHelper.cs delete mode 100644 zero.Core/Routing/PageRouteProvider/PageUrlBuilder.cs delete mode 100644 zero.Core/Routing/RedirectAutomation.cs delete mode 100644 zero.Core/Routing/RequestUrlResolver.cs delete mode 100644 zero.Core/Routing/RouteBulkRefresher.cs delete mode 100644 zero.Core/Routing/RouteExtensions.cs delete mode 100644 zero.Core/Routing/RouteResolver.cs delete mode 100644 zero.Core/Routing/Routes.cs delete mode 100644 zero.Core/Routing/ZeroEndpointRouteBuilder.cs delete mode 100644 zero.Core/Routing/ZeroEnpointRouteBuilderExtensions.cs delete mode 100644 zero.Core/Routing/ZeroEntityRouteInterceptor.cs delete mode 100644 zero.Core/Routing/ZeroRouteProvider.cs delete mode 100644 zero.Core/Routing/ZeroRoutesTransformer.cs delete mode 100644 zero.Core/Routing/ZeroRoutingModule.cs delete mode 100644 zero.Core/Search/Indexes/zero_Search.cs delete mode 100644 zero.Core/Search/Models/SearchIndexMap.cs delete mode 100644 zero.Core/Search/Models/SearchResult.cs delete mode 100644 zero.Core/Search/SearchService.cs delete mode 100644 zero.Core/Search/ZeroSearchModule.cs delete mode 100644 zero.Core/Search/ZeroSearchOptions.cs delete mode 100644 zero.Core/ServiceCollectionExtensions.cs delete mode 100644 zero.Core/Setup/SetupApi.cs delete mode 100644 zero.Core/Setup/SetupModel.cs delete mode 100644 zero.Core/Setup/SetupModelValidator.cs delete mode 100644 zero.Core/Spaces/FlavorOptionsExtensions.cs delete mode 100644 zero.Core/Spaces/Models/Space.cs delete mode 100644 zero.Core/Spaces/Models/SpaceType.cs delete mode 100644 zero.Core/Spaces/Models/SpaceView.cs delete mode 100644 zero.Core/Spaces/SpaceService.cs delete mode 100644 zero.Core/Spaces/SpaceStore.cs delete mode 100644 zero.Core/Spaces/SpaceTypeService.cs delete mode 100644 zero.Core/Spaces/ZeroSpaceModule.cs delete mode 100644 zero.Core/Stores/EntityStore.cs delete mode 100644 zero.Core/Stores/EntityStoreExtensions.cs delete mode 100644 zero.Core/Stores/Flavors/ConfigureFlavorJsonOptions.cs delete mode 100644 zero.Core/Stores/Flavors/FlavorConfig.cs delete mode 100644 zero.Core/Stores/Flavors/FlavorOptions.cs delete mode 100644 zero.Core/Stores/Flavors/FlavorProvider.cs delete mode 100644 zero.Core/Stores/Flavors/FlavorProviderOptions.cs delete mode 100644 zero.Core/Stores/Flavors/JsonFlavorVariantConverter.cs delete mode 100644 zero.Core/Stores/Flavors/JsonFlavorVariantConverterFactory.cs delete mode 100644 zero.Core/Stores/StoreCache.cs delete mode 100644 zero.Core/Stores/StoreConfig.cs delete mode 100644 zero.Core/Stores/StoreContext.cs delete mode 100644 zero.Core/Stores/StoreOperations.Delete.cs delete mode 100644 zero.Core/Stores/StoreOperations.Empty.cs delete mode 100644 zero.Core/Stores/StoreOperations.Read.cs delete mode 100644 zero.Core/Stores/StoreOperations.Tree.cs delete mode 100644 zero.Core/Stores/StoreOperations.Write.cs delete mode 100644 zero.Core/Stores/StoreOperations.cs delete mode 100644 zero.Core/Stores/StoreOperationsExtensions.cs delete mode 100644 zero.Core/Stores/TreeEntityStore.cs delete mode 100644 zero.Core/Stores/ZeroStoreModule.cs delete mode 100644 zero.Core/Usings.cs delete mode 100644 zero.Core/Utils/DateRange.cs delete mode 100644 zero.Core/Utils/Gender.cs delete mode 100644 zero.Core/Utils/JsonDiscriminatorConverter.cs delete mode 100644 zero.Core/Utils/ObjectCopycat.cs delete mode 100644 zero.Core/Utils/ObjectTraverser.cs delete mode 100644 zero.Core/Utils/PriceRange.cs delete mode 100644 zero.Core/Utils/PrimitiveTypeCollection.cs delete mode 100644 zero.Core/Utils/TableBuilder/CsvCreator.cs delete mode 100644 zero.Core/Utils/TableBuilder/ExcelCreator.cs delete mode 100644 zero.Core/Utils/TableBuilder/ITableCreator.cs delete mode 100644 zero.Core/Utils/TableBuilder/TableBuilder.cs delete mode 100644 zero.Core/Utils/TableBuilder/TableColumn.cs delete mode 100644 zero.Core/Utils/TableBuilder/TableExport.cs delete mode 100644 zero.Core/Utils/TableBuilder/TableFormat.cs delete mode 100644 zero.Core/Utils/TableBuilder/TableResult.cs delete mode 100644 zero.Core/Utils/TokenReplacement.cs delete mode 100644 zero.Core/Validation/ValidatorCamelCasePropertyResolver.cs delete mode 100644 zero.Core/Validation/ValidatorExtensions.cs delete mode 100644 zero.Core/Validation/ZeroMergedValidator.cs delete mode 100644 zero.Core/Validation/ZeroValidationModule.cs delete mode 100644 zero.Core/Validation/ZeroValidator.cs delete mode 100644 zero.Core/ZeroApplicationBuilder.cs delete mode 100644 zero.Core/ZeroBuilder.cs delete mode 100644 zero.Core/zero.Core.csproj diff --git a/zero.Core/ApplicationBuilderExtensions.cs b/zero.Core/ApplicationBuilderExtensions.cs deleted file mode 100644 index 748ecebd..00000000 --- a/zero.Core/ApplicationBuilderExtensions.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Builder; - -namespace zero; - -public static class ApplicationBuilderExtensions -{ - public static IZeroApplicationBuilder UseZero(this IApplicationBuilder app) => new ZeroApplicationBuilder(app); -} diff --git a/zero.Core/Applications/Application.cs b/zero.Core/Applications/Application.cs deleted file mode 100644 index 6e723a23..00000000 --- a/zero.Core/Applications/Application.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace zero.Applications; - -/// -/// An application is a website or app. zero can host multiple websites at once which share common assets -/// -public class Application -{ - /// - /// Raven database name for application data - /// - public string Database { get; set; } - - /// - /// Full company or product name - /// - public string FullName { get; set; } - - /// - /// Generic contact email. Can be used in various locations - /// - public string Email { get; set; } - - /// - /// Image of the application - /// - public string ImageId { get; set; } - - /// - /// Simple image of the application (can be used as favicon) - /// - public string IconId { get; set; } - - /// - /// All assigned domains for this application - /// - public Uri[] Domains { get; set; } = Array.Empty(); - - /// - /// Features which are enabled for this application. - /// Can be user-defined and affect both backoffice and frontend - /// - public List Features { get; set; } = new(); -} \ No newline at end of file diff --git a/zero.Core/Applications/ApplicationOptions.cs b/zero.Core/Applications/ApplicationOptions.cs deleted file mode 100644 index 4243c48c..00000000 --- a/zero.Core/Applications/ApplicationOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -//namespace zero.Applications; - -//public class ApplicationOptions -//{ -// public ApplicationOptions() -// { -// EnableMultiple = false; -// } - - -// public bool EnableMultiple { get; set; } -//} diff --git a/zero.Core/Applications/ApplicationRegistration.cs b/zero.Core/Applications/ApplicationRegistration.cs deleted file mode 100644 index 75ad3637..00000000 --- a/zero.Core/Applications/ApplicationRegistration.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace zero.Applications; - -public class ApplicationRegistration -{ - /// - /// Alias for this tenant - /// - public string Key { get; set; } - - /// - /// Name of the tenant - /// - public string Name { get; set; } - - /// - /// Raven database name for application data - /// - public string Database { get; set; } - - /// - /// Generic contact email. Can be used in various locations - /// - public string Email { get; set; } - - /// - /// All assigned domains for this application - /// - public Uri[] Domains { get; set; } = Array.Empty(); - - /// - /// Features which are enabled for this application. - /// Can be user-defined and affect both backoffice and frontend - /// - public List Features { get; set; } = new(); -} \ No newline at end of file diff --git a/zero.Core/Applications/ApplicationRegistry.cs b/zero.Core/Applications/ApplicationRegistry.cs deleted file mode 100644 index 2e275865..00000000 --- a/zero.Core/Applications/ApplicationRegistry.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace zero.Applications; - -public class ApplicationRegistry : IApplicationRegistry -{ - protected IZeroOptions Options { get; set; } - - - public ApplicationRegistry(IZeroOptions options) - { - Options = options; - } - - - /// - public Task> GetAll() - { - return Task.FromResult>(Options.Applications); - } -} - - -public interface IApplicationRegistry -{ - /// - /// Get all registered applications - /// - Task> GetAll(); -} \ No newline at end of file diff --git a/zero.Core/Applications/ApplicationResolver.cs b/zero.Core/Applications/ApplicationResolver.cs deleted file mode 100644 index 0dbb9f17..00000000 --- a/zero.Core/Applications/ApplicationResolver.cs +++ /dev/null @@ -1,260 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.Extensions.Logging; -using Raven.Client.Documents; -using Raven.Client.Documents.Session; -using System.Security.Claims; - -namespace zero.Applications; - -public class ApplicationResolver : IApplicationResolver -{ - protected IZeroStore Store { get; private set; } - - protected IZeroOptions Options { get; private set; } - - protected ILogger Logger { get; private set; } - - protected IHandlerHolder Handler { get; private set; } - - IList _apps; - - /// - /// The system application is bound to the context in case no application was resolved - /// - SystemApplication _systemApplication; - - - - public ApplicationResolver(IZeroStore store, IZeroOptions options, ILogger logger, IHandlerHolder handler = null) - { - Store = store; - Options = options; - Logger = logger; - Handler = handler; - - _systemApplication = new() - { - Id = "system", - Name = "zero system", - Database = Options.For().Database, - Alias = "system" - }; - } - - - /// - public async Task Resolve(HttpContext context, ClaimsPrincipal user) - { - if (context?.Request == null) - { - Logger.LogWarning("Could not resolve application as HTTP request is null"); - return _systemApplication; - } - - Application app; - - if (context.IsBackofficeRequest(Options.ZeroPath)) - { - ZeroUser userEntity = await GetBackofficeUser(user); - app = await ResolveFromBackofficeHandlers(context, userEntity) ?? _systemApplication; - } - else - { - app = await ResolveFromHandlers(context) ?? await ResolveFromRequest(context); - } - - if (app == null) - { - Logger.LogWarning("Could not resolve application for host {host}", context.Request.Host); - return _systemApplication; - } - - return app; - } - - - /// - public async Task ResolveFromHandlers(HttpContext context) - { - IList apps = await GetApplications(); - IEnumerable handlers = Handler.GetAll(); - - foreach (IApplicationResolverHandler handler in handlers) - { - if (handler.TryResolve(context, apps, out Application resolved)) - { - return resolved; - } - } - - return null; - } - - - /// - public async Task ResolveFromBackofficeHandlers(HttpContext context, ZeroUser user) - { - IList apps = await GetApplications(); - IEnumerable handlers = Handler.GetAll(); - - foreach (IBackofficeApplicationResolverHandler handler in handlers) - { - if (handler.TryResolve(context, apps, user, out Application resolved)) - { - return resolved; - } - } - - return null; - } - - - /// - public async Task ResolveFromUser(ClaimsPrincipal user) - { - ZeroUser userEntity = await GetBackofficeUser(user); - return await ResolveFromUser(userEntity); - } - - - /// - public async Task ResolveFromUser(ZeroUser user) - { - if (user == null) - { - return null; - } - - string appId; - string[] allowedAppIds = user.GetAllowedAppIds(); - - if (!user.CurrentAppId.IsNullOrEmpty()) - { - if (user.IsSuper || allowedAppIds.Contains(user.CurrentAppId, StringComparer.InvariantCultureIgnoreCase)) - { - appId = user.CurrentAppId; - } - else - { - appId = user.AppId; - } - } - else - { - appId = user.AppId; - } - - if (appId.IsNullOrEmpty()) - { - throw new Exception($"User entity ${user.Id} needs a valid AppId"); - } - - IAsyncDocumentSession session = Store.Session(global: true); - return await session.LoadAsync(appId); - } - - - /// - public Task ResolveFromRequest(HttpContext context) => ResolveFromUri(context.Request.GetEncodedUrl()); - - - /// - public async Task ResolveFromUri(string uriString) => ResolveFromUriInternal(new Uri(uriString, UriKind.Absolute), await GetApplications()); - - - /// - public async Task ResolveFromUri(Uri uri) => ResolveFromUriInternal(uri, await GetApplications()); - - - /// - /// Get matching application from an URI - /// - Application ResolveFromUriInternal(Uri uri, IList apps) - { - foreach (Application app in apps) - { - if (app.Domains?.Length < 1) - { - Logger.LogWarning("No domains specified for app {app}", app.Id); - continue; - } - - foreach (Uri domain in app.Domains) - { - int compareResult = Uri.Compare(uri, domain, UriComponents.HostAndPort, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase); - if (compareResult == 0) - { - return app; - } - } - } - - return null; - } - - - /// - /// Get all applications to choose from - /// - async Task> GetApplications() - { - if (_apps != null) - { - return _apps; - } - - IAsyncDocumentSession session = Store.Session(global: true); - _apps = await session.Query().ToListAsync(); - return _apps; - } - - - /// - /// Get backoffice user from claims principal - /// - async Task GetBackofficeUser(ClaimsPrincipal user) - { - string userId = user.FindFirstValue(Constants.Auth.Claims.UserId); - - IAsyncDocumentSession session = Store.Session(global: true); - return await session.LoadAsync(userId); - } -} - - -public interface IApplicationResolver -{ - /// - /// Resolves the current application from either the backoffice user (in case it is backoffice request) - /// or the domain (in case it is frontend request). - /// - Task Resolve(HttpContext context, ClaimsPrincipal user); - - /// - /// Resolves the current application from the request path - /// - Task ResolveFromRequest(HttpContext context); - - /// - /// Get matching application from an URI string - /// - Task ResolveFromUri(string uriString); - - /// - /// Get matching application from an URI - /// - Task ResolveFromUri(Uri uri); - - /// - /// Resolves the current application from the logged-in backoffice user. - /// This method won't return apps the user has no access to. - /// - Task ResolveFromUser(ClaimsPrincipal user); - - /// - /// Resolves the current application from a user. - /// This method won't return apps the user has no access to. - /// - Task ResolveFromUser(ZeroUser user); -} \ No newline at end of file diff --git a/zero.Core/Applications/IApplicationResolverHandler.cs b/zero.Core/Applications/IApplicationResolverHandler.cs deleted file mode 100644 index 26800c07..00000000 --- a/zero.Core/Applications/IApplicationResolverHandler.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace zero.Applications; - -public interface IApplicationResolverHandler : IHandler -{ - bool TryResolve(HttpContext context, IEnumerable applications, out Application resolved); -} \ No newline at end of file diff --git a/zero.Core/Applications/IBackofficeApplicationResolverHandler.cs b/zero.Core/Applications/IBackofficeApplicationResolverHandler.cs deleted file mode 100644 index 3946fc5c..00000000 --- a/zero.Core/Applications/IBackofficeApplicationResolverHandler.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace zero.Applications; - -public interface IBackofficeApplicationResolverHandler : IHandler -{ - bool TryResolve(HttpContext context, IEnumerable applications, ZeroUser user, out Application resolved); -} \ No newline at end of file diff --git a/zero.Core/Applications/SystemApplication.cs b/zero.Core/Applications/SystemApplication.cs deleted file mode 100644 index 3c13d2d5..00000000 --- a/zero.Core/Applications/SystemApplication.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace zero.Applications; - -public sealed class SystemApplication : Application -{ -} \ No newline at end of file diff --git a/zero.Core/Applications/ZeroApplicationModule.cs b/zero.Core/Applications/ZeroApplicationModule.cs deleted file mode 100644 index 356719b5..00000000 --- a/zero.Core/Applications/ZeroApplicationModule.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Applications; - -internal class ZeroApplicationModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - services.AddScoped(); - //services.AddOptions().Bind(configuration.GetSection("Zero:Applications")); - } -} \ No newline at end of file diff --git a/zero.Core/Architecture/AssemblyDiscovery/AssemblyDiscovery.cs b/zero.Core/Architecture/AssemblyDiscovery/AssemblyDiscovery.cs deleted file mode 100644 index c1ffceaf..00000000 --- a/zero.Core/Architecture/AssemblyDiscovery/AssemblyDiscovery.cs +++ /dev/null @@ -1,161 +0,0 @@ -using Microsoft.AspNetCore.Mvc.ApplicationParts; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyModel; -using System.Reflection; - -namespace zero.Architecture; - -public class AssemblyDiscovery : IAssemblyDiscovery -{ - public static IAssemblyDiscovery Current; - - AssemblyDiscoveryContext Context; - - IMvcBuilder Mvc; - - - - public AssemblyDiscovery(IMvcBuilder mvc) - { - Mvc = mvc; - Context = new AssemblyDiscoveryContext(); - Current = this; - } - - - /// - public void Execute(IEnumerable rules) - { - List assemblies = new List(); - DependencyContext dependencyContext = DependencyContext.Load(Context.EntryAssembly); - - if (dependencyContext == null) - { - return; - } - - string[] existingLibs = Mvc.PartManager.ApplicationParts.OfType().Select(p => p.Name).ToArray(); - - IEnumerable libraries = dependencyContext.RuntimeLibraries.Where(lib => !existingLibs.Contains(lib.Name, StringComparer.OrdinalIgnoreCase)); - - foreach (RuntimeLibrary library in libraries) - { - if (rules.Any(rule => rule.IsValid(library, Context))) - { - IEnumerable libraryAssemblies = library.GetDefaultAssemblyNames(dependencyContext).Select(name => Assembly.Load(name)); - - foreach (Assembly assembly in libraryAssemblies) - { - Mvc.AddApplicationPart(assembly); - } - } - } - } - - - /// - public void AddAssembly(Assembly assembly) - { - string[] existingLibs = Mvc.PartManager.ApplicationParts.OfType().Select(p => p.Name).ToArray(); - - if (!existingLibs.Contains(assembly.GetName().Name)) - { - Mvc.AddApplicationPart(assembly); - } - } - - - /// - public IEnumerable GetTypes(bool allowGenerics = false) => GetTypes(typeof(TService), allowGenerics); - - /// - public IEnumerable GetTypes(IEnumerable parts, bool allowGenerics = false) => GetTypes(typeof(TService), parts, allowGenerics); - - /// - public IEnumerable GetTypes(Type serviceType, bool allowGenerics = false) => GetAllClassTypes(allowGenerics).Where(t => serviceType.GetTypeInfo().IsAssignableFrom(t) && t.AsType() != serviceType); - - /// - public IEnumerable GetTypes(Type serviceType, IEnumerable parts, bool allowGenerics = false) => GetAllClassTypes(parts, allowGenerics).Where(t => serviceType.GetTypeInfo().IsAssignableFrom(t) && t.AsType() != serviceType); - - /// - public IEnumerable GetAllClassTypes(bool allowGenerics = false) => GetAllTypes().Where(t => t.IsClass && !t.IsAbstract && (allowGenerics || !t.ContainsGenericParameters)); - - /// - public IEnumerable GetAllClassTypes(IEnumerable parts, bool allowGenerics = false) => GetAllTypes(parts).Where(t => t.IsClass && !t.IsAbstract && (allowGenerics || !t.ContainsGenericParameters)); - - /// - public IEnumerable GetAssemblies() => Mvc.PartManager.ApplicationParts.OfType().Select(p => p.Assembly); - - /// - public IEnumerable GetAssemblies(IEnumerable parts) => parts.OfType().Select(p => p.Assembly); - - /// - public IEnumerable GetAllTypes() => Mvc.PartManager.ApplicationParts.OfType().SelectMany(p => p.Types); - - /// - public IEnumerable GetAllTypes(IEnumerable parts) => parts.OfType().SelectMany(p => p.Types); -} - - -public interface IAssemblyDiscovery -{ - /// - /// Discovers runtime assemblies based on the given rules - /// - void Execute(IEnumerable rules); - - /// - /// Manually add an assembly - /// - void AddAssembly(Assembly assembly); - - /// - /// Get all discovered types which implement a certain service - /// - IEnumerable GetTypes(bool allowGenerics = false); - - /// - /// Get all discovered types which implement a certain service - /// - IEnumerable GetTypes(IEnumerable parts, bool allowGenerics = false); - - /// - /// Get all discovered types which implement a certain service - /// - IEnumerable GetTypes(Type serviceType, bool allowGenerics = false); - - /// - /// Get all discovered types which implement a certain service - /// - IEnumerable GetTypes(Type serviceType, IEnumerable parts, bool allowGenerics = false); - - /// - /// Get all registered types - /// - IEnumerable GetAllTypes(); - - /// - /// Get all registered types - /// - IEnumerable GetAllTypes(IEnumerable parts); - - /// - /// Get all registered types - /// - IEnumerable GetAllClassTypes(bool allowGenerics = false); - - /// - /// Get all registered types - /// - IEnumerable GetAllClassTypes(IEnumerable parts, bool allowGenerics = false); - - /// - /// Get all registered assemblies - /// - IEnumerable GetAssemblies(); - - /// - /// Get all registered assemblies - /// - IEnumerable GetAssemblies(IEnumerable parts); -} diff --git a/zero.Core/Architecture/AssemblyDiscovery/AssemblyDiscoveryContext.cs b/zero.Core/Architecture/AssemblyDiscovery/AssemblyDiscoveryContext.cs deleted file mode 100644 index 33cd0836..00000000 --- a/zero.Core/Architecture/AssemblyDiscovery/AssemblyDiscoveryContext.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reflection; - -namespace zero.Architecture; - -public class AssemblyDiscoveryContext -{ - public Assembly EntryAssembly { get; private set; } - - public string EntryAssemblyName { get; private set; } - - public bool HasEntryAssembly => EntryAssembly != null; - - - public AssemblyDiscoveryContext() - { - EntryAssembly = Assembly.GetEntryAssembly(); - EntryAssemblyName = EntryAssembly?.GetName().Name; - } - - - public AssemblyDiscoveryContext(Assembly entryAssembly) - { - EntryAssembly = entryAssembly; - EntryAssemblyName = entryAssembly.GetName().Name; - } -} diff --git a/zero.Core/Architecture/AssemblyDiscovery/IAssemblyDiscoveryRule.cs b/zero.Core/Architecture/AssemblyDiscovery/IAssemblyDiscoveryRule.cs deleted file mode 100644 index d2f4a5ab..00000000 --- a/zero.Core/Architecture/AssemblyDiscovery/IAssemblyDiscoveryRule.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.DependencyModel; - -namespace zero.Architecture; - -public interface IAssemblyDiscoveryRule -{ - /// - /// Returns true if the specified runtime library should be added to - /// the ApplicationPartManager; otherwise false. - /// - bool IsValid(RuntimeLibrary library, AssemblyDiscoveryContext context); -} \ No newline at end of file diff --git a/zero.Core/Architecture/AssemblyDiscovery/ZeroAssemblyDiscoveryRule.cs b/zero.Core/Architecture/AssemblyDiscovery/ZeroAssemblyDiscoveryRule.cs deleted file mode 100644 index be1f7ff7..00000000 --- a/zero.Core/Architecture/AssemblyDiscovery/ZeroAssemblyDiscoveryRule.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.Extensions.DependencyModel; - -namespace zero.Architecture; - -public class ZeroAssemblyDiscoveryRule : IAssemblyDiscoveryRule -{ - const string ZERO_PREFIX = "zero."; - - /// - public bool IsValid(RuntimeLibrary library, AssemblyDiscoveryContext context) - { - StringComparison casing = StringComparison.OrdinalIgnoreCase; - // TODO we need to auto-add assemblies and discover their types which have implementations of IZeroPlugin - return library.Name.StartsWith(ZERO_PREFIX, casing) || (context.HasEntryAssembly && library.Name.Contains(context.EntryAssemblyName, casing)); - } -} \ No newline at end of file diff --git a/zero.Core/Architecture/Blueprints/Blueprint.cs b/zero.Core/Architecture/Blueprints/Blueprint.cs deleted file mode 100644 index 49ec7b8c..00000000 --- a/zero.Core/Architecture/Blueprints/Blueprint.cs +++ /dev/null @@ -1,216 +0,0 @@ -using System.Linq.Expressions; -using System.Text.Json.Serialization; - -namespace zero.Architecture; - -public sealed class DefaultShallowBlueprint : Blueprint -{ - public DefaultShallowBlueprint(Type type) : base() - { - Alias = "shallow.default"; - ContentType = type; - LockAll(); - } - - - protected override BlueprintField AddField(Expression> selector) - { - throw new InvalidOperationException("Shallow blueprints can not contain unlocked fields"); - } -} - -/// -/// -/// -public class Blueprint : Blueprint where T : ZeroEntity, new() -{ - public List> UnlockedFields { get; private set; } = new(); - - /// - /// These fields are not copied by the ObjectCloner - /// as they are either locked or copied manually by ApplyDefaults() - /// - protected static string[] DefaultUncopyFields { get; set; } = new[] { "id", "url", "name", "alias", "key", "sort", "isActive", "hash", "languageId", "createdById", "createdDate", "lastModifiedById", "lastModifiedDate", "blueprint" }; - - - public Blueprint() : base(typeof(T)) { } - - - /// - /// Merges blueprint data into a model based on the configuration - /// - public virtual T Apply(T blueprint, T model) - { - // reset blueprint options for model in case no blueprint was found - if (blueprint == null || model.Blueprint == null) - { - model.Blueprint = null; - return model; - } - - // copy all properties which are synced from the blueprint to the model - ApplyDefaults(blueprint, model); - ObjectCopycat.CopyProperties(blueprint, model, DefaultUncopyFields.Union(model.Blueprint.Desync).ToArray()); - - return model; - } - - - /// - public override ZeroEntity Apply(ZeroEntity blueprint, ZeroEntity model) => Apply(blueprint as T, model as T); - - - /// - /// Update default entity data - /// - protected virtual void ApplyDefaults(T blueprint, T model) - { - if (!model.Blueprint.Desync.Contains(nameof(ZeroEntity.Name), StringComparer)) - { - model.Name = blueprint.Name; - model.Alias = blueprint.Alias; - } - if (!model.Blueprint.Desync.Contains(nameof(ZeroEntity.Key), StringComparer)) - { - model.Key = blueprint.Key; - } - if (!model.Blueprint.Desync.Contains(nameof(ZeroEntity.Sort), StringComparer)) - { - model.Sort = blueprint.Sort; - } - if (!model.Blueprint.Desync.Contains(nameof(ZeroEntity.IsActive), StringComparer)) - { - model.IsActive = blueprint.IsActive; - } - - // these values can't be overridden - model.Hash = blueprint.Hash; - model.LanguageId = blueprint.LanguageId; - model.CreatedById = blueprint.CreatedById; - model.CreatedDate = blueprint.CreatedDate; - model.LastModifiedById = blueprint.LastModifiedById; - model.LastModifiedDate = blueprint.LastModifiedDate; - } - - - /// - /// Lock a field so it always get synchronized no and cannot be changed - /// - public virtual void LockAll() - { - UnlockedFields = new(); - } - - - /// - /// Lock a field so it always get synchronized no and cannot be changed - /// - public virtual void Lock(Expression> selector) - { - RemoveField(selector); - } - - - public virtual void Unlock(Expression> selector) - { - AddField(selector); - } - - - public virtual void UnlockDefaults() - { - AddField(x => x.Name); - AddField(x => x.Alias); - AddField(x => x.Sort); - AddField(x => x.Key); - AddField(x => x.IsActive); - } - - - /// - public override IEnumerable GetUnlockedFieldNames() - { - foreach (BlueprintField field in UnlockedFields) - { - yield return field.FieldName.ToCamelCaseId(); - } - } - - - /// - /// Get existing field or create a new one - /// - protected virtual BlueprintField AddField(Expression> selector) - { - BlueprintField tempField = new(selector); - BlueprintField storedField = UnlockedFields.FirstOrDefault(x => x.FieldName.Equals(tempField.FieldName, StringComparison.InvariantCultureIgnoreCase)); - - if (storedField != null) - { - return storedField; - } - - UnlockedFields.Add(tempField); - return tempField; - } - - - /// - /// Removes a field - /// - protected virtual void RemoveField(Expression> selector) - { - BlueprintField tempField = new(selector); - - if (tempField != null) - { - UnlockedFields.RemoveAll(x => x.FieldName.Equals(tempField.FieldName, StringComparison.InvariantCultureIgnoreCase)); - } - } -} - - -/// -/// -/// -public abstract class Blueprint -{ - /// - /// Type of the associated entity - /// - [JsonIgnore] - public Type ContentType { get; protected set; } - - public string Alias { get; protected set; } - - /// - /// String comparer for property name comparison - /// - protected readonly StringComparer StringComparer = StringComparer.InvariantCultureIgnoreCase; - - - public Blueprint(Type type) - { - ContentType = type; - Alias = type.Name.ToCamelCaseId(); - } - - /// - /// Merges blueprint data into a model based on the configuration - /// - public abstract ZeroEntity Apply(ZeroEntity blueprint, ZeroEntity model); - - /// - /// Get all fields which have been unlocked - /// - public abstract IEnumerable GetUnlockedFieldNames(); -} - - -public class DefaultBlueprint : Blueprint where T : ZeroEntity, new() -{ - public DefaultBlueprint(Action> expression = null) : base() - { - expression?.Invoke(this); - } -} diff --git a/zero.Core/Architecture/Blueprints/BlueprintChildInterceptor.cs b/zero.Core/Architecture/Blueprints/BlueprintChildInterceptor.cs deleted file mode 100644 index cb921118..00000000 --- a/zero.Core/Architecture/Blueprints/BlueprintChildInterceptor.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace zero.Architecture; - -public class BlueprintChildInterceptor : Interceptor, IBlueprintInterceptor -{ - string configuredZeroDatabase; - - - public BlueprintChildInterceptor(IZeroContext context) - { - Gravity = -1; - configuredZeroDatabase = context.Options.For().Database; - } - - /// - /// Only run this interceptor when the database is an app database - /// - public override bool CanHandle(InterceptorParameters args, Type modelType) => args.Context.Store.ResolvedDatabase != configuredZeroDatabase; - - - /// - public override Task> Deleting(InterceptorParameters args, ZeroEntity model) - { - if (model.Blueprint == null) - { - return Task.FromResult>(default); - } - - InterceptorResult result = new(); - result.Result = Result.Fail("@blueprint.errors.cannotDeleteChild"); - return Task.FromResult(result); - } -} \ No newline at end of file diff --git a/zero.Core/Architecture/Blueprints/BlueprintConfiguration.cs b/zero.Core/Architecture/Blueprints/BlueprintConfiguration.cs deleted file mode 100644 index 5e3472f6..00000000 --- a/zero.Core/Architecture/Blueprints/BlueprintConfiguration.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace zero.Architecture; - -/// -/// Defines a base entity which is synced and properties which are overridden -/// -public class BlueprintConfiguration -{ - /// - /// Id of the entity the synchronisation is based upon - /// - public string TargetId { get; set; } - - /// - /// A shallow copy of a blueprint can not be changed and is always fully synchronised with the parent entity - /// - public bool IsShallow { get; set; } - - /// - /// Properties which are not synced and have their own values - /// - public string[] Desync { get; set; } = Array.Empty(); - - /// - /// Additional custom sync options - /// - public Dictionary Options = new(); -} \ No newline at end of file diff --git a/zero.Core/Architecture/Blueprints/BlueprintField.cs b/zero.Core/Architecture/Blueprints/BlueprintField.cs deleted file mode 100644 index dd47a5e4..00000000 --- a/zero.Core/Architecture/Blueprints/BlueprintField.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Linq.Expressions; -using System.Reflection; - -namespace zero.Architecture; - -public class BlueprintField where T : ZeroEntity -{ - public Expression> Expression { get; private set; } - - public PropertyInfo PropertyInfo { get; private set; } - - public string FieldName { get; private set; } - - Action _applyHook { get; set; } - - Func _selectorFunc = null; - - - internal BlueprintField(Expression> selector) - { - Expression = selector; - FieldName = selector.GetPropertyPath(out MemberExpression member); - PropertyInfo = member?.Member as PropertyInfo; - - if (PropertyInfo == null) - { - throw new ArgumentException("selector parameter has to be a property selector"); - } - - if (FieldName.IsNullOrEmpty()) - { - throw new Exception("Could not find a matching field name"); - } - } - - - public virtual void Apply(T blueprint, T model) - { - if (_applyHook != null) - { - _applyHook(blueprint, model); - return; - } - - _selectorFunc ??= Expression.Compile(); - PropertyInfo.SetValue(model, _selectorFunc(blueprint)); - } - - - public virtual void OnUpdate(Action onUpdate) - { - _applyHook = onUpdate; - } -} \ No newline at end of file diff --git a/zero.Core/Architecture/Blueprints/BlueprintInterceptor.cs b/zero.Core/Architecture/Blueprints/BlueprintInterceptor.cs deleted file mode 100644 index ed912175..00000000 --- a/zero.Core/Architecture/Blueprints/BlueprintInterceptor.cs +++ /dev/null @@ -1,137 +0,0 @@ -using Microsoft.Extensions.Logging; -using Raven.Client.Documents; -using Raven.Client.Documents.Session; - -namespace zero.Architecture; - -public class BlueprintInterceptor : Interceptor, IBlueprintInterceptor -{ - protected IZeroContext Context { get; set; } - - protected IZeroStore Store { get; set; } - - protected ILogger Logger { get; set; } - - protected IBlueprintService BlueprintService { get; set; } - - IList apps; - - string configuredZeroDatabase; - - - public BlueprintInterceptor(IZeroContext context, IZeroStore store, ILogger logger, IBlueprintService blueprintService) - { - Gravity = -1; - Context = context; - Store = store; - Logger = logger; - BlueprintService = blueprintService; - configuredZeroDatabase = context.Options.For().Database; - } - - /// - /// Only run when operations are on the zero database - /// - public override bool CanHandle(InterceptorParameters args, Type modelType) => args.Context.Store.ResolvedDatabase == configuredZeroDatabase; - - - /// - public override Task Created(InterceptorParameters args, ZeroEntity model) => Saved(args, model, false); - - - /// - public override Task Updated(InterceptorParameters args, ZeroEntity model) => Saved(args, model, true); - - - /// - public async Task Saved(InterceptorParameters args, ZeroEntity model, bool update = false) - { - if (!BlueprintService.TryGetBlueprint(model, out Blueprint blueprint)) - { - blueprint = new DefaultShallowBlueprint(model.GetType()); - } - - int count = 0; - - foreach (Application app in await GetApplications()) - { - using ZeroContextScope scope = Context.CreateScope(app); - IZeroDocumentSession session = scope.Store.Session(scope.Database); - - ZeroEntity child = await session.LoadAsync(model.Id); - - if (child == null) - { - child = ObjectCopycat.Clone(model); - child.Blueprint = new() - { - TargetId = model.Id - }; - } - else - { - blueprint.Apply(model, child); - } - - count += 1; - - InterceptorInstruction interceptor = update ? args.Interceptors.ForUpdate(model) : args.Interceptors.ForCreate(model); - interceptor.Filter(x => x is not IBlueprintInterceptor); - - await interceptor.Start(args.Operations); - await session.StoreAsync(child); - await session.SaveChangesAsync(); - await interceptor.Complete(); - await session.SaveChangesAsync(); - } - - Logger.LogDebug("Blueprint: Synced {count} children for {name} ({id})", count, model.Name, model.Id); - } - - - /// - public override async Task Deleted(InterceptorParameters args, ZeroEntity model) - { - if (!BlueprintService.TryGetBlueprint(model, out Blueprint blueprint)) - { - return; - } - - int count = 0; - - foreach (Application app in await GetApplications()) - { - using ZeroContextScope scope = Context.CreateScope(app); - IZeroDocumentSession session = scope.Store.Session(scope.Database); - - count += 1; - - InterceptorInstruction interceptor = args.Interceptors.ForDelete(model); - interceptor.Filter(x => x is not IBlueprintInterceptor); - - await interceptor.Start(args.Operations); - session.Delete(model.Id); - await session.SaveChangesAsync(); - await interceptor.Complete(); - await session.SaveChangesAsync(); - } - - Logger.LogDebug("Blueprint: Deleted {count} children for {name} ({id})", count, model.Name, model.Id); - } - - - /// - /// Get all applications to choose from - /// - async Task> GetApplications() - { - if (apps != null) - { - return apps; - } - - IAsyncDocumentSession session = Store.Session(global: true); - apps = await session.Query().ToListAsync(); - return apps; - } -} \ No newline at end of file diff --git a/zero.Core/Architecture/Blueprints/BlueprintOptions.cs b/zero.Core/Architecture/Blueprints/BlueprintOptions.cs deleted file mode 100644 index f518c872..00000000 --- a/zero.Core/Architecture/Blueprints/BlueprintOptions.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace zero.Architecture; - -public class BlueprintOptions : List -{ - public bool Enabled { get; set; } = false; - - public void Add() where T : Blueprint, new() - { - base.Add(new T()); - } - - public void Add(Blueprint implementation) where T : ZeroEntity, new() - { - base.Add(implementation); - } - - public void Add(Action> createExpression = null) where T : ZeroEntity, new() - { - base.Add(new DefaultBlueprint(createExpression)); - } -} diff --git a/zero.Core/Architecture/Blueprints/BlueprintService.cs b/zero.Core/Architecture/Blueprints/BlueprintService.cs deleted file mode 100644 index fb37ab45..00000000 --- a/zero.Core/Architecture/Blueprints/BlueprintService.cs +++ /dev/null @@ -1,76 +0,0 @@ -namespace zero.Architecture; - -public class BlueprintService : IBlueprintService -{ - protected IZeroOptions Options { get; set; } - - protected IReadOnlyCollection Blueprints { get; set; } - - - public BlueprintService(IZeroOptions options) - { - Options = options; - Blueprints = Options.For(); - } - - - /// - public bool IsEnabled(T model) => IsEnabled(model.GetType()); - - - /// - public bool IsEnabled()=> IsEnabled(typeof(T)); - - - /// - public bool IsEnabled(Type type) => Blueprints.Any(x => x.ContentType.IsAssignableFrom(type)); - - - /// - public bool TryGetBlueprint(T model, out Blueprint blueprint) => TryGetBlueprint(model.GetType(), out blueprint); - - - /// - public bool TryGetBlueprint(out Blueprint blueprint) => TryGetBlueprint(typeof(T), out blueprint); - - - /// - public bool TryGetBlueprint(Type type, out Blueprint blueprint) - { - blueprint = Blueprints.FirstOrDefault(x => x.ContentType.IsAssignableFrom(type)); - return blueprint != null; - } - - /// - public Dictionary GetAllBlueprints() - { - return Blueprints.ToDictionary(x => x.ContentType, x => x); - } -} - - -public interface IBlueprintService -{ - /// - /// Check whether blueprinting functionality is enabled for a certain entity - /// - bool IsEnabled(); - - /// - /// Check whether blueprinting functionality is enabled for a certain entity - /// - bool IsEnabled(T model); - - bool IsEnabled(Type type); - - bool TryGetBlueprint(T model, out Blueprint blueprint); - - bool TryGetBlueprint(out Blueprint blueprint); - - bool TryGetBlueprint(Type type, out Blueprint blueprint); - - /// - /// Get all registered blueprint configurations - /// - Dictionary GetAllBlueprints(); -} diff --git a/zero.Core/Architecture/Blueprints/BlueprintStatus.cs b/zero.Core/Architecture/Blueprints/BlueprintStatus.cs deleted file mode 100644 index 8ea26131..00000000 --- a/zero.Core/Architecture/Blueprints/BlueprintStatus.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace zero.Architecture; - -public enum BlueprintStatus -{ - /// - /// This entity is standalone - /// - Standalone = 0, - /// - /// This entity is a blueprint - /// - Parent = 1, - /// - /// This entity is a child of blueprint - /// - Child = 2 -} diff --git a/zero.Core/Architecture/Blueprints/IBlueprintInterceptor.cs b/zero.Core/Architecture/Blueprints/IBlueprintInterceptor.cs deleted file mode 100644 index 564f2ad4..00000000 --- a/zero.Core/Architecture/Blueprints/IBlueprintInterceptor.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace zero.Architecture; - -public interface IBlueprintInterceptor -{ - -} \ No newline at end of file diff --git a/zero.Core/Architecture/Plugins/IZeroBuiltInPlugin.cs b/zero.Core/Architecture/Plugins/IZeroBuiltInPlugin.cs deleted file mode 100644 index 9221235b..00000000 --- a/zero.Core/Architecture/Plugins/IZeroBuiltInPlugin.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace zero.Architecture; - -internal interface IZeroBuiltInPlugin -{ - -} \ No newline at end of file diff --git a/zero.Core/Architecture/Plugins/IZeroPlugin.cs b/zero.Core/Architecture/Plugins/IZeroPlugin.cs deleted file mode 100644 index d7a3e424..00000000 --- a/zero.Core/Architecture/Plugins/IZeroPlugin.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Architecture; - -public abstract class ZeroPlugin : IZeroPlugin -{ - public IZeroPluginOptions Options { get; } = new ZeroPluginOptions(); - - public virtual void ConfigureServices(IServiceCollection services, IConfiguration configuration) { } - - public virtual void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) { } -} - - -public interface IZeroPlugin -{ - IZeroPluginOptions Options { get; } - - void ConfigureServices(IServiceCollection services, IConfiguration configuration); - - void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider); -} \ No newline at end of file diff --git a/zero.Core/Architecture/Plugins/ZeroPluginOptions.cs b/zero.Core/Architecture/Plugins/ZeroPluginOptions.cs deleted file mode 100644 index a273812f..00000000 --- a/zero.Core/Architecture/Plugins/ZeroPluginOptions.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace zero.Architecture; - -public class ZeroPluginOptions : IZeroPluginOptions -{ - public string Name { get; set; } - - public string Description { get; set; } - - public List LocalizationPaths { get; private set; } = new List(); - - public string PluginPath { get; set; } -} - - -public interface IZeroPluginOptions -{ - string Name { get; set; } - - string Description { get; set; } - - List LocalizationPaths { get; } - - string PluginPath { get; set; } -} - - -public interface IZeroPluginStartup -{ - Task Startup(); -} \ No newline at end of file diff --git a/zero.Core/Architecture/ZeroArchitectureModule.cs b/zero.Core/Architecture/ZeroArchitectureModule.cs deleted file mode 100644 index 73aae0f8..00000000 --- a/zero.Core/Architecture/ZeroArchitectureModule.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Architecture; - -internal class ZeroArchitectureModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - //services.AddScoped(); - //services.AddScoped(); - services.AddOptions().Bind(configuration.GetSection("Zero:Blueprints")); - } -} \ No newline at end of file diff --git a/zero.Core/Architecture/ZeroModule.cs b/zero.Core/Architecture/ZeroModule.cs deleted file mode 100644 index 959cdea8..00000000 --- a/zero.Core/Architecture/ZeroModule.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Architecture; - -public abstract class ZeroModule : IZeroModule -{ - /// - public virtual int Order { get; } = 0; - - /// - public virtual int ConfigureOrder => Order; - - /// - public virtual void ConfigureServices(IServiceCollection services, IConfiguration configuration) { } - - /// - public virtual void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) { } -} - - -public interface IZeroModule -{ - /// - /// Get the value to use to order startups to configure services. The default is 0. - /// - int Order { get; } - - /// - /// Get the value to use to order startups to build the pipeline. The default is the 'Order' property. - /// - int ConfigureOrder { get; } - - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 - /// - /// The collection of service descriptors. - void ConfigureServices(IServiceCollection services, IConfiguration configuration); - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - /// - /// - /// - void Configure(IApplicationBuilder builder, IEndpointRouteBuilder routes, IServiceProvider serviceProvider); -} \ No newline at end of file diff --git a/zero.Core/Architecture/ZeroModuleCollection.cs b/zero.Core/Architecture/ZeroModuleCollection.cs deleted file mode 100644 index 8ae3278b..00000000 --- a/zero.Core/Architecture/ZeroModuleCollection.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using System.Collections.Concurrent; - -namespace zero.Architecture; - -public class ZeroModuleCollection : ZeroModule -{ - ConcurrentDictionary _modules = new(); - - - /// - /// Get all registered modules - /// - public IEnumerable GetAll() => _modules.Values; - - - /// - /// Adds a zero module - /// - public void Add() where T : class, IZeroModule, new() - { - Add(new T()); - } - - - /// - /// Adds a zero module - /// - public void Add(T moduleInstance) where T : IZeroModule - { - Add(typeof(T), moduleInstance); - } - - - /// - /// Adds a zero module - /// - public void Add(Type moduleType, IZeroModule moduleInstance) - { - if (_modules.ContainsKey(moduleType)) - { - return; - } - - _modules.TryAdd(moduleType, moduleInstance); - } - - - /// - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - foreach (var module in _modules.OrderBy(x => x.Value.Order)) - { - module.Value.ConfigureServices(services, configuration); - } - } - - - /// - public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) - { - foreach (var module in _modules.OrderBy(x => x.Value.ConfigureOrder)) - { - module.Value.Configure(app, routes, serviceProvider); - } - } -} \ No newline at end of file diff --git a/zero.Core/Communication/Handlers/IHandler.cs b/zero.Core/Communication/Handlers/IHandler.cs deleted file mode 100644 index c2ea5d02..00000000 --- a/zero.Core/Communication/Handlers/IHandler.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace zero.Communication; - -public interface IHandler -{ -} diff --git a/zero.Core/Communication/Handlers/IHandlerHolder.cs b/zero.Core/Communication/Handlers/IHandlerHolder.cs deleted file mode 100644 index 096c88ef..00000000 --- a/zero.Core/Communication/Handlers/IHandlerHolder.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Communication; - -public class HandlerHolder : IHandlerHolder -{ - IServiceProvider Services { get; } - - public HandlerHolder(IServiceProvider services) - { - Services = services; - } - - - public T Get() where T : IHandler - { - return Services.GetService(); - } - - - public IEnumerable GetAll() where T : IHandler - { - return Services.GetService>(); - } -} - -public interface IHandlerHolder -{ - T Get() where T : IHandler; - - IEnumerable GetAll() where T : IHandler; -} \ No newline at end of file diff --git a/zero.Core/Communication/Interceptors/Interceptor.cs b/zero.Core/Communication/Interceptors/Interceptor.cs deleted file mode 100644 index f47498c3..00000000 --- a/zero.Core/Communication/Interceptors/Interceptor.cs +++ /dev/null @@ -1,207 +0,0 @@ -namespace zero.Communication; - - -public abstract partial class Interceptor : Interceptor, IInterceptor where T : ZeroIdEntity -{ - /// - public Type ModelType { get; protected set; } - - - public Interceptor() - { - Gravity = 0; - ModelType = typeof(T); - Name = ModelType.Name; - } - - - /// - public override bool CanHandle(InterceptorParameters args, Type modelType) => ModelType.IsAssignableFrom(modelType); - - /// - public virtual Task> Creating(InterceptorParameters args, T model) => Task.FromResult>(default); - - /// - public sealed override async Task> Creating(InterceptorParameters args, ZeroIdEntity model) => Convert(await Creating(args, model as T)); - - /// - public virtual Task> Updating(InterceptorParameters args, T model) => Task.FromResult>(default); - - /// - public sealed override async Task> Updating(InterceptorParameters args, ZeroIdEntity model) => Convert(await Updating(args, model as T)); - - /// - public virtual Task> Deleting(InterceptorParameters args, T model) => Task.FromResult>(default); - - /// - public sealed override async Task> Deleting(InterceptorParameters args, ZeroIdEntity model) => Convert(await Deleting(args, model as T)); - - /// - public virtual Task Created(InterceptorParameters args, T model) => Task.CompletedTask; - - /// - public sealed override Task Created(InterceptorParameters args, ZeroIdEntity model) => Created(args, model as T); - - /// - public virtual Task Updated(InterceptorParameters args, T model) => Task.CompletedTask; - - /// - public sealed override Task Updated(InterceptorParameters args, ZeroIdEntity model) => Updated(args, model as T); - - /// - public virtual Task Deleted(InterceptorParameters args, T model) => Task.CompletedTask; - - /// - public sealed override Task Deleted(InterceptorParameters args, ZeroIdEntity model) => Deleted(args, model as T); - - - InterceptorResult Convert(InterceptorResult result) - { - if (result == default) - { - return default; - } - - return new InterceptorResult() - { - Continue = result.Continue, - InterceptorHash = result.InterceptorHash, - Result = result.Result != null ? result.Result.ConvertTo(result.Result.Model) : null - }; - } -} - - -public abstract partial class Interceptor : IInterceptor -{ - /// - public int Gravity { get; protected set; } - - /// - public string Name { get; protected set; } - - /// - public string Hash { get; protected set; } - - - public Interceptor() - { - Gravity = 0; - Hash = IdGenerator.Create(); - } - - - /// - public virtual bool CanHandle(InterceptorParameters args, Type modelType) => true; - - /// - public virtual Task> Creating(InterceptorParameters args, ZeroIdEntity model) => Task.FromResult>(default); - - /// - public virtual Task> Updating(InterceptorParameters args, ZeroIdEntity model) => Task.FromResult>(default); - - /// - public virtual Task> Deleting(InterceptorParameters args, ZeroIdEntity model) => Task.FromResult>(default); - - /// - public virtual Task Created(InterceptorParameters args, ZeroIdEntity model) => Task.CompletedTask; - - /// - public virtual Task Updated(InterceptorParameters args, ZeroIdEntity model) => Task.CompletedTask; - - /// - public virtual Task Deleted(InterceptorParameters args, ZeroIdEntity model) => Task.CompletedTask; -} - - -public interface IInterceptor : IInterceptor where T : ZeroIdEntity -{ - /// - /// Type of the associated model - /// - Type ModelType { get; } - - /// - /// Called after an entity has been stored but before the session has saved its changes - /// - Task Created(InterceptorParameters args, T model); - - /// - /// Called before an entity is stored and validated - /// - Task> Creating(InterceptorParameters args, T model); - - /// - /// Called after an entity has been deleted but before the session has saved its changes - /// - Task Deleted(InterceptorParameters args, T model); - - /// - /// Called before an entity is deleted - /// - Task> Deleting(InterceptorParameters args, T model); - - /// - /// Called after an entity has been updated but before the session has saved its changes - /// - Task Updated(InterceptorParameters args, T model); - - /// - /// Called before an entity is stored and validated - /// - Task> Updating(InterceptorParameters args, T model); -} - - -public interface IInterceptor -{ - /// - /// Interceptors with a higher gravity will run before any with lower gravity - /// - int Gravity { get; } - - /// - /// Readable name for this interceptor - /// - string Name { get; } - - /// - /// Hash which is used for this interceptor to be able to pass results from Start to Complete methods - /// - string Hash { get; } - - /// - /// Whether any of the interceptor methods is allowed to run based on the parameters - /// - bool CanHandle(InterceptorParameters args, Type modelType); - - /// - /// Called after an entity has been stored but before the session has saved its changes - /// - Task Created(InterceptorParameters args, ZeroIdEntity model); - - /// - /// Called before an entity is stored and validated - /// - Task> Creating(InterceptorParameters args, ZeroIdEntity model); - - /// - /// Called after an entity has been deleted but before the session has saved its changes - /// - Task Deleted(InterceptorParameters args, ZeroIdEntity model); - - /// - /// Called before an entity is deleted - /// - Task> Deleting(InterceptorParameters args, ZeroIdEntity model); - - /// - /// Called after an entity has been updated but before the session has saved its changes - /// - Task Updated(InterceptorParameters args, ZeroIdEntity model); - - /// - /// Called before an entity is stored and validated - /// - Task> Updating(InterceptorParameters args, ZeroIdEntity model); -} \ No newline at end of file diff --git a/zero.Core/Communication/Interceptors/InterceptorInstruction.cs b/zero.Core/Communication/Interceptors/InterceptorInstruction.cs deleted file mode 100644 index 9b88512d..00000000 --- a/zero.Core/Communication/Interceptors/InterceptorInstruction.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace zero.Communication; - -public class InterceptorInstruction where T : ZeroIdEntity, new() -{ - public Guid Guid { get; private set; } - - public InterceptorRunType Runtype { get; private set; } - - public T Model { get; private set; } - - public T PreviousModel { get; private set; } - - public Type ModelType { get; private set; } - - public Result Result { get; private set; } - - protected IZeroContext Context { get; private set; } - - protected Lazy> Interceptors { get; private set; } - - protected Dictionary InterceptorCache { get; private set; } = new(); - - protected IInterceptors InterceptorHandler { get; private set; } - - protected ILogger Logger { get; private set; } - - protected Func InterceptorFilter { get; private set; } = x => true; - - - internal InterceptorInstruction(IInterceptors interceptors, IZeroContext context, Lazy> registrations, ILogger logger, InterceptorRunType runtype, T model, T previousModel = null) - { - InterceptorHandler = interceptors; - Context = context; - Guid = Guid.NewGuid(); - Logger = logger; - Runtype = runtype; - Model = model; - PreviousModel = previousModel; - Interceptors = registrations; - - ModelType = model.GetType(); - } - - - /// - /// Custom interceptor filter for this instruction (the CanHandle() method for each interceptor is still activated) - /// - public void Filter(Func predicate) - { - InterceptorFilter = predicate; - } - - - /// - /// Run all interceptors (in order) which can handle the given type. - /// Depending on the action any of the following methods on the interceptor is called: Creating(), Updating(), Deleting(). - /// If any of the interceptors returns a result the operation is cancelled and this result is returned. - /// - public async Task Start(IStoreOperations operations) - { - foreach (IInterceptor interceptor in GetInterceptors()) - { - InterceptorParameters parameters = new() - { - Context = Context, - Store = Context.Store, - Properties = new(), - Interceptors = InterceptorHandler, - Operations = operations, - PreviousModel = PreviousModel - }; - - if (!interceptor.CanHandle(parameters, ModelType)) - { - continue; - } - - - Logger.LogDebug("Run interceptor {interceptor} for {type}:{operation}", interceptor.Name, ModelType, Runtype); - - InterceptorResult result = (await HandleBefore(interceptor, parameters)) ?? new(); - result.InterceptorHash = IdGenerator.Create(32); - - InterceptorCache.Add(interceptor, parameters); - - // we cancel all further interceptors if a result is available and return this instead - if (result.Result != null) - { - Result = result.Result.ConvertTo(result.Result.Model as T); - return false; - } - - // the Continue task will cancel all further interceptors - if (!result.Continue) - { - break; - } - } - - return true; - } - - - /// - /// Run all interceptors (in order) which can handle the given type. - /// Depending on the action any of the following methods on the interceptor is called: Created(), Updated(), Deleted(). - /// The parameters which are returned from the Start() operation are passed to the methods. - /// - public async Task Complete() - { - foreach ((IInterceptor interceptor, InterceptorParameters parameters) in InterceptorCache) - { - await HandleAfter(interceptor, parameters); - } - } - - - protected IEnumerable GetInterceptors() - { - return Interceptors.Value.Where(InterceptorFilter).OrderByDescending(x => x.Gravity); - } - - - /// - /// Proxy for handling methods on an interceptor - /// - protected Task> HandleBefore(IInterceptor interceptor, InterceptorParameters parameters) => Runtype switch - { - InterceptorRunType.Create => interceptor.Creating(parameters, Model), - InterceptorRunType.Update => interceptor.Updating(parameters, Model), - InterceptorRunType.Delete => interceptor.Deleting(parameters, Model), - _ => throw new NotImplementedException() - }; - - - /// - /// Proxy for handling methods on an interceptor - /// - protected Task HandleAfter(IInterceptor interceptor, InterceptorParameters parameters) => Runtype switch - { - InterceptorRunType.Create => interceptor.Created(parameters, Model), - InterceptorRunType.Update => interceptor.Updated(parameters, Model), - InterceptorRunType.Delete => interceptor.Deleted(parameters, Model), - _ => throw new NotImplementedException() - }; -} \ No newline at end of file diff --git a/zero.Core/Communication/Interceptors/InterceptorParameters.cs b/zero.Core/Communication/Interceptors/InterceptorParameters.cs deleted file mode 100644 index 2e36a170..00000000 --- a/zero.Core/Communication/Interceptors/InterceptorParameters.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace zero.Communication; - -public class InterceptorParameters -{ - /// - /// The current zero context - /// - public IZeroContext Context { get; set; } - - /// - /// Raven document store - /// - public IZeroStore Store { get; set; } - - /// - /// Access to other interceptor methods - /// - public IInterceptors Interceptors { get; internal set; } - - /// - /// Access to operations - /// - public IStoreOperations Operations { get; internal set; } - - /// - /// Parameters from the interceptor which ran on before the operation (only available for completed operations) - /// - public Dictionary Properties { get; set; } = new(); - - /// - /// Get a typed property - /// - public TProp Property(string key) => Properties.GetValueOrDefault(key); - - /// - /// Get a typed property - /// - public bool TryGetProperty(string key, out TProp property) => Properties.TryGetValue(key, out property); - - /// - /// Holds a reference to the previously existing model when an update happens (can be null) - /// - public object PreviousModel { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Communication/Interceptors/InterceptorResult.cs b/zero.Core/Communication/Interceptors/InterceptorResult.cs deleted file mode 100644 index 735ae3ba..00000000 --- a/zero.Core/Communication/Interceptors/InterceptorResult.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace zero.Communication; - -public class InterceptorResult -{ - /// - /// Autoset. Hash used to match interceptors in order to correctly pass parameters - /// - internal string InterceptorHash { get; set; } - - /// - /// Prevent further interceptors from running for this operation (only valid for the current interception type, e.g. Update/Created/Purge/...) - /// - public bool Continue { get; set; } = true; - - /// - /// Set a result. This will prevent further execution of the operation and cancels all subsequent interceptors - /// - public Result Result { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Communication/Interceptors/InterceptorRunType.cs b/zero.Core/Communication/Interceptors/InterceptorRunType.cs deleted file mode 100644 index 82be301e..00000000 --- a/zero.Core/Communication/Interceptors/InterceptorRunType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace zero.Communication; - -public enum InterceptorRunType -{ - Create = 1, - Update = 2, - Delete = 3 -} \ No newline at end of file diff --git a/zero.Core/Communication/Interceptors/Interceptors.cs b/zero.Core/Communication/Interceptors/Interceptors.cs deleted file mode 100644 index 297f1676..00000000 --- a/zero.Core/Communication/Interceptors/Interceptors.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace zero.Communication; - -public class Interceptors : IInterceptors -{ - protected IZeroContext Context { get; set; } - - protected Lazy> Registrations { get; set; } - - protected ILogger Logger { get; set; } - - - public Interceptors(IZeroContext context, Lazy> registrations, ILogger logger) - { - Context = context; - Registrations = registrations; - Logger = logger; - } - - - /// - public InterceptorInstruction ForCreate(T model) where T : ZeroIdEntity, new() => new(this, Context, Registrations, Logger, InterceptorRunType.Create, model); - - /// - public InterceptorInstruction ForUpdate(T model, T previousModel = null) where T : ZeroIdEntity, new() => new(this, Context, Registrations, Logger, InterceptorRunType.Update, model, previousModel); - - /// - public InterceptorInstruction ForDelete(T model) where T : ZeroIdEntity, new() => new(this, Context, Registrations, Logger, InterceptorRunType.Delete, model); -} - - -public interface IInterceptors -{ - /// - /// Instruction which can run interceptors before and after a creating an entity - /// - InterceptorInstruction ForCreate(T model) where T : ZeroIdEntity, new(); - - /// - /// Instruction which can run interceptors before and after updating an entity - /// - InterceptorInstruction ForUpdate(T model, T previousModel = null) where T : ZeroIdEntity, new(); - - /// - /// Instruction which can run interceptors before and after deleting an entity - /// - InterceptorInstruction ForDelete(T model) where T : ZeroIdEntity, new(); -} \ No newline at end of file diff --git a/zero.Core/Communication/LazilyResolved.cs b/zero.Core/Communication/LazilyResolved.cs deleted file mode 100644 index 0666dae4..00000000 --- a/zero.Core/Communication/LazilyResolved.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Communication; - -public class LazilyResolved : Lazy -{ - public LazilyResolved(IServiceProvider serviceProvider): base(serviceProvider.GetRequiredService) - { - - } -} \ No newline at end of file diff --git a/zero.Core/Communication/Messages/IMessage.cs b/zero.Core/Communication/Messages/IMessage.cs deleted file mode 100644 index 4c03d107..00000000 --- a/zero.Core/Communication/Messages/IMessage.cs +++ /dev/null @@ -1,6 +0,0 @@ - -namespace zero.Communication; - -public interface IMessage -{ -} diff --git a/zero.Core/Communication/Messages/IMessageHandler.cs b/zero.Core/Communication/Messages/IMessageHandler.cs deleted file mode 100644 index a818e1de..00000000 --- a/zero.Core/Communication/Messages/IMessageHandler.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace zero.Communication; - -/// -/// Indicates a handler that can perform an action when a message of -/// a certain type is received. (could be an interface rather than concrete type) -/// -public interface IMessageHandler where TMessage : IMessage -{ - /// - /// Method to invoke when a message of type TMessage is published - /// - Task Handle(TMessage message); -} - - -/// -/// Indicates a handler that can perform an action when a message of -/// a certain type is received. (could be an interface rather than concrete type) -/// -public interface IBatchMessageHandler : IMessageHandler where TMessage : IMessage -{ - /// - /// Method to invoke when a batch of messages of type TMessage are published - /// - Task HandleBatchAsync(IReadOnlyCollection message); -} diff --git a/zero.Core/Communication/Messages/MessageAggregator.cs b/zero.Core/Communication/Messages/MessageAggregator.cs deleted file mode 100644 index cd6a3f37..00000000 --- a/zero.Core/Communication/Messages/MessageAggregator.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections.Concurrent; - -namespace zero.Communication; - -public class MessageAggregator : IMessageAggregator -{ - readonly ConcurrentBag Subscription = new ConcurrentBag(); - readonly IServiceProvider ServiceProvider; - - - public MessageAggregator(IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider; - } - - - /// - public async Task Publish(TMessage message) where TMessage : class, IMessage - { - IEnumerable subscriptions = Subscription.Where(s => s.CanDeliver()); - - foreach (IMessageSubscription subscription in subscriptions) - { - await subscription.Deliver(ServiceProvider, message); - } - } - - - /// - public void Activate() - where TMessage : class, IMessage - where TMessageHandler : IMessageHandler - { - Subscription.Add(new MessageSubscription()); - } -} - - -public interface IMessageAggregator -{ - /// - /// Publishes the specified message, invoking any handlers subscribed to the message. - /// - Task Publish(TMessage message) where TMessage : class, IMessage; - - /// - /// Subscribes the specified handler to the spified message type - /// - void Activate() - where TMessage : class, IMessage - where TMessageHandler : IMessageHandler; -} diff --git a/zero.Core/Communication/Messages/MessageSubscription.cs b/zero.Core/Communication/Messages/MessageSubscription.cs deleted file mode 100644 index 3698f175..00000000 --- a/zero.Core/Communication/Messages/MessageSubscription.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using System.Reflection; - -namespace zero.Communication; - -internal class MessageSubscription : IMessageSubscription - where TMessage : class, IMessage - where TMessageHandler : IMessageHandler -{ - public bool CanDeliver() - { - return typeof(TMessage).GetTypeInfo().IsAssignableFrom(typeof(T)); - } - - public async Task Deliver(IServiceProvider serviceProvider, object message) - { - if (message == null) - { - throw new ArgumentNullException(nameof(message)); - } - if (!(message is TMessage)) - { - throw new ArgumentException($"{ nameof(message) } must be of type '{typeof(TMessage).FullName}'"); - } - - var handlers = serviceProvider.GetServices(); - - foreach (var handler in handlers) - { - await handler.Handle((TMessage)message); - } - } -} - - -public interface IMessageSubscription -{ - bool CanDeliver(); - - Task Deliver(IServiceProvider serviceProvider, object message); -} diff --git a/zero.Core/Communication/ZeroCommunicationModule.cs b/zero.Core/Communication/ZeroCommunicationModule.cs deleted file mode 100644 index bd3a05c4..00000000 --- a/zero.Core/Communication/ZeroCommunicationModule.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Communication; - -internal class ZeroCommunicationModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - services.AddScoped(); - services.AddTransient(); - services.AddTransient(typeof(Lazy<>), typeof(LazilyResolved<>)); - } -} \ No newline at end of file diff --git a/zero.Core/Configuration/Constants.cs b/zero.Core/Configuration/Constants.cs deleted file mode 100644 index fa96aa14..00000000 --- a/zero.Core/Configuration/Constants.cs +++ /dev/null @@ -1,102 +0,0 @@ -namespace zero.Configuration; - -public static partial class Constants -{ - public const string ErrorFieldNone = "__zero_no_field"; - - public static partial class Tabs - { - public const string General = "general"; - } - - public static partial class Auth - { - public const string SystemUser = "system"; - public const string DefaultScheme = "zeroScheme"; - public const string BackofficeDisplayName = "Zero Backoffice"; - public const string BackofficeScheme = "zeroBackoffice"; - public const string BackofficeCookieName = "zero.be.session"; - public const string DefaultCookieName = "zero.session"; - - public static partial class Claims - { - public const string IsZero = "zero.claim.iszero"; - public const string IsSuper = "zero.claim.issuper"; - public const string UserId = "zero.claim.userid"; - public const string UserName = "zero.claim.username"; - public const string Role = "zero.claim.rolealias"; - public const string SecurityStamp = "zero.claim.securitystamp"; - public const string Email = "zero.claim.email"; - public const string Permission = "zero.claim.permission"; - public const string DefaultAppId = "zero.claim.defaultAppId"; - public const string AppId = "zero.claim.appId"; - public const string TicketExpires = "zero.claim.ticketExpires"; - } - } - - - public static partial class Permissions - { - public static partial class Groups - { - public const string Sections = "zero.permissions.groups.sections"; - public const string Spaces = "zero.permissions.groups.spaces"; - public const string Settings = "zero.permissions.groups.settings"; - public const string Pages = "zero.permissions.groups.pages"; - public const string Modules = "zero.permissions.groups.modules"; - } - } - - - public static partial class Database - { - public const string ReservationPrefix = "zero."; - public const string Expires = Raven.Client.Constants.Documents.Metadata.Expires; - } - - public static partial class Sections - { - public const string Dashboard = "dashboard"; - public const string Pages = "pages"; - public const string Spaces = "spaces"; - public const string Media = "media"; - public const string Settings = "settings"; - } - - public static partial class Settings - { - public const string Updates = "updates"; - public const string Applications = "applications"; - public const string Users = "users"; - public const string Languages = "languages"; - public const string Translations = "translations"; - public const string Countries = "countries"; - public const string Mails = "mailTemplates"; - public const string Integrations = "integrations"; - public const string Logging = "logs"; - public const string Plugins = "plugins"; - public const string CreatePlugin = "createplugin"; - } - - public static partial class PermissionCollections - { - public const string Sections = "permissionCollectionSections"; - public const string Spaces = "permissionCollectionSpaces"; - public const string Settings = "permisssionCollectionSettings"; - public const string Modules = "permissionCollectionModules"; - } - - public static partial class Pages - { - public const string FolderAlias = "zero.folder"; - public const string DefaultRootPageTypeAlias = "root"; - public const string PageRouteProviderAlias = "zero.pages"; - } - - public static partial class Routing - { - public const string InternalRoutePrefix = "/__zero/"; - - public const string ErrorRoute = InternalRoutePrefix + "error"; - } -} diff --git a/zero.Core/Configuration/Features/Feature.cs b/zero.Core/Configuration/Features/Feature.cs deleted file mode 100644 index 35a8d20d..00000000 --- a/zero.Core/Configuration/Features/Feature.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace zero.Configuration; - -/// -/// A feature can affect both the backoffice and the frontend -/// -public class Feature -{ - /// - /// The alias - /// - public string Alias { get; set; } - - /// - /// The name of the feature - /// - public string Name { get; set; } - - /// - /// Additional description - /// - public string Description { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Configuration/Features/FeatureOptions.cs b/zero.Core/Configuration/Features/FeatureOptions.cs deleted file mode 100644 index e222a407..00000000 --- a/zero.Core/Configuration/Features/FeatureOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace zero.Configuration; - -public class FeatureOptions : List -{ - public void Add() where T : Feature, new() - { - Add(new T()); - } - - public void Add(string alias, string name, string description) - { - Add(new Feature() - { - Alias = alias, - Name = name, - Description = description - }); - } -} \ No newline at end of file diff --git a/zero.Core/Configuration/IZeroCollectionOptions.cs b/zero.Core/Configuration/IZeroCollectionOptions.cs deleted file mode 100644 index 1bd70b9e..00000000 --- a/zero.Core/Configuration/IZeroCollectionOptions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace zero.Configuration; - -public interface IZeroCollectionOptions -{ - -} \ No newline at end of file diff --git a/zero.Core/Configuration/Integrations/FlavorOptionsExtensions.cs b/zero.Core/Configuration/Integrations/FlavorOptionsExtensions.cs deleted file mode 100644 index 3f8987eb..00000000 --- a/zero.Core/Configuration/Integrations/FlavorOptionsExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using FluentValidation; - -namespace zero.Configuration; - -public static class FlavorOptionsExtensions -{ - public static void AddIntegration(this FlavorOptions options, string alias, string name, string description, string editorAlias = null, List tags = default, string imagePath = null, IValidator validator = null) where T : Integration, new() - { - options.Add(new IntegrationType(typeof(T)) - { - Alias = alias, - Name = name, - Description = description, - ImagePath = imagePath, - Tags = tags, - Validator = validator, - Construct = cfg => new T(), - EditorAlias = editorAlias - }); - } -} diff --git a/zero.Core/Configuration/Integrations/Integration.cs b/zero.Core/Configuration/Integrations/Integration.cs deleted file mode 100644 index d714cbd3..00000000 --- a/zero.Core/Configuration/Integrations/Integration.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace zero.Configuration; - -/// -/// An integration is an application part which has a public configuration per app. -/// It's up to the user to provide functionality. -/// -[RavenCollection("Integrations")] -public class Integration : ZeroEntity -{ -} \ No newline at end of file diff --git a/zero.Core/Configuration/Integrations/IntegrationStore.cs b/zero.Core/Configuration/Integrations/IntegrationStore.cs deleted file mode 100644 index 2a73b9ed..00000000 --- a/zero.Core/Configuration/Integrations/IntegrationStore.cs +++ /dev/null @@ -1,210 +0,0 @@ -using FluentValidation; -using FluentValidation.Results; -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; - -namespace zero.Configuration; - -public class IntegrationStore : IIntegrationStore -{ - protected IStoreOperations Operations { get; private set; } - - protected IIntegrationTypeService IntegrationTypes { get; private set; } - - - public IntegrationStore(IStoreOperationsWithInactive operations, IIntegrationTypeService integrationTypes) - { - IntegrationTypes = integrationTypes; - Operations = operations; - } - - - /// - public virtual async Task Empty(string alias) - { - return await Operations.Empty(alias); - } - - - /// - public virtual async Task Load(string alias) - { - Paged result = await Operations.Load(1, 1, q => q.Where(x => x.Flavor == alias)); - return result.Items.FirstOrDefault(); - } - - - /// - public async Task> LoadByTag(string tag) - { - IEnumerable types = IntegrationTypes.GetByTag(tag); - - if (!types.Any()) - { - return new List(); - } - - string[] aliases = types.Select(x => x.Alias).ToArray(); - return await Operations.Session.Query().Where(x => x.Flavor.In(aliases)).ToListAsync(); - } - - - /// - public async Task Any(string tag) - { - IEnumerable types = IntegrationTypes.GetByTag(tag); - - if (!types.Any()) - { - return false; - } - - string[] aliases = types.Select(x => x.Alias).ToArray(); - return await Operations.Session.Query().AnyAsync(x => x.Flavor.In(aliases) && x.IsActive); - } - - - /// - public virtual string GetChangeToken(Integration model) => Operations.GetChangeToken(model); - - - /// - public virtual async Task> Create(Integration model) => await Save(model, true); - - - /// - public virtual async Task> Update(Integration model) => await Save(model, false); - - - /// - public virtual async Task> Activate(string alias) - { - Integration model = await Load(alias); - if (model != null) - { - model.IsActive = true; - } - return await Update(model); - } - - - /// - public virtual async Task> Deactivate(string alias) - { - Integration model = await Load(alias); - if (model != null) - { - model.IsActive = false; - } - return await Update(model); - } - - - - /// - public async Task> Delete(string alias) - { - Integration integration = await Load(alias); - return await Operations.Delete(integration); - } - - - // - protected virtual async Task Validate(Integration model, bool create = false) - { - ValidationResult result = new(); - - if (model == null) - { - result.Errors.Add(new ValidationFailure("__nofield", "@integration.errors.notfound")); - } - else - { - IntegrationType type = IntegrationTypes.GetByAlias(model.Flavor); - - if (type == null) - { - result.Errors.Add(new ValidationFailure("__nofield", "@integration.errors.typenotfound")); - } - - if (create && await Operations.Any(q => q.Where(x => x.Flavor == model.Flavor))) - { - result.Errors.Add(new ValidationFailure("__nofield", "@integration.errors.multiplenotallowed")); - } - - if (!create && await Operations.Any(q => q.Where(x => x.Flavor == model.Flavor && x.Id != model.Id))) - { - result.Errors.Add(new ValidationFailure("__nofield", "@integration.errors.alreadycreated")); - } - - if (type != null) - { - ValidationResult innerResult = await Operations.Validate(model); - result.Errors.AddRange(innerResult.Errors); - } - } - - return result; - } - - - /// - protected virtual async Task> Save(Integration model, bool create = false) - { - return await Operations.Create(model, async x => await Validate(x, create)); - } -} - - -public interface IIntegrationStore -{ - /// - /// Get new instance of an integration by integration alias - /// - Task Empty(string alias); - - /// - /// Get an integration by integration alias - /// - Task Load(string alias); - - /// - /// Get all integrations by a certain tag - /// - Task> LoadByTag(string tag); - - /// - /// Check if there are any activated integrations for a certain tag - /// - Task Any(string tag); - - /// - /// Get the change vector for a model (Proxy to IAsyncDocumentSession.GetChangeVectorFor<>) - /// - string GetChangeToken(Integration model); - - /// - /// Creates a new integration - /// - Task> Create(Integration model); - - /// - /// Updates an integration - /// - Task> Update(Integration model); - - /// - /// Activates a configured integration - /// - Task> Activate(string alias); - - /// - /// Disables a configured integration - /// - Task> Deactivate(string alias); - - /// - /// Deletes configuration of an integration and disables it - /// - Task> Delete(string alias); -} \ No newline at end of file diff --git a/zero.Core/Configuration/Integrations/IntegrationType.cs b/zero.Core/Configuration/Integrations/IntegrationType.cs deleted file mode 100644 index eaddbac2..00000000 --- a/zero.Core/Configuration/Integrations/IntegrationType.cs +++ /dev/null @@ -1,34 +0,0 @@ -using FluentValidation; -using System.Text.Json.Serialization; - -namespace zero.Configuration; - -/// -/// An integration is an application part which has a public configuration per app. -/// It's up to the user to provide functionality. -/// -public class IntegrationType : FlavorConfig -{ - public IntegrationType(Type type) : base(type) { } - - /// - /// Alias to find the editor schema - /// - public string EditorAlias { get; set; } - - /// - /// Group integrations by tags - /// - public List Tags { get; set; } = new(); - - /// - /// Image of the integration - /// - public string ImagePath { get; set; } - - /// - /// Set a validator for this integration - /// - [JsonIgnore] - public IValidator Validator { get; set; } -} diff --git a/zero.Core/Configuration/Integrations/IntegrationTypeService.cs b/zero.Core/Configuration/Integrations/IntegrationTypeService.cs deleted file mode 100644 index bd073f47..00000000 --- a/zero.Core/Configuration/Integrations/IntegrationTypeService.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace zero.Configuration; - -public class IntegrationTypeService : IIntegrationTypeService -{ - protected IZeroContext Context { get; private set; } - - protected IHandlerHolder Handler { get; private set; } - - protected FlavorOptions Flavors { get; set; } - - - public IntegrationTypeService(IZeroContext context, IZeroOptions options, IHandlerHolder handler) - { - Context = context; - Handler = handler; - Flavors = options.For(); - } - - - /// - public IEnumerable GetAll() - { - return Flavors.GetAll().Select(x => (IntegrationType)x); - } - - - /// - public IntegrationType GetByAlias(string integrationTypeAlias) - { - return Flavors.Get(integrationTypeAlias) as IntegrationType; - } - - - /// - public IntegrationType GetByType() where T : Integration - { - return Flavors.Get() as IntegrationType; - } - - - /// - public IEnumerable GetByTag(string tag) - { - return GetAll().Where(x => x.Tags.Contains(tag, StringComparer.InvariantCultureIgnoreCase)); - } -} - - -public interface IIntegrationTypeService -{ - /// - /// Get all available integration types - /// - IEnumerable GetAll(); - - /// - /// Get a specific integration type by alias - /// - IntegrationType GetByAlias(string integrationTypeAlias); - - /// - /// Get a specific integration type by model type. - /// - IntegrationType GetByType() where T : Integration; - - /// - /// Get all integrations with a certain tag. - /// - IEnumerable GetByTag(string tag); -} diff --git a/zero.Core/Configuration/Integrations/IntegrationTypeWithStatus.cs b/zero.Core/Configuration/Integrations/IntegrationTypeWithStatus.cs deleted file mode 100644 index a95cbb20..00000000 --- a/zero.Core/Configuration/Integrations/IntegrationTypeWithStatus.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace zero.Configuration; - -public class IntegrationTypeWithStatus -{ - public IntegrationType Type { get; set; } - - public bool IsActive { get; set; } - - public bool IsConfigured { get; set; } - - public string Id { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Configuration/OptionsType.cs b/zero.Core/Configuration/OptionsType.cs deleted file mode 100644 index 44d73a4a..00000000 --- a/zero.Core/Configuration/OptionsType.cs +++ /dev/null @@ -1,26 +0,0 @@ -using FluentValidation; - -namespace zero.Configuration; - -public abstract class OptionsType -{ - /// - /// Type of the associated model class - /// - public Type ContentType { get; set; } - - /// - /// The alias - /// - public string Alias { get; set; } - - /// - /// The name of the options type - /// - public string Name { get; set; } - - /// - /// Set a validator for the editor - /// - public IValidator Validator { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Configuration/ZeroConfigurationModule.cs b/zero.Core/Configuration/ZeroConfigurationModule.cs deleted file mode 100644 index 10bee414..00000000 --- a/zero.Core/Configuration/ZeroConfigurationModule.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace zero.Configuration; - -internal class ZeroConfigurationModule : ZeroModule -{ - public override int Order => -1000; - - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddOptions().Configure((opts, svc) => - { - opts.ServiceProvider = svc; - opts.Version = "0.0.1-alpha.1"; - opts.ZeroPath = "/zero"; - opts.TokenExpiration = TimeSpan.FromHours(3); - }).Bind(configuration.GetSection("Zero")); - - services.AddTransient(factory => factory.GetService>().Value); - services.AddScoped(); - services.AddScoped(); - - services.AddOptions().Bind(configuration.GetSection("Zero:Features")); - - services.Configure(opts => - { - opts.Configure(cfg => - { - cfg.CanUseWithoutFlavors = false; - }); - }); - } -} \ No newline at end of file diff --git a/zero.Core/Configuration/ZeroOptions.cs b/zero.Core/Configuration/ZeroOptions.cs deleted file mode 100644 index e580e7e0..00000000 --- a/zero.Core/Configuration/ZeroOptions.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using System.Collections.Concurrent; - -namespace zero.Configuration; - -public class ZeroOptions : IZeroOptions -{ - /// - public bool Initialized => !String.IsNullOrEmpty(For()?.Database); - - /// - public string Version { get; set; } - - /// - public string ZeroPath { get; set; } - - /// - public TimeSpan TokenExpiration { get; set; } - - /// - public List Applications { get; set; } = new(); - - - internal IServiceProvider ServiceProvider { get; set; } - - protected ConcurrentDictionary OptionsCache { get; private set; } = new(); - - - /// - public TOptions For() where TOptions : class - { - Type type = typeof(TOptions); - - if (!OptionsCache.TryGetValue(type, out object value) && ServiceProvider != null) - { - IOptions options = ServiceProvider.GetService>(); - value = options.Value; - OptionsCache.TryAdd(type, value); - } - - return value as TOptions; - } -} - - -public interface IZeroOptions -{ - /// - /// Path to the backoffice. Defaults to /zero - /// - string ZeroPath { get; set; } - - /// - /// The currently active version - /// This should not be set manually, as it is used for setup and migrations and incremented automatically - /// - string Version { get; set; } - - /// - /// Whether this zero instance is initialized (setup is completed) - /// - bool Initialized { get; } - - /// - /// Expiration of a generated change token for an entity - /// - TimeSpan TokenExpiration { get; set; } - - /// - /// Contains all registered applications - /// - List Applications { get; set; } - - /// - /// Get typed options (proxy to IOptions) - /// - TOptions For() where TOptions : class; -} diff --git a/zero.Core/Configuration/ZeroStartupOptions.cs b/zero.Core/Configuration/ZeroStartupOptions.cs deleted file mode 100644 index cc11bcb8..00000000 --- a/zero.Core/Configuration/ZeroStartupOptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Configuration; - -public class ZeroStartupOptions : IZeroStartupOptions -{ - public IList AssemblyDiscoveryRules { get; private set; } = new List(); - - public IMvcBuilder Mvc { get; private set; } - - - public ZeroStartupOptions(IMvcBuilder mvc) - { - Mvc = mvc; - } -} - -public interface IZeroStartupOptions -{ - IList AssemblyDiscoveryRules { get; } - - IMvcBuilder Mvc { get; } -} \ No newline at end of file diff --git a/zero.Core/Context/ZeroContext.cs b/zero.Core/Context/ZeroContext.cs deleted file mode 100644 index 11fafa3c..00000000 --- a/zero.Core/Context/ZeroContext.cs +++ /dev/null @@ -1,327 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using System.Security.Claims; - -namespace zero.Context; - -public class ZeroContext : IZeroContext -{ - /// - public Application Application { get; protected set; } - - /// - public string AppId { get; protected set; } - - /// - public ClaimsPrincipal BackofficeUser { get; protected set; } - - /// - public bool IsBackofficeRequest { get; protected set; } - - /// - public bool IsLoggedIntoBackoffice { get; protected set; } - - /// - public IZeroOptions Options { get; protected set; } - - /// - public Route Route => ResolvedRoute?.Route; - - /// - public IRouteModel ResolvedRoute => HttpContextAccessor?.HttpContext?.Features.Get(); - - /// - public IZeroStore Store { get; private set; } - - /// - public IServiceProvider Services { get; private set; } - - /// - public ZeroContextScope Scope { get; private set; } - - /// - public bool IsPreview { get; private set; } - - - protected IApplicationResolver AppResolver { get; private set; } - - protected ICultureResolver CultureResolver { get; private set; } - - protected ILogger Logger { get; private set; } - - protected IHandlerHolder Handler { get; private set; } - - protected IHttpContextAccessor HttpContextAccessor { get; private set; } - - protected IPrimitiveTypeCollection ValueCollection { get; private set; } - - protected IPreviewService PreviewService { get; private set; } - - - bool _resolved = false; - - - public ZeroContext(IZeroOptions options, IHttpContextAccessor httpContextAccessor, IApplicationResolver appResolver, ICultureResolver cultureResolver, - ILogger logger, IZeroStore store, IHandlerHolder handler, IServiceProvider services, IPreviewService previewService) - { - Options = options; - AppResolver = appResolver; - CultureResolver = cultureResolver; - Logger = logger; - Store = store; - Handler = handler; - ValueCollection = new PrimitiveTypeCollection(); - HttpContextAccessor = httpContextAccessor; - Services = services; - PreviewService = previewService; - } - - - /// - public async virtual Task Resolve(HttpContext context) - { - if (_resolved) - { - return; - } - - if (context?.Request is null) - { - Store.ResolvedDatabase = null; - return; - } - - //if (!Options.SetupCompleted) - //{ - // return; - //} // TODO setup - - _resolved = true; - - // check if the current request is a backoffice request - IsBackofficeRequest = context.IsBackofficeRequest(Options.ZeroPath); - - // get the currently logged-in backoffice user - BackofficeUser = new ClaimsPrincipal(); - IsLoggedIntoBackoffice = false; - AuthenticateResult authResult = await context.AuthenticateAsync(Constants.Auth.BackofficeScheme); - if (authResult?.Principal is not null) - { - BackofficeUser = authResult.Principal; - IsLoggedIntoBackoffice = true; - } - - // check whether the current request is a preview request - IsPreview = await PreviewService.IsAccessGranted(context.Request, BackofficeUser); - - // resolve current application - Application = await AppResolver.Resolve(context, BackofficeUser); - AppId = Application.Id; - - Logger.LogDebug("Resolved {appId} ({uri})", AppId, context.Request.Host.ToString() + context.Request.Path.Value.EnsureStartsWith('/')); - - // set default database for document store - Store.ResolvedDatabase = Application.Database; - - // set current culture - await CultureResolver.Resolve(this); - - // set context scope - Scope = new(Store, Store.ResolvedDatabase, Application); - } - - - /// - public T Get() => ValueCollection.Get(); - - - /// - public void Set(T value) => ValueCollection.Set(value); - - - /// - public void Remove() => ValueCollection.Remove(); - - - /// - public void SetScope(Application app) - { - // TODO this is only used for console app as we do not have an HttpRequest there - // needs to be deleted and definitely replaced - - _resolved = true; - IsBackofficeRequest = false; - BackofficeUser = new ClaimsPrincipal(); - IsLoggedIntoBackoffice = false; - Application = app; - AppId = app.Id; - Store.ResolvedDatabase = Application.Database; - Scope = new(Store, Store.ResolvedDatabase, Application); - } - - - /// - public ZeroContextScope CreateScope(Application app) - { - ApplyScope(app.Database, app); - return new ZeroContextScope(Store, app.Database, app, Scope, scope => - { - Scope = scope.Previous; - ApplyScope(Scope.Database, Scope.Application); - }); - } - - - /// - public ZeroContextScope CreateScope(string database) - { - ApplyScope(database); - return new ZeroContextScope(Store, database, null, Scope, scope => - { - Scope = scope.Previous; - ApplyScope(Scope.Database, Scope.Application); - }); - } - - - /// - /// Apply a database scope - /// - void ApplyScope(string database, Application app = null) - { - Application = app; - AppId = app?.Id; - Store.ResolvedDatabase = database; - } -} - - - -public class ZeroContextScope : IDisposable -{ - public ZeroContextScope(IZeroStore store, string database, Application application, ZeroContextScope previous = null, Action onDispose = null) - { - Store = store; - Database = database; - Application = application; - Previous = previous; - _onDispose = onDispose; - } - - public IZeroStore Store { get; set; } - - public Application Application { get; private set; } - - public string Database { get; private set; } - - public ZeroContextScope Previous { get; private set; } - - Action _onDispose = null; - - public void Dispose() - { - _onDispose?.Invoke(this); - } -} - - - -public interface IZeroContext -{ - /// - /// Currently loaded application - /// - Application Application { get; } - - /// - /// Current loaded application Id - /// - string AppId { get; } - - /// - /// Resolved backoffice user principal - /// - ClaimsPrincipal BackofficeUser { get; } - - /// - /// Whether the current request is a backoffice request or not - /// - bool IsBackofficeRequest { get; } - - /// - /// Whether the user is logged into the backoffice - /// - bool IsLoggedIntoBackoffice { get; } - - /// - /// Determines whether the current request is a preview request - /// - bool IsPreview { get; } - - /// - /// Global zero options - /// - IZeroOptions Options { get; } - - /// - /// Document store - /// - IZeroStore Store { get; } - - /// - /// Service container - /// - IServiceProvider Services { get; } - - /// - /// Current context scope - /// - ZeroContextScope Scope { get; } - - /// - /// Matching (frontend) path route - /// - Route Route { get; } - - /// - /// Matching (frontend) resolved route - /// - IRouteModel ResolvedRoute { get; } - - /// - /// Resolves the current application (for backoffice + frontend requests) and - /// the currently active backoffice user, as users are not signed in with the default scheme and do therefore not populate HttpContext.User - /// - Task Resolve(HttpContext context); - - /// - /// Get a custom property from this scoped context - /// - T Get(); - - /// - /// Add a custom property to this scoped context - /// - void Set(T value); - - /// - /// Remove a custom property from this scoped context - /// - void Remove(); - - /// - /// Manually set a scope - /// - void SetScope(Application app); - - /// - /// Scope the current context to a specific application database - /// - ZeroContextScope CreateScope(Application app); - - /// - /// Scope the current context to a specific database - /// - ZeroContextScope CreateScope(string database); -} \ No newline at end of file diff --git a/zero.Core/Context/ZeroContextMiddleware.cs b/zero.Core/Context/ZeroContextMiddleware.cs deleted file mode 100644 index 3fd1938b..00000000 --- a/zero.Core/Context/ZeroContextMiddleware.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace zero.Context -{ - public class ZeroContextMiddleware - { - RequestDelegate Next; - - public ZeroContextMiddleware(RequestDelegate next) - { - Next = next; - } - - public async Task Invoke(HttpContext httpContext, IZeroContext zeroContext) - { - await zeroContext.Resolve(httpContext); - await Next(httpContext); - } - } -} diff --git a/zero.Core/Context/ZeroContextModule.cs b/zero.Core/Context/ZeroContextModule.cs deleted file mode 100644 index 63083b08..00000000 --- a/zero.Core/Context/ZeroContextModule.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Context; - -internal class ZeroContextModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - services.AddHttpContextAccessor(); - } -} \ No newline at end of file diff --git a/zero.Core/Extensions/CharExtensions.cs b/zero.Core/Extensions/CharExtensions.cs deleted file mode 100644 index 1560fc9b..00000000 --- a/zero.Core/Extensions/CharExtensions.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace zero.Extensions; - -public static class CharExtensions -{ - private static Dictionary accents { get; } = new Dictionary() - { - { 'ä', new char[2] { 'a', 'e' } }, - { 'á', new char[1] { 'a' } }, - { 'à', new char[1] { 'a' } }, - { 'ó', new char[1] { 'o' } }, - { 'ò', new char[1] { 'o' } }, - { 'é', new char[1] { 'e' } }, - { 'è', new char[1] { 'e' } }, - { 'ú', new char[1] { 'u' } }, - { 'ù', new char[1] { 'u' } }, - { 'í', new char[1] { 'i' } }, - { 'ì', new char[1] { 'i' } }, - { 'ö', new char[2] { 'o', 'e' } }, - { 'ü', new char[2] { 'u', 'e' } }, - { 'ß', new char[2] { 's', 's' } }, - { '&', new char[1] { '+' } } - }; - - - /// - /// Check if a character is from a-z, A-Z or 0-9 - /// - public static bool IsAZor09(this char value) - { - return (value >= 0x41 && value <= 0x5A) || (value >= 0x61 && value <= 0x7a) || (value >= 0x30 && value <= 0x39); - } - - - /// - /// Check if a character is in ASCII range - /// - public static bool IsASCII(this char value) - { - return value < 128; - } - - - /// - /// Replaces an accent or umlaut with the appropriate URL + file ready variant - /// - public static bool TryReplaceAccent(this char value, out char[] result) - { - if (!accents.ContainsKey(value)) - { - result = null; - return false; - } - - result = accents[value]; - return true; - } -} diff --git a/zero.Core/Extensions/ColorExtensions.cs b/zero.Core/Extensions/ColorExtensions.cs deleted file mode 100644 index 0d35f1d1..00000000 --- a/zero.Core/Extensions/ColorExtensions.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Drawing; - -namespace zero.Extensions; - -public static class ColorExtensions -{ - /// - /// Get color object from a hex string - /// - public static Color FromHex(this string color) - { - if (color.StartsWith("#")) - { - color = color[1..]; - } - - if (color.Length != 6) - { - throw new Exception("Hex color has to be in format #RRGGBB"); - } - - return Color.FromArgb( - int.Parse(color.Substring(0, 2), System.Globalization.NumberStyles.HexNumber), - int.Parse(color.Substring(2, 2), System.Globalization.NumberStyles.HexNumber), - int.Parse(color.Substring(4, 2), System.Globalization.NumberStyles.HexNumber) - ); - } - - - /// - /// Get contrast ratio between two color values - /// - /// - public static double GetContrastRatio(this Color color1, Color color2) - { - double lum1 = color1.GetLuminance(); - double lum2 = color2.GetLuminance(); - double brightest = Math.Max(lum1, lum2); - double darkest = Math.Min(lum1, lum2); - return (brightest + 0.05) / (darkest + 0.05); - } - - - /// - /// Get luminance value of a color based on WCAG-conform JS implementation - /// - /// - public static double GetLuminance(this Color color) - { - double[] a = new double[3] { color.R, color.G, color.B }.Select(v => - { - v /= 255; - return v <= 0.03928 ? v / 12.92 : Math.Pow((v + 0.055) / 1.055, 2.4); - }).ToArray(); - - return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; - } -} diff --git a/zero.Core/Extensions/DictionaryExtensions.cs b/zero.Core/Extensions/DictionaryExtensions.cs deleted file mode 100644 index 6575f3a8..00000000 --- a/zero.Core/Extensions/DictionaryExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace zero.Extensions; - -public static class DictionaryExtensions -{ - public static bool TryGetValue(this Dictionary model, string key, out T value) - { - if (!model.TryGetValue(key, out object valueObj) || !(valueObj is T)) - { - value = default; - return false; - } - - value = (T)valueObj; - return true; - } - - - public static T GetValueOrDefault(this Dictionary model, string key) - { - object value = model.GetValueOrDefault(key); - return value == default || !(value is T) ? default : (T)value; - } - - - public static Dictionary ToDistinctDictionary(this IEnumerable source, Func keySelector, Func elementSelector) - { - Dictionary result = new(); - - foreach (TSource sourceElement in source) - { - result.TryAdd(keySelector(sourceElement), elementSelector(sourceElement)); - } - - return result; - } -} diff --git a/zero.Core/Extensions/EnumerableExtensions.cs b/zero.Core/Extensions/EnumerableExtensions.cs deleted file mode 100644 index 9dfd9d9f..00000000 --- a/zero.Core/Extensions/EnumerableExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace zero.Extensions; - -public static class EnumerableExtensions -{ - public static IEnumerable Paging(this IEnumerable source, int pageNumber, int pageSize) - { - pageNumber = pageNumber.Limit(1, 10_000_000); - pageSize = pageSize.Limit(1, 1_000); - - if (pageNumber <= 0 || pageSize <= 0) - { - throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); - } - - return source.Skip((pageNumber - 1) * pageSize).Take(pageSize); - } - - - public static bool TryGet(this IEnumerable source, Func predicate, out T model) - { - model = source.FirstOrDefault(predicate); - return model != null; - } - - - public static bool TryAdd(this IList source, T model) where T : class - { - if (model == default) - { - return false; - } - - source.Add(model); - return true; - } -} \ No newline at end of file diff --git a/zero.Core/Extensions/ExpressionExtensions.cs b/zero.Core/Extensions/ExpressionExtensions.cs deleted file mode 100644 index 589fbc25..00000000 --- a/zero.Core/Extensions/ExpressionExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Linq.Expressions; -using System.Text; - -namespace zero.Extensions; - -public static class ExpressionExtensions -{ - internal static string GetPropertyPath(this Expression expr, out MemberExpression member) - { - StringBuilder path = new(); - MemberExpression memberExpression = GetMemberExpression(expr); - do - { - member = memberExpression; - - if (path.Length > 0) - { - path.Insert(0, "."); - } - path.Insert(0, memberExpression.Member.Name); - memberExpression = GetMemberExpression(memberExpression.Expression); - } - while (memberExpression != null); - - return path.ToString(); - } - - internal static MemberExpression GetMemberExpression(this Expression expression) - { - if (expression is MemberExpression) - { - return (MemberExpression)expression; - } - else if (expression is LambdaExpression) - { - var lambdaExpression = expression as LambdaExpression; - if (lambdaExpression.Body is MemberExpression) - { - return (MemberExpression)lambdaExpression.Body; - } - else if (lambdaExpression.Body is UnaryExpression) - { - return ((MemberExpression)((UnaryExpression)lambdaExpression.Body).Operand); - } - } - return null; - } -} diff --git a/zero.Core/Extensions/HttpContextExtensions.cs b/zero.Core/Extensions/HttpContextExtensions.cs deleted file mode 100644 index 5eed83cf..00000000 --- a/zero.Core/Extensions/HttpContextExtensions.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace zero.Extensions; - -public static class HttpContextExtensions -{ - /// - /// Whether the current request is a backoffice request - /// - public static bool IsBackofficeRequest(this HttpContext context, string backofficePath) - { - string path = backofficePath.EnsureStartsWith('/').TrimEnd('/'); - return context.Request.Path.ToString().StartsWith(path); - } - - - /// - /// Whether the current request is an AJAX request - /// - public static bool IsAjaxRequest(this HttpContext context) - { - if (context?.Request?.Headers == null) - { - return false; - } - - return context.Request.Headers["X-Requested-With"] == "XMLHttpRequest"; - } - - - /// - /// Whether the current request only accepts application/json - /// - public static bool IsJsonRequest(this HttpContext context) - { - if (context?.Request?.Headers == null) - { - return false; - } - - return context.Request.Headers["Accept"] == "application/json"; - } - - - /// - /// Get IP Address of the client - /// - public static string GetClientIpAddress(this HttpContext context) - { - return context.Connection.RemoteIpAddress?.ToString(); - //string ipAddress = context.GetServerVariable("HTTP_X_FORWARDED_FOR"); - //return !ipAddress.IsNullOrEmpty() ? ipAddress.Split(',')[0] : context.GetServerVariable("REMOTE_ADDR"); - } -} diff --git a/zero.Core/Extensions/NumberExtensions.cs b/zero.Core/Extensions/NumberExtensions.cs deleted file mode 100644 index 26f13f4f..00000000 --- a/zero.Core/Extensions/NumberExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace zero.Extensions; - -public static class NumberExtensions -{ - static Dictionary FileSizeUnits = new() - { - { FileSizeNotation.SI, new string[] { "B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" } }, - { FileSizeNotation.IEC, new string[] { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB" } }, - { FileSizeNotation.JEDEC, new string[] { "B", "KB", "MB", "GB" } } - }; - - public static int Limit(this int input, int min, int max) - { - if (input < min) - { - return min; - } - if (input > max) - { - return max; - } - return input; - } - - - public static string GetFileSize(this long sizeInBytes, FileSizeNotation notation = FileSizeNotation.JEDEC) - { - if (!FileSizeUnits.ContainsKey(notation)) - { - throw new NotImplementedException($"The notation {notation} has no implementation for generating human-readable file sizes"); - } - - int power = notation == FileSizeNotation.SI ? 1000 : 1024; - - string[] units = FileSizeUnits[notation]; - - int order = 0; - - while (sizeInBytes >= power && order + 1 < units.Length) - { - order++; - sizeInBytes = sizeInBytes / power; - } - - return String.Format("{0:0.##} {1}", sizeInBytes, units[order]); - } -} diff --git a/zero.Core/Extensions/ObjectExtensions.cs b/zero.Core/Extensions/ObjectExtensions.cs deleted file mode 100644 index 4f10bbf0..00000000 --- a/zero.Core/Extensions/ObjectExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -//using Newtonsoft.Json; - -//namespace zero.Extensions; - -//[Obsolete("we don't want this for every object (use a Utils class instead)")] -//public static class ObjectExtensions -//{ -// public static bool Is(this Type type) -// { -// return type.IsAssignableFrom(typeof(T)); -// } - - -// public static bool Is(this object obj) -// { -// return obj.GetType().IsAssignableFrom(typeof(T)); -// } - - -// public static T Clone(this T obj) -// { -// Type type = obj.GetType(); -// return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj), type); -// } - -// public static T CloneLax(this object obj) -// { -// return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); -// } -//} diff --git a/zero.Core/Extensions/ResultExtensions.cs b/zero.Core/Extensions/ResultExtensions.cs deleted file mode 100644 index d726b0ab..00000000 --- a/zero.Core/Extensions/ResultExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace zero.Extensions; - -public static class ResultExtensions -{ - public static Result WithoutModel(this Result origin) - { - Result result = new(); - result.IsSuccess = origin.IsSuccess; - result.Errors = origin.Errors; - return result; - } - - public static Result ConvertTo(this Result origin, TTarget model) - { - Result result = new(); - result.IsSuccess = origin.IsSuccess; - result.Errors = origin.Errors; - result.Model = model; - return result; - } - - - public static Result AddError(this Result origin, string property, string message) - { - origin.IsSuccess = false; - origin.Errors.Add(new(property, message)); - return origin; - } - - - public static Result AddError(this Result origin, string message) - { - origin.IsSuccess = false; - origin.Errors.Add(new(Constants.ErrorFieldNone, message)); - return origin; - } - - - public static Result AppendErrors(this Result origin, Result with) - { - foreach (ResultError error in with.Errors) - { - origin.Errors.Add(error); - } - origin.IsSuccess = origin.Errors.Any(); - return origin; - } -} \ No newline at end of file diff --git a/zero.Core/Extensions/ServiceCollectionExtensions.cs b/zero.Core/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index 9d7e0a5c..00000000 --- a/zero.Core/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using System.Reflection; - -namespace zero.Extensions; - -public static class ServiceCollectionExtensions -{ - /// - /// Adds all found implementations based on the service type and assembly discovery rules - /// - public static void AddAll(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Transient, bool asSelf = false) => services.AddAll(typeof(TService), lifetime, asSelf); - - - /// - /// Adds all found implementations based on the service type and assembly discovery rules - /// - public static void AddAll(this IServiceCollection services, Type serviceType, ServiceLifetime lifetime = ServiceLifetime.Transient, bool asSelf = false) - { - if (AssemblyDiscovery.Current == null) - { - throw new Exception("services.AddAll() can only be run after mvcBuilder.AddZero()"); - } - - // add implementations with generic service types - if (serviceType.GetTypeInfo().IsGenericTypeDefinition) - { - IEnumerable<(Type, TypeInfo)> matches = AssemblyDiscovery.Current.GetAllClassTypes().SelectMany(type => - { - var interfaces = type.GetInterfaces().Select(x => x.GetTypeInfo()); - IEnumerable genericTypes = interfaces.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == serviceType); - if (genericTypes.Any()) - { - var genericTypeDefinitions = genericTypes.First(); - } - return genericTypes.Select(x => (x, type)); - }); - - foreach ((Type, Type) match in matches) - { - services.Add(new ServiceDescriptor(asSelf ? match.Item2 : match.Item1, match.Item2, lifetime)); - } - } - // add implementations with specific service types - else - { - foreach (Type type in AssemblyDiscovery.Current.GetTypes(serviceType)) - { - services.Add(new ServiceDescriptor(asSelf ? type : serviceType, type, lifetime)); - } - } - } - - - /// - /// Adds or overrides an implementation - /// - public static void Replace(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Transient) - where TService : class - where TImplementation : class, TService - { - services.RemoveAll(); - services.Add(new ServiceDescriptor(typeof(TService), typeof(TImplementation), lifetime)); - } - - - /// - /// Adds or overrides an implementation - /// - public static void Replace(this IServiceCollection services, Func implementationFactory, ServiceLifetime lifetime = ServiceLifetime.Transient) - where TService : class - where TImplementation : class, TService - { - services.RemoveAll(); - services.Add(new ServiceDescriptor(typeof(TService), implementationFactory, lifetime)); - } - - - /// - /// Adds or overrides an implementation - /// - public static void Replace(this IServiceCollection services) - where TService : class - where TOldImplementation : class, TService - where TNewImplementation : class, TService - { - ServiceDescriptor oldDescriptor = services.FirstOrDefault(x => x.ImplementationType == typeof(TOldImplementation)); - if (oldDescriptor != null) - { - services.Remove(oldDescriptor); - services.Add(new ServiceDescriptor(typeof(TService), typeof(TNewImplementation), oldDescriptor.Lifetime)); - } - } - - - /// - /// Removes an implementation - /// - public static void Remove(this IServiceCollection services) - where TService : class - where TImplementation : class, TService - { - ServiceDescriptor oldDescriptor = services.FirstOrDefault(x => x.ImplementationType == typeof(TImplementation)); - if (oldDescriptor != null) - { - services.Remove(oldDescriptor); - } - } -} diff --git a/zero.Core/Extensions/StringExtensions.cs b/zero.Core/Extensions/StringExtensions.cs deleted file mode 100644 index 6c3b8da7..00000000 --- a/zero.Core/Extensions/StringExtensions.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System.Globalization; -using System.Text.RegularExpressions; - -namespace zero.Extensions; - -public static class StringExtensions -{ - const string SPACE = " "; - - static Regex replaceMultipleSpacesRegex { get; } = new Regex("[ ]{2,}", RegexOptions.None); - - static Regex newLineCharsRegex { get; } = new Regex("\t|\n|\r", RegexOptions.None); - - - public static string FullTrim(this string value) - { - if (String.IsNullOrEmpty(value)) - { - return value; - } - - return replaceMultipleSpacesRegex.Replace(value, SPACE).Trim(); - } - - - public static string EnsureStartsWith(this string input, string toStartWith) - { - if (input.StartsWith(toStartWith)) return input; - return toStartWith + input.TrimStart(toStartWith); - } - - - public static string EnsureStartsWith(this string input, char value) - { - return input.StartsWith(value.ToString(CultureInfo.InvariantCulture)) ? input : value + input; - } - - - public static string EnsureEndsWith(this string input, char value) - { - return input.EndsWith(value.ToString(CultureInfo.InvariantCulture)) ? input : input + value; - } - - - public static string EnsureEndsWith(this string input, string toEndWith) - { - return input.EndsWith(toEndWith.ToString(CultureInfo.InvariantCulture)) ? input : input + toEndWith; - } - - - public static string EnsureSurroundedWith(this string input, char toSurroundWith) - { - return input.EnsureStartsWith(toSurroundWith).EnsureEndsWith(toSurroundWith); - } - - - public static string Or(this string value, string fallback) - { - return String.IsNullOrWhiteSpace(value) ? fallback : value; - } - - - public static string TrimEnd(this string value, string forRemoving) - { - if (String.IsNullOrEmpty(value)) return value; - if (String.IsNullOrEmpty(forRemoving)) return value; - - while (value.EndsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) - { - value = value.Remove(value.LastIndexOf(forRemoving, StringComparison.InvariantCultureIgnoreCase)); - } - return value; - } - - - public static string TrimStart(this string value, string forRemoving) - { - if (String.IsNullOrEmpty(value)) return value; - if (String.IsNullOrEmpty(forRemoving)) return value; - - while (value.StartsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) - { - value = value.Substring(forRemoving.Length); - } - return value; - } - - - public static bool HasValue(this string input) - { - return !String.IsNullOrWhiteSpace(input); - } - - - public static bool IsNullOrEmpty(this string input) - { - return String.IsNullOrEmpty(input); - } - - - public static bool IsNullOrWhiteSpace(this string input) - { - return String.IsNullOrWhiteSpace(input); - } - - public static string ToCamelCase(this string input) - { - if (!String.IsNullOrEmpty(input) && input.Length > 1) - { - return Char.ToLowerInvariant(input[0]) + input.Substring(1); - } - return input; - } - - public static string ToPascalCase(this string input) - { - if (!String.IsNullOrEmpty(input) && input.Length > 1) - { - return Char.ToUpperInvariant(input[0]) + input.Substring(1); - } - return input; - } - - public static string ToCamelCaseId(this string input) - { - if (String.IsNullOrEmpty(input)) - { - return input; - } - - if (input.Length < 2) - { - return input.ToLowerInvariant(); - } - - string[] parts = input.Split('.'); - - return String.Join(".", parts.Select(x => x.ToCamelCase())); - } - - - public static string ToPascalCaseId(this string input) - { - if (String.IsNullOrEmpty(input)) - { - return input; - } - - if (input.Length < 2) - { - return input.ToUpperInvariant(); - } - - string[] parts = input.Split('.'); - - return String.Join(".", parts.Select(x => x.ToPascalCase())); - } - - - public static string RemoveNewLines(this string input) - { - if (String.IsNullOrEmpty(input)) - { - return input; - } - - return newLineCharsRegex.Replace(input, String.Empty).Trim(); - } - - - public static string Shorten(this string input, int maxLength) - { - if (maxLength < 4) - { - throw new ArgumentOutOfRangeException("maxLength", "Has to be at least 4 characters long"); - } - - if (String.IsNullOrEmpty(input) || input.Length <= maxLength) - { - return input; - } - - return input.Substring(0, maxLength - 3) + "..."; - } - - - public static string ReplaceFirst(this string text, string search, string replace) - { - int pos = text.IndexOf(search); - if (pos < 0) - { - return text; - } - return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); - } -} diff --git a/zero.Core/FileStorage/FileResult.cs b/zero.Core/FileStorage/FileResult.cs deleted file mode 100644 index d4bcc98b..00000000 --- a/zero.Core/FileStorage/FileResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace zero.FileStorage; - -public class FileResult -{ - public string Filename { get; set; } - - public string Path { get; set; } - - public string ContentType { get; set; } -} diff --git a/zero.Core/FileStorage/FileSizeNotation.cs b/zero.Core/FileStorage/FileSizeNotation.cs deleted file mode 100644 index d85a36e0..00000000 --- a/zero.Core/FileStorage/FileSizeNotation.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace zero.FileStorage; - -public enum FileSizeNotation -{ - /// - /// Multiply by 1.000, as specified by the International System of Units (e.g. kB, MB, GB) - /// - SI = 0, - /// - /// Binary notation with a multiplier of 1.024 (e.g. KiB, MiB, GiB) - /// - IEC = 1, - /// - /// Binary notation with a multiplier of 1.024 (e.g. KB, MB, GB). - /// Ends with GB, there is no TB, ... - /// - JEDEC = 2 -} diff --git a/zero.Core/FileStorage/FileSystemException.cs b/zero.Core/FileStorage/FileSystemException.cs deleted file mode 100644 index fc9d39b0..00000000 --- a/zero.Core/FileStorage/FileSystemException.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace zero.FileStorage; - -public class FileSystemException : Exception -{ - public FileSystemException() { } - - public FileSystemException(string message) : base(message) { } - - public FileSystemException(string message, Exception innerException) : base(message, innerException) { } -} \ No newline at end of file diff --git a/zero.Core/FileStorage/FileSystemOptions.cs b/zero.Core/FileStorage/FileSystemOptions.cs deleted file mode 100644 index 97c25f95..00000000 --- a/zero.Core/FileStorage/FileSystemOptions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace zero.FileStorage; - -public class FileSystemOptions -{ - public string ZeroAssetsPath { get; set; } -} \ No newline at end of file diff --git a/zero.Core/FileStorage/IFileMeta.cs b/zero.Core/FileStorage/IFileMeta.cs deleted file mode 100644 index cd6b49dd..00000000 --- a/zero.Core/FileStorage/IFileMeta.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace zero.FileStorage; - -public interface IFileMeta -{ - /// - /// Full path of the item within the file system - /// - string Path { get; } - - /// - /// Absolute path of the item - /// - string AbsolutePath { get; } - - /// - /// Name of the item - /// - string Name { get; } - - /// - /// Full path to the directory containing the item - /// - string DirectoryPath { get; } - - /// - /// Length of the file in bytes - /// - long Length { get; } - - /// - /// Date when the item was last modified - /// - DateTimeOffset LastModifiedDate { get; } - - /// - /// Whether this item is directory or a file - /// - bool IsDirectory { get; } - - /// - /// Additional properties - /// - Dictionary Properties { get; } -} diff --git a/zero.Core/FileStorage/IFileSystem.cs b/zero.Core/FileStorage/IFileSystem.cs deleted file mode 100644 index bd8c5238..00000000 --- a/zero.Core/FileStorage/IFileSystem.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.IO; - -namespace zero.FileStorage; - -public interface IFileSystem -{ - /// - /// Map a relative path to an absolute internal path - /// - string Map(string path); - - /// - /// Map an internal path to a public accessible path - /// - string MapToPublicPath(string path); - - /// - /// Determines whether a file or directory at the given path already exists - /// - Task Exists(string path, CancellationToken cancellationToken = default); - - /// - /// Get information of a file - /// - Task GetFileInfo(string path, CancellationToken cancellationToken = default); - - /// - /// Returns a readable stream for a file - /// - Task StreamFile(string path, CancellationToken cancellationToken = default); - - /// - /// Creates a file at the given path - /// - Task CreateFile(string path, Stream fileStream, bool overwrite = false, CancellationToken cancellationToken = default); - - /// - /// Deletes a file at the given path. This method returns if the file does not exist. - /// - Task DeleteFile(string path, CancellationToken cancellationToken = default); - - /// - /// Moves a file to another path (can also be used to rename resources) - /// - Task MoveFile(string oldPath, string newPath, CancellationToken cancellationToken = default); - - /// - /// Copies a file to another path - /// - Task CopyFile(string oldPath, string newPath, CancellationToken cancellationToken = default); - - /// - /// Get information of a directory - /// - Task GetDirectoryInfo(string path, CancellationToken cancellationToken = default); - - /// - /// Get all items within a directory - /// - IAsyncEnumerable GetDirectoryContent(string path = null, bool recursive = false, CancellationToken cancellationToken = default); - - /// - /// Tries to create a directory at the given path. This method returns if the directory already exists. - /// - Task CreateDirectory(string path, CancellationToken cancellationToken = default); - - /// - /// Deletes a directory at the given path. This method returns if the directory does not exist. - /// - Task DeleteDirectory(string path, bool recursive = false, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/zero.Core/FileStorage/Paths.cs b/zero.Core/FileStorage/Paths.cs deleted file mode 100644 index 602ab9de..00000000 --- a/zero.Core/FileStorage/Paths.cs +++ /dev/null @@ -1,217 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.StaticFiles; -using System.IO; -using System.Text; - -namespace zero.FileStorage; - -public class Paths : IPaths -{ - public string WebRoot { get; set; } - - public string ContentRoot { get; set; } - - public string SecureRoot { get; set; } - - public string Media { get; set; } - - protected IWebHostEnvironment Env { get; set; } - - public const string MEDIA_FOLDER = "Uploads"; - - public const char PATH_SEPARATOR = '/'; - - static char[] InvalidFilenameChars = null; - - const char REPLACEMENT_CHAR = '-'; - - FileExtensionContentTypeProvider FileExtensionContentTypeProvider { get; set; } - - static readonly Dictionary Replacements = new() - { - { 'ä', "ae" }, - { 'ü', "ue" }, - { 'ö', "oe" }, - { 'ß', "ss" } - }; - - - public Paths(IWebHostEnvironment env) - { - Env = env; - WebRoot = env.WebRootPath; - ContentRoot = env.ContentRootPath; - SecureRoot = Path.Combine(ContentRoot, "wwwroot.secure"); - Media = Path.Combine(WebRoot, MEDIA_FOLDER); - FileExtensionContentTypeProvider = new(); - } - - - /// - /// Combine a path - /// - public string Combine(params string[] paths) - { - return Path.Combine(paths); - } - - - /// - /// Map a path - /// - public string Map(string path) - { - return Path.Combine(WebRoot, path); - } - - - /// - /// Map a secure path - /// - public string MapSecure(string path) - { - return Path.Combine(SecureRoot, path); - } - - - /// - /// Map a path - /// - public string Map(params string[] paths) - { - return Path.Combine(WebRoot, Path.Combine(paths)); - } - - - /// - /// Map a secure path - /// - public string MapSecure(params string[] paths) - { - return Path.Combine(SecureRoot, Path.Combine(paths)); - } - - - /// - /// Create a directory if it does not exist yet - /// - public void Create(string directory) - { - if (!Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - } - - - /// - /// Get content type for a filename - /// - public string GetContentType(string filename, string fallback = "application/octet-stream") - { - if (filename == null || !FileExtensionContentTypeProvider.TryGetContentType(Path.GetFileName(filename), out string contentType)) - { - contentType = fallback; - } - return contentType; - } - - - /// - /// Normalizes a filename and removes invalid chars - /// - public string ToFilename(string value) - { - if (String.IsNullOrWhiteSpace(value)) - { - return value; - } - - // lowercase for string - value = value.ToLower(); - - StringBuilder sb = new(value.Length); - - var invalids = InvalidFilenameChars ??= Path.GetInvalidFileNameChars(); - bool changed = false; - - for (int i = 0; i < value.Length; i++) - { - char c = value[i]; - - if (Replacements.ContainsKey(c)) - { - changed = true; - sb.Append(Replacements[c]); - } - else if (invalids.Contains(c)) - { - changed = true; - sb.Append(REPLACEMENT_CHAR); - } - else - { - sb.Append(c); - } - } - - if (sb.Length == 0) - { - return "_"; - } - - return changed ? sb.ToString() : value; - } -} - - -public interface IPaths -{ - string ContentRoot { get; set; } - - string WebRoot { get; set; } - - string SecureRoot { get; set; } - - string Media { get; set; } - - /// - /// Combine a path - /// - string Combine(params string[] paths); - - /// - /// Map a path - /// - string Map(string path); - - /// - /// Map a secure path - /// - string MapSecure(string path); - - /// - /// Map a path - /// - string Map(params string[] paths); - - /// - /// Map a secure path - /// - string MapSecure(params string[] paths); - - /// - /// Create a directory if it does not exist yet - /// - void Create(string directory); - - /// - /// Get content type for a filename - /// - string GetContentType(string filename, string fallback = "application/octet-stream"); - - /// - /// Normalizes a filename and removes invalid chars - /// - string ToFilename(string value); -} \ No newline at end of file diff --git a/zero.Core/FileStorage/PhysicalFileMeta.cs b/zero.Core/FileStorage/PhysicalFileMeta.cs deleted file mode 100644 index 6f89c6dc..00000000 --- a/zero.Core/FileStorage/PhysicalFileMeta.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.Extensions.FileProviders; - -namespace zero.FileStorage; - -public class PhysicalFileMeta : IFileMeta -{ - /// - public string Name { get; } - - /// - public string Path { get; } - - /// - public string AbsolutePath { get; } - - /// - public string DirectoryPath { get; } - - /// - public long Length { get; } - - /// - public DateTimeOffset LastModifiedDate { get; } - - /// - public bool IsDirectory { get; } - - /// - public Dictionary Properties { get; } - - - public PhysicalFileMeta(IFileInfo fileInfo, string path) - { - Path = path; - AbsolutePath = fileInfo.PhysicalPath; - Name = fileInfo.Name; - DirectoryPath = path.Substring(0, path.Length - fileInfo.Name.Length).TrimEnd('/'); - LastModifiedDate = fileInfo.LastModified; - Length = fileInfo.Length; - IsDirectory = fileInfo.IsDirectory; - Properties = new(); - } -} \ No newline at end of file diff --git a/zero.Core/FileStorage/PhysicalFileSystem.cs b/zero.Core/FileStorage/PhysicalFileSystem.cs deleted file mode 100644 index c5b3cf33..00000000 --- a/zero.Core/FileStorage/PhysicalFileSystem.cs +++ /dev/null @@ -1,305 +0,0 @@ -using Microsoft.Extensions.FileProviders.Physical; -using System.IO; - -namespace zero.FileStorage; - -public class PhysicalFileSystem : IFileSystem -{ - readonly string _root; - - - public PhysicalFileSystem(string root) - { - _root = root; - } - - - /// - public virtual string Map(string path) - { - return ResolvePath(path); - } - - - /// - public virtual string MapToPublicPath(string path) - { - return path.EnsureStartsWith('/'); - } - - - /// - public virtual Task Exists(string path, CancellationToken cancellationToken = default) - { - try - { - string resolvedPath = ResolvePath(path); - return Task.FromResult(File.Exists(resolvedPath) || Directory.Exists(resolvedPath)); - } - catch (Exception ex) when (ex is not FileSystemException) - { - throw new FileSystemException($"Could not get file exists info for path '{path}'", ex); - } - } - - - /// - public virtual Task GetFileInfo(string path, CancellationToken cancellationToken = default) - { - try - { - string resolvedPath = ResolvePath(path); - - PhysicalFileInfo fileInfo = new(new FileInfo(resolvedPath)); - - if (!fileInfo.Exists) - { - return Task.FromResult(default); - } - - return Task.FromResult(new PhysicalFileMeta(fileInfo, path)); - } - catch (Exception ex) when (ex is not FileSystemException) - { - throw new FileSystemException($"Could not get file info for path '{path}'", ex); - } - } - - - /// - public virtual Task StreamFile(string path, CancellationToken cancellationToken = default) - { - try - { - string resolvedPath = ResolvePath(path); - - if (resolvedPath.IsNullOrEmpty() || !File.Exists(resolvedPath)) - { - throw new FileSystemException($"The path '{path}' does not exist in the file system"); - } - - return Task.FromResult(File.OpenRead(resolvedPath)); - } - catch (Exception ex) when (ex is not FileSystemException) - { - throw new FileSystemException($"Could not get file stream for path '{path}'", ex); - } - } - - - /// - public virtual async Task CreateFile(string path, Stream fileStream, bool overwrite = false, CancellationToken cancellationToken = default) - { - try - { - string resolvedPath = ResolvePath(path); - - if ((!overwrite && File.Exists(resolvedPath)) || Directory.Exists(resolvedPath)) - { - throw new FileSystemException($"A file/directory at the path '{path}' already existsin the file system"); - } - - string directoryPath = Path.GetDirectoryName(resolvedPath); - Directory.CreateDirectory(directoryPath); - - FileInfo fileInfo = new(resolvedPath); - using var outputStream = fileInfo.Create(); - await fileStream.CopyToAsync(outputStream, cancellationToken); - } - catch (Exception ex) when (ex is not FileSystemException) - { - throw new FileSystemException($"Could not get file stream for path '{path}'", ex); - } - } - - - /// - public virtual Task DeleteFile(string path, CancellationToken cancellationToken = default) - { - try - { - string resolvedPath = ResolvePath(path); - File.Delete(resolvedPath); - return Task.CompletedTask; - } - catch (Exception ex) when (ex is not FileSystemException) - { - throw new FileSystemException($"Could not delete file for path '{path}'", ex); - } - } - - - /// - public virtual Task MoveFile(string oldPath, string newPath, CancellationToken cancellationToken = default) - { - try - { - string resolvedOldPath = ResolvePath(oldPath); - string resolvedNewPath = ResolvePath(newPath); - - if (!File.Exists(resolvedOldPath)) - { - throw new FileSystemException($"The path '{oldPath}' does not exist in the file system"); - } - - if (File.Exists(resolvedNewPath) || Directory.Exists(resolvedNewPath)) - { - throw new FileSystemException($"A file/directory at the path '{newPath}' already exists in the file system"); - } - - File.Move(resolvedOldPath, resolvedNewPath); - return Task.CompletedTask; - } - catch (Exception ex) when (ex is not FileSystemException) - { - throw new FileSystemException($"Could not move file from path '{oldPath}' to '{newPath}'", ex); - } - } - - - /// - public virtual Task CopyFile(string oldPath, string newPath, CancellationToken cancellationToken = default) - { - try - { - string resolvedOldPath = ResolvePath(oldPath); - string resolvedNewPath = ResolvePath(newPath); - - if (!File.Exists(resolvedOldPath)) - { - throw new FileSystemException($"The path '{oldPath}' does not exist in the file system"); - } - - if (File.Exists(resolvedNewPath) || Directory.Exists(resolvedNewPath)) - { - throw new FileSystemException($"A file/directory at the path '{newPath}' already exists in the file system"); - } - - File.Copy(resolvedOldPath, resolvedNewPath); - return Task.CompletedTask; - } - catch (Exception ex) when (ex is not FileSystemException) - { - throw new FileSystemException($"Could not copy file from path '{oldPath}' to '{newPath}'", ex); - } - } - - - /// - public virtual Task GetDirectoryInfo(string path, CancellationToken cancellationToken = default) => GetFileInfo(path, cancellationToken); - - - /// - public virtual IAsyncEnumerable GetDirectoryContent(string path = null, bool recursive = false, CancellationToken cancellationToken = default) - { - try - { - string resolvedPath = ResolvePath(path); - List results = new(); - SearchOption searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; - - if (!Directory.Exists(resolvedPath)) - { - return results.ToAsyncEnumerable(); - } - - results.AddRange(Directory.GetDirectories(resolvedPath, "*", searchOption).Select(f => - { - PhysicalDirectoryInfo fileSystemInfo = new(new DirectoryInfo(f)); - return new PhysicalFileMeta(fileSystemInfo, ResolvePath(f.Substring(_root.Length))); - })); - - results.AddRange(Directory.GetFiles(resolvedPath, "*", searchOption).Select(f => - { - PhysicalFileInfo fileSystemInfo = new(new FileInfo(f)); - return new PhysicalFileMeta(fileSystemInfo, ResolvePath(f.Substring(_root.Length))); - })); - - return results.ToAsyncEnumerable(); - } - catch (Exception ex) when (ex is not FileSystemException) - { - throw new FileSystemException($"Could not get directory content for path '{path}'", ex); - } - } - - - /// - public virtual Task CreateDirectory(string path, CancellationToken cancellationToken = default) - { - try - { - string resolvedPath = ResolvePath(path); - - if (File.Exists(resolvedPath)) - { - throw new FileSystemException($"A file at the path '{path}' already exists in the file system"); - } - - if (!Directory.Exists(resolvedPath)) - { - Directory.CreateDirectory(resolvedPath); - } - - return Task.CompletedTask; - } - catch (Exception ex) when (ex is not FileSystemException) - { - throw new FileSystemException($"Could not create directory at path '{path}'", ex); - } - } - - - /// - public virtual Task DeleteDirectory(string path, bool recursive = false, CancellationToken cancellationToken = default) - { - try - { - string resolvedPath = ResolvePath(path); - Directory.Delete(resolvedPath, recursive); - return Task.CompletedTask; - } - catch (Exception ex) when (ex is not FileSystemException) - { - throw new FileSystemException($"Could not delete directory at path '{path}'", ex); - } - } - - - /// - /// Creates a fully-usable absolte path from the given path - /// - protected virtual string ResolvePath(string path) - { - try - { - if (path.IsNullOrWhiteSpace()) - { - return _root; - } - - // normalize path - path = path.Replace('\\', '/').FullTrim().Trim('/'); - - string physicalPath = Path.Combine(_root, path); - string rootPath = Path.GetFullPath(_root); - - // do not allow paths which are outside this file system - // the boundaries are set be the basePath which is assigned in the file system constructor - if (!Path.GetFullPath(physicalPath).StartsWith(rootPath, StringComparison.InvariantCultureIgnoreCase)) - { - throw new FileSystemException($"The path '{path}' is outside the file system"); - } - - return physicalPath; - } - catch (FileSystemException) - { - throw; - } - catch (Exception ex) - { - throw new FileSystemException($"Could not resolve path '{path}'", ex); - } - } -} diff --git a/zero.Core/FileStorage/WebRootFileSystem.cs b/zero.Core/FileStorage/WebRootFileSystem.cs deleted file mode 100644 index d1c64cf6..00000000 --- a/zero.Core/FileStorage/WebRootFileSystem.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace zero.Media; - -public class WebRootFileSystem : PhysicalFileSystem, IWebRootFileSystem -{ - protected string PublicPathPrefix { get; } - - - public WebRootFileSystem(string root, string publicPathPrefix) : base(root) - { - PublicPathPrefix = (publicPathPrefix ?? String.Empty).EnsureEndsWith('/'); - } - - - /// - public override string MapToPublicPath(string path) - { - if (path.IsNullOrEmpty()) - { - return null; - } - return PublicPathPrefix + path.TrimStart('/'); - } -} - - -public interface IWebRootFileSystem : IFileSystem -{ -} \ No newline at end of file diff --git a/zero.Core/FileStorage/ZeroFileStorageModule.cs b/zero.Core/FileStorage/ZeroFileStorageModule.cs deleted file mode 100644 index f195cdc0..00000000 --- a/zero.Core/FileStorage/ZeroFileStorageModule.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace zero.FileStorage; - -internal class ZeroFileStorageModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(factory => new Paths(factory.GetService())); - - services.AddOptions().Bind(configuration.GetSection("Zero:FileSystem")).Configure(opts => - { - opts.ZeroAssetsPath = "zero"; - }); - - services.AddSingleton(svc => - { - IWebHostEnvironment env = svc.GetRequiredService(); - return new(env.WebRootPath, null); - }); - } -} \ No newline at end of file diff --git a/zero.Core/Identity/AuthenticationBuilderExtensions.cs b/zero.Core/Identity/AuthenticationBuilderExtensions.cs deleted file mode 100644 index d30f48fc..00000000 --- a/zero.Core/Identity/AuthenticationBuilderExtensions.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace zero.Identity; - -public static class AuthenticationBuilderExtensions -{ - public static AuthenticationBuilder AddZeroBackofficeCookie(this AuthenticationBuilder builder, Action> setupAction = null) - where TUser : ZeroUser - where TRole : ZeroUserRole - { - return builder.AddCookie(Constants.Auth.BackofficeScheme, Constants.Auth.BackofficeDisplayName, true, b => - { - b.Configure((options, zero) => - { - options.ExpireTimeSpan = TimeSpan.FromMinutes(90); - options.Cookie.SameSite = SameSiteMode.Lax; - options.Cookie.Name = Constants.Auth.BackofficeCookieName; - - options.CookieManager = new ContextualCookieManager((ctx, key) => - { - return ctx.Request.Path.ToString().StartsWith(zero.ZeroPath.EnsureStartsWith('/').TrimEnd('/')); - }); - - options.Events.OnRedirectToLogin = ctx => - { - ctx.Response.StatusCode = 401; - return Task.CompletedTask; - }; - }); - - setupAction?.Invoke(b); - }); - } - - - - public static AuthenticationBuilder AddZeroCookie(this AuthenticationBuilder builder, string scheme, string cookieDisplayName = null, Action> setupAction = null) - where TUser : ZeroIdentityUser - where TRole : ZeroIdentityRole - { - return builder.AddCookie(scheme, cookieDisplayName, false, setupAction); - } - - - - public static AuthenticationBuilder AddZeroCookie(this AuthenticationBuilder builder, string scheme, string cookieDisplayName = null, Action> setupAction = null) - where TUser : ZeroIdentityUser - { - return builder.AddCookie(scheme, cookieDisplayName, false, setupAction); - } - - - - static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string scheme, string displayName, bool isBackoffice, Action> setupAction = null) - where TUser : ZeroIdentityUser - { - IServiceCollection services = builder.Services; - - services - .AddOptions>() - .Configure((options, zero) => - { - options.Scheme = scheme; - options.Path = isBackoffice ? zero.ZeroPath : "/"; - }); - - var optionsBuilder = services - .AddOptions(scheme) - .Configure>>((options, monitor) => - { - ZeroAuthOptions opts = monitor.CurrentValue; - - options.SlidingExpiration = true; - options.ExpireTimeSpan = TimeSpan.FromDays(90); - options.Cookie.HttpOnly = true; - options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; - options.Cookie.SameSite = SameSiteMode.Lax; - options.Cookie.Path = "/"; - - options.LoginPath = opts.Path; - options.LogoutPath = opts.Path; - options.AccessDeniedPath = opts.Path; - }); - - setupAction?.Invoke(optionsBuilder); - - builder.AddScheme(scheme, displayName, null); - - return builder; - } -} \ No newline at end of file diff --git a/zero.Core/Identity/BackofficeUserExtensions.cs b/zero.Core/Identity/BackofficeUserExtensions.cs deleted file mode 100644 index a1df0fae..00000000 --- a/zero.Core/Identity/BackofficeUserExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace zero.Identity; - -public static class BackofficeUserExtensions -{ - public static string[] GetAllowedAppIds(this ZeroUser user) - { - if (user == null) - { - return Array.Empty(); - } - - - return Array.Empty(); - // TODO permissions - //IEnumerable permissions = user.Claims - // .Where(claim => claim.Type == Constants.Auth.Claims.Permission && claim.Value.StartsWith(Permissions.Applications)) - // .Select(x => Permission.FromClaim(x.ToClaim(), Permissions.Applications)); - - //return permissions.Where(x => x.IsTrue).Select(x => x.NormalizedKey).ToArray(); - } -} \ No newline at end of file diff --git a/zero.Core/Identity/Base32.cs b/zero.Core/Identity/Base32.cs deleted file mode 100644 index 2abcc510..00000000 --- a/zero.Core/Identity/Base32.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System.Text; - -namespace zero.Identity; - -// This is a re-implementation of ASP.NET Core Base32, as they have marked it as internal -// by using this we can create user security stamps on the fly and don't need -// see: https://github.com/dotnet/aspnetcore/blob/release/5.0/src/Identity/Extensions.Core/src/Base32.cs -public static class Base32 -{ - private static readonly string _base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - - public static string ToBase32(byte[] input) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - - StringBuilder sb = new StringBuilder(); - for (int offset = 0; offset < input.Length;) - { - byte a, b, c, d, e, f, g, h; - int numCharsToOutput = GetNextGroup(input, ref offset, out a, out b, out c, out d, out e, out f, out g, out h); - - sb.Append((numCharsToOutput >= 1) ? _base32Chars[a] : '='); - sb.Append((numCharsToOutput >= 2) ? _base32Chars[b] : '='); - sb.Append((numCharsToOutput >= 3) ? _base32Chars[c] : '='); - sb.Append((numCharsToOutput >= 4) ? _base32Chars[d] : '='); - sb.Append((numCharsToOutput >= 5) ? _base32Chars[e] : '='); - sb.Append((numCharsToOutput >= 6) ? _base32Chars[f] : '='); - sb.Append((numCharsToOutput >= 7) ? _base32Chars[g] : '='); - sb.Append((numCharsToOutput >= 8) ? _base32Chars[h] : '='); - } - - return sb.ToString(); - } - - public static byte[] FromBase32(string input) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - input = input.TrimEnd('=').ToUpperInvariant(); - if (input.Length == 0) - { - return new byte[0]; - } - - var output = new byte[input.Length * 5 / 8]; - var bitIndex = 0; - var inputIndex = 0; - var outputBits = 0; - var outputIndex = 0; - while (outputIndex < output.Length) - { - var byteIndex = _base32Chars.IndexOf(input[inputIndex]); - if (byteIndex < 0) - { - throw new FormatException(); - } - - var bits = Math.Min(5 - bitIndex, 8 - outputBits); - output[outputIndex] <<= bits; - output[outputIndex] |= (byte)(byteIndex >> (5 - (bitIndex + bits))); - - bitIndex += bits; - if (bitIndex >= 5) - { - inputIndex++; - bitIndex = 0; - } - - outputBits += bits; - if (outputBits >= 8) - { - outputIndex++; - outputBits = 0; - } - } - return output; - } - - // returns the number of bytes that were output - private static int GetNextGroup(byte[] input, ref int offset, out byte a, out byte b, out byte c, out byte d, out byte e, out byte f, out byte g, out byte h) - { - uint b1, b2, b3, b4, b5; - - int retVal; - switch (input.Length - offset) - { - case 1: retVal = 2; break; - case 2: retVal = 4; break; - case 3: retVal = 5; break; - case 4: retVal = 7; break; - default: retVal = 8; break; - } - - b1 = (offset < input.Length) ? input[offset++] : 0U; - b2 = (offset < input.Length) ? input[offset++] : 0U; - b3 = (offset < input.Length) ? input[offset++] : 0U; - b4 = (offset < input.Length) ? input[offset++] : 0U; - b5 = (offset < input.Length) ? input[offset++] : 0U; - - a = (byte)(b1 >> 3); - b = (byte)(((b1 & 0x07) << 2) | (b2 >> 6)); - c = (byte)((b2 >> 1) & 0x1f); - d = (byte)(((b2 & 0x01) << 4) | (b3 >> 4)); - e = (byte)(((b3 & 0x0f) << 1) | (b4 >> 7)); - f = (byte)((b4 >> 2) & 0x1f); - g = (byte)(((b4 & 0x3) << 3) | (b5 >> 5)); - h = (byte)(b5 & 0x1f); - - return retVal; - } -} \ No newline at end of file diff --git a/zero.Core/Identity/Models/LoginResult.cs b/zero.Core/Identity/Models/LoginResult.cs deleted file mode 100644 index ed1be65f..00000000 --- a/zero.Core/Identity/Models/LoginResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace zero.Identity.Models; - -public enum LoginResult -{ - [Localize("@login.errors.unknown")] - Unknown = 0, - [Localize("@login.errors.wrongcredentials")] - WrongCredentials = 1, - [Localize("@login.errors.lockedout")] - LockedOut = 2, - [Localize("@login.errors.notallowed")] - NotAllowed = 3, - [Localize("@login.errors.requirestwofactor")] - RequiresTwoFactor = 4, - Success = 10 -} \ No newline at end of file diff --git a/zero.Core/Identity/Models/UserClaim.cs b/zero.Core/Identity/Models/UserClaim.cs deleted file mode 100644 index ed3aad62..00000000 --- a/zero.Core/Identity/Models/UserClaim.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Security.Claims; - -namespace zero.Identity; - -public class UserClaim -{ - /// - /// Gets or sets the claim type for this claim - /// - public string Type { get; set; } - - /// - /// Gets or sets the claim value for this claim - /// - public string Value { get; set; } - - /// - /// Convert to a claim - /// - public Claim ToClaim() => new(Type, Value); - - public UserClaim() { } - - public UserClaim(string type, string value) - { - Type = type; - Value = value; - } - - public UserClaim(string type, string key, string value) - { - Type = type; - Value = key + ":" + value; - } - - public UserClaim(Claim claim) - { - Type = claim?.Type; - Value = claim?.Value; - } -} \ No newline at end of file diff --git a/zero.Core/Identity/Models/UserClaimComparer.cs b/zero.Core/Identity/Models/UserClaimComparer.cs deleted file mode 100644 index 8765ff60..00000000 --- a/zero.Core/Identity/Models/UserClaimComparer.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace zero.Identity; - -public class UserClaimComparer : IEqualityComparer -{ - public bool Equals(UserClaim x, UserClaim y) - { - return (x == null && y == null) || (x.Type.Equals(y.Type, StringComparison.InvariantCultureIgnoreCase) && x.Value.Equals(y.Value, StringComparison.InvariantCultureIgnoreCase)); - } - - public int GetHashCode(UserClaim obj) - { - return (obj.Type + obj.Value).GetHashCode(); - } -} diff --git a/zero.Core/Identity/Models/ZeroIdentityRole.cs b/zero.Core/Identity/Models/ZeroIdentityRole.cs deleted file mode 100644 index 0f78fbbb..00000000 --- a/zero.Core/Identity/Models/ZeroIdentityRole.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace zero.Identity; - -public abstract class ZeroIdentityRole : ZeroEntity -{ - /// - /// The role's claims, for use in claims-based authentication. - /// - public List Claims { get; set; } = new(); -} diff --git a/zero.Core/Identity/Models/ZeroIdentityUser.cs b/zero.Core/Identity/Models/ZeroIdentityUser.cs deleted file mode 100644 index c29dea33..00000000 --- a/zero.Core/Identity/Models/ZeroIdentityUser.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace zero.Identity; - -public abstract class ZeroIdentityUser : ZeroEntity -{ - /// - /// Optional username (can also be used as login when configured) - /// - public string Username { get; set; } - - /// - /// E-Mail address which is also used as the username - /// - public string Email { get; set; } - - /// - /// Whether the email address has been confirmed - /// - public bool IsEmailConfirmed { get; set; } - - /// - /// The password hash - /// - public string PasswordHash { get; set; } - - /// - /// The security stamp - /// - public string SecurityStamp { get; set; } - - /// - /// The user's claims, for use in claims-based authentication. - /// - public List Claims { get; set; } = new(); - - /// - /// The roles (aliases) of the user - /// - public List RoleIds { get; set; } = new(); - - - - /// - /// Number of times sign in failed. - /// - public int AccessFailedCount { get; set; } - - /// - /// Whether the user is locked out. - /// - public bool LockoutEnabled { get; set; } - - /// - /// When the user lock out is over. - /// - public DateTimeOffset? LockoutEnd { get; set; } - - - - ///// - ///// Whether 2-factor authentication is enabled or not - ///// - //public bool TwoFactorEnabled { get; set; } - - ///// - ///// The two-factor authenticator key - ///// - //public string TwoFactorAuthenticatorKey { get; set; } - - ///// - ///// The list of two factor authentication recovery codes - ///// - //public List TwoFactorRecoveryCodes { get; set; } -} diff --git a/zero.Core/Identity/Models/ZeroUser.cs b/zero.Core/Identity/Models/ZeroUser.cs deleted file mode 100644 index f06444c4..00000000 --- a/zero.Core/Identity/Models/ZeroUser.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace zero.Identity; - -/// -/// A zero user can access the zero API and backoffice by granting the necessary permissions -/// -[RavenCollection("Users")] -public class ZeroUser : ZeroIdentityUser -{ - /// - /// Application the user registered in - /// - public string AppId { get; set; } - - /// - /// Currently selected app id for the backoffice - /// - public string CurrentAppId { get; set; } - - /// - /// Super user - /// - public bool IsSuper { get; set; } - - /// - /// Avatar image - /// - public string AvatarId { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Identity/Models/ZeroUserRole.cs b/zero.Core/Identity/Models/ZeroUserRole.cs deleted file mode 100644 index 5136247c..00000000 --- a/zero.Core/Identity/Models/ZeroUserRole.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace zero.Identity; - -[RavenCollection("UserRoles")] -public class ZeroUserRole : ZeroIdentityRole -{ - /// - /// Additional description - /// - public string Description { get; set; } - - /// - /// Displayed icon alongside name - /// - public string Icon { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Identity/PasswordGenerator.cs b/zero.Core/Identity/PasswordGenerator.cs deleted file mode 100644 index be20c9dd..00000000 --- a/zero.Core/Identity/PasswordGenerator.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace zero.Identity; - -public class PasswordGenerator -{ - const string CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-@#.:!?*"; - - private static Random random = new(); - - /// - /// Create a new password - /// - public static string Random(int length = -1) - { - if (length < 1) - { - length = 12; - } - - return new string(Enumerable.Repeat(CHARS, length).Select(s => s[random.Next(s.Length)]).ToArray()); - } -} diff --git a/zero.Core/Identity/Permissions/EntityPermission.cs b/zero.Core/Identity/Permissions/EntityPermission.cs deleted file mode 100644 index 0b98a3c1..00000000 --- a/zero.Core/Identity/Permissions/EntityPermission.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace zero.Identity; - -public class EntityPermission -{ - /// - /// Whether an entity can be read - /// - public bool CanRead { get; set; } - - /// - /// Whether an entity can be created - /// - public bool CanCreate { get; set; } - - /// - /// Whether an entity can be created in the shared app space - /// - public bool CanCreateShared { get; set; } - - /// - /// Whether an entity can be edited or only viewed - /// - public bool CanEdit { get; set; } - - /// - /// Whether an entity can be deleted - /// - public bool CanDelete { get; set; } - - /// - /// Wehther an entity is application aware - /// - public bool IsAppAware { get; set; } - - /// - /// Whether an entity can be shared across applications (only for IsAppAware=true) - /// - public bool IsShareable { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Identity/Permissions/Permission.cs b/zero.Core/Identity/Permissions/Permission.cs deleted file mode 100644 index d6e6e18a..00000000 --- a/zero.Core/Identity/Permissions/Permission.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Security.Claims; - -namespace zero.Identity; - -public struct Permission -{ - /// - /// Full key (stored in left part claim value) of the permission - /// - public string Key { get; set; } - - /// - /// Title of this permission for output - /// - public string Label { get; set; } - - /// - /// When a permission is disabled it can not be updated anymore - /// - public bool IsDisabled { get; set; } - - /// - /// Child permissions - /// - public HashSet Children { get; set; } - - - public Permission(string key, string label) - { - Key = key; - Label = label; - IsDisabled = false; - Children = new(); - } - - ///// - ///// Whether a resource (with authorization based on the key) can be read by any of the given permisssions - ///// - //public static bool CanReadKey(IEnumerable permissions, string key, bool isNormalized = false) - //{ - // Permission permission = permissions.FirstOrDefault(p => isNormalized ? p.NormalizedKey == key : p.Key == key); - // return permission?.CanRead ?? false; - //} - - ///// - ///// Whether a resource (with authorization based on the key) can be written to by any of the given permisssions - ///// - //public static bool CanWriteKey(IEnumerable permissions, string key, bool isNormalized = false) - //{ - // Permission permission = permissions.FirstOrDefault(p => isNormalized ? p.NormalizedKey == key : p.Key == key); - // return permission?.CanWrite ?? false; - //} - - ///// - ///// Create a permission from a claim - ///// - //public static Permission FromClaim(Claim claim, string prefixToRemove = null) - //{ - // if (claim == null) - // { - // return null; - // } - // return FromClaim(claim.Value, prefixToRemove); - //} - - ///// - ///// Create a permission from a claim - ///// - //public static Permission FromClaim(string claimValue, string prefixToRemove = null) - //{ - // Permission permission = new Permission(); - // string[] valueParts = claimValue.Split(':'); - - // permission.Key = valueParts[0]; - // permission.NormalizedKey = permission.Key; - // permission.Value = valueParts.Length > 1 ? valueParts[1] : null; - - // if (!prefixToRemove.IsNullOrEmpty()) - // { - // permission.NormalizedKey = valueParts[0].TrimStart(prefixToRemove); - // } - - // return permission; - //} -} \ No newline at end of file diff --git a/zero.Core/Identity/Permissions/PermissionContext.cs b/zero.Core/Identity/Permissions/PermissionContext.cs deleted file mode 100644 index f5815f91..00000000 --- a/zero.Core/Identity/Permissions/PermissionContext.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace zero.Identity; - -public class PermissionContext : IPermissionContext -{ - /// - public HashSet Groups { get; protected set; } - - /// - public IZeroOptions Options { get; protected set; } - - - public PermissionContext(IZeroOptions options) - { - Groups = new(); - Options = options; - } - - - /// - public PermissionGroup? GetGroup(string key) - { - return Groups.FirstOrDefault(x => x.Key == key); - } - - - /// - public bool TryGetGroup(string key, out PermissionGroup group) - { - group = Groups.FirstOrDefault(x => x.Key == key); - return Groups.Contains(group); - } - - - /// - public void AddGroup(PermissionGroup group) - { - Groups.Add(group); - } - - - /// - public void RemoveGroup(PermissionGroup group) - { - Groups.Remove(group); - } -} - - -public interface IPermissionContext -{ - /// - /// All registered permission groups - /// - HashSet Groups { get; } - - /// - /// Zero options - /// - IZeroOptions Options { get; } - - /// - /// Try to get a permission group by key - /// - PermissionGroup? GetGroup(string key); - - /// - /// Get permission group by key - /// - bool TryGetGroup(string key, out PermissionGroup group); - - /// - /// Add a new permission group to the context - /// - void AddGroup(PermissionGroup group); - - /// - /// Remove a permission group from the context - /// - void RemoveGroup(PermissionGroup group); -} \ No newline at end of file diff --git a/zero.Core/Identity/Permissions/PermissionGroup.cs b/zero.Core/Identity/Permissions/PermissionGroup.cs deleted file mode 100644 index 50375634..00000000 --- a/zero.Core/Identity/Permissions/PermissionGroup.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace zero.Identity; - -public struct PermissionGroup -{ - /// - /// Full key of the group - /// - public string Key { get; set; } - - /// - /// Title of this permission group for output - /// - public string Label { get; set; } - - /// - /// Permissions for this group - /// - public HashSet Permissions { get; set; } - - - public PermissionGroup(string key, string label) - { - Key = key; - Label = label; - Permissions = new(); - } - - - /// - /// Add a new permission to this group - /// - public void Add(Permission permission) - { - Permissions.Add(permission); - } - - - /// - /// Remove a permission - /// - public void Remove(Permission permission) - { - Permissions.Remove(permission); - } - - - /// - /// Get permission by key - /// - public Permission? Get(string key) - { - return Permissions.FirstOrDefault(x => x.Key == key); - } - - - /// - /// Get permission by key - /// - public object this[string key] - { - get => Permissions.FirstOrDefault(x => x.Key == key); - } -} \ No newline at end of file diff --git a/zero.Core/Identity/Permissions/PermissionProvider.cs b/zero.Core/Identity/Permissions/PermissionProvider.cs deleted file mode 100644 index b5b9449f..00000000 --- a/zero.Core/Identity/Permissions/PermissionProvider.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace zero.Identity; - -public abstract class PermissionProvider : IPermissionProvider -{ - /// - public virtual Task Configure(IPermissionContext context) => Task.CompletedTask; -} - - -public interface IPermissionProvider -{ - /// - /// Manage permissions (add/remove/update permission groups and containing permissions) - /// - Task Configure(IPermissionContext context); - - /// - /// In order for this permissions to work, the specified provider requires the following permission - /// - //IEnumerable Requires() => Array.Empty(); -} \ No newline at end of file diff --git a/zero.Core/Identity/Permissions/Permissions.cs b/zero.Core/Identity/Permissions/Permissions.cs deleted file mode 100644 index 9799c330..00000000 --- a/zero.Core/Identity/Permissions/Permissions.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace zero.Identity; - -public struct Permissions -{ - /// - /// Ability to switch zero applications and read/write other applications out of the user appId - /// If PermissionsValue.Write is set for this permission, the user will be limited to his/her claims in other applications too - /// - public const string Applications = "applications."; - - - public struct Settings - { - public const string PREFIX = "settings.area."; - public const string Updates = PREFIX + Constants.Settings.Updates; - public const string Applications = PREFIX + Constants.Settings.Applications; - public const string Users = PREFIX + Constants.Settings.Users; - public const string Languages = PREFIX + Constants.Settings.Languages; - public const string Translations = PREFIX + Constants.Settings.Translations; - public const string Countries = PREFIX + Constants.Settings.Countries; - public const string Mails = PREFIX + Constants.Settings.Mails; - public const string Logging = PREFIX + Constants.Settings.Logging; - public const string Plugins = PREFIX + Constants.Settings.Plugins; - public const string CreatePlugin = PREFIX + Constants.Settings.CreatePlugin; - } - - - public struct Sections - { - public const string PREFIX = "section."; - public const string Dashboard = PREFIX + Constants.Sections.Dashboard; - public const string Pages = PREFIX + Constants.Sections.Pages; - public const string Spaces = PREFIX + Constants.Sections.Spaces; - public const string Media = PREFIX + Constants.Sections.Media; - public const string Settings = PREFIX + Constants.Sections.Settings; - } - - - public struct Spaces - { - public const string PREFIX = "space."; - } - - - public struct Modules - { - public const string PREFIX = "module."; - } -} - - -public struct PermissionsValue -{ - public const string Create = "create"; - - public const string Read = "read"; - - public const string Update = "update"; - - public const string Delete = "delete"; - - public const string None = "none"; - - public const string True = "true"; - - public const string False = "false"; -} diff --git a/zero.Core/Identity/RavenRoleStore(TRole).cs b/zero.Core/Identity/RavenRoleStore(TRole).cs deleted file mode 100644 index bb676ce5..00000000 --- a/zero.Core/Identity/RavenRoleStore(TRole).cs +++ /dev/null @@ -1,150 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; -using Raven.Client.Exceptions; -using System.Security.Claims; - -namespace zero.Identity; - -public class RavenRoleStore : - EntityStore, - IRoleStore, - IRoleClaimStore - where TRole : ZeroIdentityRole, new() -{ - protected IdentityErrorDescriber ErrorDescriber { get; private set; } - - protected bool Global { get; private set; } - - - public RavenRoleStore(IStoreContext storeContext, IdentityErrorDescriber describer = null, bool global = false) : base(storeContext) - { - Global = global; - Config.Database = global ? Options.For().Database : null; - Config.IncludeInactive = true; - ErrorDescriber = describer ?? new IdentityErrorDescriber(); - } - - - // - /// Scope queries to an optional application or something else - /// - protected virtual IRavenQueryable ScopeQuery(IRavenQueryable query) => query; - - - /// - public async Task CreateAsync(TRole role, CancellationToken cancellationToken) - { - await Create(role); - return IdentityResult.Success; - } - - - /// - public async Task UpdateAsync(TRole role, CancellationToken cancellationToken) - { - try - { - await Update(role); - } - catch (ConcurrencyException) - { - return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); - } - return IdentityResult.Success; - } - - - /// - public async Task DeleteAsync(TRole role, CancellationToken cancellationToken) - { - try - { - await Delete(role); - } - catch (ConcurrencyException) - { - return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); - } - return IdentityResult.Success; - } - - - /// - public Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken) => Task.FromResult(role.Id); - - - /// - public Task GetRoleNameAsync(TRole role, CancellationToken cancellationToken) => Task.FromResult(role.Name); - - - /// - public Task SetRoleNameAsync(TRole role, string roleName, CancellationToken cancellationToken) - { - role.Name = roleName; - return Task.CompletedTask; - } - - - /// - public Task GetNormalizedRoleNameAsync(TRole role, CancellationToken cancellationToken) => Task.FromResult(role.Name); - - - /// - public async Task SetNormalizedRoleNameAsync(TRole role, string normalizedName, CancellationToken cancellationToken) - { - await SetRoleNameAsync(role, normalizedName, cancellationToken); - } - - - /// - public async Task FindByIdAsync(string roleId, CancellationToken cancellationToken) - { - return await ScopeQuery(Session.Query()).FirstOrDefaultAsync(x => x.Id == roleId, cancellationToken); - } - - - /// - public async Task FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken) - { - return await ScopeQuery(Session.Query()).FirstOrDefaultAsync(x => x.Name == normalizedRoleName, cancellationToken); - } - - - /// - public void Dispose() - { - - } - - - /* - * **************************************************** - * CLAIM - * **************************************************** - */ - - - /// - public Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default) - { - role.Claims.Add(new(claim)); - return Task.CompletedTask; - } - - - /// - public Task> GetClaimsAsync(TRole role, CancellationToken cancellationToken = default) - { - return Task.FromResult((IList)role.Claims.Select(claim => claim.ToClaim()).ToList()); - } - - - /// - public Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default) - { - UserClaim userClaim = new(claim); - role.Claims = role.Claims.Except(new List() { userClaim }, new UserClaimComparer()).ToList(); - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/zero.Core/Identity/RavenScopedStores.cs b/zero.Core/Identity/RavenScopedStores.cs deleted file mode 100644 index 67c782ef..00000000 --- a/zero.Core/Identity/RavenScopedStores.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace zero.Identity; - -public class RavenCoreRoleStore : RavenRoleStore - where TRole : ZeroIdentityRole, new() -{ - public RavenCoreRoleStore(IStoreContext context, IdentityErrorDescriber describer = null) : base(context, describer, true) { } -} - - - -public class RavenCoreUserStore : RavenUserStore - where TUser : ZeroIdentityUser, new() -{ - public RavenCoreUserStore(IStoreContext context) : base(context, true) { } -} - - - -public class RavenCoreUserStore : RavenUserStore - where TUser : ZeroIdentityUser, new() - where TRole : ZeroIdentityRole, new() -{ - public RavenCoreUserStore(IStoreContext context) : base(context, true) { } - -} \ No newline at end of file diff --git a/zero.Core/Identity/RavenUserStore(TUser).cs b/zero.Core/Identity/RavenUserStore(TUser).cs deleted file mode 100644 index 11c16d19..00000000 --- a/zero.Core/Identity/RavenUserStore(TUser).cs +++ /dev/null @@ -1,349 +0,0 @@ -using FluentValidation.Results; -using Microsoft.AspNetCore.Identity; -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; -using System.Security.Claims; - -namespace zero.Identity; - - -public partial class RavenUserStore : - EntityStore, - IUserStore, - IUserEmailStore, - IUserLockoutStore, - IUserPasswordStore, - IUserClaimStore, - IUserSecurityStampStore, - IProtectedUserStore - where TUser : ZeroIdentityUser, new() -{ - protected bool Global { get; private set; } - - public RavenUserStore(IStoreContext storeContext, bool global = false) : base(storeContext) - { - Global = global; - Config.Database = global ? Options.For().Database : null; - Config.IncludeInactive = true; - } - - - /// - /// Get the Raven compare/exchange key for this user. - /// This should be unique within the store, otherwise you can't create users with the same email in different stores. - /// - protected virtual string GetReservationKey(TUser user) => Constants.Database.ReservationPrefix + typeof(TUser) + ':' + user.Email; - - // - /// Scope queries to an optional application or something else - /// - protected virtual IRavenQueryable ScopeQuery(IRavenQueryable query) => query; - - /// - /// Whether an email is already reserved - /// - protected virtual async Task IsEmailReserved(TUser user, CancellationToken cancellationToken = default) - { - TUser existingUser = await ScopeQuery(Session.Query()).FirstOrDefaultAsync(x => x.Email == user.Email, cancellationToken); - return existingUser != null && existingUser.Id != user.Id; - } - - - /// - public async Task CreateAsync(TUser user, CancellationToken cancellationToken) - { - if (await IsEmailReserved(user, cancellationToken)) - { - return IdentityResult.Failed(new IdentityError - { - Code = "DuplicateEmail", - Description = $"The email address {user.Email} is already taken." - }); - } - - Result result = await Create(user); - return IdentityResult.Success; - } - - - /// - public async Task DeleteAsync(TUser user, CancellationToken cancellationToken) - { - Result result = await Delete(user); - return IdentityResult.Success; - } - - - /// - public async Task UpdateAsync(TUser user, CancellationToken cancellationToken) - { - TUser source = await Load(user.Id); - - if (source == null) - { - return IdentityResult.Failed(new IdentityError - { - Code = "UserNotFound", - Description = $"Could not find stored user with id {user.Id}." - }); - } - - if (source.Email != user.Email && await IsEmailReserved(user, cancellationToken)) - { - return IdentityResult.Failed(new IdentityError - { - Code = "DuplicateEmail", - Description = $"The email address {user.Email} is already taken." - }); - } - - await Update(user); - - return IdentityResult.Success; - } - - - /// - public async Task FindByIdAsync(string userId, CancellationToken cancellationToken) - { - return await ScopeQuery(Session.Query()).FirstOrDefaultAsync(x => x.Id == userId, cancellationToken); - } - - - /// - public async Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken) - { - return await ScopeQuery(Session.Query()).FirstOrDefaultAsync(x => x.Username == normalizedUserName, cancellationToken); - } - - - /// - public Task GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.Username); - - - /// - public Task GetUserIdAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.Id); - - - /// - public Task GetUserNameAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.Username); - - - /// - public Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken) - { - user.Username = normalizedName; - return Task.CompletedTask; - } - - - /// - public async Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken) - { - await SetNormalizedUserNameAsync(user, userName, cancellationToken); - } - - - /// - void IDisposable.Dispose() { } - - - /* - * **************************************************** - * SECURITY STAMP - * **************************************************** - */ - - - /// - public Task GetSecurityStampAsync(TUser user, CancellationToken cancellationToken) - { - return Task.FromResult(user.SecurityStamp); - } - - - /// - public Task SetSecurityStampAsync(TUser user, string stamp, CancellationToken cancellationToken) - { - user.SecurityStamp = stamp; - return Task.CompletedTask; - } - - - /* - * **************************************************** - * EMAIL - * **************************************************** - */ - - - /// - public async Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken) - { - return await Session.Query().FirstOrDefaultAsync(x => x.Email == normalizedEmail, cancellationToken); - } - - - /// - public Task GetEmailAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.Email); - - - /// - public Task GetEmailConfirmedAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.IsEmailConfirmed); - - - /// - public Task GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.Email); - - - /// - public Task SetEmailAsync(TUser user, string email, CancellationToken cancellationToken) - { - user.Email = email.ToLowerInvariant(); - return Task.CompletedTask; - } - - - /// - public Task SetEmailConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken) - { - user.IsEmailConfirmed = confirmed; - return Task.CompletedTask; - } - - - /// - public Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken) - { - user.Email = normalizedEmail.ToLowerInvariant(); - return Task.CompletedTask; - } - - - /* - * **************************************************** - * LOCKOUT - * **************************************************** - */ - - - /// - public Task GetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.AccessFailedCount); - - - /// - public Task GetLockoutEnabledAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.LockoutEnabled); - - - /// - public Task GetLockoutEndDateAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.LockoutEnd); - - - /// - public Task IncrementAccessFailedCountAsync(TUser user, CancellationToken cancellationToken) - { - user.AccessFailedCount += 1; - return Task.FromResult(user.AccessFailedCount); - } - - - /// - public Task ResetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken) - { - user.AccessFailedCount = 0; - return Task.CompletedTask; - } - - - /// - public Task SetLockoutEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken) - { - user.LockoutEnabled = enabled; - return Task.CompletedTask; - } - - - /// - public Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken) - { - user.LockoutEnd = lockoutEnd; - return Task.CompletedTask; - } - - - /* - * **************************************************** - * PASSWORD - * **************************************************** - */ - - - /// - public Task GetPasswordHashAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.PasswordHash); - - - /// - public Task HasPasswordAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(!user.PasswordHash.IsNullOrEmpty()); - - - /// - public Task SetPasswordHashAsync(TUser user, string passwordHash, CancellationToken cancellationToken) - { - user.PasswordHash = passwordHash; - return Task.CompletedTask; - } - - - /* - * **************************************************** - * CLAIM - * **************************************************** - */ - - - /// - public async Task AddClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken) - { - user.Claims.AddRange(claims.Select(claim => new UserClaim(claim))); - await Update(user); - } - - - /// - public Task> GetClaimsAsync(TUser user, CancellationToken cancellationToken) - { - return Task.FromResult((IList)user.Claims.Select(claim => claim.ToClaim()).ToList()); - } - - - /// - public async Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken) - { - UserClaim userClaim = new(claim); - return await ScopeQuery(Session.Query()).Where(x => x.Claims.Any(c => c.Type == userClaim.Type && c.Value == userClaim.Value)).ToListAsync(token: cancellationToken); - } - - - /// - public async Task RemoveClaimsAsync(TUser user, IEnumerable claims, CancellationToken cancellationToken) - { - IEnumerable userClaims = claims.Select(c => new UserClaim(c)).ToList(); - - user.Claims = user.Claims.Except(userClaims, new UserClaimComparer()).ToList(); - - await Update(user); - } - - - /// - public async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken) - { - UserClaim userClaim = new(claim); - UserClaim newUserClaim = new(newClaim); - - user.Claims.Remove(userClaim); - user.Claims.Add(newUserClaim); - - await Update(user); - } -} diff --git a/zero.Core/Identity/RavenUserStore(TUser,TRole).cs b/zero.Core/Identity/RavenUserStore(TUser,TRole).cs deleted file mode 100644 index e2a4a796..00000000 --- a/zero.Core/Identity/RavenUserStore(TUser,TRole).cs +++ /dev/null @@ -1,53 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; - -namespace zero.Identity; - -// TODO we can't inject IZeroContext here for app-context as the IApplicationContext itself -// relies on UserManager and therefore this UserStore, i.e. circular dependency - -public partial class RavenUserStore : RavenUserStore, - IUserRoleStore - where TUser : ZeroIdentityUser, new() - where TRole : ZeroIdentityRole, new() -{ - public RavenUserStore(IStoreContext context, bool global = false) : base(context, global) { } - - - /// - public Task AddToRoleAsync(TUser user, string roleName, CancellationToken cancellationToken) - { - user.RoleIds.Add(roleName); - return Task.CompletedTask; - } - - - /// - public Task> GetRolesAsync(TUser user, CancellationToken cancellationToken) - { - return Task.FromResult((IList)user.RoleIds.ToList()); - } - - - /// - public async Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken) - { - return await ScopeQuery(Session.Query()).Where(x => roleName.In(x.RoleIds)).ToListAsync(); - } - - - /// - public Task IsInRoleAsync(TUser user, string roleName, CancellationToken cancellationToken) - { - return Task.FromResult(user.RoleIds.Contains(roleName, StringComparer.InvariantCultureIgnoreCase)); - } - - - /// - public Task RemoveFromRoleAsync(TUser user, string roleName, CancellationToken cancellationToken) - { - user.RoleIds.Remove(roleName); - return Task.CompletedTask; - } -} diff --git a/zero.Core/Identity/Security/ContextualCookieManager.cs b/zero.Core/Identity/Security/ContextualCookieManager.cs deleted file mode 100644 index e47c3db7..00000000 --- a/zero.Core/Identity/Security/ContextualCookieManager.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Http; - -namespace zero.Identity; - -public class ContextualCookieManager : ChunkingCookieManager, ICookieManager -{ - protected Func Validate { get; private set; } = (ctx, key) => true; - - - public ContextualCookieManager(Func onValidate) - { - Validate = onValidate; - } - - - /// - /// Explicitly implement this so that we filter the request - /// - string ICookieManager.GetRequestCookie(HttpContext context, string key) - { - return !Validate(context, key) ? null : GetRequestCookie(context, key); - } -} \ No newline at end of file diff --git a/zero.Core/Identity/Security/SchemedSignInManager.cs b/zero.Core/Identity/Security/SchemedSignInManager.cs deleted file mode 100644 index 1d34ec85..00000000 --- a/zero.Core/Identity/Security/SchemedSignInManager.cs +++ /dev/null @@ -1,102 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using System.Security.Claims; - -namespace zero.Identity; - -// TODO although the login per application works -// the authentication breaks when another application signs in a user -// they share one cookie on both sites/applications, maybe this is an issue - -public class SchemedSignInManager : SignInManager where TUser : ZeroIdentityUser -{ - protected IOptionsMonitor> AuthOptions { get; private set; } - - protected IZeroContext Zero { get; private set; } - - public SchemedSignInManager(UserManager userManager, IHttpContextAccessor contextAccessor, IUserClaimsPrincipalFactory claimsFactory, - IOptions optionsAccessor, ILogger> logger, IAuthenticationSchemeProvider schemes, IUserConfirmation confirmation, IOptionsMonitor> authOptions, - IZeroContext zero) - : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation) - { - AuthOptions = authOptions; - Zero = zero; - } - - - /// - public override Task CanSignInAsync(TUser user) - { - if (!user.IsActive) - { - return Task.FromResult(false); - } - - return base.CanSignInAsync(user); - } - - - /// - public override bool IsSignedIn(ClaimsPrincipal principal) - { - if (principal?.Identities == null) - { - return false; - } - if (!principal.Identities.Any(x => x.AuthenticationType == AuthOptions.CurrentValue.Scheme)) - { - return false; - } - - string userAppId = principal.FindFirstValue(Constants.Auth.Claims.AppId); - return userAppId == null || Zero.AppId.Equals(userAppId, StringComparison.InvariantCultureIgnoreCase); - } - - - /// - public override async Task RefreshSignInAsync(TUser user) - { - var auth = await Context.AuthenticateAsync(AuthOptions.CurrentValue.Scheme); - IList claims = Array.Empty(); - - var authenticationMethod = auth?.Principal?.FindFirst(ClaimTypes.AuthenticationMethod); - var amr = auth?.Principal?.FindFirst("amr"); - - if (authenticationMethod != null || amr != null) - { - claims = new List(); - if (authenticationMethod != null) - { - claims.Add(authenticationMethod); - } - if (amr != null) - { - claims.Add(amr); - } - } - - await SignInWithClaimsAsync(user, auth?.Properties, claims); - } - - - /// - public override async Task SignInWithClaimsAsync(TUser user, AuthenticationProperties authenticationProperties, IEnumerable additionalClaims) - { - var userPrincipal = await CreateUserPrincipalAsync(user); - foreach (var claim in additionalClaims) - { - userPrincipal.Identities.First().AddClaim(claim); - } - await Context.SignInAsync(AuthOptions.CurrentValue.Scheme, userPrincipal, authenticationProperties ?? new AuthenticationProperties()); - } - - - /// - public override async Task SignOutAsync() - { - await Context.SignOutAsync(AuthOptions.CurrentValue.Scheme); - } -} diff --git a/zero.Core/Identity/Security/ZeroAuthOptions.cs b/zero.Core/Identity/Security/ZeroAuthOptions.cs deleted file mode 100644 index e3cd155e..00000000 --- a/zero.Core/Identity/Security/ZeroAuthOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace zero.Identity; - -public class ZeroAuthOptions where TUser : ZeroIdentityUser -{ - public string Scheme { get; set; } - - public string Path { get; set; } = "/"; -} diff --git a/zero.Core/Identity/Security/ZeroAuthorizeAttribute.cs b/zero.Core/Identity/Security/ZeroAuthorizeAttribute.cs deleted file mode 100644 index 49b8c4c1..00000000 --- a/zero.Core/Identity/Security/ZeroAuthorizeAttribute.cs +++ /dev/null @@ -1,136 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using System.Security.Claims; - -namespace zero.Identity; - -public class ZeroAuthorizeAttribute : TypeFilterAttribute -{ - public ZeroAuthorizeAttribute() : this(true, String.Empty) { } - - public ZeroAuthorizeAttribute(bool enabled) : this(enabled, String.Empty) { } - - public ZeroAuthorizeAttribute(params string[] permissionKeys) : this(true, permissionKeys) { } - - public ZeroAuthorizeAttribute(bool enabled, params string[] permissionKeys) : base(typeof(ZeroAuthorizeFilter)) - { - Arguments = new object[] { enabled, permissionKeys }; - } -} - - -public class ZeroAuthorizeFilter : IAuthorizationFilter -{ - IEnumerable PermissionKeys { get; set; } - - bool Enabled { get; set; } - - - public ZeroAuthorizeFilter(bool enabled, IEnumerable permissionKeys) : base() - { - PermissionKeys = permissionKeys; - Enabled = enabled; - } - - public void OnAuthorization(AuthorizationFilterContext context) - { - // allow anonymous skips all authorization - if (!Enabled) // context.Filters.Any(item => item is IAllowAnonymousFilter) || - { - return; - } - - // get all auth filters - IEnumerable authFilters = context.Filters.Where(filter => filter is ZeroAuthorizeFilter).Select(filter => filter as ZeroAuthorizeFilter); - - // find parent auth filter in case the permission key is empty - //if (Permission.IsNullOrEmpty()) - //{ - // ZeroAuthorizeFilter parentFilter = authFilters.LastOrDefault(filter => filter.Enabled && !filter.Permission.IsNullOrEmpty()); - - // if (parentFilter != null) - // { - // Permission = parentFilter.Permission; - // } - // else - // { - // throw new InvalidOperationException("The ZeroAuthorize attribute requires a permission key if it has no parent where it can inherit the key"); - // } - //} - - // find all filters which could possible interrupt/override this filter - // these are filters which handle the same permission or disable authorization - string permissionKey = PermissionKeys != null ? String.Join("-", PermissionKeys) : String.Empty; - - ZeroAuthorizeFilter[] siblingFilters = authFilters - .Where(filter => !filter.Enabled || permissionKey.Equals(filter.PermissionKeys != null ? String.Join("-", filter.PermissionKeys) : String.Empty, StringComparison.InvariantCultureIgnoreCase)) - .ToArray(); - - // get index of the current filter - int currentIndex = Array.IndexOf(siblingFilters, this); - - // do not run this filter if it is overridden - if (siblingFilters.Length > 1 && currentIndex < siblingFilters.Length - 1) - { - return; - } - - ClaimsPrincipal user = context.HttpContext.User; - - bool isAuthenticated = user.Identity.IsAuthenticated; - bool isZeroUser = user.HasClaim(Constants.Auth.Claims.IsZero, PermissionsValue.True); - - if (!isAuthenticated || !isZeroUser) - { - context.Result = new StatusCodeResult(401); - return; - } - - // check claims - //if (!PermissionKey.IsNullOrEmpty()) - //{ - // bool isSuperUser = false; // TODO user.HasClaim(Constants.Auth.Claims.IsSuper, PermissionsValue.True); - // bool hasPassed = isSuperUser; - - // if (!isSuperUser) - // { - // // automatically request write permission for methods which start with `Save` or `Delete` or `Post` or `Put` - // string actionName = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor).ActionName; - - // //if (actionName.StartsWith("Create")) - // //{ - // // PermissionValues.Add(PermissionsValue.Create); - // //} - // //if (actionName.StartsWith("Update") || actionName.StartsWith("Save")) - // //{ - // // PermissionValues.Add(PermissionsValue.Update); - // //} - // //if (actionName.StartsWith("Delete") || actionName.StartsWith("Remove")) - // //{ - // // PermissionValues.Add(PermissionsValue.Delete); - // //} - - // //foreach (string value in PermissionValues) - // //{ - // // bool fulfillsClaim = user.HasClaim(Constants.Auth.Claims.Permission, PermissionKey + ":" + value); - - // // if (!fulfillsClaim && value == PermissionsValue.Read) - // // { - // // fulfillsClaim = user.HasClaim(Constants.Auth.Claims.Permission, PermissionKey + ":" + PermissionsValue.Update); - // // } - - // // if (fulfillsClaim) - // // { - // // hasPassed = true; - // // break; - // // } - // //} - // } - - //if (!hasPassed) // TODO - //{ - // context.Result = new StatusCodeResult(403); - //} - //} - } -} diff --git a/zero.Core/Identity/Security/ZeroBackofficeClaimsPrincipalFactory.cs b/zero.Core/Identity/Security/ZeroBackofficeClaimsPrincipalFactory.cs deleted file mode 100644 index 25448e84..00000000 --- a/zero.Core/Identity/Security/ZeroBackofficeClaimsPrincipalFactory.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using System.Security.Claims; - -namespace zero.Identity; - -public class ZeroBackofficeClaimsPrincipalFactory : ZeroClaimsPrinicipalFactory - where TUser : ZeroUser - where TRole : ZeroUserRole -{ - public ZeroBackofficeClaimsPrincipalFactory(UserManager userManager, RoleManager roleManager, IOptions optionsAccessor, IOptions> authOptions, IZeroContext zero) - : base(userManager, roleManager, optionsAccessor, authOptions, zero) - { } - - - protected override async Task GenerateClaimsAsync(TUser user) - { - List claims = await CreateClaimList(user); - - // create the user identity - ClaimsIdentity identity = new(claims, AuthOptions.Scheme, Constants.Auth.Claims.UserName, Constants.Auth.Claims.Role); // "Identity.Application" - - if (BackofficeUserIdentity.TryCreate(identity, out BackofficeUserIdentity userIdentity)) - { - Claim isZeroClaim = userIdentity.FindFirst(Constants.Auth.Claims.IsZero); - Claim appIdClaim = userIdentity.FindFirst(Constants.Auth.Claims.AppId); - - if (appIdClaim is null || isZeroClaim is null or not { Value: PermissionsValue.True }) - { - return null; - } - - return userIdentity; - } - - return null; - } - - - protected override async Task> CreateClaimList(TUser user) - { - List claims = await base.CreateClaimList(user); - - claims.RemoveAll(x => x.Type == Constants.Auth.Claims.IsZero); - claims.Add(new Claim(Constants.Auth.Claims.IsZero, PermissionsValue.True)); - - if (user.IsSuper) - { - claims.Add(new Claim(Constants.Auth.Claims.IsSuper, PermissionsValue.True)); - } - - // get all allowed app ids - //string[] appIds = claims - // .Where(x => x.Type == Constants.Auth.Claims.Permission && x.Value.StartsWith(Permissions.Applications)) - // .Select(x => Permission.FromClaim(x, Permissions.Applications)) - // .Where(x => x.IsTrue) - // .Select(x => x.NormalizedKey) - // .ToArray(); - string[] appIds = Array.Empty(); // TODO permissions - - string currentAppId = user.CurrentAppId ?? user.AppId; - - if (!user.IsSuper && !appIds.Contains(currentAppId)) - { - currentAppId = user.AppId; - } - - claims.RemoveAll(x => x.Type == Constants.Auth.Claims.AppId); - claims.Add(new Claim(Constants.Auth.Claims.AppId, currentAppId)); - claims.Add(new Claim(Constants.Auth.Claims.DefaultAppId, user.AppId)); - - return claims; - } -} diff --git a/zero.Core/Identity/Security/ZeroClaimsPrinicipalFactory.cs b/zero.Core/Identity/Security/ZeroClaimsPrinicipalFactory.cs deleted file mode 100644 index 052d12f9..00000000 --- a/zero.Core/Identity/Security/ZeroClaimsPrinicipalFactory.cs +++ /dev/null @@ -1,119 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using System.Security.Claims; - -namespace zero.Identity; - -public class ZeroClaimsPrinicipalFactory : ZeroClaimsPrinicipalFactory - where TUser : ZeroIdentityUser - where TRole : ZeroIdentityRole -{ - public RoleManager RoleManager { get; private set; } - - public ZeroClaimsPrinicipalFactory(UserManager userManager, RoleManager roleManager, IOptions optionsAccessor, IOptions> authOptions, IZeroContext zero) : base(userManager, optionsAccessor, authOptions, zero) - { - RoleManager = roleManager; - } - - protected override async Task> CreateClaimList(TUser user) - { - var claims = await base.CreateClaimList(user); - - if (UserManager.SupportsUserRole) - { - var roles = await UserManager.GetRolesAsync(user); - - foreach (var roleName in roles) - { - claims.Add(new Claim(Constants.Auth.Claims.Role, roleName)); - - if (RoleManager.SupportsRoleClaims) - { - var role = await RoleManager.FindByNameAsync(roleName); - if (role != null) - { - claims.AddRange(await RoleManager.GetClaimsAsync(role)); - } - } - } - } - - return claims; - } -} - - -public class ZeroClaimsPrinicipalFactory : UserClaimsPrincipalFactory, IUserClaimsPrincipalFactory - where TUser : ZeroIdentityUser -{ - protected ZeroAuthOptions AuthOptions { get; private set; } - - protected IZeroContext Zero { get; private set; } - - - public ZeroClaimsPrinicipalFactory(UserManager userManager, IOptions optionsAccessor, IOptions> authOptions, IZeroContext zero) : base(userManager, optionsAccessor) - { - AuthOptions = authOptions.Value; - Zero = zero; - } - - - public async override Task CreateAsync(TUser user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - ClaimsIdentity principal = await GenerateClaimsAsync(user); - - if (principal == null) - { - return null; - } - - return new ClaimsPrincipal(principal); - } - - - protected override async Task GenerateClaimsAsync(TUser user) - { - List claims = await CreateClaimList(user); - - // create the user identity - ClaimsIdentity identity = new(claims, AuthOptions.Scheme, Constants.Auth.Claims.UserName, Constants.Auth.Claims.Role); // "Identity.Application" - - UserIdentity.TryCreate(identity, AuthOptions.Scheme, out UserIdentity userIdentity); - return userIdentity; - } - - - protected virtual async Task> CreateClaimList(TUser user) - { - string userId = await UserManager.GetUserIdAsync(user); - string userName = await UserManager.GetUserNameAsync(user); - - List claims = new(); - - claims.Add(new Claim(Constants.Auth.Claims.IsZero, PermissionsValue.False)); - claims.Add(new Claim(Constants.Auth.Claims.UserId, userId)); - claims.Add(new Claim(Constants.Auth.Claims.UserName, userName)); - - if (UserManager.SupportsUserSecurityStamp) - { - claims.Add(new Claim(Constants.Auth.Claims.SecurityStamp, await UserManager.GetSecurityStampAsync(user))); - } - - if (UserManager.SupportsUserClaim) - { - claims.AddRange(await UserManager.GetClaimsAsync(user)); - } - - if (UserManager.SupportsUserEmail) - { - claims.Add(new Claim(Constants.Auth.Claims.Email, await UserManager.GetEmailAsync(user))); - } - - return claims; - } -} diff --git a/zero.Core/Identity/Services/AuthenticationService.cs b/zero.Core/Identity/Services/AuthenticationService.cs deleted file mode 100644 index 9b2d810f..00000000 --- a/zero.Core/Identity/Services/AuthenticationService.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using zero.Identity.Models; - -namespace zero.Identity; - -public class AuthenticationService : IAuthenticationService -{ - protected IZeroContext Context { get; set; } - - protected SignInManager SignInManager { get; private set; } - - protected IZeroStore Store { get; set; } - - - public AuthenticationService(IZeroContext context, SignInManager signInManager, IZeroStore store) - { - Context = context; - SignInManager = signInManager; - Store = store; - } - - - /// - public bool IsLoggedIn() - { - //ClaimsPrincipal principal = HttpContextAccessor.HttpContext.User; - //bool isAuthenticated = principal.Identity.IsAuthenticated; - //bool isZeroUser = principal.HasClaim(Constants.Auth.Claims.IsZero, PermissionsValue.True); - //bool isSignedIn = SignInManager.IsSignedIn(principal); - //return isAuthenticated && isZeroUser && isSignedIn; - - return SignInManager.IsSignedIn(Context.BackofficeUser); - } - - - /// - public bool IsSuper() - { - return Context.BackofficeUser.HasClaim(Constants.Auth.Claims.IsSuper, PermissionsValue.True); - } - - - /// - public bool IsAdmin() - { - return Context.BackofficeUser.HasClaim(Constants.Auth.Claims.Role, "administrator"); // TODO use constant (in setup as well) - } - - - /// - public async Task GetUser() - { - return await SignInManager.UserManager.GetUserAsync(Context.BackofficeUser); - } - - - /// - public async Task Login(string email, string password, bool isPersistent) - { - SignInResult signInResult = await SignInManager.PasswordSignInAsync(email, password, isPersistent, true); - - if (!signInResult.Succeeded) - { - if (signInResult.IsLockedOut) - { - return LoginResult.LockedOut; - } - else if (signInResult.IsNotAllowed) - { - return LoginResult.NotAllowed; - } - else if (signInResult.RequiresTwoFactor) - { - return LoginResult.RequiresTwoFactor; - } - - return LoginResult.WrongCredentials; - } - - return LoginResult.Success; - } - - - /// - public async Task Logout() - { - await SignInManager.SignOutAsync(); - } - - - /// - public string GetUserId() - { - return SignInManager.UserManager.GetUserId(Context.BackofficeUser); - } -} - - -public interface IAuthenticationService -{ - /// - /// Get currently logged-in user - /// - Task GetUser(); - - /// - /// Whether a user is currently logged-in - /// - bool IsLoggedIn(); - - /// - /// Whether the current user is the super user who created the zero instance - /// - bool IsSuper(); - - /// - /// Whether the current user belongs to the administrator role (will always return false if this role gets deleted) - /// - bool IsAdmin(); - - /// - /// Logs a zero-user in and sets cookie - /// - Task Login(string email, string password, bool isPersistent); - - /// - /// Logs out the current user - /// - Task Logout(); - - /// - /// Get the ID of the currently logged in user - /// - string GetUserId(); -} \ No newline at end of file diff --git a/zero.Core/Identity/Services/AuthorizationService.cs b/zero.Core/Identity/Services/AuthorizationService.cs deleted file mode 100644 index 1f777968..00000000 --- a/zero.Core/Identity/Services/AuthorizationService.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using System.Security.Claims; - -namespace zero.Identity; - -public class AuthorizationService : IAuthorizationService -{ - protected IHttpContextAccessor HttpContextAccessor { get; set; } - - protected SignInManager SignInManager { get; private set; } - - protected ClaimsPrincipal Principal => HttpContextAccessor.HttpContext?.User; - - - public AuthorizationService(IHttpContextAccessor httpContextAccessor, SignInManager signInManager) - { - HttpContextAccessor = httpContextAccessor; - SignInManager = signInManager; - } - - - /// - public bool IsLoggedIn() - { - ClaimsPrincipal principal = HttpContextAccessor.HttpContext.User; - - bool isAuthenticated = principal.Identity.IsAuthenticated; - bool isZeroUser = principal.HasClaim(Constants.Auth.Claims.IsZero, PermissionsValue.True); - - bool isSignedIn = SignInManager.IsSignedIn(principal); - - return isAuthenticated && isZeroUser && isSignedIn; - } - - - /// - public bool IsSuper() - { - return Principal.HasClaim(Constants.Auth.Claims.IsSuper, PermissionsValue.True); - } - - - /// - public bool IsAdmin() - { - return Principal.HasClaim(Constants.Auth.Claims.Role, "administrator"); // TODO use constant (in setup as well) - } - - - /// - public IList GetPermissions(string prefix = null) - { - return new List(); // TODO - //return Principal.Claims - // .Where(claim => claim.Type == Constants.Auth.Claims.Permission && (prefix == null || claim.Value.StartsWith(prefix))) - // .Select(claim => Permission.FromClaim(claim, prefix)) - // .ToList(); - } - - - /// - public Permission GetPermission(string key = null) - { - return new Permission(); - //Claim claim = Principal.Claims.FirstOrDefault(claim => claim.Type == Constants.Auth.Claims.Permission && claim.Value.StartsWith(key + ":")); - - //return Permission.FromClaim(claim); - } - - - public EntityPermission GetPermissionForEntity(T model, string permissionKey) where T : ZeroEntity - { - EntityPermission result = new(); - - if (!IsLoggedIn()) - { - return result; - } - - //Type type = typeof(T); - bool isSuperUser = Principal.HasClaim(Constants.Auth.Claims.IsSuper, PermissionsValue.True); - - //result.IsAppAware = AppAwareType.IsAssignableFrom(type); // TODO appx - //result.IsShareable = result.IsAppAware && AppAwareShareableType.IsAssignableFrom(type); - - if (isSuperUser) - { - result.CanCreate = true; - result.CanCreateShared = result.CanCreate && result.IsShareable; - result.CanEdit = true; - result.CanRead = true; - result.CanDelete = true; - return result; - } - - Permission? permission = GetPermission(permissionKey); - - if (permission == null) - { - - } - - return result; - } -} - - -public interface IAuthorizationService -{ - /// - /// Whether a user is currently logged-in - /// - bool IsLoggedIn(); - - /// - /// Whether the current user is the super user who created the zero instance - /// - bool IsSuper(); - - /// - /// Whether the current user belongs to the administrator role (will always return false if this role gets deleted) - /// - bool IsAdmin(); - - /// - /// Get all permissions for the current user with an optional prefix - /// - IList GetPermissions(string prefix = null); - - /// - /// Get a single permissions by key - /// - public Permission GetPermission(string key = null); -} \ No newline at end of file diff --git a/zero.Core/Identity/Services/UserRolesService.cs b/zero.Core/Identity/Services/UserRolesService.cs deleted file mode 100644 index 43d061a7..00000000 --- a/zero.Core/Identity/Services/UserRolesService.cs +++ /dev/null @@ -1,117 +0,0 @@ -using FluentValidation.Results; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Raven.Client.Documents; -using System.Security.Claims; - -namespace zero.Identity; - -public class UserRolesService : IUserRolesService -{ - protected IHttpContextAccessor HttpContextAccessor { get; set; } - - protected UserManager UserManager { get; private set; } - - protected RoleManager RoleManager { get; private set; } - - protected IZeroContext Context { get; private set; } - - protected IZeroDocumentSession Session => Context.Store.Session(); - - - public UserRolesService(IZeroContext context, IHttpContextAccessor httpContextAccessor, UserManager userManager, RoleManager roleManager) - { - Context = context; - HttpContextAccessor = httpContextAccessor; - UserManager = userManager; - RoleManager = roleManager; - } - - - /// - public async Task> GetAll() - { - return await Session.Query().OrderBy(x => x.Sort).ThenBy(x => x.Name).ToListAsync(); - } - - - /// - public async Task GetById(string id) - { - return await RoleManager.FindByIdAsync(id); - } - - - /// - public async Task> Save(ZeroUserRole model) - { - //ValidationResult validation = await new UserRoleValidator().ValidateAsync(model); - - //if (!validation.IsValid) - //{ - // return EntityResult.Fail(validation); - //} - - if (model.Id.IsNullOrEmpty()) - { - model.CreatedDate = DateTimeOffset.Now; - } - - model.Alias = Safenames.Alias(model.Name); - - await Session.StoreAsync(model); - - string id = Session.Advanced.GetDocumentId(model); - - await Session.SaveChangesAsync(); - - if (model.Id.IsNullOrEmpty()) - { - model.Id = id; - } - - return Result.Success(model); - } - - - /// - public async Task> Delete(string id) - { - ZeroUserRole country = await Session.LoadAsync(id); - - if (country == null) - { - return Result.Fail("@errors.ondelete.idnotfound"); - } - - Session.Delete(country); - - await Session.SaveChangesAsync(); - - return Result.Success(); - } -} - - -public interface IUserRolesService -{ - /// - /// Get all user roles - /// - Task> GetAll(); - - /// - /// Get role by id - /// - Task GetById(string id); - - /// - /// Create or update a role - /// - Task> Save(ZeroUserRole model); - - /// - /// Deletes a role - /// - Task> Delete(string id); -} \ No newline at end of file diff --git a/zero.Core/Identity/Services/UserService.cs b/zero.Core/Identity/Services/UserService.cs deleted file mode 100644 index fa557b09..00000000 --- a/zero.Core/Identity/Services/UserService.cs +++ /dev/null @@ -1,350 +0,0 @@ -using FluentValidation.Results; -using Microsoft.AspNetCore.Identity; -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; - -namespace zero.Identity; - -public class UserService : IUserService -{ - protected UserManager UserManager { get; private set; } - - protected IStoreOperations Operations { get; private set; } - - protected IZeroContext Context { get; private set; } - - public UserService(ISharedStoreOperationsWithInactive operations, IZeroContext context, IZeroOptions options, UserManager userManager) - { - UserManager = userManager; - Operations = operations; - Context = context; - } - - - /// - public async Task GetCurrentUser() - { - return await UserManager.GetUserAsync(Context.BackofficeUser); - } - - - /// - public async Task GetUserById(string id) - { - ZeroUser user = await UserManager.FindByIdAsync(id); - return user; - } - - - /// - public async Task GetUserByEmail(string email) - { - ZeroUser user = await UserManager.FindByEmailAsync(email); - return user; - } - - - /// - public async Task> GetByIds(params string[] ids) - { - return await Operations.Load(ids); - } - - - /// - public async Task> GetAll(int pageNumber, int pageSize) - { - return await Operations.Load(pageNumber, pageSize, q => q.OrderByDescending(x => x.CreatedDate)); - } - - - /// - public async Task> Save(ZeroUser model) - { - bool updateSecurityStamp = false; - bool isUpdate = false; - - ZeroUser input = model; - - if (model.Id.HasValue()) - { - model = await GetUserById(model.Id); - - if (model != null) - { - if (input.PasswordHash.StartsWith("raw:")) - { - string newPassword = input.PasswordHash.Substring(4); - model.PasswordHash = UserManager.PasswordHasher.HashPassword(model, newPassword); - } - - ObjectCopycat.CopyProperties(input, model, nameof(ZeroUser.PasswordHash), nameof(ZeroUser.SecurityStamp)); - } - - isUpdate = model != null; - updateSecurityStamp = true; // origin != null && model.PasswordHash != origin.PasswordHash; - } - else - { - // this is bullshit ;-) - model.AppId = "app.hofbauer"; - model.CurrentAppId = "app.hofbauer"; - model.RoleIds = new List() - { - "userRoles.c9ozj4vboing", - "userRoles.nc5ezcu660fo" - }; - } - - model.Username = model.Email?.ToUpper(); - BackofficeUserValidator validator = new(!isUpdate); - ValidationResult validation = await validator.ValidateAsync(model); - - if (!validation.IsValid) - { - return Result.Fail(validation); - } - - Result result = isUpdate ? await Operations.Update(model) : await Operations.Create(model); - - if (updateSecurityStamp) - { - await UserManager.UpdateSecurityStampAsync(model); - } - - return result; - } - - - /// - public async Task> Delete(string id) - { - return await Operations.Delete(id); - } - - - /// - public async Task> HashPassword(ZeroUser user, string currentPassword, string newPassword, string confirmNewPassword) - { - if (newPassword != confirmNewPassword) - { - return Result.Fail(nameof(newPassword), "@errors.changepassword.newpasswordsnotmatching"); - } - - if (currentPassword.IsNullOrWhiteSpace() || newPassword.IsNullOrWhiteSpace()) - { - return Result.Fail(nameof(currentPassword), "@errors.changepassword.emptyfields"); - } - - if (user == null) - { - return Result.Fail("@errors.changepassword.nouser"); - } - - if (UserManager.PasswordHasher.VerifyHashedPassword(user, user.PasswordHash, currentPassword) != PasswordVerificationResult.Success) - { - return Result.Fail("@errors.changepassword.passwordincorrect"); - } - - // validate new password - List errors = new(); - bool isValid = true; - foreach (var v in UserManager.PasswordValidators) - { - var result = await v.ValidateAsync(UserManager, user, newPassword); - if (!result.Succeeded) - { - if (result.Errors.Any()) - { - errors.AddRange(result.Errors); - } - - isValid = false; - } - } - - if (!isValid) - { - Result result = Result.Fail(); - foreach (IdentityError error in errors) - { - result.AddError(error.Description); - } - return result; - } - - return Result.Success(UserManager.PasswordHasher.HashPassword(user, newPassword)); - } - - - /// - public async Task> UpdatePassword(ZeroUser user, string currentPassword, string newPassword) - { - if (currentPassword.IsNullOrWhiteSpace() || newPassword.IsNullOrWhiteSpace()) - { - return Result.Fail(nameof(currentPassword), "@errors.changepassword.emptyfields"); - } - - if (user == null) - { - return Result.Fail("@errors.changepassword.nouser"); - } - - IdentityResult identityResult = await UserManager.ChangePasswordAsync(user as ZeroUser, currentPassword, newPassword); - - if (!identityResult.Succeeded) - { - Result result = Result.Fail(); - - foreach (IdentityError error in identityResult.Errors) - { - result.AddError(error.Description); - } - - return result; - } - - return Result.Success(user); - } - - - /// - public async Task> Enable(ZeroUser user) - { - return await UpdateActiveState(user, true); - } - - - /// - public async Task> Disable(ZeroUser user) - { - return await UpdateActiveState(user, false); - } - - - /// - /// Updates the active state of user. - /// If IsActive=false, the user cannot login anymore - /// - async Task> UpdateActiveState(ZeroUser user, bool isActive) - { - user.IsActive = isActive; - - IdentityResult identityResult = await UserManager.UpdateAsync(user as ZeroUser); - - if (!identityResult.Succeeded) - { - Result result = Result.Fail(); - - foreach (IdentityError error in identityResult.Errors) - { - result.AddError(error.Description); - } - - return result; - } - - await UserManager.UpdateSecurityStampAsync(user as ZeroUser); - - return Result.Success(user); - } - - - /// - public async Task TrySwitchApp(string appId) - { - IZeroDocumentSession session = Context.Store.Session(global: true); - ZeroUser user = await UserManager.GetUserAsync(Context.BackofficeUser); - - if (user == null || appId.IsNullOrEmpty()) - { - return false; - } - - string[] allowedAppIds = user.GetAllowedAppIds(); - - bool isMainId = appId.Equals(user.AppId, StringComparison.InvariantCultureIgnoreCase); - bool isAllowedId = allowedAppIds.Contains(appId, StringComparer.InvariantCultureIgnoreCase); - - if (user.IsSuper || isMainId || isAllowedId) - { - user.CurrentAppId = appId; - - //byte[] bytes = new byte[20]; - //RandomNumberGenerator.Fill(bytes); - //user.SecurityStamp = Base32.ToBase32(bytes); // TODO update security stamp but Base32 is .net core internal - - await session.StoreAsync(user); - await session.SaveChangesAsync(); - - return true; - } - - return false; - } -} - - -public interface IUserService -{ - /// - /// Get currently logged-in backoffice user - /// - Task GetCurrentUser(); - - /// - /// Find user by id - /// - Task GetUserById(string id); - - /// - /// Find user by email - /// - Task GetUserByEmail(string email); - - /// - /// Get users by ids - /// - Task> GetByIds(params string[] ids); - - /// - /// Get all users for the current application - /// - Task> GetAll(int pageNumber, int pageSize); - - /// - /// Creates or updates a user - /// - Task> Save(ZeroUser model); - - /// - /// Deletes a user - /// - Task> Delete(string id); - - /// - /// Changes the password of the current user. - /// User is logged out if this operation succeeds. - /// - Task> UpdatePassword(ZeroUser user, string currentPassword, string newPassword); - - /// - /// Tries to hash a new password - /// - Task> HashPassword(ZeroUser user, string currentPassword, string newPassword, string confirmNewPassword); - - /// - /// Enables a user - /// - Task> Enable(ZeroUser user); - - /// - /// Disables a user - /// - Task> Disable(ZeroUser user); - - /// - /// Tries to switch the currently loaded backoffice application for the current user - /// - Task TrySwitchApp(string appId); -} \ No newline at end of file diff --git a/zero.Core/Identity/UserIdentity.cs b/zero.Core/Identity/UserIdentity.cs deleted file mode 100644 index 599c963b..00000000 --- a/zero.Core/Identity/UserIdentity.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System.Security.Claims; -using System.Security.Principal; - -namespace zero.Identity; - -public class BackofficeUserIdentity : UserIdentity -{ - protected BackofficeUserIdentity(ClaimsIdentity identity) : base(identity, Constants.Auth.BackofficeScheme) { } - - public static bool TryCreate(ClaimsIdentity identity, out BackofficeUserIdentity user) - { - user = null; - - if (!RequiredClaims.All(claim => identity.HasClaim(x => x.Type == claim && !x.Value.IsNullOrWhiteSpace()))) - { - Console.Error.WriteLine("Could not find all claims for UserIdentity creation"); // TODO use logger - return false; - } - if (!identity.HasClaim(Constants.Auth.Claims.IsZero, PermissionsValue.True)) - { - Console.Error.WriteLine("Could not find all claims for UserIdentity creation"); // TODO use logger - return false; - } - - user = new BackofficeUserIdentity(identity); - return true; - } - - - public static bool TryGet(IPrincipal principal, out BackofficeUserIdentity user) - { - if (TryGet(principal, out UserIdentity _user)) - { - return TryCreate(_user, out user); - } - - user = null; - return false; - } -} - - -public class UserIdentity : ClaimsIdentity -{ - protected UserIdentity(ClaimsIdentity identity, string authenticationType = null) : base(identity, identity.Claims, authenticationType ?? identity.AuthenticationType, Constants.Auth.Claims.UserName, Constants.Auth.Claims.Role) - { - - } - - - public static bool TryCreate(ClaimsIdentity identity, string authenticationType, out UserIdentity user) - { - user = null; - - if (!RequiredClaims.All(claim => identity.HasClaim(x => x.Type == claim && !x.Value.IsNullOrWhiteSpace()))) - { - Console.Error.WriteLine("Could not find all claims for UserIdentity creation"); // TODO use logger - return false; - } - - user = new UserIdentity(identity, authenticationType); - return true; - } - - - /// - /// Build user identity from a matching principal identity - /// - public static bool TryGet(IPrincipal principal, out UserIdentity user) - { - if (principal.Identity is UserIdentity identity) - { - user = identity; - return true; - } - - if (principal is ClaimsPrincipal claimsPrincipal) - { - user = claimsPrincipal.Identities.OfType().FirstOrDefault(); - if (user != null) - { - return true; - } - } - - if (principal.Identity is ClaimsIdentity claimsIdentity && claimsIdentity.IsAuthenticated) - { - if (TryCreate(claimsIdentity, claimsIdentity.AuthenticationType, out user)) - { - return true; - } - } - - user = null; - return false; - } - - - public static IEnumerable RequiredClaims => new[] - { - Constants.Auth.Claims.UserId, - Constants.Auth.Claims.UserName, - Constants.Auth.Claims.Email, - //Constants.Auth.Claims.Role, - Constants.Auth.Claims.SecurityStamp, - Constants.Auth.Claims.IsZero - }; -} diff --git a/zero.Core/Identity/Validation/BackofficeUserRoleValidator.cs b/zero.Core/Identity/Validation/BackofficeUserRoleValidator.cs deleted file mode 100644 index 86bf56b5..00000000 --- a/zero.Core/Identity/Validation/BackofficeUserRoleValidator.cs +++ /dev/null @@ -1,35 +0,0 @@ -using FluentValidation; - -namespace zero.Identity; - -public class BackofficeUserRoleValidator : ZeroValidator -{ - const string SECTION_CLAIM = "section."; - - const string TRUE_CLAIM_VALUE = ":true"; - - public BackofficeUserRoleValidator() - { - RuleFor(x => x.Name).Length(2, 80); - RuleFor(x => x.Description).MaximumLength(200); - RuleFor(x => x.Icon).NotEmpty(); - - RuleFor(x => x.Claims).NotEmpty().Must((role, claims, context) => - { - foreach (UserClaim claim in claims) - { - if (claim.Value.StartsWith(SECTION_CLAIM, StringComparison.InvariantCultureIgnoreCase) && claim.Value.EndsWith(TRUE_CLAIM_VALUE, StringComparison.InvariantCultureIgnoreCase)) - { - return true; - } - } - - return false; - }).WithMessage("@errors.role.nosection"); - - RuleForEach(x => x.Claims).Must((role, claim, context) => - { - return !claim.Type.IsNullOrEmpty() && !claim.Value.IsNullOrEmpty(); - }).WithMessage("@errors.role.emptyclaim"); - } -} diff --git a/zero.Core/Identity/Validation/BackofficeUserValidator.cs b/zero.Core/Identity/Validation/BackofficeUserValidator.cs deleted file mode 100644 index c022b50e..00000000 --- a/zero.Core/Identity/Validation/BackofficeUserValidator.cs +++ /dev/null @@ -1,20 +0,0 @@ -using FluentValidation; - -namespace zero.Identity; - -public class BackofficeUserValidator : ZeroValidator -{ - public BackofficeUserValidator(bool isCreate = false) - { - RuleFor(x => x.Name).NotEmpty().Length(2, 80); - RuleFor(x => x.Email).NotEmpty().Email().MaximumLength(120); - //RuleFor(x => x.PasswordHash).NotEmpty(); - RuleFor(x => x.LanguageId).NotEmpty(); - RuleFor(x => x.RoleIds).NotEmpty(); - - if (isCreate) - { - RuleFor(x => x.IsSuper).Equals(false); - } - } -} diff --git a/zero.Core/Identity/ZeroAuthenticationSchemeProvider.cs b/zero.Core/Identity/ZeroAuthenticationSchemeProvider.cs deleted file mode 100644 index fea52c63..00000000 --- a/zero.Core/Identity/ZeroAuthenticationSchemeProvider.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; - -namespace zero.Identity; - -public class ZeroAuthenticationSchemeProvider : AuthenticationSchemeProvider -{ - readonly IHttpContextAccessor httpContextAccessor; - - public ZeroAuthenticationSchemeProvider(IHttpContextAccessor httpContextAccessor, IOptions options) : base(options) - { - this.httpContextAccessor = httpContextAccessor; - } - - private async Task GetRequestSchemeAsync() - { - var request = httpContextAccessor.HttpContext?.Request; - if (request == null) - { - throw new ArgumentNullException("The HTTP request cannot be retrieved."); - } - - // For API requests, use authentication tokens. - if (request.Path.StartsWithSegments("/zero")) // TODO from options. but this one needs to be a singleton - { - return await GetSchemeAsync(Constants.Auth.BackofficeScheme); - } - - // For the other requests, return null to let the base methods - // decide what's the best scheme based on the default schemes - // configured in the global authentication options. - return null; - } - - public override async Task GetDefaultAuthenticateSchemeAsync() => - await GetRequestSchemeAsync() ?? - await base.GetDefaultAuthenticateSchemeAsync(); - - public override async Task GetDefaultChallengeSchemeAsync() => - await GetRequestSchemeAsync() ?? - await base.GetDefaultChallengeSchemeAsync(); - - public override async Task GetDefaultForbidSchemeAsync() => - await GetRequestSchemeAsync() ?? - await base.GetDefaultForbidSchemeAsync(); - - public override async Task GetDefaultSignInSchemeAsync() => - await GetRequestSchemeAsync() ?? - await base.GetDefaultSignInSchemeAsync(); - - public override async Task GetDefaultSignOutSchemeAsync() => - await GetRequestSchemeAsync() ?? - await base.GetDefaultSignOutSchemeAsync(); -} \ No newline at end of file diff --git a/zero.Core/Identity/ZeroIdentityExtensions.cs b/zero.Core/Identity/ZeroIdentityExtensions.cs deleted file mode 100644 index f92a04dc..00000000 --- a/zero.Core/Identity/ZeroIdentityExtensions.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace zero.Identity; - -public static class ZeroIdentityExtensions -{ - public static IdentityBuilder AddZeroIdentity(this IServiceCollection services) - where TUser : ZeroIdentityUser, new() - where TRole : ZeroIdentityRole, new() - { - services.AddZeroIdentityCore(); - - IdentityBuilder builder = new(typeof(TUser), typeof(TRole), services); - - builder.AddDefaultTokenProviders(); - - builder.AddUserStore>(); - builder.AddSignInManager>(); - builder.AddClaimsPrincipalFactory>(); - - builder.AddRoleValidator>(); - builder.AddRoleManager>(); - builder.AddRoleStore>(); - - return builder; - } - - - public static IdentityBuilder AddZeroIdentity(this IServiceCollection services) - where TUser : ZeroIdentityUser, new() - { - services.AddZeroIdentityCore(); - - IdentityBuilder builder = new(typeof(TUser), services); - - builder.AddDefaultTokenProviders(); - builder.AddUserStore>(); - builder.AddSignInManager>(); - builder.AddClaimsPrincipalFactory>(); - - return builder; - } - - - static IServiceCollection AddZeroIdentityCore(this IServiceCollection services) - where TUser : ZeroIdentityUser, new() - { - services.AddHttpContextAccessor(); - services.AddOptions(); - services.AddLogging(); - - services.TryAddScoped, UserValidator>(); - services.TryAddScoped, PasswordValidator>(); - services.TryAddScoped, PasswordHasher>(); - services.TryAddScoped(); - services.TryAddScoped, DefaultUserConfirmation>(); - services.TryAddScoped(); - services.TryAddScoped>(); - - services.Configure(opts => - { - opts.ClaimsIdentity.UserIdClaimType = Constants.Auth.Claims.UserId; - opts.ClaimsIdentity.UserNameClaimType = Constants.Auth.Claims.UserName; - opts.ClaimsIdentity.RoleClaimType = Constants.Auth.Claims.Role; - opts.ClaimsIdentity.SecurityStampClaimType = Constants.Auth.Claims.SecurityStamp; - opts.ClaimsIdentity.EmailClaimType = Constants.Auth.Claims.Email; - - opts.Password.RequireDigit = false; - opts.Password.RequireLowercase = false; - opts.Password.RequireUppercase = false; - opts.Password.RequireNonAlphanumeric = false; - opts.Password.RequiredLength = 8; - opts.Password.RequiredUniqueChars = 1; - - opts.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(60); - opts.Lockout.MaxFailedAccessAttempts = 5; - opts.Lockout.AllowedForNewUsers = true; - - opts.User.RequireUniqueEmail = true; - }); - - services.Configure(opts => - { - opts.ValidationInterval = TimeSpan.FromMinutes(90); - }); - - return services; - } -} \ No newline at end of file diff --git a/zero.Core/Identity/ZeroIdentityModule.cs b/zero.Core/Identity/ZeroIdentityModule.cs deleted file mode 100644 index e3eef001..00000000 --- a/zero.Core/Identity/ZeroIdentityModule.cs +++ /dev/null @@ -1,44 +0,0 @@ -using FluentValidation; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; - -namespace zero.Identity; - -internal class ZeroIdentityModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.TryAddEnumerable(ServiceDescriptor.Singleton, PostConfigureCookieAuthenticationOptions>()); - - services.AddZeroIdentity(); - services.Replace, ZeroBackofficeClaimsPrincipalFactory>(); - services.Replace, RavenCoreUserStore>(ServiceLifetime.Scoped); - services.AddScoped, BackofficeUserValidator>(); - services.Replace, RavenCoreRoleStore>(ServiceLifetime.Scoped); - services.AddSingleton(); - - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - services.AddAuthentication(Constants.Auth.BackofficeScheme) - .AddZeroBackofficeCookie(); - - services.AddAuthorization(); - - services.Configure(opts => - { - opts.Map("fth-user-check").Boost(5).Display((x, res) => - { - res.Description = x.Email; - res.Url = "/settings/users/edit/" + x.Id; - }); - }); - } -} \ No newline at end of file diff --git a/zero.Core/Localization/CountryStore.cs b/zero.Core/Localization/CountryStore.cs deleted file mode 100644 index 19dd07ed..00000000 --- a/zero.Core/Localization/CountryStore.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace zero.Localization; - -public class CountryStore : EntityStore, ICountryStore -{ - public CountryStore(IStoreContext context) : base(context) { } -} - - -public interface ICountryStore : IEntityStore { } \ No newline at end of file diff --git a/zero.Core/Localization/CountryValidator.cs b/zero.Core/Localization/CountryValidator.cs deleted file mode 100644 index f9d572cd..00000000 --- a/zero.Core/Localization/CountryValidator.cs +++ /dev/null @@ -1,12 +0,0 @@ -using FluentValidation; - -namespace zero.Localization; - -public class CountryValidator : ZeroValidator -{ - public CountryValidator(IZeroStore store) - { - RuleFor(x => x.Code).NotEmpty().Length(2).Unique(store); - RuleFor(x => x.Name).NotEmpty().Length(2, 120); - } -} \ No newline at end of file diff --git a/zero.Core/Localization/CultureResolver.cs b/zero.Core/Localization/CultureResolver.cs deleted file mode 100644 index ecfa41b9..00000000 --- a/zero.Core/Localization/CultureResolver.cs +++ /dev/null @@ -1,109 +0,0 @@ -using FluentValidation; -using Microsoft.Extensions.Logging; -using Raven.Client.Documents; -using Raven.Client.Documents.Session; -using System.Globalization; -using System.Security.Claims; - -namespace zero.Localization; - -public class CultureResolver : ICultureResolver -{ - protected ILogger Logger { get; private set; } - - - public CultureResolver(ILogger logger) - { - Logger = logger; - } - - - /// - public async Task Resolve(IZeroContext context) - { - // TODO this is just fake, we need to correctly resolve culture here - - if (context.IsBackofficeRequest) - { - if (context.IsLoggedIntoBackoffice) - { - ZeroUser user = await GetBackofficeUser(context.Store, context.BackofficeUser); - string cultureCode = (user?.LanguageId).Or("en-US"); - - try - { - CultureInfo culture = CultureInfo.CreateSpecificCulture(cultureCode); - - if (culture.ThreeLetterISOLanguageName.IsNullOrEmpty()) - { - throw new Exception("ThreeLetterISOLanguageName is empty"); - } - - CultureInfo.CurrentCulture = culture; - CultureInfo.CurrentUICulture = culture; - ValidatorOptions.Global.LanguageManager.Culture = culture; - } - catch (Exception ex) - { - Logger.LogError(ex, "Could not create culture from Language code {code}", cultureCode); - return CultureInfo.CurrentCulture; - } - } - } - else - { - //var session = context.Store.Session(); - //Language language = await session.Query().FirstOrDefaultAsync(); - Language language = new() { Code = "de-DE" }; - - if (language == null) - { - Logger.LogWarning("Could not set request culture as there is no available Language stored"); - return CultureInfo.CurrentCulture; - } - - try - { - CultureInfo culture = CultureInfo.CreateSpecificCulture(language.Code); - - if (culture.ThreeLetterISOLanguageName.IsNullOrEmpty()) - { - throw new Exception("ThreeLetterISOLanguageName is empty"); - } - - CultureInfo.CurrentCulture = culture; - CultureInfo.CurrentUICulture = culture; - ValidatorOptions.Global.LanguageManager.Culture = culture; - } - catch (Exception ex) - { - Logger.LogError(ex, "Could not create culture from Language code {code}", language.Code); - return CultureInfo.CurrentCulture; - } - } - - return CultureInfo.CurrentCulture; - } - - - /// - /// Get backoffice user from claims principal - /// - async Task GetBackofficeUser(IZeroStore store, ClaimsPrincipal user) - { - string userId = user.FindFirstValue(Constants.Auth.Claims.UserId); - - IAsyncDocumentSession session = store.Session(global: true); - return await session.LoadAsync(userId); - } -} - - -public interface ICultureResolver -{ - /// - /// Resolves the current application from either the backoffice user (in case it is backoffice request) - /// or the domain (in case it is frontend request). - /// - Task Resolve(IZeroContext context); -} diff --git a/zero.Core/Localization/CultureService.cs b/zero.Core/Localization/CultureService.cs deleted file mode 100644 index b1a111a4..00000000 --- a/zero.Core/Localization/CultureService.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Globalization; - -namespace zero.Localization; - -public class CultureService : ICultureService -{ - /// - public List GetAllCultures(params string[] codes) - { - return CultureInfo.GetCultures(CultureTypes.AllCultures) - .Where(x => !x.Name.IsNullOrWhiteSpace()) - .Select(x => new CultureInfo(x.Name)) - .Where(x => codes.Length == 0 || codes.Contains(x.Name, StringComparer.InvariantCultureIgnoreCase)) - .OrderBy(x => x.DisplayName) - .Select(x => new Culture() - { - Code = x.Name, - Name = x.DisplayName - }) - .ToList(); - } -} - - -public interface ICultureService -{ - /// - /// Get all available cultures - /// - List GetAllCultures(params string[] codes); -} \ No newline at end of file diff --git a/zero.Core/Localization/LanguageStore.cs b/zero.Core/Localization/LanguageStore.cs deleted file mode 100644 index adcaf2f2..00000000 --- a/zero.Core/Localization/LanguageStore.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace zero.Localization; - -public class LanguageStore : EntityStore, ILanguageStore -{ - public LanguageStore(IStoreContext context) : base(context) { } -} - - -public interface ILanguageStore : IEntityStore { } \ No newline at end of file diff --git a/zero.Core/Localization/LanguageValidator.cs b/zero.Core/Localization/LanguageValidator.cs deleted file mode 100644 index 606cfaf6..00000000 --- a/zero.Core/Localization/LanguageValidator.cs +++ /dev/null @@ -1,18 +0,0 @@ -using FluentValidation; - -namespace zero.Localization; - -public class LanguageValidator : ZeroValidator -{ - public LanguageValidator(IZeroStore store) - { - RuleFor(x => x.Name).NotEmpty().Length(2, 60); - RuleFor(x => x.Code).NotEmpty().Length(2, 10).Culture(); - RuleFor(x => x.IsDefault).Unique(store).When(x => x.IsDefault).WithMessage("@language.errors.default_unique"); - RuleFor(x => x.IsDefault).ExpectAnyUnique(store, expectedValue: true).When(x => !x.IsDefault).WithMessage("@language.errors.needs_default"); - RuleFor(x => x.InheritedLanguageId).Must((entity, value) => !entity.Id.Equals(value, StringComparison.InvariantCultureIgnoreCase)).When(x => !x.Id.IsNullOrEmpty()).WithMessage("@language.errors.fallback_invalid"); - RuleFor(x => x.InheritedLanguageId).Equal((string)null).When(x => x.IsDefault).WithMessage("@language.errors.default_no_fallback"); - RuleFor(x => x.InheritedLanguageId).Exists(store); - RuleFor(x => x.IsOptional).Equal(false).When(x => x.IsDefault).WithMessage("@language.errors.default_not_optional"); - } -} \ No newline at end of file diff --git a/zero.Core/Localization/LocalizeAttribute.cs b/zero.Core/Localization/LocalizeAttribute.cs deleted file mode 100644 index aa8c9de2..00000000 --- a/zero.Core/Localization/LocalizeAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace zero.Localization; - -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = false)] -public class LocalizeAttribute : Attribute -{ - public string Key; - - public LocalizeAttribute(string key) - { - Key = key; - } -} diff --git a/zero.Core/Localization/Localizer.cs b/zero.Core/Localization/Localizer.cs deleted file mode 100644 index 5d89a598..00000000 --- a/zero.Core/Localization/Localizer.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System.Collections.Concurrent; -using System.Reflection; - -namespace zero.Localization; - -public class Localizer : ILocalizer -{ - protected ConcurrentDictionary Cache { get; private set; } = new(); - - protected IZeroStore Store { get; private set; } - - public Localizer(IZeroStore store) - { - Store = store; - } - - - /// - public string Text(string key) => Text(key, null); - - - /// - public string Text(string key, Dictionary tokens) - { - if (key.IsNullOrEmpty()) - { - return null; - } - - if (!Cache.TryGetValue(key, out string value)) - { - Translation translation = LoadTranslation(key); - - if (translation == null) - { - return null; - } - - value = translation.Value; - Cache.TryAdd(key, value); - } - - if (tokens != null) - { - value = TokenReplacement.Apply(value, tokens); - } - - return value; - } - - - /// - public string Text(T enumValue) where T : Enum => Text(enumValue, null); - - - /// - public string Text(T enumValue, Dictionary tokens) where T : Enum - { - Type type = enumValue.GetType(); - MemberInfo memInfo = type.GetMember(enumValue.ToString())[0]; - return Text(memInfo.GetCustomAttribute()?.Key, tokens); - } - - - /// - public string Maybe(string key) => Maybe(key, null); - - - /// - public string Maybe(string key, Dictionary tokens) - { - return key.IsNullOrEmpty() || !key.StartsWith("@") ? key : Text(key.Substring(1), tokens); - } - - - /// - /// Get translation from database or any other source - /// - protected virtual Translation LoadTranslation(string key) - { - return Store.Session().Synchronous.Query().FirstOrDefault(x => x.Key == key); - } -} - -public interface ILocalizer -{ - /// - /// - /// - string Text(string key); - - /// - /// - /// - string Text(string key, Dictionary tokens); - - /// - /// Get a text string from a [Localize] attribute - /// - string Text(T enumValue) where T : Enum; - - /// - /// Get a text string from a [Localize] attribute - /// - string Text(T enumValue, Dictionary tokens) where T : Enum; - - /// - /// Only tries to resolve the key when it is prefixed with an @ - /// - string Maybe(string key); - - /// - /// Only tries to resolve the key when it is prefixed with an @ - /// - string Maybe(string key, Dictionary tokens); -} diff --git a/zero.Core/Localization/LocalizerExtensions.cs b/zero.Core/Localization/LocalizerExtensions.cs deleted file mode 100644 index 256cd417..00000000 --- a/zero.Core/Localization/LocalizerExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.AspNetCore.Html; - -namespace zero.Localization; - -public static class LocalizerExtensions -{ - public static IHtmlContent Html(this ILocalizer localizer, string key) - { - string value = localizer.Text(key); - - HtmlContentBuilder builder = new(); - builder.SetHtmlContent(value); - return builder; - } - - - public static IHtmlContent Html(this ILocalizer localizer, string key, Dictionary tokens) - { - string value = localizer.Text(key, tokens); - - HtmlContentBuilder builder = new(); - builder.SetHtmlContent(value); - return builder; - } -} \ No newline at end of file diff --git a/zero.Core/Localization/Models/Country.cs b/zero.Core/Localization/Models/Country.cs deleted file mode 100644 index 4323ad0a..00000000 --- a/zero.Core/Localization/Models/Country.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace zero.Localization; - -[RavenCollection("Countries")] -public class Country : ZeroEntity -{ - /// - /// Preferred countries are displayed on top in lists - /// - public bool IsPreferred { get; set; } - - /// - /// Country code (ISO 3166-1) - /// - public string Code { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Localization/Models/Culture.cs b/zero.Core/Localization/Models/Culture.cs deleted file mode 100644 index eed5ff88..00000000 --- a/zero.Core/Localization/Models/Culture.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace zero.Localization; - -public class Culture -{ - public string Code { get; set; } - - public string Name { get; set; } -} diff --git a/zero.Core/Localization/Models/Language.cs b/zero.Core/Localization/Models/Language.cs deleted file mode 100644 index ffcd0cd4..00000000 --- a/zero.Core/Localization/Models/Language.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace zero.Localization; - -[RavenCollection("Languages")] -public class Language : ZeroEntity, IAlwaysActive -{ - /// - /// Language code (ISO 3166-1) - /// - public string Code { get; set; } - - /// - /// Whether this is the default language - /// - public bool IsDefault { get; set; } - - /// - /// Whether this language is optional and does not have to be filled out - /// - public bool IsOptional { get; set; } - - /// - /// If this language is inherited it gets all missing properties from its parent - /// - public string InheritedLanguageId { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Localization/Models/Translation.cs b/zero.Core/Localization/Models/Translation.cs deleted file mode 100644 index 0d893918..00000000 --- a/zero.Core/Localization/Models/Translation.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace zero.Localization; - -[RavenCollection("Translations")] -public class Translation : ZeroEntity, IAlwaysActive -{ - public Translation() - { - IsActive = true; - } - - /// - /// Value of the translation - /// - public string Value { get; set; } - - /// - /// Display + input type - /// - public TranslationDisplay Display { get; set; } -} - - -public enum TranslationDisplay -{ - Text = 0, - HTML = 1 -} \ No newline at end of file diff --git a/zero.Core/Localization/TranslationStore.cs b/zero.Core/Localization/TranslationStore.cs deleted file mode 100644 index 74368243..00000000 --- a/zero.Core/Localization/TranslationStore.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace zero.Localization; - -public class TranslationStore : EntityStore, ITranslationStore -{ - public TranslationStore(IStoreContext context) : base(context) { } - - - /// - public async Task LoadString(string id) - { - return (await Load(id))?.Value; - } -} - - -public interface ITranslationStore : IEntityStore -{ - /// - /// Get a translated string by id - /// - Task LoadString(string id); -} \ No newline at end of file diff --git a/zero.Core/Localization/TranslationValidator.cs b/zero.Core/Localization/TranslationValidator.cs deleted file mode 100644 index e6fc3317..00000000 --- a/zero.Core/Localization/TranslationValidator.cs +++ /dev/null @@ -1,12 +0,0 @@ -using FluentValidation; - -namespace zero.Localization; - -public class TranslationValidator : ZeroValidator -{ - public TranslationValidator(IZeroStore store) - { - RuleFor(x => x.Key).NotEmpty().Length(2, 300).Unique(store); - RuleFor(x => x.Value).MaximumLength(10 * 1000); - } -} \ No newline at end of file diff --git a/zero.Core/Localization/ZeroLocalizationModule.cs b/zero.Core/Localization/ZeroLocalizationModule.cs deleted file mode 100644 index a064f212..00000000 --- a/zero.Core/Localization/ZeroLocalizationModule.cs +++ /dev/null @@ -1,39 +0,0 @@ -using FluentValidation; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Localization; - -internal class ZeroLocalizationModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - services.AddScoped, CountryValidator>(); - services.AddScoped, LanguageValidator>(); - services.AddScoped, TranslationValidator>(); - - services.Configure(opts => - { - opts.Map("fth-map-pin").Fields().Display((x, res) => - { - res.Url = "/settings/countries/edit/" + x.Id; - }); - opts.Map("fth-globe").Fields().Display((x, res) => - { - res.Url = "/settings/languages/edit/" + x.Id; - }); - opts.Map("fth-type").Fields("Value").Display((x, res) => - { - res.Description = x.Value; - res.Url = "/settings/translations/edit/" + x.Id; - }); - }); - } -} \ No newline at end of file diff --git a/zero.Core/Mails/FileMailDispatcher.cs b/zero.Core/Mails/FileMailDispatcher.cs deleted file mode 100644 index b7843b9c..00000000 --- a/zero.Core/Mails/FileMailDispatcher.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Newtonsoft.Json; -using System.IO; -using System.Text; - -namespace zero.Mails; - -/// -/// Default implementation of an IMailSender which sends the mail a flat file -/// and therefore not using the SMTP channel. -/// Implementing real mail sending is up to the consuming application. -/// -public class FileMailDispatcher : IMailDispatcher -{ - protected Queue Queue { get; private set; } = new Queue(); - - protected IPaths PathResolver { get; private set; } - - string MailDirectory; - - - public FileMailDispatcher(IPaths pathResolver) - { - PathResolver = pathResolver; - MailDirectory = "mails"; - } - - - /// - public void Enqueue(Mail message) - { - Queue.Enqueue(message); - } - - - /// - public async Task Send(CancellationToken token = default) - { - if (Queue.Count < 1) - { - return; - } - - string folder = PathResolver.Map(MailDirectory); - PathResolver.Create(folder); - - while (Queue.Count > 0) - { - Mail message = Queue.Dequeue(); - string content = JsonConvert.SerializeObject(message, new JsonSerializerSettings() - { - Formatting = Formatting.Indented, - TypeNameHandling = TypeNameHandling.None, - ReferenceLoopHandling = ReferenceLoopHandling.Ignore - }); - - string filename = Safenames.File(DateTimeOffset.Now.ToString("yyyy-MM-dd-HH-mm-ss") + "_" + message.To[0].Address + ".txt"); - string path = PathResolver.Map(folder, filename); - - await File.WriteAllTextAsync(path, content, token); - } - } - - - /// - public void Dispose() { } - - - /// - /// Creats the file content from a mail message - /// - string CreateContent(Mail message) - { - StringBuilder text = new(); - text.AppendLine("To: " + message.To); - text.AppendLine("CC: " + message.CC); - text.AppendLine("Bcc: " + message.Bcc); - text.AppendLine("From: " + message.From); - text.AppendLine("Subject: " + message.Subject); - text.Append("Date: " + DateTimeOffset.UtcNow.ToString()); - - text.AppendLine(); - text.AppendLine("=============== BODY ==============="); - text.AppendLine(); - - text.AppendLine(message.Body); - - return text.ToString(); - } -} diff --git a/zero.Core/Mails/IMailDispatcher.cs b/zero.Core/Mails/IMailDispatcher.cs deleted file mode 100644 index 4f1d95d1..00000000 --- a/zero.Core/Mails/IMailDispatcher.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace zero.Mails; - -public interface IMailDispatcher : IDisposable -{ - /// - /// Adds a new mail message to the outgoing queue - /// - void Enqueue(Mail message); - - /// - /// Sends all mails which have been added to the queue previously - /// - Task Send(CancellationToken token = default); - - /// - /// Whether a certain sender signature is supported by this dispatcher - /// - Task IsSenderSupported(string email) => Task.FromResult(true); -} \ No newline at end of file diff --git a/zero.Core/Mails/LoggerMailDispatcher.cs b/zero.Core/Mails/LoggerMailDispatcher.cs deleted file mode 100644 index 9fb4f5e2..00000000 --- a/zero.Core/Mails/LoggerMailDispatcher.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace zero.Mails; - -/// -/// Default implementation of an IMailSender which sends the mail to the attached logger -/// and therefore not using the SMTP channel. -/// Implementing real mail sending is up to the consuming application. -/// -public class LoggerMailDispatcher : IMailDispatcher -{ - protected Queue Queue { get; private set; } = new Queue(); - - protected ILogger Logger { get; set; } - - - public LoggerMailDispatcher(ILogger logger) - { - Logger = logger; - } - - - /// - public void Enqueue(Mail message) - { - Queue.Enqueue(message); - } - - - /// - public Task Send(CancellationToken token = default) - { - while (Queue.Count > 0) - { - Mail message = Queue.Dequeue(); - Logger.LogInformation("Mail to {to}. Subject: {subject}", message.To[0].Address, message.Subject); - } - - return Task.CompletedTask; - } - - - /// - public void Dispose() { } -} \ No newline at end of file diff --git a/zero.Core/Mails/Mail.cs b/zero.Core/Mails/Mail.cs deleted file mode 100644 index 1f5c6bcf..00000000 --- a/zero.Core/Mails/Mail.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Net.Mail; - -namespace zero.Mails; - -public class Mail : Mail where T : class -{ - public T Model { get; set; } -} - -public class Mail : MailMessage -{ - public bool IsDeactivated { get; set; } - - public string ViewPath { get; set; } - - public bool HasView { get; set; } = true; - - public bool IsRendered { get; set; } - - public string Preheader { get; set; } - - public MailTemplate Template { get; set; } - - public MailPlaceholders Placeholders { get; set; } = new(); - - public MailMetadata Metadata { get; set; } = new(); -} \ No newline at end of file diff --git a/zero.Core/Mails/MailMetadata.cs b/zero.Core/Mails/MailMetadata.cs deleted file mode 100644 index 2b6d227e..00000000 --- a/zero.Core/Mails/MailMetadata.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace zero.Mails; - -public class MailMetadata : Dictionary -{ - -} diff --git a/zero.Core/Mails/MailOptions.cs b/zero.Core/Mails/MailOptions.cs deleted file mode 100644 index 8adfe6da..00000000 --- a/zero.Core/Mails/MailOptions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace zero.Mails; - -public class MailOptions -{ - public Func BuildViewPath { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Mails/MailPlaceholders.cs b/zero.Core/Mails/MailPlaceholders.cs deleted file mode 100644 index 799f661e..00000000 --- a/zero.Core/Mails/MailPlaceholders.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace zero.Mails; - -public class MailPlaceholders : Dictionary -{ - -} \ No newline at end of file diff --git a/zero.Core/Mails/MailProvider.cs b/zero.Core/Mails/MailProvider.cs deleted file mode 100644 index 1ab22f57..00000000 --- a/zero.Core/Mails/MailProvider.cs +++ /dev/null @@ -1,208 +0,0 @@ -using Microsoft.Extensions.Logging; -using System.Net.Mail; -using System.Text; -using System.Text.RegularExpressions; - -namespace zero.Mails; - -public class MailProvider : IMailProvider -{ - protected IMailTemplatesStore Collection { get; set; } - - protected ILogger Logger { get; set; } - - protected IZeroContext Zero { get; set; } - - protected IMailDispatcher MailSender { get; set; } - - protected IRazorRenderer Renderer { get; set; } - - protected Regex PlaceholderRegex { get; set; } - - private Encoding encoding = Encoding.UTF8; - - - public MailProvider(IZeroContext zero, IMailTemplatesStore collection, ILogger logger, IMailDispatcher mailSender, IRazorRenderer renderer) - { - Zero = zero; - Collection = collection; - Logger = logger; - MailSender = mailSender; - Renderer = renderer; - PlaceholderRegex = new Regex("{([\\w-_.]+)}", RegexOptions.IgnoreCase); - } - - - /// - public virtual async Task Create(string mailTemplateKey, Action onCreate = null) where T : Mail, new() - { - MailTemplate template = await GetMailTemplate(mailTemplateKey); - - if (template == null) - { - Logger.LogError("Could not find a mail template with the key {key}", mailTemplateKey); - return null; - } - - onCreate?.Invoke(template); - - return Merge(new T(), template); - } - - - /// - public virtual async Task Create(string mailTemplateKey, Action onCreate = null) - { - MailTemplate template = await GetMailTemplate(mailTemplateKey); - - if (template == null) - { - Logger.LogError("Could not find a mail template with the key {key}", mailTemplateKey); - return null; - } - - onCreate?.Invoke(template); - - return Merge(new Mail(), template); - } - - - /// - public virtual async Task Send(Mail message, CancellationToken token = default) - { - await Send(message, MailSender, token); - } - - - /// - public virtual async Task Send(Mail message, IMailDispatcher dispatcher, CancellationToken token = default) - { - if (message.IsDeactivated) - { - return; - } - - try - { - await Render(message); - dispatcher.Enqueue(message); - await dispatcher.Send(token); - Logger.LogInformation("Dispatched email (template: {template}) to {recipient} (cc: {cc}, bcc: {bcc})", message.Template?.Alias ?? "none", message.To, message.CC, message.Bcc); - } - catch (Exception ex) - { - Logger.LogError(ex, "Failed to send mail message with template @{template}", message.Template.Key); - } - } - - - - protected virtual async Task GetMailTemplate(string key) - { - MailTemplate mailTemplate = await Collection.GetByKey(key); - - if (mailTemplate != null) - { - mailTemplate = ObjectCopycat.Clone(mailTemplate); - } - - return mailTemplate; - } - - - /// - public virtual async Task Render(Mail message) - { - message.Subject = TokenReplacement.Apply(message.Subject, message.Placeholders); - message.Body = TokenReplacement.Apply(message.Body, message.Placeholders); - message.Preheader = TokenReplacement.Apply(message.Preheader, message.Placeholders); - - if (!message.HasView) - { - message.IsRendered = true; - return message.Body; - } - - string viewPath = message.ViewPath; - - if (viewPath.IsNullOrEmpty()) - { - viewPath = Zero.Options.For().BuildViewPath(message); - } - - message.Body = await Renderer.ViewAsync(viewPath, message); - message.IsBodyHtml = true; - message.IsRendered = true; - return message.Body; - } - - - protected virtual T Merge(T mail, MailTemplate template) where T : Mail - { - mail.Template = template; - mail.IsDeactivated = !mail.Template.IsActive; - - // get sender from template or fall back to application - mail.From = new MailAddress(template.SenderEmail.Or(Zero.Application.Email), template.SenderName.Or(Zero.Application.FullName), encoding); - mail.Sender = mail.From; - mail.ReplyToList.Add(mail.From); - - // cc + bcc (multiple addresses are separated with commas - if (!template.Cc.IsNullOrWhiteSpace()) - { - mail.CC.Add(template.Cc); - } - if (!template.Bcc.IsNullOrWhiteSpace()) - { - mail.Bcc.Add(template.Bcc); - } - - // recipient - if (!template.RecipientEmail.IsNullOrWhiteSpace()) - { - mail.To.Add(template.RecipientEmail); - } - - // subject - mail.Subject = template.Subject; - mail.SubjectEncoding = encoding; - - // body - mail.Body = mail.Template.Body; - mail.IsBodyHtml = true; - mail.BodyEncoding = encoding; - mail.Preheader = mail.Template.Preheader; - - return mail; - } -} - - -public interface IMailProvider -{ - /// - /// Creates a maling from a template - /// - Task Create(string mailTemplateKey, Action onCreate = null); - - /// - /// Creates a maling from a template - /// - Task Create(string mailTemplateKey, Action onCreate = null) where T : Mail, new(); - - /// - /// Renders the message body. - /// This is automatically called when sending messages. - /// - Task Render(Mail message); - - /// - /// Sends a message with the default dispatcher - /// - Task Send(Mail message, CancellationToken token = default); - - /// - /// Sends a message with the specified dispatcher - /// - Task Send(Mail message, IMailDispatcher dispatcher, CancellationToken token = default); -} diff --git a/zero.Core/Mails/MailSendResult.cs b/zero.Core/Mails/MailSendResult.cs deleted file mode 100644 index ede3721d..00000000 --- a/zero.Core/Mails/MailSendResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace zero.Mails; - -public class MailSendResult -{ - public string Alias { get; set; } - - public DateTimeOffset LastRunDate { get; set; } - - public MailSendResult() { } -} diff --git a/zero.Core/Mails/MailTemplateValidator.cs b/zero.Core/Mails/MailTemplateValidator.cs deleted file mode 100644 index 08facc44..00000000 --- a/zero.Core/Mails/MailTemplateValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FluentValidation; - -namespace zero.Mails; - -public class MailTemplateValidator : ZeroValidator -{ - public MailTemplateValidator(IZeroStore store, IMailDispatcher dispatcher = null) - { - RuleFor(x => x.Name).NotEmpty().Length(2, 80); - RuleFor(x => x.Key).NotEmpty().Unique(store); - RuleFor(x => x.Subject).NotEmpty().Length(2, 120); - RuleFor(x => x.Preheader).MaximumLength(240); - - RuleFor(x => x.SenderEmail).Email(); - RuleFor(x => x.SenderName).MaximumLength(120); - - RuleFor(x => x.RecipientEmail).Emails(); - RuleFor(x => x.Cc).Emails(); - RuleFor(x => x.Bcc).Emails(); - - if (dispatcher != null) - { - RuleFor(x => x.SenderEmail).MustAsync(async (value, ct) => - { - return await dispatcher.IsSenderSupported(value); - }).WithMessage("@mailTemplate.errors.senderNotAllowed"); - } - } -} \ No newline at end of file diff --git a/zero.Core/Mails/MailTemplatesStore.cs b/zero.Core/Mails/MailTemplatesStore.cs deleted file mode 100644 index ec902a55..00000000 --- a/zero.Core/Mails/MailTemplatesStore.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Raven.Client.Documents; - -namespace zero.Mails; - -public class MailTemplatesStore : EntityStore, IMailTemplatesStore -{ - public MailTemplatesStore(IStoreContext context) : base(context) { } - - - /// - public async Task GetByKey(string key) - { - return await Session.Query().FirstOrDefaultAsync(x => x.Key == key); - } -} - - -public interface IMailTemplatesStore : IEntityStore -{ - /// - /// Get mail template by associated key - /// - Task GetByKey(string key); -} \ No newline at end of file diff --git a/zero.Core/Mails/Models/MailTemplate.cs b/zero.Core/Mails/Models/MailTemplate.cs deleted file mode 100644 index 48d39f54..00000000 --- a/zero.Core/Mails/Models/MailTemplate.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace zero.Mails; - -[RavenCollection("MailTemplates")] -public class MailTemplate : ZeroEntity, IAlwaysActive -{ - /// - /// Email address of the sender (overrides email from application) - /// - public string SenderEmail { get; set; } - - /// - /// Name of the sender (overrides name from application) - /// - public string SenderName { get; set; } - - /// - /// Email address of the recipient. This is only necessary for templates which do not have a dynamic recipient (e.g. reports). - /// - public string RecipientEmail { get; set; } - - /// - /// Additional comma-separated emails to send a copy to - /// - public string Cc { get; set; } - - /// - /// Additional comma-separated emails to send a hidden copy to - /// - public string Bcc { get; set; } - - /// - /// Email subject (can contain placeholders) - /// - public string Subject { get; set; } - - /// - /// Email body (can contain placeholders) - /// - public string Body { get; set; } - - /// - /// Preheader which is displayed in the preview pane (can contain placeholders) - /// - public string Preheader { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Mails/ZeroMailModule.cs b/zero.Core/Mails/ZeroMailModule.cs deleted file mode 100644 index 58b8871e..00000000 --- a/zero.Core/Mails/ZeroMailModule.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Mails; - -internal class ZeroMailModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - services.AddOptions().Configure(opts => - { - opts.BuildViewPath = mail => $"~/Views/Mails/{mail.Template.Key.Replace('.', '/')}.cshtml"; - }); - - services.Configure(opts => - { - opts.Map("fth-mail").Fields("Key").Display((x, res) => - { - res.Description = x.Key; - res.Url = "/settings/mailtemplates/edit/" + x.Id; - }); - }); - } -} \ No newline at end of file diff --git a/zero.Core/Mapper/MapperExtensions.cs b/zero.Core/Mapper/MapperExtensions.cs deleted file mode 100644 index d5e8160b..00000000 --- a/zero.Core/Mapper/MapperExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace zero.Mapper; - -public static class MapperExtensions -{ - //public static TDestination Register(this IZeroMapper mapper, Action map) where TDestination : class, new() - //{ - - //} - - - //public static TDestination Register(this IZeroMapper mapper, Action map, TDestination destination) - //{ - - //} - - - public static TDestination Map(this IZeroMapper mapper, TSource source, TDestination destination) - { - return mapper.Map(source, typeof(TSource), destination); - } - - public static TDestination Map(this IZeroMapper mapper, TSource source) - { - return mapper.Map(source, typeof(TSource), default); - } - - public static Paged Map(this IZeroMapper mapper, Paged source, Action modify = null) - { - return source.MapTo(srcItem => - { - TDestination destination = mapper.Map(srcItem); - modify?.Invoke(srcItem, destination); - return destination; - }); - } - - public static Result Map(this IZeroMapper mapper, Result source) - { - TDestination model = mapper.Map(source.Model); - return source.ConvertTo(model); - } - - - public static Dictionary Map(this IZeroMapper mapper, Dictionary source) - { - Dictionary model = new(); - - foreach ((string key, TSource sourceItem) in source) - { - model.Add(key, sourceItem == null ? default : mapper.Map(sourceItem)); - } - - return model; - } - - - public static IEnumerable Map(this IZeroMapper mapper, IEnumerable source) - { - return source.Select(x => mapper.Map(x)); - } -} diff --git a/zero.Core/Mapper/ZeroMapper.cs b/zero.Core/Mapper/ZeroMapper.cs deleted file mode 100644 index 9134182b..00000000 --- a/zero.Core/Mapper/ZeroMapper.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System.Collections.Concurrent; - -namespace zero.Mapper; - -public class ZeroMapper : IZeroMapper -{ - /// - /// Concurrent cache for all ctor definitions - /// - protected ConcurrentDictionary>> MapDefinitions { get; private set; } = new(); - - /// - /// Concurrent cache for all constructor definitions - /// - protected ConcurrentDictionary>> ConstructorDefinitions { get; private set; } = new(); - - - public ZeroMapper(IEnumerable profiles) - { - foreach (IMapperProfile profile in profiles) - { - profile.Configure(this); - } - } - - - /// - public void Define(Func ctor, Action map) - { - Type sourceType = typeof(TSource); - Type destinationType = typeof(TDestination); - - var sourceMaps = MapDefinitions.GetOrAdd(sourceType, _ => new()); - var sourceCtors = ConstructorDefinitions.GetOrAdd(sourceType, _ => new()); - - sourceCtors[destinationType] = (source, ctx) => ctor((TSource)source, ctx); - sourceMaps[destinationType] = (source, destination, ctx) => map((TSource)source, (TDestination)destination, ctx); - } - - - /// - public void Override(Func ctor, Action> map) - { - Type sourceType = typeof(TSource); - Type destinationType = typeof(TDestination); - - var sourceMaps = MapDefinitions.GetValueOrDefault(sourceType); - var sourceCtors = ConstructorDefinitions.GetValueOrDefault(sourceType); - - if (sourceMaps == null || sourceCtors == null || !sourceMaps.ContainsKey(destinationType)) - { - return; - } - - var baseMap = sourceMaps[destinationType]; - - sourceCtors[destinationType] = (source, ctx) => ctor((TSource)source, ctx); - sourceMaps[destinationType] = (source, destination, ctx) => map((TSource)source, (TDestination)destination, ctx, (source, destination, ctx) => baseMap(source, destination, ctx)); - } - - - /// - public TDestination Map(object source, Type sourceType, TDestination destination = default) - { - if (source == null) - { - return default; - } - - Type destinationType = typeof(TDestination); - - if (destinationType == source.GetType()) - { - return (TDestination)source; - } - - ZeroMapperContext mapperContext = new(this); - - var constructor = GetConstructor(sourceType, destinationType); - var map = GetMap(sourceType, destinationType); - - if (constructor != null && map != null) - { - destination ??= (TDestination)constructor(source, mapperContext); - - map(source, destination, mapperContext); - - return destination; - } - - // TODO enumerables - - throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {destinationType.FullName}."); - } - - - protected virtual Func GetConstructor(Type sourceType, Type targetType) - { - if (ConstructorDefinitions.TryGetValue(sourceType, out var sourceCtor) && sourceCtor.TryGetValue(targetType, out var ctor)) - { - return ctor; - } - - // we *may* run this more than once but it does not matter - - ctor = null; - foreach (var (stype, sctors) in ConstructorDefinitions) - { - if (!stype.IsAssignableFrom(sourceType)) continue; - if (!sctors.TryGetValue(targetType, out ctor)) continue; - - sourceCtor = sctors; - break; - } - - if (ctor == null) - { - return null; - } - - ConstructorDefinitions.AddOrUpdate(sourceType, sourceCtor, (k, v) => - { - // Add missing constructors - foreach (var c in sourceCtor) - { - if (!v.ContainsKey(c.Key)) - { - v.Add(c.Key, c.Value); - } - } - - return v; - }); - - - return ctor; - } - - protected virtual Action GetMap(Type sourceType, Type targetType) - { - if (MapDefinitions.TryGetValue(sourceType, out var sourceMap) && sourceMap.TryGetValue(targetType, out var map)) - { - return map; - } - - // we *may* run this more than once but it does not matter - - map = null; - foreach (var (stype, smap) in MapDefinitions) - { - if (!stype.IsAssignableFrom(sourceType)) continue; - - // TODO: consider looking for assignable types for target too? - if (!smap.TryGetValue(targetType, out map)) continue; - - sourceMap = smap; - break; - } - - if (map == null) return null; - - if (MapDefinitions.ContainsKey(sourceType)) - { - foreach (var m in sourceMap) - { - if (!MapDefinitions[sourceType].TryGetValue(m.Key, out _)) - { - MapDefinitions[sourceType].Add(m.Key, m.Value); - } - } - } - else - { - MapDefinitions[sourceType] = sourceMap; - } - - return map; - } -} - - -public interface IZeroMapper -{ - /// - /// Register a new mapping - /// - void Define(Func ctor, Action map); - - /// - void Override(Func ctor, Action> map); - - /// - /// Map a source type to the destination type - /// - TDestination Map(object source, Type sourceType, TDestination destination = default); -} \ No newline at end of file diff --git a/zero.Core/Mapper/ZeroMapperContext.cs b/zero.Core/Mapper/ZeroMapperContext.cs deleted file mode 100644 index 31b06070..00000000 --- a/zero.Core/Mapper/ZeroMapperContext.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace zero.Mapper; - -public class ZeroMapperContext : IZeroMapperContext -{ - /// - public IZeroMapper Mapper { get; } - - - public ZeroMapperContext(IZeroMapper mapper) - { - Mapper = mapper; - } -} - - -public interface IZeroMapperContext -{ - /// - /// Access to the runtime mapper - /// - IZeroMapper Mapper { get; } -} diff --git a/zero.Core/Mapper/ZeroMapperModule.cs b/zero.Core/Mapper/ZeroMapperModule.cs deleted file mode 100644 index e94d5ae8..00000000 --- a/zero.Core/Mapper/ZeroMapperModule.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Mapper; - -internal class ZeroMapperModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - } -} \ No newline at end of file diff --git a/zero.Core/Mapper/ZeroMapperProfile.cs b/zero.Core/Mapper/ZeroMapperProfile.cs deleted file mode 100644 index 59e4ee24..00000000 --- a/zero.Core/Mapper/ZeroMapperProfile.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace zero.Mapper; - -public abstract class ZeroMapperProfile : IMapperProfile -{ - /// - public abstract void Configure(IZeroMapper mapper); -} - - -public interface IMapperProfile -{ - /// - /// Configure maps for this profile - /// - void Configure(IZeroMapper mapper); -} \ No newline at end of file diff --git a/zero.Core/Media/Indexes/Media_ByChildren.cs b/zero.Core/Media/Indexes/Media_ByChildren.cs deleted file mode 100644 index 3c91dfa7..00000000 --- a/zero.Core/Media/Indexes/Media_ByChildren.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Raven.Client.Documents.Indexes; - -namespace zero.Media; - -public class Media_ByChildren : ZeroMultiMapIndex -{ - public class Result : ZeroIdEntity, ISupportsDbConventions - { - public string ParentId { get; set; } - - public int ChildrenCount { get; set; } - - public string[] ChildrenIds { get; set; } - } - - - protected override void Create() - { - AddMap(items => items.Select(item => new Result() - { - Id = item.Id, - ParentId = item.ParentId, - ChildrenCount = 1, - ChildrenIds = new string[] { } - })); - - Reduce = results => results.GroupBy(x => new { x.ParentId }).Select(group => new Result() - { - Id = null, - ParentId = group.Key.ParentId, - ChildrenCount = group.Sum(x => x.ChildrenCount), - ChildrenIds = group.Select(x => x.Id).ToArray() - }); - - StoreAllFields(FieldStorage.Yes); - Index(x => x.ParentId, FieldIndexing.Exact); - } -} \ No newline at end of file diff --git a/zero.Core/Media/Indexes/Media_ByHierarchy.cs b/zero.Core/Media/Indexes/Media_ByHierarchy.cs deleted file mode 100644 index 565dff31..00000000 --- a/zero.Core/Media/Indexes/Media_ByHierarchy.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Raven.Client.Documents.Indexes; - -namespace zero.Media; - -public class Media_ByHierarchy : ZeroIndex -{ - public class Result : ZeroIdEntity, ISupportsDbConventions - { - public string Name { get; set; } - - public List Path { get; set; } = new List(); - - public string[] PathIds { get; set; } = Array.Empty(); - } - - - public class PathResult - { - public string Id { get; set; } - - public string Name { get; set; } - } - - - protected override void Create() - { - Map = items => items - .Select(item => new - { - Item = item, - Path = Recurse(item, x => LoadDocument(x.ParentId)).Where(x => x != null && x.Id != null && x.Id != item.Id).Reverse() - }) - .Select(item => new Result - { - Id = item.Item.Id, - Name = item.Item.Name, - Path = item.Path.Select(current => new PathResult() { Id = current.Id, Name = current.Name }).ToList(), - PathIds = item.Path.Select(current => current.Id).ToArray() - }); - - StoreAllFields(FieldStorage.Yes); - Index("PathIds", FieldIndexing.Exact); - //Index(x => x.ChannelId, FieldIndexing.Exact); - } -} diff --git a/zero.Core/Media/MediaCreator.cs b/zero.Core/Media/MediaCreator.cs deleted file mode 100644 index a37f0703..00000000 --- a/zero.Core/Media/MediaCreator.cs +++ /dev/null @@ -1,150 +0,0 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using System.IO; - -namespace zero.Media; - -public class MediaCreator : IMediaCreator -{ - protected IMediaFileSystem FileSystem { get; set; } - - protected IMediaStore Store { get; set; } - - protected MediaOptions Options { get; set; } - - - public MediaCreator(IMediaFileSystem fileSystem, IMediaStore store, IZeroOptions options) - { - FileSystem = fileSystem; - Store = store; - Options = options.For(); - } - - - /// - public async Task> UploadFile(Stream fileStream, string filename, string folderId = null, CancellationToken cancellationToken = default) - { - string fileExtension = Path.GetExtension(filename); - string normalizedFilename = Safenames.File(filename); - - bool isImage = Options.AllowedImageFileExtensions.Contains(fileExtension, StringComparer.InvariantCultureIgnoreCase); - bool isDocument = !isImage && Options.AllowedOtherFileExtensions.Contains(fileExtension, StringComparer.InvariantCultureIgnoreCase); - - if (!isImage && !isDocument) - { - // TODO error - return Result.Fail("ERROR"); - } - - Media model = await Store.Empty(); - model.Name = normalizedFilename; - model.ParentId = folderId; - model.IsFolder = false; - - // create directory which hosts the media file - // the media directory is a flat folder where each folder contains one media file (+ thumbs) - string directory = await CreateDirectory(cancellationToken); - model.FileId = directory; - - // the path is a combination of the folder name + filename, e.g. 129021309123/myfile.jpg - model.Path = directory + '/' + normalizedFilename; - - // store the file in the attached file system - // we do not need to check if the file already exists in the file system - // as we only use newly created directories, therefore a collision shouldn't happen - await FileSystem.CreateFile(model.Path, fileStream, cancellationToken: cancellationToken); - - // we need file metadata to get info about file size and the physical path for image modification - IFileMeta fileInfo = await FileSystem.GetFileInfo(model.Path, cancellationToken); - model.Size = fileInfo.Length; - - if (isImage) - { - using Image image = await Image.LoadAsync(fileInfo.AbsolutePath); - model.ImageMeta = GetImageMetadata(image); - - string extension = Path.GetExtension(model.Path); - - foreach ((string key, ResizeOptions opts) in Options.Thumbnails) - { - Image imageFrame = image.Frames.Count > 1 ? image.Frames.CloneFrame(0) : image.Clone(); - imageFrame.Mutate(x => x.Resize(opts)); - - using MemoryStream stream = new(); - await imageFrame.SaveAsync(stream, new PngEncoder(), cancellationToken); - - stream.Position = 0; - - string thumbFilename = normalizedFilename.TrimEnd(extension) + "." + Safenames.File(key) + ".png"; - string path = directory + '/' + thumbFilename; - - await FileSystem.CreateFile(path, stream, cancellationToken: cancellationToken); - - model.ImageMeta.Thumbnails[key] = path; - } - } - - return Result.Success(model); - } - - - /// - protected virtual MediaImageMetadata GetImageMetadata(Image image) - { - PngMetadata pngMetadata = image.Metadata.GetPngMetadata(); - //WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata(); - - return new MediaImageMetadata() - { - Width = image.Width, - Height = image.Height, - ImageTakenDate = new DateTimeOffset(image.Metadata.IccProfile?.Header?.CreationDate ?? DateTime.Now), - DPI = image.Metadata.HorizontalResolution, - ColorSpace = image.Metadata.IccProfile?.Header?.DataColorSpace.ToString(), - HasTransparency = pngMetadata?.HasTransparency ?? false, - Frames = image.Frames.Count - }; - } - - - /// - /// Create a new directory for a file. - /// This method is collision-aware and repeats until a directory can be created. - /// - protected virtual async Task CreateDirectory(CancellationToken cancellationToken = default) - { - try - { - string directoryName = GetNewDirectoryName(); - await FileSystem.CreateDirectory(directoryName, cancellationToken); - return directoryName; - } - catch (FileSystemException ex) when (ex.Message.Contains("already exists")) - { - return await CreateDirectory(cancellationToken); - } - } - - - /// - /// Builds a directory name for a new media item - /// - protected virtual string GetNewDirectoryName() - { - return Guid.NewGuid().ToString(); - } -} - - - -public interface IMediaCreator -{ - /// - /// Uploads a file by using the attached file system - /// - /// A temporary media file which can be persisted in a store - Task> UploadFile(Stream fileStream, string filename, string folderId = null, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/zero.Core/Media/MediaFileSystem.cs b/zero.Core/Media/MediaFileSystem.cs deleted file mode 100644 index d90dfdde..00000000 --- a/zero.Core/Media/MediaFileSystem.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace zero.Media; - -public class MediaFileSystem : PhysicalFileSystem, IMediaFileSystem -{ - protected string PublicPathPrefix { get; } - - - public MediaFileSystem(string root, string publicPathPrefix) : base(root) - { - PublicPathPrefix = (publicPathPrefix ?? String.Empty).EnsureEndsWith('/'); - } - - - /// - public override string MapToPublicPath(string path) - { - if (path.IsNullOrEmpty()) - { - return null; - } - return PublicPathPrefix + path.TrimStart('/'); - } - - - /// - public bool IsMediaPath(string path) - { - return path.StartsWith(PublicPathPrefix, StringComparison.InvariantCultureIgnoreCase); - } -} - - -public interface IMediaFileSystem : IFileSystem -{ - /// - /// Determine whether the given path is part of the media file system - /// - bool IsMediaPath(string path); -} \ No newline at end of file diff --git a/zero.Core/Media/MediaLinkProvider.cs b/zero.Core/Media/MediaLinkProvider.cs deleted file mode 100644 index 55515ca5..00000000 --- a/zero.Core/Media/MediaLinkProvider.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Raven.Client.Documents.Session; - -namespace zero.Media; - -public class MediaLinkProvider : ILinkProvider -{ - public const string AREA = "zero.media"; - - protected IRoutes Routes { get; set; } - protected IMediaStore Media { get; set; } - protected IMediaManagement MediaManagement { get; set; } - - public MediaLinkProvider(IRoutes routes, IMediaStore media, IMediaManagement mediaManagement) - { - Routes = routes; - Media = media; - MediaManagement = mediaManagement; - } - - - /// - /// Creates a new link object from a media item - /// - public Link Create(Media media, LinkTarget target = LinkTarget.Default, string title = null) - { - return new Link() - { - Area = AREA, - Target = target, - Title = title, - Values = new() - { - { "id", media.Id } - } - }; - } - - - /// - public bool CanProcess(Link link) => link.Area == AREA; - - - /// - public async Task Resolve(Link link) - { - Media media = await Media.Load(link.Values.GetValueOrDefault("id")); - - if (media == null) - { - return null; - } - - link.IsActive = true; - link.Url = MediaManagement.GetPublicFilePath(media); - return link.Url; - } - - - /// - public async Task Preview(IAsyncDocumentSession session, Link link) - { - string id = link.Values.GetValueOrDefault("id"); - - if (id.IsNullOrEmpty()) - { - return null; - } - - Media media = await session.LoadAsync(id); - - if (media == null) - { - return null; - } - - string url = MediaManagement.GetPublicFilePath(media); - - return new() - { - Id = media.Id, - Icon = media.IsFolder ? "fth-folder" : (media.ImageMeta != null ? "fth-image" : "fth-file"), - Name = media.Name, - IsActive = media.IsActive, - Text = url - }; - } -} diff --git a/zero.Core/Media/MediaManagement.cs b/zero.Core/Media/MediaManagement.cs deleted file mode 100644 index db8f6288..00000000 --- a/zero.Core/Media/MediaManagement.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System.IO; - -namespace zero.Media; - -public class MediaManagement : IMediaManagement -{ - protected IMediaFileSystem FileSystem { get; set; } - - protected IMediaStore Store { get; set; } - - protected IMediaCreator Creator { get; set; } - - - public MediaManagement(IMediaFileSystem fileSystem, IMediaStore store, IMediaCreator creator) - { - FileSystem = fileSystem; - Store = store; - Creator = creator; - } - - - - /// - public virtual string GetPublicFilePath(Media file, string thumbnailKey = null) - { - string path = file?.Path; - - if (!thumbnailKey.IsNullOrWhiteSpace()) - { - path = file?.ImageMeta?.Thumbnails?.GetValueOrDefault(thumbnailKey); - } - - if (path.IsNullOrEmpty()) - { - return null; - } - - return FileSystem.MapToPublicPath(path); - } - - - /// - public virtual async Task GetFileStream(Media file, string thumbnailKey = null) - { - if (file == null) - { - return null; - } - - string path = file?.Path; - - if (!thumbnailKey.IsNullOrWhiteSpace()) - { - path = file?.ImageMeta?.Thumbnails?.GetValueOrDefault(thumbnailKey); - } - - if (path.IsNullOrEmpty()) - { - return null; - } - - return await FileSystem.StreamFile(path); - } - - - /// - public virtual async Task GetFile(string id) - { - Media file = await Store.Load(id); - return file != null && !file.IsFolder ? file : null; - } - - - /// - public virtual async Task> UpdateFile(Media file) - { - // TODO check new file/image/media - return await Store.Update(file); - } - - - /// - public virtual async Task> DeleteFile(Media file) - { - // TODO delete in file system - return await Store.Delete(file); - } - - - /// - public virtual async Task> UploadFile(Stream fileStream, string filename, string folderId = null, CancellationToken cancellationToken = default) - { - Result result = await Creator.UploadFile(fileStream, filename, folderId, cancellationToken); - - if (!result.IsSuccess) - { - return result; - } - - return await Store.Create(result.Model); - } - - - /// - public virtual async Task GetFolder(string id) - { - Media folder = await Store.Load(id); - return folder != null && folder.IsFolder ? folder : null; - } - - - /// - public virtual async Task> CreateFolder(Media folder) - { - folder.IsActive = true; - folder.IsFolder = true; - return await Store.Create(folder); - } - - - /// - public virtual async Task> CreateFolder(string name, string parentId = null) - { - Media media = await Store.Empty(); - media.Name = name; - media.ParentId = parentId; - return await CreateFolder(media); - } - - - /// - public virtual async Task> UpdateFolder(Media folder) - { - return await Store.Update(folder); - } - - - /// - public virtual async Task> DeleteFolder(Media folder) - { - // TODO recursive - return await Store.DeleteWithDescendants(folder); - } -} - - -public interface IMediaManagement -{ - /// - /// Get publicly accessible file path for a media file - /// - /// The media file - /// An optional thumbnail key which returns the path to a generated thumbnail - string GetPublicFilePath(Media file, string thumbnailKey = null); - - /// - /// Get file stream for a media file (stream has to be disposed manually) - /// - Task GetFileStream(Media file, string thumbnailKey = null); - - /// - /// Get a media file by id - /// - Task GetFile(string id); - - /// - /// Update and store a media file - /// - Task> UpdateFile(Media file); - - /// - /// Deletes a media file (collection entry as well as physical file) - /// - Task> DeleteFile(Media file); - - /// - /// Uploads a file and persists it - /// - Task> UploadFile(Stream fileStream, string filename, string folderId = null, CancellationToken cancellationToken = default); - - /// - /// Get a media folder by id - /// - Task GetFolder(string id); - - /// - /// Creates a new media folder - /// - Task> CreateFolder(Media folder); - - /// - /// Creates a new media folder - /// - Task> CreateFolder(string name, string parentId = null); - - /// - /// Rename and store a media folder - /// - Task> UpdateFolder(Media folder); - - /// - /// Deletes a folder, as well as all descendant folders and files - /// - Task> DeleteFolder(Media folder); -} diff --git a/zero.Core/Media/MediaManagementExtensions.cs b/zero.Core/Media/MediaManagementExtensions.cs deleted file mode 100644 index c6f80a38..00000000 --- a/zero.Core/Media/MediaManagementExtensions.cs +++ /dev/null @@ -1,120 +0,0 @@ -using Microsoft.AspNetCore.Http; -using System.IO; - -namespace zero.Media -{ - public static class MediaManagementExtensions - { - /// - /// Get publicly accessible file path for a media file - /// - /// ID of a media file - /// An optional thumbnail key which returns the path to a generated thumbnail - public static async Task GetPublicFilePath(this IMediaManagement media, string mediaId, string thumbnailKey = null) - { - Media file = await media.GetFile(mediaId); - if (file == null) - { - return null; - } - return media.GetPublicFilePath(file, thumbnailKey); - } - - /// - /// Get file stream for a media file (stream has to be disposed manually) - /// - public static async Task GetFileStream(this IMediaManagement media, string mediaId, string thumbnailKey = null) - { - Media file = await media.GetFile(mediaId); - if (file == null) - { - return null; - } - return await media.GetFileStream(file, thumbnailKey); - } - - /// - /// Uploads a file and persists it - /// - public static async Task> UploadFile(this IMediaManagement media, IFormFile formFile, string folderId = null, CancellationToken cancellationToken = default) - { - using Stream stream = formFile.OpenReadStream(); - return await media.UploadFile(stream, formFile.FileName, folderId, cancellationToken); - } - - - /// - /// Uploads a file and persists it - /// - public static async Task> UploadFile(this IMediaManagement media, byte[] fileBytes, string filename, string folderId = null, CancellationToken cancellationToken = default) - { - using Stream stream = new MemoryStream(fileBytes); - return await media.UploadFile(stream, filename, folderId, cancellationToken); - } - - - /// - /// Rename and store a media folder - /// - public static async Task> RenameFolder(this IMediaManagement media, Media folder, string newName) - { - folder.Name = newName; - return await media.UpdateFolder(folder); - } - - - /// - /// Rename and store a media folder - /// - public static async Task> RenameFolder(this IMediaManagement media, string folderId, string newName) - { - Media folder = await media.GetFolder(folderId); - if (folder == null) - { - return Result.Fail("@errors.idnotfound"); - } - - return await RenameFolder(media, folder, newName); - } - - - /// - /// Move a media folder to a new parent - /// - public static async Task> MoveFolder(this IMediaManagement media, Media folder, string newParentId) - { - folder.ParentId = newParentId; - return await media.UpdateFolder(folder); - } - - - /// - /// Move a media folder to a new parent - /// - public static async Task> MoveFolder(this IMediaManagement media, string folderId, string newParentId) - { - Media folder = await media.GetFolder(folderId); - if (folder == null) - { - return Result.Fail("@errors.idnotfound"); - } - - return await MoveFolder(media, folder, newParentId); - } - - - /// - /// Deletes a folder by id - /// - public static async Task> DeleteFolder(this IMediaManagement media, string folderId) - { - Media folder = await media.GetFolder(folderId); - if (folder == null) - { - return Result.Fail("@errors.idnotfound"); - } - - return await media.DeleteFolder(folder); - } - } -} diff --git a/zero.Core/Media/MediaOptions.cs b/zero.Core/Media/MediaOptions.cs deleted file mode 100644 index 999124e8..00000000 --- a/zero.Core/Media/MediaOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using SixLabors.ImageSharp.Processing; - -namespace zero.Media; - -public class MediaOptions -{ - public string FolderPath { get; set; } - - public string PublicPathPrefix { get; set; } = string.Empty; - - public List AllowedOtherFileExtensions { get; set; } - - public List AllowedImageFileExtensions { get; set; } - - public Dictionary Thumbnails { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Media/MediaStore.cs b/zero.Core/Media/MediaStore.cs deleted file mode 100644 index b974d057..00000000 --- a/zero.Core/Media/MediaStore.cs +++ /dev/null @@ -1,42 +0,0 @@ -using FluentValidation; - -namespace zero.Media; - -public class MediaStore : TreeEntityStore, IMediaStore -{ - public MediaStore(IStoreContext context) : base(context) { } - - /// - /// A media (either file or folder) can only be saved for the following circumstances: - /// 1. The parent is null (=root) - /// 2. The parent is a folder - /// - public override async Task IsAllowedAsChild(Media model, string parentId) - { - if (parentId.IsNullOrEmpty()) - { - return true; - } - - Media parent = await Load(parentId); - return parent != null && parent.IsFolder; - } - - - /// - protected override void ValidationRules(ZeroValidator validator) - { - validator.RuleFor(x => x.Name).Length(2, 120); - validator.RuleFor(x => x.IsActive).Equal(true); - - //validator.When(x => x.IsFolder, () => - //{ - // validator.RuleFor(x => x.ParentId).Exists(Context.Store); - //}); - } -} - - -public interface IMediaStore : ITreeEntityStore -{ -} \ No newline at end of file diff --git a/zero.Core/Media/Models/Media.cs b/zero.Core/Media/Models/Media.cs deleted file mode 100644 index 13184b15..00000000 --- a/zero.Core/Media/Models/Media.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace zero.Media; - -/// -/// A media file (can contain an image or other media like videos and documents) -/// -[RavenCollection("Media")] -public class Media : ZeroEntity, ISupportsTrees, IAlwaysActive -{ - public Media() - { - IsActive = true; - } - - /// - /// Whether this media item is a folder or a file - /// - public bool IsFolder { get; set; } - - /// - /// Id/name of the phyiscal folder which is stored on disk/cloud - /// - public string FileId { get; set; } - - /// - /// Id of the parent folder - /// - public string ParentId { get; set; } - - /// - /// Additional caption text - /// - public string Caption { get; set; } - - /// - /// Path of the media item - /// - public string Path { get; set; } - - /// - /// Filesize in bytes - /// - public long Size { get; set; } - - /// - /// Meta data for images - /// - public MediaImageMetadata ImageMeta { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Media/Models/MediaFocalPoint.cs b/zero.Core/Media/Models/MediaFocalPoint.cs deleted file mode 100644 index 6a89bc3b..00000000 --- a/zero.Core/Media/Models/MediaFocalPoint.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace zero.Media; - -/// -/// The focal point sets the point of interest in an image with x/y coordinates from 0-1 -/// -public class MediaFocalPoint -{ - public decimal Left { get; set; } - - public decimal Top { get; set; } - - public bool NotDefault() => Left != 0.5m || Top != 0.5m; -} diff --git a/zero.Core/Media/Models/MediaImageMetadata.cs b/zero.Core/Media/Models/MediaImageMetadata.cs deleted file mode 100644 index 9862869b..00000000 --- a/zero.Core/Media/Models/MediaImageMetadata.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace zero.Media; - -/// -/// Metadata for images -/// -public class MediaImageMetadata -{ - /// - /// Alternative text which is used when the image can't be loaded - /// - public string AlternativeText { get; set; } - - /// - /// Define custom thumbnails which are generated on upload - /// (see IZeroOptions.For().Thumbnails) - /// - public Dictionary Thumbnails { get; set; } = new(); - - /// - /// Optional focal point for an image - /// - public MediaFocalPoint FocalPoint { get; set; } - - /// - /// Width in pixels - /// - public int Width { get; set; } - - /// - /// Height in pixels - /// - public int Height { get; set; } - - /// - /// Resolution factor - /// - public double DPI { get; set; } - - /// - /// Date the image was taken - /// - public DateTimeOffset? ImageTakenDate { get; set; } - - /// - /// Original color space of the image - /// - public string ColorSpace { get; set; } - - /// - /// Whether this image contains transparent pixels - /// - public bool HasTransparency { get; set; } - - /// - /// How many frames contains this image (for animation) - /// - public int Frames { get; set; } = 1; -} \ No newline at end of file diff --git a/zero.Core/Media/Models/MediaListItem.cs b/zero.Core/Media/Models/MediaListItem.cs deleted file mode 100644 index 6d7d92cc..00000000 --- a/zero.Core/Media/Models/MediaListItem.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace zero.Media; - -public class MediaListItem : ZeroIdEntity -{ - public string ParentId { get; set; } - - public string Name { get; set; } - - public DateTimeOffset CreatedDate { get; set; } - - public bool IsFolder { get; set; } - - public string Image { get; set; } - - public long Size { get; set; } - - public int Children { get; set; } - - public bool HasTransparency { get; set; } - - public float AspectRatio { get; set; } - - public bool IsShared { get; set; } -} diff --git a/zero.Core/Media/Models/Video.cs b/zero.Core/Media/Models/Video.cs deleted file mode 100644 index 648c2f41..00000000 --- a/zero.Core/Media/Models/Video.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace zero.Media; - -public class Video -{ - public VideoProvider Provider { get; set; } - - public string VideoId { get; set; } - - public string VideoUrl { get; set; } - - public string VideoPreviewImageUrl { get; set; } - - public string Title { get; set; } - - public string PreviewImageId { get; set; } -} - - -public enum VideoProvider -{ - Html, - Youtube, - Vimeo -} diff --git a/zero.Core/Media/ZeroMediaModule.cs b/zero.Core/Media/ZeroMediaModule.cs deleted file mode 100644 index f9f9ab95..00000000 --- a/zero.Core/Media/ZeroMediaModule.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using SixLabors.ImageSharp.Processing; -using System.IO; - -namespace zero.Media; - -internal class ZeroMediaModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddSingleton(svc => - { - IOptions options = svc.GetRequiredService>(); - IWebHostEnvironment env = svc.GetRequiredService(); - return new(Path.Combine(env.WebRootPath, options.Value.FolderPath), options.Value.PublicPathPrefix + options.Value.FolderPath.EnsureStartsWith('/')); - }); - - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - services.AddOptions().Bind(configuration.GetSection("Zero:Media")).Configure(opts => - { - opts.FolderPath = "uploads"; - opts.AllowedOtherFileExtensions = new() { ".pdf", ".docx", ".doc" }; - opts.AllowedImageFileExtensions = new() { ".jpg", ".jpeg", ".png", ".bmp", ".webp", ".gif" }; - opts.Thumbnails = new() - { - { "thumb", new ResizeOptions() { Size = new(100, 100), Mode = ResizeMode.Max } }, - { "preview", new ResizeOptions() { Size = new(210, 210), Mode = ResizeMode.Min } } - }; - }); - - services.Configure(opts => - { - RavenOptions raven = opts.For(); - raven.Indexes.Add(); - raven.Indexes.Add(); - }); - - //services.Configure(opts => - //{ - // // TODO should we use MediaType here? - // opts.Provide("zero.media", "@media.name", icon: "fth-image"); - //}); - } -} \ No newline at end of file diff --git a/zero.Core/Models/IAlwaysActive.cs b/zero.Core/Models/IAlwaysActive.cs deleted file mode 100644 index 34f7dbce..00000000 --- a/zero.Core/Models/IAlwaysActive.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace zero.Models; - -/// -/// Entities decorated with this interface are always set to IsActive=true -/// -public interface IAlwaysActive -{ - /// - /// Whether the entity is visible in the frontend - /// - bool IsActive { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Models/ISupportsFlavors.cs b/zero.Core/Models/ISupportsFlavors.cs deleted file mode 100644 index aac608cd..00000000 --- a/zero.Core/Models/ISupportsFlavors.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace zero.Models; - -public interface ISupportsFlavors -{ - /// - /// Id of the entity - /// - string Id { get; set; } - - /// - /// Alias of the used flavor - /// - string Flavor { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Models/ISupportsRouting.cs b/zero.Core/Models/ISupportsRouting.cs deleted file mode 100644 index 4189bbb3..00000000 --- a/zero.Core/Models/ISupportsRouting.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace zero.Models; - -public interface ISupportsRouting -{ - /// - /// Id of the entity - /// - string Id { get; set; } - - /// - /// Unique hash for this entity (primarily used for routing) - /// - string Hash { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Models/ISupportsSorting.cs b/zero.Core/Models/ISupportsSorting.cs deleted file mode 100644 index 166998cc..00000000 --- a/zero.Core/Models/ISupportsSorting.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace zero.Models; - -public interface ISupportsSorting -{ - /// - /// Sort order - /// - uint Sort { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Models/ISupportsTrees.cs b/zero.Core/Models/ISupportsTrees.cs deleted file mode 100644 index 437433ac..00000000 --- a/zero.Core/Models/ISupportsTrees.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace zero.Models; - -public interface ISupportsTrees -{ - /// - /// Id of the entity - /// - string Id { get; set; } - - /// - /// Parent id - /// - string ParentId { get; set; } - - /// - /// Sort order - /// - uint Sort { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Models/Results/Paged.cs b/zero.Core/Models/Results/Paged.cs deleted file mode 100644 index d78ed5ad..00000000 --- a/zero.Core/Models/Results/Paged.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace zero.Models; - -public class Paged : Paged -{ - public IList Items { get; set; } = new List(); - - public Paged(IList items, long totalItems, long pageNumber, long pageSize) : base(totalItems, pageNumber, pageSize) - { - Items = items; - } - - public Paged(long totalItems, long pageNumber, long pageSize) : base(totalItems, pageNumber, pageSize) { } - - public Paged MapTo(Func convertItem) - { - return new Paged(Items.Select(x => convertItem(x)).Where(x => x != null).ToList(), TotalItems, Page, PageSize); - } - - public override IEnumerable GetItems() => Items; -} - - -public abstract class Paged -{ - public long Page { get; protected set; } - - public long PageSize { get; protected set; } - - public long TotalPages { get; protected set; } - - public long TotalItems { get; protected set; } - - public bool HasMore { get; protected set; } - - public Dictionary Properties { get; set; } = new(); - - - public Paged(long totalItems, long page, long pageSize) - { - TotalItems = totalItems; - Page = page; - PageSize = pageSize; - - if (pageSize > 0) - { - TotalPages = (long)Math.Ceiling(totalItems / (decimal)pageSize); - } - else - { - TotalPages = 1; - } - - HasMore = TotalPages > Page; - } - - public int GetSkipSize() - { - if (Page > 0 && PageSize > 0) - { - return Convert.ToInt32((Page - 1) * PageSize); - } - return 0; - } - - public virtual IEnumerable GetItems() => Array.Empty(); -} \ No newline at end of file diff --git a/zero.Core/Models/Results/Result.cs b/zero.Core/Models/Results/Result.cs deleted file mode 100644 index 90efb299..00000000 --- a/zero.Core/Models/Results/Result.cs +++ /dev/null @@ -1,114 +0,0 @@ -using FluentValidation.Results; -using System.Runtime.Serialization; - -namespace zero.Models; - - -[DataContract(Name = "result", Namespace = "")] -public class Result -{ - [DataMember(Name = "success")] - public bool IsSuccess { get; set; } - - [DataMember(Name = "errors")] - public List Errors { get; set; } = new(); - - public Result() { } - - public Result(ValidationResult validation) - { - IsSuccess = validation.IsValid; - Errors = validation.Errors.Select(x => new ResultError() - { - Property = x.PropertyName, - Message = x.ErrorMessage - }).ToList(); - } - - public virtual object GetModel() => null; - - public static Result Maybe(bool isSuccess) => isSuccess ? Success() : Fail(); - - public static Result Success() => new() { IsSuccess = true }; - - public static Result Fail() => new() { }; - - public static Result Fail(string property, string message) - { - Result result = new(); - result.Errors.Add(new(property, message)); - return result; - } - - public static Result Fail(string message) - { - Result result = new(); - result.AddError(message); - return result; - } - - public static Result Fail(ValidationResult validation) => new(validation); - - public static Result Fail(ResultError error) => Fail(error.Property, error.Message); - - public static Result Fail(IEnumerable errors) => new() { IsSuccess = !errors.Any(), Errors = errors.ToList() }; -} - - -public class Result : Result -{ - [DataMember(Name = "model")] - public T Model { get; set; } - - public Result() : base() { } - - public Result(ValidationResult validation) : base(validation) { } - - - public new static Result Success() => new() { IsSuccess = true }; - - public static Result Success(T model) => new() { IsSuccess = true, Model = model }; - - public new static Result Fail() => new() { }; - - public new static Result Fail(string property, string message) - { - Result result = new(); - result.Errors.Add(new(property, message)); - return result; - } - - public new static Result Fail(string message) - { - Result result = new(); - result.AddError(message); - return result; - } - - public new static Result Fail(ValidationResult validation) => new(validation); - - public new static Result Fail(ResultError error) => Fail(error.Property, error.Message); - - public new static Result Fail(IEnumerable errors) => new() { IsSuccess = !errors.Any(), Errors = errors.ToList() }; - - public override object GetModel() => Model; -} - - -[DataContract(Name = "resultError", Namespace = "")] -public class ResultError -{ - [DataMember(Name = "property")] - public string Property { get; set; } - - [DataMember(Name = "message")] - public string Message { get; set; } - - public ResultError() { } - - public ResultError(string property, string message) - { - Property = property; - Message = message; - } -} diff --git a/zero.Core/Models/Results/UrlsResult.cs b/zero.Core/Models/Results/UrlsResult.cs deleted file mode 100644 index 212c52ec..00000000 --- a/zero.Core/Models/Results/UrlsResult.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace zero.Models; - -public class UrlResult -{ - public string Url { get; set; } - - public string Domain { get; set; } - - public UrlResult(string domain, string url) - { - Domain = domain; - Url = url; - } - - public UrlResult(Uri domain, string url) - { - if (domain != null) - { - Domain = domain.Scheme + "://" + domain.Authority.TrimEnd("/"); - } - Url = url; - } -} - -public class UrlsResult -{ - public string[] Urls { get; set; } = Array.Empty(); - - public string Domain { get; set; } - - public UrlsResult(string domain, params string[] urls) - { - Domain = domain; - Urls = urls.Where(x => x.HasValue()).ToArray(); - } - - public UrlsResult(Uri domain, params string[] urls) - { - if (domain != null) - { - Domain = domain.Scheme + "://" + domain.Authority.TrimEnd("/"); - } - Urls = urls.Where(x => x.HasValue()).ToArray(); - } -} - - -public class PreviewUrlResult -{ - public string Url { get; set; } - - public PreviewUrlResult(string url) - { - Url = url; - } -} \ No newline at end of file diff --git a/zero.Core/Models/ZeroEntity.cs b/zero.Core/Models/ZeroEntity.cs deleted file mode 100644 index 85a06436..00000000 --- a/zero.Core/Models/ZeroEntity.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Newtonsoft.Json; -using System.Diagnostics; - -namespace zero.Models; - -[DebuggerDisplay("Id = {Id,nq}, Name = {Name}, Alias = {Alias}")] -public class ZeroEntity : ZeroIdEntity, ISupportsDbConventions, ISupportsRouting, ISupportsFlavors, ISupportsSorting -{ - /// - /// Full name of the entity - /// - public string Name { get; set; } - - /// - /// Alias (non-unique) which can be used in the frontend and URLs - /// - public string Alias { get; set; } - - /// - /// A key which can be used to query this entity in code - /// - public string Key { get; set; } - - /// - /// Sort order - /// - public uint Sort { get; set; } - - /// - /// Whether the entity is visible in the frontend - /// - public bool IsActive { get; set; } - - /// - /// Unique hash for this entity (primarily used for routing) - /// - public string Hash { get; set; } - - /// - /// Backoffice user who last modified this content - /// - public string LastModifiedById { get; set; } - - /// - /// Date of last modification - /// - public DateTimeOffset LastModifiedDate { get; set; } - - /// - /// Backoffice user who created this content - /// - public string CreatedById { get; set; } - - /// - /// Date of creation - /// - public DateTimeOffset CreatedDate { get; set; } - - /// - /// Configuration of the base entity (which this one inherits from) - /// - public BlueprintConfiguration Blueprint { get; set; } - - /// - /// Language of the entity - /// - public string LanguageId { get; set; } - - /// - /// Alias of the used flavor (uses default if empty) - /// - public string Flavor { get; set; } - - /// - /// Additional properties for this entity - /// - public Dictionary Properties { get; set; } = new(); - - /// - /// [Warning] This field is always empty when bound to the database. - /// It is only filled in the app-code for routing. - /// - [JsonIgnore] - public string Url { get; set; } -} - \ No newline at end of file diff --git a/zero.Core/Models/ZeroEntityPreview.cs b/zero.Core/Models/ZeroEntityPreview.cs deleted file mode 100644 index 455b26e7..00000000 --- a/zero.Core/Models/ZeroEntityPreview.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace zero.Models; - -[RavenCollection("Previews")] -public class ZeroEntityPreview : ZeroEntity -{ - /// - /// Id of the original entity - /// - public string OriginalId { get; set; } - - /// - /// Contains the entity content - /// - public ZeroEntity Content { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Models/ZeroIdEntity.cs b/zero.Core/Models/ZeroIdEntity.cs deleted file mode 100644 index 8dec8272..00000000 --- a/zero.Core/Models/ZeroIdEntity.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace zero.Models; - -public class ZeroIdEntity -{ - /// - /// Id of the entity - /// - public string Id { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Models/ZeroReference.cs b/zero.Core/Models/ZeroReference.cs deleted file mode 100644 index a8808e26..00000000 --- a/zero.Core/Models/ZeroReference.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace zero.Models; - -public class ZeroReference -{ - public ZeroReference() { } - - public ZeroReference(ZeroEntity entity) - { - Id = entity.Id; - Name = entity.Name; - } - - public static ZeroReference From(ZeroEntity entity) - { - return entity == null ? null : new ZeroReference(entity); - } - - public static ZeroReference From(T entity, Func transform) where T : ZeroEntity - { - return entity == null ? null : new ZeroReference() - { - Id = entity.Id, - Name = transform(entity) - }; - } - - public string Id { get; set; } - - public string Name { get; set; } -} diff --git a/zero.Core/Pages/FlavorOptionsExtensions.cs b/zero.Core/Pages/FlavorOptionsExtensions.cs deleted file mode 100644 index 406de3ed..00000000 --- a/zero.Core/Pages/FlavorOptionsExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace zero.Pages; - -public static class FlavorOptionsExtensions -{ - public static void AddPage(this FlavorOptions options, string alias, string name, string description, string icon) where T : Page, new() - { - options.Add(new FlavorConfig(typeof(T)) - { - Alias = alias, - //EditorAlias = editorAlias.Or(alias), - Construct = _ => new T(), - Name = name, - Description = description, - Icon = icon - }); - } -} diff --git a/zero.Core/Pages/IPageTypeHandler.cs b/zero.Core/Pages/IPageTypeHandler.cs deleted file mode 100644 index b77d44e8..00000000 --- a/zero.Core/Pages/IPageTypeHandler.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace zero.Pages; - -public interface IPageTypeHandler : IHandler -{ - Task> GetAllowedPageTypes(Application application, IEnumerable registeredTypes, IEnumerable parents); -} diff --git a/zero.Core/Pages/Indexes/zero_Pages_AsHistory.cs b/zero.Core/Pages/Indexes/zero_Pages_AsHistory.cs deleted file mode 100644 index 973546dc..00000000 --- a/zero.Core/Pages/Indexes/zero_Pages_AsHistory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Raven.Client.Documents.Indexes; - -namespace zero.Pages; - -public class zero_Pages_AsHistory : ZeroIndex -{ - public class Result : ZeroIdEntity, ISupportsDbConventions - { - public DateTimeOffset LastModified { get; set; } - } - - - protected override void Create() - { - Map = items => items - .Select(x => new Result() - { - Id = x.Id, - LastModified = x.LastModifiedDate - }); - - StoreAllFields(FieldStorage.Yes); - //Index(x => x.ChannelId, FieldIndexing.Exact); - } -} diff --git a/zero.Core/Pages/Indexes/zero_Pages_ByHierarchy.cs b/zero.Core/Pages/Indexes/zero_Pages_ByHierarchy.cs deleted file mode 100644 index 8367549e..00000000 --- a/zero.Core/Pages/Indexes/zero_Pages_ByHierarchy.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Raven.Client.Documents.Indexes; - -namespace zero.Pages; - -public class zero_Pages_ByHierarchy : ZeroIndex -{ - public class Result : ZeroIdEntity, ISupportsDbConventions - { - public string Name { get; set; } - - public List Path { get; set; } = new List(); - - public string[] PathIds { get; set; } = Array.Empty(); - } - - - public class PathResult - { - public string Id { get; set; } - - public string Name { get; set; } - } - - - protected override void Create() - { - Map = items => items - .Select(item => new - { - Item = item, - Path = Recurse(item, x => LoadDocument(x.ParentId)).Where(x => x != null && x.Id != null && x.Id != item.Id).Reverse() - }) - .Select(item => new Result - { - Id = item.Item.Id, - Name = item.Item.Name, - Path = item.Path.Select(current => new PathResult() { Id = current.Id, Name = current.Name }).ToList(), - PathIds = item.Path.Select(current => current.Id).ToArray() - }); - - StoreAllFields(FieldStorage.Yes); - Index("PathIds", FieldIndexing.Exact); - //Index(x => x.ChannelId, FieldIndexing.Exact); - } -} diff --git a/zero.Core/Pages/Indexes/zero_Pages_ByType.cs b/zero.Core/Pages/Indexes/zero_Pages_ByType.cs deleted file mode 100644 index e4bc222f..00000000 --- a/zero.Core/Pages/Indexes/zero_Pages_ByType.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Raven.Client.Documents.Indexes; - -namespace zero.Pages; - -public class zero_Pages_ByType : ZeroIndex -{ - protected override void Create() - { - Map = items => items.Select(item => new - { - Flavor = item.Flavor - }); - - Index(x => x.Flavor, FieldIndexing.Exact); - } -} \ No newline at end of file diff --git a/zero.Core/Pages/Indexes/zero_Pages_WithChildren.cs b/zero.Core/Pages/Indexes/zero_Pages_WithChildren.cs deleted file mode 100644 index 11ac6106..00000000 --- a/zero.Core/Pages/Indexes/zero_Pages_WithChildren.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Raven.Client.Documents.Indexes; - -namespace zero.Pages; - -public class zero_Pages_WithChildren : ZeroIndex -{ - public class Result : ZeroIdEntity, ISupportsDbConventions - { - public string ParentId { get; set; } - - public string Name { get; set; } - - public string[] ChildrenIds { get; set; } - } - - - protected override void Create() - { - Map = items => items.Where(x => x.ParentId != null).Select(item => new Result - { - Id = item.Id, - ParentId = item.ParentId, - Name = item.Name, - ChildrenIds = new string[] { } - }); - - Reduce = results => results.GroupBy(x => new { x.ParentId }).Select(group => new Result() - { - Id = group.Key.ParentId, - ParentId = group.Key.ParentId, - Name = String.Empty, - ChildrenIds = group.Select(x => x.Id).ToArray() - }); - - StoreAllFields(FieldStorage.Yes); - //Index(x => x.ChannelId, FieldIndexing.Exact); - } -} \ No newline at end of file diff --git a/zero.Core/Pages/Models/Page.cs b/zero.Core/Pages/Models/Page.cs deleted file mode 100644 index eaa90da9..00000000 --- a/zero.Core/Pages/Models/Page.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace zero.Pages; - -/// -/// A page can consist of unlimited properties and be rendered as you wish -/// The backoffice rendering is done by an IRenderer -/// -[RavenCollection("Pages")] -public class Page : ZeroEntity, ISupportsTrees -{ - /// - /// Use this field (when filled out) instead of the alias for URL generation - /// - public string UrlAlias { get; set; } - - /// - public string ParentId { get; set; } - - /// - /// Date when the page is published - /// - public DateTimeOffset? PublishDate { get; set; } - - /// - /// Date when the page is unpublished - /// - public DateTimeOffset? UnpublishDate { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Pages/Models/PageFolder.cs b/zero.Core/Pages/Models/PageFolder.cs deleted file mode 100644 index 8f45ace2..00000000 --- a/zero.Core/Pages/Models/PageFolder.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace zero.Pages; - -/// -/// A page folder is used to group pages together but should not contain any content -/// -public sealed class PageFolder : Page -{ - public PageFolder() - { - IsActive = true; - } - - /// - /// Whether this folder is included in route building - /// - public bool IsPartOfRoute { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Pages/Modules/ConfigureModuleTypeJsonOptions.cs b/zero.Core/Pages/Modules/ConfigureModuleTypeJsonOptions.cs deleted file mode 100644 index 709c3158..00000000 --- a/zero.Core/Pages/Modules/ConfigureModuleTypeJsonOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; - -namespace zero.Stores; - -public class ConfigureModuleTypeJsonOptions : IConfigureOptions -{ - private readonly IZeroOptions zeroOptions; - - public ConfigureModuleTypeJsonOptions(IZeroOptions options) - { - zeroOptions = options; - } - - public void Configure(JsonOptions options) - { - options.JsonSerializerOptions.Converters.Add(new JsonModuleTypeConverter(zeroOptions)); - } -} \ No newline at end of file diff --git a/zero.Core/Pages/Modules/IModuleTypeHandler.cs b/zero.Core/Pages/Modules/IModuleTypeHandler.cs deleted file mode 100644 index c7ca1700..00000000 --- a/zero.Core/Pages/Modules/IModuleTypeHandler.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace zero.Pages; - -public interface IModuleTypeHandler : IHandler -{ - IEnumerable GetAllowedModuleTypes(Application application, IEnumerable registeredTypes, Page page = default, string[] tags = default); -} \ No newline at end of file diff --git a/zero.Core/Pages/Modules/JsonModuleTypeConverter.cs b/zero.Core/Pages/Modules/JsonModuleTypeConverter.cs deleted file mode 100644 index 8a435e02..00000000 --- a/zero.Core/Pages/Modules/JsonModuleTypeConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace zero.Stores; - -internal class JsonModuleTypeConverter : JsonDiscriminatorConverter -{ - Type _converterType = typeof(JsonModuleTypeConverter); - - readonly IZeroOptions _options; - - - public JsonModuleTypeConverter(IZeroOptions options) : base(nameof(PageModule.ModuleTypeAlias)) - { - _options = options; - } - - - protected override Type GetTypeFromDiscriminator(Type requestedType, string discriminator) - { - PageModuleType config = _options.For().FirstOrDefault(x => x.Alias.Equals(discriminator, StringComparison.InvariantCultureIgnoreCase)); - return config?.FlavorType ?? requestedType; - } - - - protected override JsonSerializerOptions CreateOptions(JsonSerializerOptions baseOptions) - { - JsonSerializerOptions newOptions = base.CreateOptions(baseOptions); - JsonConverter toRemove = newOptions.Converters.FirstOrDefault(x => x.GetType() == _converterType); - newOptions.Converters.Remove(toRemove); - return newOptions; - } -} \ No newline at end of file diff --git a/zero.Core/Pages/Modules/ModulePermissions.cs b/zero.Core/Pages/Modules/ModulePermissions.cs deleted file mode 100644 index 5b37ad4d..00000000 --- a/zero.Core/Pages/Modules/ModulePermissions.cs +++ /dev/null @@ -1,17 +0,0 @@ -//using zero.Core; -//using zero.Core.Identity; - -//namespace zero.Web.Defaults -//{ -// public class ModulePermissions : PermissionCollection -// { -// public ModulePermissions() -// { -// Alias = Constants.PermissionCollections.Modules; -// Label = "@permission.collections.modules"; -// Description = "@permission.collections.modules_description"; - -// // Items get added at runtime -// } -// } -//} diff --git a/zero.Core/Pages/Modules/PageModule.cs b/zero.Core/Pages/Modules/PageModule.cs deleted file mode 100644 index 08811191..00000000 --- a/zero.Core/Pages/Modules/PageModule.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace zero.Pages; - -/// -/// A module can consist of unlimited properties and be rendered as you wish -/// The backoffice rendering is done by an IRenderer -/// -public class PageModule : ZeroIdEntity -{ - /// - /// Sort order - /// - public uint Sort { get; set; } - - /// - /// Whether the module is visible in the frontend - /// - public bool IsActive { get; set; } - - /// - /// Alias of the used module type - /// - public string ModuleTypeAlias { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Pages/Modules/PageModuleOptions.cs b/zero.Core/Pages/Modules/PageModuleOptions.cs deleted file mode 100644 index 3128b72b..00000000 --- a/zero.Core/Pages/Modules/PageModuleOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace zero.Pages; - -public class PageModuleOptions : List -{ - public void Add(string alias, string name, string description, string icon, string group = null, List tags = null, List disallowedPageTypes = null) where T : PageModule, new() - { - Add(new PageModuleType(typeof(T)) - { - Alias = alias, - Name = name, - Description = description, - Icon = icon, - Group = group, - Tags = tags ?? new(), - DisallowedPageTypes = disallowedPageTypes ?? new(), - Construct = _ => new T() - }); - } -} \ No newline at end of file diff --git a/zero.Core/Pages/Modules/PageModuleType.cs b/zero.Core/Pages/Modules/PageModuleType.cs deleted file mode 100644 index 2fc73ccf..00000000 --- a/zero.Core/Pages/Modules/PageModuleType.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace zero.Pages; - -/// -/// A module type holds information about a Module implementation -/// -public class PageModuleType : FlavorConfig -{ - /// - /// Optionally group modules together - /// - public string Group { get; set; } - - /// - /// Defining tags for a module type allows you to query for them - /// - public List Tags { get; set; } = new(); - - /// - /// Page types where this module type is not allowed. - /// This will only work when operating within the page context. - /// - public List DisallowedPageTypes { get; set; } = new(); - - - public PageModuleType(Type type) : base(type) {} -} \ No newline at end of file diff --git a/zero.Core/Pages/Modules/PageModuleTypeService.cs b/zero.Core/Pages/Modules/PageModuleTypeService.cs deleted file mode 100644 index 50b3aef7..00000000 --- a/zero.Core/Pages/Modules/PageModuleTypeService.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; - -namespace zero.Pages; - -public class PageModuleTypeService : IPageModuleTypeService -{ - protected IZeroContext Context { get; private set; } - - protected IHandlerHolder Handler { get; private set; } - - protected IPagesStore PageStore { get; private set; } - - protected PageModuleOptions Options { get; set; } - - - public PageModuleTypeService(IZeroContext context, IZeroOptions options, IHandlerHolder handler, IPagesStore pageStore) - { - Context = context; - Handler = handler; - Options = options.For(); - PageStore = pageStore; - } - - - /// - public T GetEmpty(string alias) where T : PageModule - { - PageModuleType type = GetModuleType(alias); - T module = type?.Construct(type) as T; - - if (module == null) - { - return default; - } - - module.ModuleTypeAlias = type.Alias; - module.Id = IdGenerator.Create(8); - module.IsActive = true; - - return module; - } - - - /// - public PageModule GetEmpty(string alias) => GetEmpty(alias); - - - /// - public async Task> GetModuleTypes(string[] tags = default, string pageId = default) - { - List modules = Options; - Page page = null; - - if (!pageId.IsNullOrEmpty()) - { - page = await PageStore.Load(pageId); - } - - if (tags?.Length > 0) - { - modules = Options.Where(x => x.Tags.Any(t => tags.Contains(t, StringComparer.InvariantCultureIgnoreCase))).ToList(); - } - - IModuleTypeHandler handler = Handler.Get(); - - // if there is no registered handler we just allow all page types - if (handler == null) - { - return modules; - } - - return handler.GetAllowedModuleTypes(Context.Application, Options, page, tags)?.ToList() ?? new(); - } - - - /// - public PageModuleType GetModuleType(string alias) - { - return Options.FirstOrDefault(x => x.Alias == alias); - } -} - - -public interface IPageModuleTypeService -{ - /// - /// Get a new instance of a page module of the specified type - /// - T GetEmpty(string alias) where T : PageModule; - - /// - /// Get a new instance of a page module of the specified type - /// - PageModule GetEmpty(string alias); - - /// - /// Get all available module types (can be limited to the passed tags) - /// - Task> GetModuleTypes(string[] tags = default, string pageId = default); - - /// - /// Get a specific module type by alias - /// - PageModuleType GetModuleType(string alias); -} diff --git a/zero.Core/Pages/PageOptions.cs b/zero.Core/Pages/PageOptions.cs deleted file mode 100644 index 137d4f7b..00000000 --- a/zero.Core/Pages/PageOptions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace zero.Pages; - -public class PageOptions -{ - public string Root { get; set; } = Constants.Pages.DefaultRootPageTypeAlias; -} diff --git a/zero.Core/Pages/PageTypeService.cs b/zero.Core/Pages/PageTypeService.cs deleted file mode 100644 index c3f18438..00000000 --- a/zero.Core/Pages/PageTypeService.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; - -namespace zero.Pages; - -public class PageTypeService : IPageTypeService -{ - protected IZeroContext Context { get; private set; } - - protected IHandlerHolder Handler { get; private set; } - - protected FlavorOptions Flavors { get; set; } - - - public PageTypeService(IZeroContext context, IZeroOptions options, IHandlerHolder handler) - { - Context = context; - Handler = handler; - Flavors = options.For(); - } - - - /// - public IEnumerable GetAll() - { - return Flavors.GetAll(); - } - - - /// - public FlavorConfig GetByAlias(string flavorAlias) - { - return Flavors.Get(flavorAlias); - } - - - /// - public async Task> GetAllowedTypes(string pageParentId = null) - { - List parents = new(); - - var session = Context.Store.Session(); - - if (!pageParentId.IsNullOrEmpty()) - { - zero_Pages_ByHierarchy.Result result = await session.Query() - .ProjectInto() - .Include(x => x.Id) - .Include(x => x.Path.Select(p => p.Id)) - .FirstOrDefaultAsync(x => x.Id == pageParentId); - - if (result != null) - { - List ids = result.Path.Select(x => x.Id).ToList(); - ids.Add(result.Id); - parents = (await session.LoadAsync(ids)).Select(x => x.Value).Reverse().ToList(); - } - } - - IPageTypeHandler handler = Handler.Get(); - - // if there is no registered handler we just allow all page types - if (handler == null) - { - return Flavors.GetAll(); - } - - return (await handler.GetAllowedPageTypes(Context.Application, Flavors.GetAll(), parents))?.ToList() ?? new(); - } -} - - -public interface IPageTypeService -{ - /// - /// Get all available page types - /// - IEnumerable GetAll(); - - /// - /// Get all page types which are allowed below a selected parent page - /// - Task> GetAllowedTypes(string pageParentId = null); - - /// - /// Get a specific page type by alias - /// - FlavorConfig GetByAlias(string flavorAlias); -} diff --git a/zero.Core/Pages/PagesStore.cs b/zero.Core/Pages/PagesStore.cs deleted file mode 100644 index c5586a26..00000000 --- a/zero.Core/Pages/PagesStore.cs +++ /dev/null @@ -1,62 +0,0 @@ -using FluentValidation; - -namespace zero.Pages; - -public class PagesStore : TreeEntityStore, IPagesStore -{ - protected IPageTypeService PageTypes { get; private set; } - - - public PagesStore(IStoreContext context, IPageTypeService pageTypes) : base(context) - { - PageTypes = pageTypes; - } - - - /// - public override async Task IsAllowedAsChild(Page model, string parentId) - { - return true; // TODO v3 this method fails as it is also called on move and update (which should not happen) - IEnumerable pageTypes = await PageTypes.GetAllowedTypes(parentId); - return pageTypes.Any(x => x.Alias == model.Flavor); - } - - - /// - public async Task Empty(string pageTypeAlias, string parentId = null) - { - FlavorConfig type = PageTypes.GetByAlias(pageTypeAlias); - - if (type == null) - { - return null; - } - - Page model = await base.Empty(pageTypeAlias); - model.ParentId = parentId; - - if (!await IsAllowedAsChild(model, parentId)) - { - return null; // TODO we have no way to return an error here :-/ - } - - return model; - } - - - /// - protected override void ValidationRules(ZeroValidator validator) - { - validator.RuleFor(x => x.Name).NotEmpty(); - validator.RuleFor(x => x.Flavor).NotEmpty(); - } -} - - -public interface IPagesStore : ITreeEntityStore -{ - /// - /// Get new instance of an entity for a specific page type - /// - Task Empty(string pageType, string parentId = null); -} \ No newline at end of file diff --git a/zero.Core/Pages/ZeroPageModule.cs b/zero.Core/Pages/ZeroPageModule.cs deleted file mode 100644 index d996aed5..00000000 --- a/zero.Core/Pages/ZeroPageModule.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using zero.Search; - -namespace zero.Pages; - -internal class ZeroPageModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - services.AddOptions().Bind(configuration.GetSection("Zero:Pages")); - services.AddOptions().Bind(configuration.GetSection("Zero:PageModules")); - - services.ConfigureOptions(); - - services.Configure(opts => - { - opts.Indexes.Add(); - opts.Indexes.Add(); - opts.Indexes.Add(); - opts.Indexes.Add(); - }); - - services.Configure(opts => - { - opts.Configure(cfg => - { - cfg.CanUseWithoutFlavors = false; - cfg.Add(Constants.Pages.FolderAlias, "@page.folder.name", "@page.folder.description", "fth-folder"); - }); - }); - - services.Configure(opts => - { - opts.Map().Display((x, res, ctx) => - { - FlavorConfig flavor = ctx.Options.For().Get(x.Flavor); - if (flavor != null) - { - res.Icon = flavor.Icon; - } - res.Url = "/pages/edit/" + x.Id; - }); - }); - } -} \ No newline at end of file diff --git a/zero.Core/Persistence/GenerateIdAttribute.cs b/zero.Core/Persistence/GenerateIdAttribute.cs deleted file mode 100644 index 8318aa43..00000000 --- a/zero.Core/Persistence/GenerateIdAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace zero.Persistence; - -/// -/// Automatically generate ID with the specified length and insert it into this property on entity save -/// -[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] -public class GenerateIdAttribute : Attribute -{ - public int? Length = null; - - public GenerateIdAttribute(int length) - { - Length = length; - } - - public GenerateIdAttribute() { } -} diff --git a/zero.Core/Persistence/ISupportsDbConventions.cs b/zero.Core/Persistence/ISupportsDbConventions.cs deleted file mode 100644 index 1ce4ec3b..00000000 --- a/zero.Core/Persistence/ISupportsDbConventions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace zero.Persistence; - -/// -/// Triggers custom Raven conventions for database operations -/// -public interface ISupportsDbConventions { } \ No newline at end of file diff --git a/zero.Core/Persistence/IdGenerator.cs b/zero.Core/Persistence/IdGenerator.cs deleted file mode 100644 index 63483b0c..00000000 --- a/zero.Core/Persistence/IdGenerator.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.Text.Json; - -namespace zero.Persistence; - -public class IdGenerator -{ - const string CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"; - const string CHARS_COMPLEX = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-@#.:!?*"; - - private static Random random = new(); - - /// - /// Create a new unique Id - /// - public static string Create(int length = -1, Charset charset = Charset.az09) - { - if (length < 1) - { - length = 12; - } - - string chars = charset == Charset.az09 ? CHARS : CHARS_COMPLEX; - - return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); - - //if (length > 0) - //{ - //return Convert.ToBase64String(Guid.NewGuid().ToByteArray()) - // .Replace("/", String.Empty) - // .Replace("+", String.Empty) - // .Replace("-", String.Empty) - // .ToLowerInvariant() - // .Substring(0, length); - //} - - //return Guid.NewGuid().ToString(); - } - - - /// - /// Creates a simple hash from a string - /// - public static string HashString(string value) - { - return GetStableHashCode(value).ToString().Replace("-", String.Empty); - } - - - /// - /// Creates a simple hash from a string - /// - public static string HashObject(params object[] values) - { - return GetStableHashCode(JsonSerializer.Serialize(values)).ToString().Replace("-", String.Empty); - } - - - /// - /// Autofill IDs on an object with [GenerateId] attributes - /// - public static T Autofill(T model) - { - // find all Raven Ids - List> ravenIds = ObjectTraverser.FindAttribute(model); - - // set unset Raven Ids - foreach (ObjectTraverser.Result item in ravenIds) - { - string id = item.Property.GetValue(item.Parent, null) as string; - if (id.IsNullOrWhiteSpace()) - { - id = item.Item.Length.HasValue ? Create(item.Item.Length.Value) : Create(); - item.Property.SetValue(item.Parent, id); - } - } - - return model; - } - - - static int GetStableHashCode(string str) - { - unchecked - { - int hash1 = 5381; - int hash2 = hash1; - - for (int i = 0; i < str.Length && str[i] != '\0'; i += 2) - { - hash1 = ((hash1 << 5) + hash1) ^ str[i]; - if (i == str.Length - 1 || str[i + 1] == '\0') - break; - hash2 = ((hash2 << 5) + hash2) ^ str[i + 1]; - } - - return hash1 + (hash2 * 1566083941); - } - } - - - public enum Charset - { - /// - /// a-z, 0-9 - /// - az09 = 0, - /// - /// a-z, A-Z, 0-9, _-@#.:!?* - /// - azAZ09x = 1 - } -} \ No newline at end of file diff --git a/zero.Core/Persistence/Indexes/ZeroIndex.cs b/zero.Core/Persistence/Indexes/ZeroIndex.cs deleted file mode 100644 index b86cee27..00000000 --- a/zero.Core/Persistence/Indexes/ZeroIndex.cs +++ /dev/null @@ -1,288 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Indexes; -using Raven.Client.Documents.Indexes.Spatial; -using Raven.Client.Documents.Operations.Attachments; -using System.Linq.Expressions; - -namespace zero.Persistence; - - -public abstract class ZeroJavascriptIndex : AbstractJavaScriptIndexCreationTask, IZeroIndexDefinition -{ - public ZeroJavascriptIndex() { Create(); } - protected virtual void Create() { } - - public virtual void Setup(IZeroOptions options, IDocumentStore store) { } - - public new string Reduce { get => base.Reduce; set => base.Reduce = value; } - public new string OutputReduceToCollection { get => base.OutputReduceToCollection; set => base.OutputReduceToCollection = value; } - public new string PatternReferencesCollectionName { get => base.PatternReferencesCollectionName; set => base.PatternReferencesCollectionName = value; } - public new string PatternForOutputReduceToCollectionReferences { get => base.PatternForOutputReduceToCollectionReferences; set => base.PatternForOutputReduceToCollectionReferences = value; } - - // AbstractIndexCreationTask - public new IJsonObject AsJson(object doc) => base.AsJson(doc); - public new IEnumerable AttachmentsFor(object doc) => base.AttachmentsFor(doc); - public new IEnumerable CounterNamesFor(object doc) => base.CounterNamesFor(doc); - public new IJsonObject.IMetadata MetadataFor(object doc) => base.MetadataFor(doc); - public new IEnumerable TimeSeriesNamesFor(object doc) => base.TimeSeriesNamesFor(doc); - - // AbstractIndexCreationTaskBase - public new object CreateField(string name, object value, CreateFieldOptions options) => base.CreateField(name, value, options); - public new object CreateField(string name, object value, bool stored, bool analyzed) => base.CreateField(name, value, stored, analyzed); - public new object CreateField(string name, object value) => base.CreateField(name, value); - - // AbstractCommonApiForIndexes - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new T? TryConvert(object value) where T : struct => base.TryConvert(value); -} - -public abstract class ZeroMultiMapIndex : AbstractMultiMapIndexCreationTask, IZeroIndexDefinition -{ - public ZeroMultiMapIndex() { Create(); } - protected virtual void Create() { } - - public virtual void Setup(IZeroOptions options, IDocumentStore store) { } - - // AbstractMultiMapIndexCreationTask - public new void AddMap(Expression, IEnumerable>> map) => base.AddMap(map); - public new void AddMapForAll(Expression, IEnumerable>> map) => base.AddMapForAll(map); - - // AbstractGenericIndexCreationTask - public new Expression, IEnumerable>> Reduce { get => base.Reduce; set => base.Reduce = value; } - public new string OutputReduceToCollection { get => base.OutputReduceToCollection; set => base.OutputReduceToCollection = value; } - public new IDictionary IndexesStrings { get => base.IndexesStrings; set => base.IndexesStrings = value; } - public new IDictionary>, FieldIndexing> Indexes { get => base.Indexes; set => base.Indexes = value; } - public new IDictionary SpatialIndexesStrings { get => base.SpatialIndexesStrings; set => base.SpatialIndexesStrings = value; } - public new IDictionary>, SpatialOptions> SpatialIndexes { get => base.SpatialIndexes; set => base.SpatialIndexes = value; } - public new IDictionary TermVectorsStrings { get => base.TermVectorsStrings; set => base.TermVectorsStrings = value; } - public new IDictionary>, FieldTermVector> TermVectors { get => base.TermVectors; set => base.TermVectors = value; } - public new IDictionary AnalyzersStrings { get => base.AnalyzersStrings; set => base.AnalyzersStrings = value; } - public new IDictionary>, string> Analyzers { get => base.Analyzers; set => base.Analyzers = value; } - public new ISet>> IndexSuggestions { get => base.IndexSuggestions; set => base.IndexSuggestions = value; } - public new IDictionary StoresStrings { get => base.StoresStrings; set => base.StoresStrings = value; } - public new IDictionary>, FieldStorage> Stores { get => base.Stores; set => base.Stores = value; } - public new string PatternReferencesCollectionName { get => base.PatternReferencesCollectionName; set => base.PatternReferencesCollectionName = value; } - public new Expression> PatternForOutputReduceToCollectionReferences { get => base.PatternForOutputReduceToCollectionReferences; set => base.PatternForOutputReduceToCollectionReferences = value; } - public new void AddAssembly(AdditionalAssembly assembly) => base.AddAssembly(assembly); - public new void Analyze(string field, string analyzer) => base.Analyze(field, analyzer); - public new void Analyze(Expression> field, string analyzer) => base.Analyze(field, analyzer); - public new void Index(Expression> field, FieldIndexing indexing) => base.Index(field, indexing); - public new void Index(string field, FieldIndexing indexing) => base.Index(field, indexing); - public new void Spatial(string field, Func indexing) => base.Spatial(field, indexing); - public new void Spatial(Expression> field, Func indexing) => base.Spatial(field, indexing); - public new void Store(string field, FieldStorage storage) => base.Store(field, storage); - public new void Store(Expression> field, FieldStorage storage) => base.Store(field, storage); - public new void StoreAllFields(FieldStorage storage) => base.StoreAllFields(storage); - public new void Suggestion(Expression> field) => base.Suggestion(field); - public new void TermVector(string field, FieldTermVector termVector) => base.TermVector(field, termVector); - public new void TermVector(Expression> field, FieldTermVector termVector) => base.TermVector(field, termVector); - - // AbstractIndexCreationTask - public new IJsonObject AsJson(object doc) => base.AsJson(doc); - public new IEnumerable AttachmentsFor(object doc) => base.AttachmentsFor(doc); - public new IEnumerable CounterNamesFor(object doc) => base.CounterNamesFor(doc); - public new IJsonObject.IMetadata MetadataFor(object doc) => base.MetadataFor(doc); - public new IEnumerable TimeSeriesNamesFor(object doc) => base.TimeSeriesNamesFor(doc); - - // AbstractIndexCreationTaskBase - public new object CreateField(string name, object value, CreateFieldOptions options) => base.CreateField(name, value, options); - public new object CreateField(string name, object value, bool stored, bool analyzed) => base.CreateField(name, value, stored, analyzed); - public new object CreateField(string name, object value) => base.CreateField(name, value); - - // AbstractCommonApiForIndexes - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new T? TryConvert(object value) where T : struct => base.TryConvert(value); -} - - -public abstract class ZeroMultiMapIndex : AbstractMultiMapIndexCreationTask, IZeroIndexDefinition -{ - public ZeroMultiMapIndex() { Create(); } - protected virtual void Create() { } - - public virtual void Setup(IZeroOptions options, IDocumentStore store) { } - - // AbstractMultiMapIndexCreationTask - public new void AddMap(Expression, IEnumerable>> map) => base.AddMap(map); - public new void AddMapForAll(Expression, IEnumerable>> map) => base.AddMapForAll(map); - - // AbstractGenericIndexCreationTask - public new Expression, IEnumerable>> Reduce { get => base.Reduce; set => base.Reduce = value; } - public new string OutputReduceToCollection { get => base.OutputReduceToCollection; set => base.OutputReduceToCollection = value; } - public new IDictionary IndexesStrings { get => base.IndexesStrings; set => base.IndexesStrings = value; } - public new IDictionary>, FieldIndexing> Indexes { get => base.Indexes; set => base.Indexes = value; } - public new IDictionary SpatialIndexesStrings { get => base.SpatialIndexesStrings; set => base.SpatialIndexesStrings = value; } - public new IDictionary>, SpatialOptions> SpatialIndexes { get => base.SpatialIndexes; set => base.SpatialIndexes = value; } - public new IDictionary TermVectorsStrings { get => base.TermVectorsStrings; set => base.TermVectorsStrings = value; } - public new IDictionary>, FieldTermVector> TermVectors { get => base.TermVectors; set => base.TermVectors = value; } - public new IDictionary AnalyzersStrings { get => base.AnalyzersStrings; set => base.AnalyzersStrings = value; } - public new IDictionary>, string> Analyzers { get => base.Analyzers; set => base.Analyzers = value; } - public new ISet>> IndexSuggestions { get => base.IndexSuggestions; set => base.IndexSuggestions = value; } - public new IDictionary StoresStrings { get => base.StoresStrings; set => base.StoresStrings = value; } - public new IDictionary>, FieldStorage> Stores { get => base.Stores; set => base.Stores = value; } - public new string PatternReferencesCollectionName { get => base.PatternReferencesCollectionName; set => base.PatternReferencesCollectionName = value; } - public new Expression> PatternForOutputReduceToCollectionReferences { get => base.PatternForOutputReduceToCollectionReferences; set => base.PatternForOutputReduceToCollectionReferences = value; } - public new void AddAssembly(AdditionalAssembly assembly) => base.AddAssembly(assembly); - public new void Analyze(string field, string analyzer) => base.Analyze(field, analyzer); - public new void Analyze(Expression> field, string analyzer) => base.Analyze(field, analyzer); - public new void Index(Expression> field, FieldIndexing indexing) => base.Index(field, indexing); - public new void Index(string field, FieldIndexing indexing) => base.Index(field, indexing); - public new void Spatial(string field, Func indexing) => base.Spatial(field, indexing); - public new void Spatial(Expression> field, Func indexing) => base.Spatial(field, indexing); - public new void Store(string field, FieldStorage storage) => base.Store(field, storage); - public new void Store(Expression> field, FieldStorage storage) => base.Store(field, storage); - public new void StoreAllFields(FieldStorage storage) => base.StoreAllFields(storage); - public new void Suggestion(Expression> field) => base.Suggestion(field); - public new void TermVector(string field, FieldTermVector termVector) => base.TermVector(field, termVector); - public new void TermVector(Expression> field, FieldTermVector termVector) => base.TermVector(field, termVector); - - // AbstractIndexCreationTask - public new IJsonObject AsJson(object doc) => base.AsJson(doc); - public new IEnumerable AttachmentsFor(object doc) => base.AttachmentsFor(doc); - public new IEnumerable CounterNamesFor(object doc) => base.CounterNamesFor(doc); - public new IJsonObject.IMetadata MetadataFor(object doc) => base.MetadataFor(doc); - public new IEnumerable TimeSeriesNamesFor(object doc) => base.TimeSeriesNamesFor(doc); - - // AbstractIndexCreationTaskBase - public new object CreateField(string name, object value, CreateFieldOptions options) => base.CreateField(name, value, options); - public new object CreateField(string name, object value, bool stored, bool analyzed) => base.CreateField(name, value, stored, analyzed); - public new object CreateField(string name, object value) => base.CreateField(name, value); - - // AbstractCommonApiForIndexes - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new T? TryConvert(object value) where T : struct => base.TryConvert(value); -} - - -public abstract class ZeroIndex : AbstractIndexCreationTask, IZeroIndexDefinition -{ - public ZeroIndex() { Create(); } - protected virtual void Create() { } - - public virtual void Setup(IZeroOptions options, IDocumentStore store) { } - - // AbstractIndexCreationTask - public new Expression, IEnumerable>> Map { get => base.Map; set => base.Map = value; } - - // AbstractGenericIndexCreationTask - public new Expression, IEnumerable>> Reduce { get => base.Reduce; set => base.Reduce = value; } - public new string OutputReduceToCollection { get => base.OutputReduceToCollection; set => base.OutputReduceToCollection = value; } - public new IDictionary IndexesStrings { get => base.IndexesStrings; set => base.IndexesStrings = value; } - public new IDictionary>, FieldIndexing> Indexes { get => base.Indexes; set => base.Indexes = value; } - public new IDictionary SpatialIndexesStrings { get => base.SpatialIndexesStrings; set => base.SpatialIndexesStrings = value; } - public new IDictionary>, SpatialOptions> SpatialIndexes { get => base.SpatialIndexes; set => base.SpatialIndexes = value; } - public new IDictionary TermVectorsStrings { get => base.TermVectorsStrings; set => base.TermVectorsStrings = value; } - public new IDictionary>, FieldTermVector> TermVectors { get => base.TermVectors; set => base.TermVectors = value; } - public new IDictionary AnalyzersStrings { get => base.AnalyzersStrings; set => base.AnalyzersStrings = value; } - public new IDictionary>, string> Analyzers { get => base.Analyzers; set => base.Analyzers = value; } - public new ISet>> IndexSuggestions { get => base.IndexSuggestions; set => base.IndexSuggestions = value; } - public new IDictionary StoresStrings { get => base.StoresStrings; set => base.StoresStrings = value; } - public new IDictionary>, FieldStorage> Stores { get => base.Stores; set => base.Stores = value; } - public new string PatternReferencesCollectionName { get => base.PatternReferencesCollectionName; set => base.PatternReferencesCollectionName = value; } - public new Expression> PatternForOutputReduceToCollectionReferences { get => base.PatternForOutputReduceToCollectionReferences; set => base.PatternForOutputReduceToCollectionReferences = value; } - public new void AddAssembly(AdditionalAssembly assembly) => base.AddAssembly(assembly); - public new void Analyze(string field, string analyzer) => base.Analyze(field, analyzer); - public new void Analyze(Expression> field, string analyzer) => base.Analyze(field, analyzer); - public new void Index(Expression> field, FieldIndexing indexing) => base.Index(field, indexing); - public new void Index(string field, FieldIndexing indexing) => base.Index(field, indexing); - public new void Spatial(string field, Func indexing) => base.Spatial(field, indexing); - public new void Spatial(Expression> field, Func indexing) => base.Spatial(field, indexing); - public new void Store(string field, FieldStorage storage) => base.Store(field, storage); - public new void Store(Expression> field, FieldStorage storage) => base.Store(field, storage); - public new void StoreAllFields(FieldStorage storage) => base.StoreAllFields(storage); - public new void Suggestion(Expression> field) => base.Suggestion(field); - public new void TermVector(string field, FieldTermVector termVector) => base.TermVector(field, termVector); - public new void TermVector(Expression> field, FieldTermVector termVector) => base.TermVector(field, termVector); - - // AbstractIndexCreationTask - public new IJsonObject AsJson(object doc) => base.AsJson(doc); - public new IEnumerable AttachmentsFor(object doc) => base.AttachmentsFor(doc); - public new IEnumerable CounterNamesFor(object doc) => base.CounterNamesFor(doc); - public new IJsonObject.IMetadata MetadataFor(object doc) => base.MetadataFor(doc); - public new IEnumerable TimeSeriesNamesFor(object doc) => base.TimeSeriesNamesFor(doc); - - // AbstractIndexCreationTaskBase - public new object CreateField(string name, object value, CreateFieldOptions options) => base.CreateField(name, value, options); - public new object CreateField(string name, object value, bool stored, bool analyzed) => base.CreateField(name, value, stored, analyzed); - public new object CreateField(string name, object value) => base.CreateField(name, value); - - // AbstractCommonApiForIndexes - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new T? TryConvert(object value) where T : struct => base.TryConvert(value); -} - - -public abstract class ZeroIndex : ZeroIndex -{ -} - - -public abstract class ZeroIndex : AbstractIndexCreationTask, IZeroIndexDefinition -{ - public ZeroIndex() { Create(); } - protected virtual void Create() { } - - public virtual void Setup(IZeroOptions options, IDocumentStore store) { } - - // AbstractIndexCreationTask - public new IJsonObject AsJson(object doc) => base.AsJson(doc); - public new IEnumerable AttachmentsFor(object doc) => base.AttachmentsFor(doc); - public new IEnumerable CounterNamesFor(object doc) => base.CounterNamesFor(doc); - public new IJsonObject.IMetadata MetadataFor(object doc) => base.MetadataFor(doc); - public new IEnumerable TimeSeriesNamesFor(object doc) => base.TimeSeriesNamesFor(doc); - - // AbstractIndexCreationTaskBase - public new object CreateField(string name, object value, CreateFieldOptions options) => base.CreateField(name, value, options); - public new object CreateField(string name, object value, bool stored, bool analyzed) => base.CreateField(name, value, stored, analyzed); - public new object CreateField(string name, object value) => base.CreateField(name, value); - - // AbstractCommonApiForIndexes - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func func) => base.Recurse(source, func); - public new IEnumerable Recurse(TSource source, Func> func) => base.Recurse(source, func); - public new T? TryConvert(object value) where T : struct => base.TryConvert(value); -} - - -public interface IZeroIndexDefinition : IAbstractIndexCreationTask -{ - void Setup(IZeroOptions options, IDocumentStore store); -} diff --git a/zero.Core/Persistence/Indexes/ZeroIndexExtensions.cs b/zero.Core/Persistence/Indexes/ZeroIndexExtensions.cs deleted file mode 100644 index 41ead020..00000000 --- a/zero.Core/Persistence/Indexes/ZeroIndexExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace zero.Persistence; - -public static class ZeroIndexExtensions -{ - internal static void RunModifiers(this T index, RavenOptions options) where T : IZeroIndexDefinition - { - IEnumerable modifiers = options.Indexes.Modifiers.GetAllForType(index.GetType()); - - foreach (RavenIndexModifiersOptions.Modifier modifier in modifiers) - { - Action action = modifier.Modify.Compile(); - action.Invoke(index); - } - } -} diff --git a/zero.Core/Persistence/Indexes/ZeroTreeHierachyIndex.cs b/zero.Core/Persistence/Indexes/ZeroTreeHierachyIndex.cs deleted file mode 100644 index f1f2d6b6..00000000 --- a/zero.Core/Persistence/Indexes/ZeroTreeHierachyIndex.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Indexes; - -namespace zero.Persistence; - -public class ZeroTreeHierarchyIndexResult : ZeroIdEntity, ISupportsDbConventions -{ - public List Path { get; set; } = new List(); -} - -public abstract class ZeroTreeHierarchyIndex : ZeroIndex where T : ZeroIdEntity, ISupportsTrees -{ - protected override void Create() - { - Map = items => items.Select(item => new ZeroTreeHierarchyIndexResult - { - Id = item.Id, - Path = Recurse(item, x => LoadDocument(x.ParentId)) - .Where(x => x != null && x.Id != null && x.Id != item.Id) - .Reverse() - .Select(current => current.Id) - .ToList() - }); - - StoreAllFields(FieldStorage.Yes); - //Index(x => x.ChannelId, FieldIndexing.Exact); - } -} \ No newline at end of file diff --git a/zero.Core/Persistence/RavenCollectionAttribute.cs b/zero.Core/Persistence/RavenCollectionAttribute.cs deleted file mode 100644 index d45d9701..00000000 --- a/zero.Core/Persistence/RavenCollectionAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace zero.Persistence; - -/// -/// This attribute will allow the usage of custom collection names for Raven collections -/// -[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, AllowMultiple = false)] -public class RavenCollectionAttribute : Attribute -{ - public string Name { get; set; } - - public RavenCollectionAttribute(string name) - { - Name = name; - } -} diff --git a/zero.Core/Persistence/RavenConstants.cs b/zero.Core/Persistence/RavenConstants.cs deleted file mode 100644 index 698754c2..00000000 --- a/zero.Core/Persistence/RavenConstants.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace zero.Persistence; - -public static partial class RavenConstants -{ - public static partial class Indexing - { - public const string ThrottlingTimeIntervalInMs = "Indexing.Throttling.TimeIntervalInMs"; - } -} \ No newline at end of file diff --git a/zero.Core/Persistence/RavenIndexExtensions.cs b/zero.Core/Persistence/RavenIndexExtensions.cs deleted file mode 100644 index 016894fc..00000000 --- a/zero.Core/Persistence/RavenIndexExtensions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Raven.Client.Documents.Indexes; - -namespace zero.Persistence; - -public static class RavenIndexExtensions -{ - public static void Throttle(this AbstractCommonApiForIndexes index, TimeSpan delay) - { - index.Configuration[RavenConstants.Indexing.ThrottlingTimeIntervalInMs] = delay.TotalMilliseconds.ToString(); - } -} diff --git a/zero.Core/Persistence/RavenOptions.cs b/zero.Core/Persistence/RavenOptions.cs deleted file mode 100644 index 9ff3d2de..00000000 --- a/zero.Core/Persistence/RavenOptions.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Raven.Client.Documents; -using System.Linq.Expressions; - -namespace zero.Persistence; - -public class RavenOptions -{ - public string Url { get; set; } - - public string Database { get; set; } - - public bool DatabaseIsDefault { get; set; } = false; - - public string CollectionPrefix { get; set; } = String.Empty; - - public RavenIndexesOptions Indexes { get; set; } = new(); -} - - -public class RavenIndexesOptions : List -{ - public class Map - { - internal Type Type { get; set; } - - internal Expression> CreateIndex { get; set; } - - internal Map(Type type, Expression> create) - { - Type = type; - CreateIndex = create; - } - } - - - public RavenIndexModifiersOptions Modifiers { get; private set; } = new(); - - public void Add() where T : IZeroIndexDefinition, new() - { - base.Add(new Map(typeof(T), () => new T())); - } - - public void Add(Type indexType) - { - base.Add(new Map(indexType, () => (IZeroIndexDefinition)Activator.CreateInstance(indexType))); - } - - public void Add(T index) where T : IZeroIndexDefinition - { - base.Add(new Map(typeof(T), () => index)); - } - - public void AddRange(params Type[] indexes) - { - foreach (Type type in indexes) - { - Add(type); - } - } - - public void Replace() - where T : IZeroIndexDefinition, new() - where TReplaceWith : IZeroIndexDefinition, new() - { - Replace(typeof(T), typeof(TReplaceWith)); - } - - public void Replace(Type origin, Type replaceWith) - { - var item = this.FirstOrDefault(x => x.Type == origin); - if (item != null) - { - Remove(item); - } - Add(replaceWith); - } - - public IEnumerable BuildAll(IZeroOptions options, IDocumentStore store) - { - RavenOptions ravenOptions = options.For(); - - foreach (Map map in this) - { - IZeroIndexDefinition index = map.CreateIndex.Compile().Invoke(); - index.Setup(options, store); - index.RunModifiers(ravenOptions); - yield return index; - } - } -} - - -public class RavenIndexModifiersOptions : List -{ - public class Modifier - { - public Type Type { get; set; } - - public Expression> Modify { get; set; } - } - - public void Add(Action modify) where T : IZeroIndexDefinition, new() - { - Add(new() - { - Type = typeof(T), - Modify = x => modify((T)x) - }); - } - - - public IEnumerable GetAllForType() where T : IZeroIndexDefinition, new() => GetAllForType(typeof(T)); - - - public IEnumerable GetAllForType(Type type) - { - foreach (Modifier modifier in this.Where(x => x.Type.IsAssignableFrom(type))) - { - yield return modifier; - } - } -} diff --git a/zero.Core/Persistence/RavenQueryableExtensions.cs b/zero.Core/Persistence/RavenQueryableExtensions.cs deleted file mode 100644 index 332154e4..00000000 --- a/zero.Core/Persistence/RavenQueryableExtensions.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; -using Raven.Client.Documents.Queries; -using Raven.Client.Documents.Session; -using System.Linq.Expressions; - -namespace zero.Persistence; - -public static class RavenQueryableExtensions -{ - public static IOrderedQueryable OrderBy(this IQueryable source, string path, bool isDescending, OrderingType type = OrderingType.String) - { - if (!String.IsNullOrEmpty(path)) - { - char[] a = path.ToCharArray(); - a[0] = char.ToUpper(a[0]); - path = new string(a); - } - - if (isDescending) - { - return source.OrderByDescending(path, type); - } - return source.OrderBy(path, type); - } - - - public static IOrderedQueryable ThenBy(this IOrderedQueryable source, string path, bool isDescending, OrderingType type = OrderingType.String) - { - if (!String.IsNullOrEmpty(path)) - { - char[] a = path.ToCharArray(); - a[0] = char.ToUpper(a[0]); - path = new string(a); - } - - if (isDescending) - { - return source.ThenByDescending(path, type); - } - return source.ThenBy(path, type); - } - - - public static IQueryable Paging(this IQueryable source, int pageNumber, int pageSize) - { - pageNumber = pageNumber.Limit(1, 10_000_000); - pageSize = pageSize.Limit(1, 1_000); - - if (pageNumber <= 0 || pageSize <= 0) - { - throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); - } - - return source.Skip((pageNumber - 1) * pageSize).Take(pageSize); - } - - - public static IRavenQueryable WhereIf(this IRavenQueryable source, Expression> predicate, bool condition, Expression> elsePredicate = null) - { - if (!condition) - { - if (elsePredicate != null) - { - return source.Where(elsePredicate); - } - return source; - } - - return source.Where(predicate); - } - - - public static IQueryable WhereIf(this IQueryable source, Expression> predicate, bool condition, Expression> elsePredicate = null) - { - if (!condition) - { - if (elsePredicate != null) - { - return source.Where(elsePredicate); - } - return source; - } - - return source.Where(predicate); - } - - - public static IQueryable SearchIf(this IQueryable source, Expression> fieldSelector, string searchTerms, string suffix = null, string prefix = null, SearchOperator @operator = SearchOperator.Or) - { - if (String.IsNullOrWhiteSpace(searchTerms)) - { - return source; - } - - searchTerms = searchTerms.Trim(); - - string[] searchParts = searchTerms.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries).Select(x => - { - if (suffix != null) - { - x += suffix; - } - if (prefix != null) - { - x = prefix + x; - } - return x; - }).ToArray(); - - if (searchTerms.StartsWith('"') && searchTerms.EndsWith('"')) - { - searchParts = new[] { searchTerms }; - } - - return source.Search(fieldSelector, searchParts, @operator: @operator); - } -} diff --git a/zero.Core/Persistence/Revisions/Revision.cs b/zero.Core/Persistence/Revisions/Revision.cs deleted file mode 100644 index d8aa45ce..00000000 --- a/zero.Core/Persistence/Revisions/Revision.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace zero.Persistence; - -public class Revision : Revision where T : ZeroEntity -{ - public T Model { get; set; } -} - -public class Revision -{ - public string ModelId { get; set; } - - public RevisionUser User { get; set; } - - public DateTimeOffset Date { get; set; } - - public string ChangeVector { get; set; } -} - -public class RevisionUser -{ - public string Id { get; set; } - - public string Name { get; set; } - - public string AvatarId { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Persistence/Revisions/RevisionService.cs b/zero.Core/Persistence/Revisions/RevisionService.cs deleted file mode 100644 index 3d3d13cb..00000000 --- a/zero.Core/Persistence/Revisions/RevisionService.cs +++ /dev/null @@ -1,96 +0,0 @@ -namespace zero.Persistence; - -public class RevisionService : IRevisionService -{ - protected IZeroStore Store { get; set; } - - public RevisionService(IZeroStore store) - { - Store = store; - } - - - /// - /// Get revision list for an entity - /// - public async Task>> GetPagedWithModel(string id, int pageNumber = 1, int pageSize = 10) where T : ZeroEntity - { - IZeroDocumentSession session = Store.Session(); - - // get paged revisions - List items = await session.Advanced.Revisions.GetForAsync(id, (pageNumber - 1) * pageSize, pageSize); - long totalResults = await session.Advanced.Revisions.GetCountForAsync(id); - - // add creation for last page - //if (!items.Any() && pageNumber >= (int)Math.Ceiling((decimal)totalResults / pageSize)) - //{ - // T currentRevision = await Session.LoadAsync(id); - // if (currentRevision != null) - // { - // items.Add(currentRevision); - // } - //} - - List> revisions = new(); - - // load affected users as the revisions could have been edited by other users too - string[] userIds = items.Select(x => x.LastModifiedById).Distinct().ToArray(); - Dictionary users = await session.LoadAsync(userIds); - - // create revision objects - foreach (T item in items) - { - Revision revision = new() - { - Model = item, - ModelId = item.Id, - ChangeVector = session.Advanced.GetChangeVectorFor(item), - Date = item.LastModifiedDate - }; - - if (!item.LastModifiedById.IsNullOrEmpty() && users.TryGetValue(item.LastModifiedById, out ZeroUser user) && user != null) - { - revision.User = new RevisionUser() - { - AvatarId = user.AvatarId, - Id = user.Id, - Name = user.Name - }; - } - - revisions.Add(revision); - } - - return new Paged>(revisions, totalResults, pageNumber, pageSize); - } - - - /// - /// Get revision list for an entity - /// - public async Task> GetPaged(string id, int pageNumber = 1, int pageSize = 10) where T : ZeroEntity - { - Paged> items = await GetPagedWithModel(id, pageNumber, pageSize); - return items.MapTo(model => new Revision() - { - ChangeVector = model.ChangeVector, - Date = model.Date, - ModelId = model.ModelId, - User = model.User - }); - } -} - - -public interface IRevisionService -{ - /// - /// Get revision list including models for an entity - /// - Task>> GetPagedWithModel(string id, int pageNumber = 1, int pageSize = 10) where T : ZeroEntity; - - /// - /// Get revision list for an entity - /// - Task> GetPaged(string id, int pageNumber = 1, int pageSize = 10) where T : ZeroEntity; -} diff --git a/zero.Core/Persistence/Safenames.cs b/zero.Core/Persistence/Safenames.cs deleted file mode 100644 index 7d41297d..00000000 --- a/zero.Core/Persistence/Safenames.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System.IO; -using System.Text; - -namespace zero.Persistence; - -public class Safenames -{ - public enum Scope - { - Url, - File - } - - const char HYPHEN = '-'; - - const char DOT = '.'; - - const char PLUS = '+'; - - const char AMPERSAND = '&'; - - - /// - /// Converts an untrusted to a safe filename - /// - public static string File(string value) - { - return Generate(Path.GetFileName(value), Scope.File); - } - - - /// - /// Converts a term to a safe alias (suitable for URLs) - /// - public static string Alias(string value) - { - return Generate(value, Scope.Url); - } - - - /// - /// Converts a term to a safe alias (suitable for URLs) - /// - public static string Alias(object value) - { - return Generate(value?.ToString(), Scope.Url); - } - - - /// - /// - /// - static string Generate(string value, Scope scope) - { - if (String.IsNullOrWhiteSpace(value)) - { - return String.Empty; - } - - char previous = default; - StringBuilder output = new(); - - for (int i = 0; i < value.Length; i++) - { - // get character in lower case - char character = char.ToLower(value[i]); - char target; - - // do not handle surrogates - if (char.IsSurrogate(character)) - { - continue; - } - - // special replacements accents + umlauts - if (character.TryReplaceAccent(out char[] replacement)) - { - if (replacement.Length > 1) - { - output.Append(replacement); - output.Remove(output.Length - 1, 1); - } - target = replacement[replacement.Length - 1]; - } - // append character a-z, 0-9 - else if (character.IsAZor09()) - { - target = character; - } - // + sign for + and & - else if (character == PLUS || character == AMPERSAND) - { - target = PLUS; - } - else if (scope == Scope.File && character == DOT) - { - target = DOT; - } - // add hyphen for all other characters - else - { - target = HYPHEN; - } - - // add default characters - if (target != HYPHEN && target != PLUS) - { - output.Append(target); - } - // add hyphen if it isn't first and previous char is not + or - - else if (target == HYPHEN && previous != default && previous != PLUS && previous != HYPHEN) - { - output.Append(target); - } - // add plus. do remove hyphen it is the previous character - else if (target == PLUS) - { - if (previous == HYPHEN) - { - output.Remove(output.Length - 1, 1); - } - output.Append(target); - } - - if (output.Length > 0) - { - previous = output[output.Length - 1]; - } - } - - if (output.Length > 0 && !output[output.Length - 1].IsAZor09()) - { - output.Remove(output.Length - 1, 1); - } - - if (output.Length == 0) - { - output.Append(HYPHEN); - } - - return output.ToString(); - } -} \ No newline at end of file diff --git a/zero.Core/Persistence/Tokens/Rfc6238AuthenticationService.cs b/zero.Core/Persistence/Tokens/Rfc6238AuthenticationService.cs deleted file mode 100644 index 8c576663..00000000 --- a/zero.Core/Persistence/Tokens/Rfc6238AuthenticationService.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Diagnostics; -using System.Net; -using System.Security.Cryptography; -using System.Text; - -namespace zero.Persistence; - -/// -/// Replicates the Rfc6238AuthenticationService from ASP.NET Core internals as it isn't part of the public API -/// (see: https://github.com/dotnet/aspnetcore/blob/release/5.0/src/Identity/Extensions.Core/src/Rfc6238AuthenticationService.cs) -/// -public static class Rfc6238AuthenticationService -{ - private static readonly TimeSpan _timestep = TimeSpan.FromMinutes(3); - private static readonly Encoding _encoding = new UTF8Encoding(false, true); - - // Generates a new 80-bit security token - public static byte[] GenerateRandomKey() - { - byte[] bytes = new byte[20]; - RandomNumberGenerator.Fill(bytes); - return bytes; - } - - internal static int ComputeTotp(HashAlgorithm hashAlgorithm, ulong timestepNumber, string modifier) - { - // # of 0's = length of pin - const int Mod = 1000000; - - // See https://tools.ietf.org/html/rfc4226 - // We can add an optional modifier - var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber)); - var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifier)); - - // Generate DT string - var offset = hash[^1] & 0xf; - Debug.Assert(offset + 4 < hash.Length); - var binaryCode = (hash[offset] & 0x7f) << 24 - | (hash[offset + 1] & 0xff) << 16 - | (hash[offset + 2] & 0xff) << 8 - | (hash[offset + 3] & 0xff); - - return binaryCode % Mod; - } - - private static byte[] ApplyModifier(byte[] input, string modifier) - { - if (String.IsNullOrEmpty(modifier)) - { - return input; - } - - var modifierBytes = _encoding.GetBytes(modifier); - var combined = new byte[checked(input.Length + modifierBytes.Length)]; - Buffer.BlockCopy(input, 0, combined, 0, input.Length); - Buffer.BlockCopy(modifierBytes, 0, combined, input.Length, modifierBytes.Length); - return combined; - } - - // More info: https://tools.ietf.org/html/rfc6238#section-4 - private static ulong GetCurrentTimeStepNumber() - { - var delta = DateTimeOffset.UtcNow - DateTimeOffset.UnixEpoch; - return (ulong)(delta.Ticks / _timestep.Ticks); - } - - public static int GenerateCode(byte[] securityToken, string modifier = null) - { - if (securityToken == null) - { - throw new ArgumentNullException(nameof(securityToken)); - } - - // Allow a variance of no greater than 9 minutes in either direction - var currentTimeStep = GetCurrentTimeStepNumber(); - using var hashAlgorithm = new HMACSHA1(securityToken); - return ComputeTotp(hashAlgorithm, currentTimeStep, modifier); - } - - public static bool ValidateCode(byte[] securityToken, int code, string modifier = null) - { - if (securityToken == null) - { - throw new ArgumentNullException(nameof(securityToken)); - } - - // Allow a variance of no greater than 9 minutes in either direction - var currentTimeStep = GetCurrentTimeStepNumber(); - using (var hashAlgorithm = new HMACSHA1(securityToken)) - { - for (var i = -2; i <= 2; i++) - { - var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep + i), modifier); - if (computedTotp == code) - { - return true; - } - } - } - - // No match - return false; - } -} \ No newline at end of file diff --git a/zero.Core/Persistence/Tokens/Token.cs b/zero.Core/Persistence/Tokens/Token.cs deleted file mode 100644 index 866b85fd..00000000 --- a/zero.Core/Persistence/Tokens/Token.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace zero.Persistence; - -[RavenCollection("Tokens")] -public class SecurityToken : ISupportsDbConventions -{ - public string Id { get; set; } - - public string Key { get; set; } - - public string Token { get; set; } - - public Dictionary Metadata { get; set; } = new(); -} \ No newline at end of file diff --git a/zero.Core/Persistence/Tokens/ZeroTokenProvider.cs b/zero.Core/Persistence/Tokens/ZeroTokenProvider.cs deleted file mode 100644 index ff1e37a0..00000000 --- a/zero.Core/Persistence/Tokens/ZeroTokenProvider.cs +++ /dev/null @@ -1,295 +0,0 @@ -using Microsoft.AspNetCore.Cryptography.KeyDerivation; -using Raven.Client.Documents.Session; -using System.Security.Cryptography; -using System.Text; - -namespace zero.Persistence; - -public class ZeroTokenProvider : IZeroTokenProvider -{ - readonly char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-".ToCharArray(); - - readonly RandomNumberGenerator randonNumberGenerator; - - protected IZeroStore Store { get; private set; } - - - public ZeroTokenProvider(IZeroStore store) - { - Store = store; - randonNumberGenerator = RandomNumberGenerator.Create(); - } - - - /// - public virtual async Task Create(string key, TimeSpan expires, int length = 82, Dictionary metadata = default) - { - if (key.IsNullOrWhiteSpace()) - { - throw new ArgumentException("Key cannot be empty"); - } - if (length < 16) - { - throw new ArgumentOutOfRangeException("Use at least a length of 16 for the token"); - } - - string tokenKey = Random(length); - - SecurityToken securityToken = new() - { - Id = TokenToId(tokenKey), - Token = tokenKey, - Key = HashKey(key), - Metadata = metadata ?? new() - }; - - IZeroDocumentSession session = Store.Session(); - - // saves the token - await session.StoreAsync(securityToken); - - // set the expires flag for the token - session.Expires(securityToken, expires); - await session.SaveChangesAsync(); - - return tokenKey; - } - - - /// - public virtual async Task Verify(string key, string token) - { - if (token.IsNullOrWhiteSpace() || key.IsNullOrWhiteSpace()) - { - return false; - } - - IZeroDocumentSession session = Store.Session(); - - // try to find a valid token - SecurityToken securityToken = await session.LoadAsync(TokenToId(token)); - bool isValid = securityToken != null && VerifyKey(securityToken.Key, key); - - // remove token from DB if it is valid - if (isValid) - { - session.Delete(securityToken); - await session.SaveChangesAsync(); - } - - return isValid; - } - - - /// - public virtual async Task VerifyAndReturn(string key, string token) - { - if (token.IsNullOrWhiteSpace() || key.IsNullOrWhiteSpace()) - { - return null; - } - - IZeroDocumentSession session = Store.Session(); - - // try to find a valid token - SecurityToken securityToken = await session.LoadAsync(TokenToId(token)); - bool isValid = securityToken != null && VerifyKey(securityToken.Key, key); - - // remove token from DB if it is valid - if (isValid) - { - session.Delete(securityToken); - await session.SaveChangesAsync(); - return securityToken; - } - - return null; - } - - - /// - public string Random(int length) - { - byte[] data = new byte[4 * length]; - randonNumberGenerator.GetBytes(data); - - StringBuilder result = new(length); - - for (int i = 0; i < length; i++) - { - var rnd = BitConverter.ToUInt32(data, i * 4); - var idx = rnd % chars.Length; - - result.Append(chars[idx]); - } - - return result.ToString(); - } - - - /// - public virtual async Task Exists(string token) - { - if (token.IsNullOrWhiteSpace()) - { - return false; - } - - IZeroDocumentSession session = Store.Session(); - - // try to find a valid token - SecurityToken securityToken = await session.LoadAsync(TokenToId(token)); - return securityToken != null; - } - - - /// - /// Converts the token to a database id - /// - string TokenToId(string token) - { - string collection = Store.Raven.Conventions.GetCollectionName(typeof(SecurityToken)); - string idPrefix = Store.Raven.Conventions.TransformTypeCollectionNameToDocumentIdPrefix(collection); - return idPrefix + Store.Raven.Conventions.IdentityPartsSeparator + token; - } - - - /// - /// Creates a hash for the security token key. - /// Borrowed from the .NET Core PasswordHasher - /// (see: https://github.com/dotnet/aspnetcore/blob/release/5.0/src/Identity/Extensions.Core/src/PasswordHasher.cs) - /// - string HashKey(string key) - { - key = key.ToLowerInvariant(); - - KeyDerivationPrf prf = KeyDerivationPrf.HMACSHA256; - int iterationCount = 10000; - int saltSize = 128 / 8; - int numBytesRequested = 256 / 8; - - byte[] salt = new byte[saltSize]; - randonNumberGenerator.GetBytes(salt); - byte[] subkey = KeyDerivation.Pbkdf2(key, salt, prf, iterationCount, numBytesRequested); - - var outputBytes = new byte[13 + salt.Length + subkey.Length]; - outputBytes[0] = 0x01; - WriteNetworkByteOrder(outputBytes, 1, (uint)prf); - WriteNetworkByteOrder(outputBytes, 5, (uint)iterationCount); - WriteNetworkByteOrder(outputBytes, 9, (uint)saltSize); - Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length); - Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length); - return Convert.ToBase64String(outputBytes); - } - - - /// - /// Verifies the given key. - /// Borrowed from the .NET Core PasswordHasher - /// (see: https://github.com/dotnet/aspnetcore/blob/release/5.0/src/Identity/Extensions.Core/src/PasswordHasher.cs) - /// - bool VerifyKey(string storedKey, string key) - { - byte[] decodedStoredKey = Convert.FromBase64String(storedKey); - - try - { - KeyDerivationPrf prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedStoredKey, 1); - int embeddedIterCount = (int)ReadNetworkByteOrder(decodedStoredKey, 5); - int saltLength = (int)ReadNetworkByteOrder(decodedStoredKey, 9); - - // Read the salt: must be >= 128 bits - if (saltLength < 128 / 8) - { - return false; - } - - byte[] salt = new byte[saltLength]; - Buffer.BlockCopy(decodedStoredKey, 13, salt, 0, salt.Length); - - // Read the subkey (the rest of the payload): must be >= 128 bits - int subkeyLength = decodedStoredKey.Length - 13 - salt.Length; - if (subkeyLength < 128 / 8) - { - return false; - } - byte[] expectedSubkey = new byte[subkeyLength]; - Buffer.BlockCopy(decodedStoredKey, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length); - - // Hash the incoming key and verify it - byte[] actualSubkey = KeyDerivation.Pbkdf2(key, salt, prf, embeddedIterCount, subkeyLength); - - return CryptographicOperations.FixedTimeEquals(actualSubkey, expectedSubkey); - } - catch - { - return false; - } - } - - - static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value) - { - buffer[offset + 0] = (byte)(value >> 24); - buffer[offset + 1] = (byte)(value >> 16); - buffer[offset + 2] = (byte)(value >> 8); - buffer[offset + 3] = (byte)(value >> 0); - } - - static uint ReadNetworkByteOrder(byte[] buffer, int offset) - { - return ((uint)(buffer[offset + 0]) << 24) - | ((uint)(buffer[offset + 1]) << 16) - | ((uint)(buffer[offset + 2]) << 8) - | ((uint)(buffer[offset + 3])); - } -} - - -public interface IZeroTokenProvider -{ - /// - /// Generates a token for a with a specified lifespan. - /// - /// The purpose the token will be used for. The key must match when verifying the token and should always be hidden from the user. - /// The token will automatically expire after this timespan. - /// Length of the geneated token. - /// Additional metadata to store with the token. This data can be retrieved on verification with - /// - /// The generated token with the specified . - /// - Task Create(string key, TimeSpan expires, int length = 82, Dictionary metadata = default); - - /// - /// Validates the passed for the specified . - /// - /// The purpose the token was used for. - /// The previously generated token. - /// - /// False, if the token could not be found or it has already expired. - /// - Task Verify(string key, string token); - - /// - /// Validates the passed for the specified . - /// - /// The purpose the token was used for. - /// The previously generated token. - /// - /// Null, if the token could not be found or it has already expired. - /// - Task VerifyAndReturn(string key, string token); - - /// - /// Generates a random token with the specified using the RNGCryptoServiceProvider. - /// - /// - /// This method won't store the token in the database and can't be used for verification. Use instead. - /// - string Random(int length); - - /// - /// Determines whether a token exists in the database. - /// - Task Exists(string token); -} diff --git a/zero.Core/Persistence/ZeroDocumentConventionsBuilder.cs b/zero.Core/Persistence/ZeroDocumentConventionsBuilder.cs deleted file mode 100644 index 423a11e2..00000000 --- a/zero.Core/Persistence/ZeroDocumentConventionsBuilder.cs +++ /dev/null @@ -1,142 +0,0 @@ -using Raven.Client.Documents.Conventions; -using System.Collections.Concurrent; -using System.Reflection; -using System.Text; - -namespace zero.Persistence; - -public class ZeroDocumentConventionsBuilder : IZeroDocumentConventionsBuilder -{ - protected HashSet PolymorphTypes { get; private set; } = new(); - - protected Type AcceptsZeroConventionsType { get; set; } = typeof(ISupportsDbConventions); - - protected char IdentityPartsSeparator { get; set; } = '.'; - - protected IZeroOptions Options { get; private set; } - - protected static ConcurrentDictionary CachedTypeCollectionNameMap = new(); - - - public ZeroDocumentConventionsBuilder(IZeroOptions options) - { - Options = options; - } - - - /// - public void Run(DocumentConventions conventions) - { - conventions.MaxNumberOfRequestsPerSession = 1000; - conventions.IdentityPartsSeparator = IdentityPartsSeparator; - conventions.TransformTypeCollectionNameToDocumentIdPrefix = TransformTypeCollectionNameToDocumentIdPrefix; - conventions.FindCollectionName = FindCollectionName; - conventions.RegisterAsyncIdConvention((_, entity) => GetDocumentId(conventions, entity)); - } - - - /// - /// Get a document ID from an entity - /// - protected virtual Task GetDocumentId(DocumentConventions conventions, T entity) - { - string collection = conventions.GetCollectionName(entity); - - StringBuilder documentId = new(); - documentId.Append(conventions.TransformTypeCollectionNameToDocumentIdPrefix(collection)); - documentId.Append(IdentityPartsSeparator); - documentId.Append(IdGenerator.Create()); - - return Task.FromResult(documentId.ToString()); - } - - - /// - /// Finds the collection name for a certain type based on internal rules - /// - protected virtual string FindCollectionName(Type originalType) - { - string collection = null; - - Type type = originalType; - - Func cache = name => - { - CachedTypeCollectionNameMap.TryAdd(type, name); - return name; - }; - - // get inner type for revisions - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Raven.Client.Documents.Subscriptions.Revision<>)) - { - type = type.GetGenericArguments().FirstOrDefault(); - } - - // try to resolve from cache - if (CachedTypeCollectionNameMap.TryGetValue(type, out collection)) - { - return collection; - } - - // do not alter non-internal entities - if (!AcceptsZeroConventionsType.IsAssignableFrom(type)) - { - return cache(DocumentConventions.DefaultGetCollectionName(type)); - } - - // use name from attribute if available - RavenCollectionAttribute collectionAttribute = type.GetCustomAttribute(true); - - if (collectionAttribute != null) - { - return cache(collectionAttribute.Name); - } - - // use base interface if available - Type interfaceBaseType = type.GetInterfaces().FirstOrDefault(x => x.IsInterface && AcceptsZeroConventionsType.IsAssignableFrom(x) && x.Name != AcceptsZeroConventionsType.Name); - - if (interfaceBaseType != null) - { - // use name from attribute if available - collectionAttribute = interfaceBaseType.GetCustomAttribute(true); - if (collectionAttribute != null) - { - return cache(collectionAttribute.Name); - } - } - - // use base type for polymorphism - Type polymorphBaseType = PolymorphTypes.FirstOrDefault(x => type.IsSubclassOf(x)); - - if (polymorphBaseType != null) - { - return cache(DocumentConventions.DefaultGetCollectionName(polymorphBaseType)); - } - - return cache(DocumentConventions.DefaultGetCollectionName(type)); - } - - - /// - /// Translates the types collection name to the document id prefix - /// - protected virtual string TransformTypeCollectionNameToDocumentIdPrefix(string name) - { - RavenOptions options = Options.For(); - if (options != null && !options.CollectionPrefix.IsNullOrWhiteSpace()) - { - name = options.CollectionPrefix.EnsureEndsWith(IdentityPartsSeparator) + name.TrimStart(options.CollectionPrefix); - } - - return name.ToCamelCaseId(); - } -} - - -public interface IZeroDocumentConventionsBuilder -{ - /// - /// Applies internal rules to the RavenDB document conventions - /// - void Run(DocumentConventions conventions); -} \ No newline at end of file diff --git a/zero.Core/Persistence/ZeroDocumentSession.cs b/zero.Core/Persistence/ZeroDocumentSession.cs deleted file mode 100644 index 20fda22f..00000000 --- a/zero.Core/Persistence/ZeroDocumentSession.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Session; - -namespace zero.Persistence; - -public class ZeroDocumentSession : AsyncDocumentSession, IZeroDocumentSession -{ - public IZeroDocumentSession Core { get; private set; } - - public IDocumentSession Synchronous => _synchronous ?? (_synchronous = DocumentStore.OpenSession(DatabaseName)); - - IDocumentSession _synchronous; - - public ZeroDocumentSession(DocumentStore documentStore, Guid id, SessionOptions options, string coreDatabase = null) : base(documentStore, id, options) - { - if (coreDatabase.HasValue()) - { - Core = new ZeroDocumentSession(documentStore, id, new SessionOptions() - { - Database = coreDatabase, - DisableAtomicDocumentWritesInClusterWideTransaction = options.DisableAtomicDocumentWritesInClusterWideTransaction, - NoCaching = options.NoCaching, - NoTracking = options.NoTracking, - RequestExecutor = options.RequestExecutor, - TransactionMode = options.TransactionMode - }); - } - else - { - Core = this; - } - } - - public bool IsDisposed { get; set; } - - public override void Dispose() - { - _synchronous?.Dispose(); - base.Dispose(); - } -} - - -public interface IZeroDocumentSession : IAsyncDocumentSession -{ - IZeroDocumentSession Core { get; } - - IDocumentSession Synchronous { get; } - - bool IsDisposed { get; } -} diff --git a/zero.Core/Persistence/ZeroDocumentSessionExtensions.cs b/zero.Core/Persistence/ZeroDocumentSessionExtensions.cs deleted file mode 100644 index bf923c30..00000000 --- a/zero.Core/Persistence/ZeroDocumentSessionExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace zero.Persistence; - -public static class ZeroDocumentSessionExtensions -{ - public static void SetCollection(this IZeroDocumentSession session, T model, string collectionName) - { - session.Advanced.GetMetadataFor(model)[Raven.Client.Constants.Documents.Metadata.Collection] = collectionName; - } - - public static void Expires(this IZeroDocumentSession session, T model, TimeSpan expires) - { - session.Advanced.GetMetadataFor(model)[Raven.Client.Constants.Documents.Metadata.Expires] = DateTime.UtcNow.AddSeconds(expires.TotalSeconds); - } -} diff --git a/zero.Core/Persistence/ZeroDocumentStore.cs b/zero.Core/Persistence/ZeroDocumentStore.cs deleted file mode 100644 index 7abda123..00000000 --- a/zero.Core/Persistence/ZeroDocumentStore.cs +++ /dev/null @@ -1,147 +0,0 @@ -using Raven.Client; -using Raven.Client.Documents; -using Raven.Client.Documents.BulkInsert; -using Raven.Client.Documents.Operations; -using Raven.Client.Documents.Queries; -using Raven.Client.Documents.Session; - -namespace zero.Persistence; - -public class ZeroDocumentStore : DocumentStore, IZeroDocumentStore -{ - public ZeroDocumentStore(IZeroOptions options) : base() - { - Options = options.For(); - Database = null; - } - - protected RavenOptions Options { get; set; } - - /// - public string ResolvedDatabase { get; set; } - - - /// - public OperationExecutor GetOperationExecutor(string database = null) - { - return Operations.ForDatabase(database ?? ResolvedDatabase); - } - - /// - public override IAsyncDocumentSession OpenAsyncSession(string database) - { - return OpenAsyncSession(new SessionOptions() - { - Database = database - }); - } - - /// - public override IAsyncDocumentSession OpenAsyncSession(SessionOptions options) - { - options.Database = options.Database ?? ResolvedDatabase; - - AssertInitialized(); - EnsureNotClosed(); - - var sessionId = Guid.NewGuid(); - var session = new ZeroDocumentSession(this, sessionId, options, Options.Database); - RegisterEvents(session); - AfterSessionCreated(session); - session.OnSessionDisposing += (sender, args) => - { - session.IsDisposed = true; - }; - - session.Advanced.WaitForIndexesAfterSaveChanges(TimeSpan.FromSeconds(0.5), throwOnTimeout: false); - session.Advanced.DocumentStore.AggressivelyCacheFor(TimeSpan.FromHours(1), options.Database); // TODO I guess this will not work in backoffice when we want to see changes immediately - // maybe use caching for frontend but not for backoffice? - - return session; - } - - /// - public override IAsyncDocumentSession OpenAsyncSession() - { - return OpenAsyncSession(new SessionOptions() - { - Database = ResolvedDatabase - }); - } - - /// - public override IDocumentSession OpenSession(SessionOptions options) - { - options.Database = options.Database ?? ResolvedDatabase; - - AssertInitialized(); - EnsureNotClosed(); - - var sessionId = Guid.NewGuid(); - var session = new DocumentSession(this, sessionId, options); - RegisterEvents(session); - AfterSessionCreated(session); - - session.Advanced.DocumentStore.AggressivelyCacheFor(TimeSpan.FromHours(1), options.Database); - - return session; - } - - /// - public override IDocumentSession OpenSession(string database) - { - return OpenSession(new SessionOptions() - { - Database = database - }); - } - - /// - public override IDocumentSession OpenSession() - { - return OpenSession(new SessionOptions() - { - Database = ResolvedDatabase - }); - } - - /// - public override BulkInsertOperation BulkInsert(string database = null, CancellationToken token = default) - { - return base.BulkInsert(database ?? ResolvedDatabase, token); - } - - /// - public async Task PurgeAsync(string database = null, string querySuffix = null, Parameters parameters = null) - { - var collectionName = Conventions.FindCollectionName(typeof(T)); - var operationQuery = new DeleteByQueryOperation(new IndexQuery() - { - Query = $"from {collectionName} c {querySuffix ?? String.Empty}", - QueryParameters = parameters - }, new QueryOperationOptions { AllowStale = true }); - - Operation operation = await GetOperationExecutor(database ?? ResolvedDatabase).SendAsync(operationQuery); - - await operation.WaitForCompletionAsync(); - } -} - - -public interface IZeroDocumentStore : IDocumentStore -{ - /// - /// The database which has been resolved from the current application - /// - string ResolvedDatabase { get; set; } - - /// - /// Get operation executor - /// - OperationExecutor GetOperationExecutor(string database = null); - - /// - /// Purges a collection - /// - Task PurgeAsync(string database = null, string querySuffix = null, Parameters parameters = null); -} \ No newline at end of file diff --git a/zero.Core/Persistence/ZeroPersistenceModule.cs b/zero.Core/Persistence/ZeroPersistenceModule.cs deleted file mode 100644 index b539d5d0..00000000 --- a/zero.Core/Persistence/ZeroPersistenceModule.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Raven.Client.Documents; -using Raven.Client.Documents.Indexes; -using Raven.Client.Http; - -namespace zero.Persistence; - -internal class ZeroPersistenceModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddSingleton(); - services.AddSingleton(CreateRavenStore); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - services.AddOptions().Bind(configuration.GetSection("Zero:Raven")); - } - - /// - /// Creates and configures the raven store - /// - protected ZeroDocumentStore CreateRavenStore(IServiceProvider services) - { - IZeroOptions options = services.GetService(); - RavenOptions ravenOptions = options.For(); - IZeroDocumentConventionsBuilder conventionsBuilder = services.GetService(); - - IZeroDocumentStore store = new ZeroDocumentStore(options) - { - Database = ravenOptions.DatabaseIsDefault ? ravenOptions.Database : null, - Urls = new string[1] { ravenOptions.Url }, - Conventions = - { - AggressiveCache = - { - Duration = TimeSpan.FromHours(1), - Mode = AggressiveCacheMode.TrackChanges - } - } - }; - - conventionsBuilder.Run(store.Conventions); - - IDocumentStore raven = store.Initialize(); - - // create all indexes - if (options.Initialized) - { - var indexes = ravenOptions.Indexes.BuildAll(options, store); - IndexCreation.CreateIndexes(indexes, store, database: ravenOptions.Database); - } - - return (ZeroDocumentStore)raven; - } -} \ No newline at end of file diff --git a/zero.Core/Persistence/ZeroStore.cs b/zero.Core/Persistence/ZeroStore.cs deleted file mode 100644 index 035e0807..00000000 --- a/zero.Core/Persistence/ZeroStore.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Raven.Client.Documents.Session; - -namespace zero.Persistence; - -public class ZeroStore : IZeroStore -{ - public ZeroStore(IZeroDocumentStore raven, IZeroOptions options) : base() - { - Options = options; - Raven = raven; - //Database = null; - } - - protected IZeroOptions Options { get; set; } - protected Dictionary ScopedSessions { get; set; } = new(); - - /// - string _resolvedDatabase = null; - public string ResolvedDatabase - { - get => _resolvedDatabase; - set - { - _resolvedDatabase = value; - Raven.ResolvedDatabase = value; - } - } - - - /// - public IZeroDocumentStore Raven { get; private set; } - - - /// - public IZeroDocumentSession Session(string database = null, ZeroSessionResolution resolution = ZeroSessionResolution.Reuse, SessionOptions options = null) - { - database ??= ResolvedDatabase; - options ??= new SessionOptions() { Database = database }; - - if (!database.HasValue()) - { - return Session(true, database, resolution, options); - } - - if (resolution == ZeroSessionResolution.Create) - { - return Raven.OpenAsyncSession(options) as IZeroDocumentSession; - } - - if (!ScopedSessions.TryGetValue(database, out IZeroDocumentSession session)) - { - session = Raven.OpenAsyncSession(options) as IZeroDocumentSession; - ScopedSessions.TryAdd(database, session); - } - - if (session.IsDisposed) - { - session = Raven.OpenAsyncSession(options) as IZeroDocumentSession; - ScopedSessions.Remove(database); - ScopedSessions.TryAdd(database, session); - } - - return session; - } - - - /// - public IZeroDocumentSession Session(bool global, string database = null, ZeroSessionResolution resolution = ZeroSessionResolution.Reuse, SessionOptions options = null) - { - return Session(global ? Options.For().Database : database, resolution, options); - } -} - - -public enum ZeroSessionResolution -{ - Reuse = 0, - Create = 1 -} - - -public interface IZeroStore -{ - /// - /// The database which has been resolved from the current application - /// - string ResolvedDatabase { get; set; } - - /// - /// Get underlying raven document store - /// - IZeroDocumentStore Raven { get; } - - /// - /// Use a specific session - /// - IZeroDocumentSession Session(string database = null, ZeroSessionResolution resolution = ZeroSessionResolution.Reuse, SessionOptions options = null); - - /// - /// Use a session for the core database - /// - IZeroDocumentSession Session(bool global, string database = null, ZeroSessionResolution resolution = ZeroSessionResolution.Reuse, SessionOptions options = null); -} \ No newline at end of file diff --git a/zero.Core/Preview/PreviewOptions.cs b/zero.Core/Preview/PreviewOptions.cs deleted file mode 100644 index 6c3d4a3a..00000000 --- a/zero.Core/Preview/PreviewOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace zero.Routing; - -public class PreviewOptions -{ - public PreviewOptions() - { - Path = "/preview"; - QueryParameter = "zero_preview"; - TokenExpirationInMinutes = 10; - } - - public string Path { get; set; } - - public string QueryParameter { get; set; } - - public double TokenExpirationInMinutes { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Preview/PreviewService.cs b/zero.Core/Preview/PreviewService.cs deleted file mode 100644 index d2ed3c90..00000000 --- a/zero.Core/Preview/PreviewService.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using System.Security.Claims; - -namespace zero.Preview; - -public class PreviewService : IPreviewService -{ - protected IZeroTokenProvider TokenProvider { get; private set; } - - protected IZeroOptions Options { get; private set; } - - - public PreviewService(IZeroTokenProvider tokenProvider, IZeroOptions options) - { - TokenProvider = tokenProvider; - Options = options; - } - - - /// - public async Task CreateAccessToken(string key, ClaimsPrincipal backofficeUser) - { - string fullKey = GetKey(key, backofficeUser); - return await TokenProvider.Create(fullKey, TimeSpan.FromMinutes(Options.For().TokenExpirationInMinutes), 32); - } - - - /// - public async Task IsAccessGranted(HttpRequest request, ClaimsPrincipal backofficeUser) - { - string token = request.Query[Options.For().QueryParameter]; - - if (!token.HasValue()) - { - return false; - } - - return await IsAccessGranted(token, backofficeUser); - } - - - /// - public async Task IsAccessGranted(string token, ClaimsPrincipal backofficeUser) - { - return await TokenProvider.Exists(token); - } - - - /// - /// Generate the key for a new preview access - /// - protected string GetKey(string keyPrefix, ClaimsPrincipal backofficeUser) - { - //string userId = UserManager.GetUserId(backofficeUser); - - //if (userId.IsNullOrEmpty()) - //{ - // throw new InvalidOperationException("Preview access is only granted for authenticated users"); - //} - - if (keyPrefix.IsNullOrEmpty()) - { - throw new ArgumentNullException(nameof(keyPrefix), "Please define a key for generating preview access"); - } - - return "zero:preview:" + /*userId + ":" + */ keyPrefix; - } -} - -public interface IPreviewService -{ - /// - /// Create a token for a new preview access. - /// - Task CreateAccessToken(string key, ClaimsPrincipal backofficeUser); - - // - /// Determine whether the access to the preview is granted based on an HTTP request - /// - Task IsAccessGranted(HttpRequest request, ClaimsPrincipal backofficeUser); - - /// - /// Determine whether the access to the preview is granted based on the passed token - /// - Task IsAccessGranted(string token, ClaimsPrincipal backofficeUser); -} \ No newline at end of file diff --git a/zero.Core/Preview/Routing/PreviewRoute.cs b/zero.Core/Preview/Routing/PreviewRoute.cs deleted file mode 100644 index b1dee14a..00000000 --- a/zero.Core/Preview/Routing/PreviewRoute.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace zero.Preview; - -public class PreviewRoute : IRouteModel -{ - public Route Route { get; set; } - - public IRouteModel PreviewedRouteModel { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Preview/Routing/PreviewRouteModel.cs b/zero.Core/Preview/Routing/PreviewRouteModel.cs deleted file mode 100644 index c7c5b687..00000000 --- a/zero.Core/Preview/Routing/PreviewRouteModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace zero.Routing; - -public class PreviewRouteModel : ISupportsRouting -{ - public string Id { get; set; } - - public string Hash { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Preview/Routing/PreviewRouteProvider.cs b/zero.Core/Preview/Routing/PreviewRouteProvider.cs deleted file mode 100644 index 7bd6faac..00000000 --- a/zero.Core/Preview/Routing/PreviewRouteProvider.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace zero.Preview; - -public class PreviewRouteProvider : ZeroRouteProvider -{ - public PreviewRouteProvider() : base("zero.preview") { } - - - /// - public override Task IsRouteStale(RoutingContext context, PreviewRouteModel previous, PreviewRouteModel current) => Task.FromResult(false); - - /// - public override async Task Create(RoutingContext context, PreviewRouteModel model) - { - Route route = await base.Create(context, model); - route.Url = context.Context.Options.For().Path.EnsureStartsWith('/').TrimEnd('/'); - return route; - } - - /// - public override async IAsyncEnumerable Seed(RoutingContext context) - { - yield return await Create(context, new PreviewRouteModel() - { - Id = String.Empty, - Hash = String.Empty - }); - } - - /// - public override Task Model(RoutingContext context, RouteResponse response) - { - return Task.FromResult(new PreviewRoute() - { - Route = response.Route - }); - } - - /// - public override IRouteEndpoint Map(RoutingContext context, IRouteModel route) - { - return base.Map(context, route); - } -} \ No newline at end of file diff --git a/zero.Core/Preview/ZeroPreviewModule.cs b/zero.Core/Preview/ZeroPreviewModule.cs deleted file mode 100644 index c9166e24..00000000 --- a/zero.Core/Preview/ZeroPreviewModule.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Preview; - -internal class ZeroPreviewModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - services.AddOptions().Bind(configuration.GetSection("Zero:Preview")); - } -} \ No newline at end of file diff --git a/zero.Core/RecycleBin/RecycleBinApi.cs b/zero.Core/RecycleBin/RecycleBinApi.cs deleted file mode 100644 index 11afbded..00000000 --- a/zero.Core/RecycleBin/RecycleBinApi.cs +++ /dev/null @@ -1,258 +0,0 @@ -//using Raven.Client.Documents; -//using Raven.Client.Documents.Linq; -//using Raven.Client.Documents.Session; -//using System.Collections.Generic; -//using System.Threading.Tasks; -//using zero.Core.Collections; -//using zero.Core.Database.Indexes; -//using zero.Core.Entities; -//using zero.Core.Extensions; -//using zero.Core.Utils; - -//namespace zero.Core.Api -//{ -// public class RecycleBinApi : BackofficeApi, IRecycleBinApi -// { -// RecycledEntity Blueprint; - -// public RecycleBinApi(IStoreContext store, RecycledEntity blueprint) : base(store) -// { -// Blueprint = blueprint; -// } - - -// /// -// public async Task> Add(TEntity model, string group = null, string operationId = null) where TEntity : ZeroEntity -// { -// RecycledEntity entity = Blueprint.Clone(); -// entity.Group = group; -// entity.Content = model; -// entity.OriginalId = model.Id; -// entity.OperationId = operationId; -// entity.Name = model.Name; - -// return await SaveModel(entity); -// } - - -// /// -// public async Task>> Add(IEnumerable models, string group = null) where TEntity : ZeroEntity -// { -// IList results = new List(); -// string operationId = IdGenerator.Create(); - -// foreach (TEntity model in models) -// { -// EntityResult result = await Add(model, group, operationId); - -// if (!result.IsSuccess) -// { -// return EntityResult>.Fail(result.Errors); -// } - -// results.Add(result.Model); -// } - -// return EntityResult>.Success(results); -// } - - -// /// -// public async Task GetById(string id) -// { -// return await GetById(id); -// } - - -// /// -// public async Task> GetByQuery(RecycleBinListQuery query) -// { -// query.SearchSelector = x => x.Name; - -// return await Session.Query() -// .WhereIf(x => x.Group == query.Group, !query.Group.IsNullOrWhiteSpace()) -// .WhereIf(x => x.OperationId == query.OperationId, !query.OperationId.IsNullOrWhiteSpace()) -// .ToQueriedListAsync(query); -// } - - -// /// -// public async Task> GetByOperation(string operationId) -// { -// return await Session.Query() -// .Where(x => x.OperationId == operationId) -// .ToListAsync(); -// } - - -// /// -// /// Get affected entity count from a specific operation -// /// -// public async Task GetCountByOperation(string operationId) -// { -// return await Session.Query() -// .Where(x => x.OperationId == operationId) -// .CountAsync(); -// } - - -// /// -// public async Task> Delete(string id) -// { -// return await DeleteById(id); -// } - - -// /// -// public async Task> DeleteAll() -// { -// // TODO make Purge operations app-aware -// return await Purge(); -// } - - -// /// -// public async Task> DeleteByOperation(string operationId) -// { -// return await Purge("where c.OperationId = $id", new Raven.Client.Parameters() { { "id", operationId } }); -// } - - -// /// -// public async Task> DeleteByGroup(string group) -// { -// return await Purge("where c.Group = $group", new Raven.Client.Parameters() { { "group", group } }); -// } -// } - -// public interface IRecycleBinApi -// { -// /// -// /// Adds an entity to the recycle bin. This operation will not remove this entity from it's own collection! -// /// -// Task> Add(TEntity model, string group = null, string operationId = null) where TEntity : ZeroEntity; - -// /// -// /// Adds the specified entities to the recycle bin. This operation will not remove entities from their own collection! -// /// -// Task>> Add(IEnumerable models, string group = null) where TEntity : ZeroEntity; - -// /// -// /// Get recycled entity by Id -// /// -// Task GetById(string id); - -// /// -// /// Get all recycled items -// /// -// Task> GetByQuery(RecycleBinListQuery query); - -// /// -// /// Get affected entities from a specific operation -// /// -// Task> GetByOperation(string operationId); - -// /// -// /// Get affected entity count from a specific operation -// /// -// Task GetCountByOperation(string operationId); - -// // -// /// Deletes a recycled entity by Id -// /// -// Task> Delete(string id); - -// /// -// /// Purges the recycle bin -// /// -// Task> DeleteAll(); - -// /// -// /// Deletes all recycled items from a specific operation -// /// -// Task> DeleteByOperation(string operationId); - -// /// -// /// Deletes all recycled items from a specific group -// /// -// Task> DeleteByGroup(string group); -// } -//} - - -/* - * RESTORE FOR PAGES - * - * - * - -/// - /// Restores a page from the recycle bin - /// - Task> Restore(string id, bool includeDescendants = false); - - - /// - public async Task> Restore(string id, bool includeDescendants = false) - { - EntityResult result = new EntityResult(); - RecycledEntity recycledEntity = await RecycleBinApi.GetById(id); - List entities = new List() { recycledEntity }; - - if (recycledEntity == null) - { - return EntityResult.Fail(); // TODO correct error message - } - - // get descendants from the operation - if (includeDescendants && !recycledEntity.OperationId.IsNullOrEmpty()) - { - entities = (await RecycleBinApi.GetByOperation(recycledEntity.OperationId)).ToList(); - } - - // fill ids - string[] ids = entities.Select(x => x.OriginalId).ToArray(); - - // check if parents are available - string[] parentIds = entities.Select(x => x.Content as Page).Where(x => x != null).Select(x => x.ParentId).ToArray(); - parentIds = (await Load(parentIds)).Where(x => x.Value != null).Select(x => x.Value.Id).ToArray(); - - // validate and restore all items - foreach (RecycledEntity entity in entities) - { - // check if it contains page data - if (entity.Group != RECYCLE_BIN_GROUP || !(entity.Content is Page)) - { - //result.AddError("Cannot parse recycled entity as an Page in group \"" + RECYCLE_BIN_GROUP + "\""); // TODO correct error message - continue; - } - - // get page - Page page = entity.Content as Page; - page.IsActive = false; - - // validate app and parent - if (!page.ParentId.IsNullOrEmpty() && !ids.Contains(page.ParentId) && !parentIds.Contains(page.ParentId)) - { - // TODO correct error message - continue; - } - - // restore to pages - EntityResult saveResult = await Create(page); - } - - // delete affected entities from recycle bin - if (!recycledEntity.OperationId.IsNullOrEmpty()) - { - await RecycleBinApi.DeleteByOperation(recycledEntity.OperationId); - } - - // set result - result.Model = ids; - result.IsSuccess = true; - - return result; - } - - */ \ No newline at end of file diff --git a/zero.Core/RecycleBin/RecycleBinListQuery.cs b/zero.Core/RecycleBin/RecycleBinListQuery.cs deleted file mode 100644 index c1ee3f7d..00000000 --- a/zero.Core/RecycleBin/RecycleBinListQuery.cs +++ /dev/null @@ -1,9 +0,0 @@ -//namespace zero.Core.Entities -//{ -// public class RecycleBinListQuery : ListQuery -// { -// public string Group { get; set; } - -// public string OperationId { get; set; } -// } -//} diff --git a/zero.Core/RecycleBin/RecycledEntity.cs b/zero.Core/RecycleBin/RecycledEntity.cs deleted file mode 100644 index 714a2bfd..00000000 --- a/zero.Core/RecycleBin/RecycledEntity.cs +++ /dev/null @@ -1,28 +0,0 @@ -//using zero.Core.Attributes; - -//namespace zero.Core.Entities -//{ -// [RavenCollection("RecycleBin")] -// public class RecycledEntity : ZeroEntity -// { -// /// -// /// Id of the recycled entity -// /// -// public string OriginalId { get; set; } - -// /// -// /// Group recycled entities together which have been recycled in a single operation -// /// -// public string OperationId { get; set; } - -// /// -// /// Contains the entity content -// /// -// public ZeroEntity Content { get; set; } - -// /// -// /// Recycled entities can be grouped together (e.g. pages, media, ...) -// /// -// public string Group { get; set; } -// } -//} diff --git a/zero.Core/Rendering/RazorRenderer.cs b/zero.Core/Rendering/RazorRenderer.cs deleted file mode 100644 index 98b08fa1..00000000 --- a/zero.Core/Rendering/RazorRenderer.cs +++ /dev/null @@ -1,345 +0,0 @@ -using Microsoft.AspNetCore.Html; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.Razor; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.ViewEngines; -using Microsoft.AspNetCore.Mvc.ViewFeatures; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using System.Diagnostics; -using System.IO; -using System.Text.Encodings.Web; - -namespace zero.Rendering; - -public class RazorRenderer : IRazorRenderer, IDisposable -{ - protected IRazorViewEngine ViewEngine { get; set; } - - protected IRazorPageActivator PageActivator { get; set; } - - protected ITempDataDictionaryFactory TempDataDictionaryFactory { get; set; } - - protected IModelMetadataProvider ModelMetadataProvider { get; set; } - - protected IHttpContextAccessor HttpContextAccessor { get; set; } - - protected IServiceScope ServiceScope { get; set; } - - protected HtmlHelperOptions HtmlHelperOptions { get; set; } - - - public RazorRenderer(IRazorViewEngine viewEngine, IRazorPageActivator pageActivator, IHttpContextAccessor httpContextAccessor, ITempDataDictionaryFactory tempDataDictionaryFactory, - IModelMetadataProvider modelMetadataProvider, IServiceProvider serviceProvider, IOptions mvcHelperOptions) - { - ViewEngine = viewEngine; - PageActivator = pageActivator; - HttpContextAccessor = httpContextAccessor; - TempDataDictionaryFactory = tempDataDictionaryFactory; - ModelMetadataProvider = modelMetadataProvider; - ServiceScope = serviceProvider.CreateScope(); - HtmlHelperOptions = mvcHelperOptions.Value.HtmlHelperOptions; - } - - - /// - /// Renders a razor component to a string - /// - public async Task ComponentAsync(object args = null) where T : ViewComponent - { - return await ComponentAsync(typeof(T), args); - } - - - /// - /// Renders a razor component to a string - /// - public async Task ComponentAsync(Type componentType, object args = null) - { - return await ComponentAsync(componentType, BuildActionContext(), args); - } - - - /// - /// Renders a razor component to a string - /// - public async Task ComponentAsync(string componentName, object args = null) - { - return await ComponentAsync(componentName, BuildActionContext(), args); - } - - - /// - /// Renders a razor component to a string - /// - public async Task ComponentAsync(ActionContext context, object args = null) where T : ViewComponent - { - return await ComponentAsync(typeof(T), context, args); - } - - - /// - /// Renders a razor component to a string - /// - public async Task ComponentAsync(Type componentType, ActionContext context, object args = null) - { - IViewComponentHelper viewComponentHelper = context.HttpContext.RequestServices.GetRequiredService(); - - using StringWriter stringWriter = new(); - - ViewContext viewContext = BuildViewContext(context, stringWriter); - - (viewComponentHelper as IViewContextAware)?.Contextualize(viewContext); - IHtmlContent result = await viewComponentHelper.InvokeAsync(componentType, args); - - result.WriteTo(stringWriter, HtmlEncoder.Default); - await stringWriter.FlushAsync(); - return stringWriter.ToString(); - } - - - /// - /// Renders a razor component to a string - /// - public async Task ComponentAsync(string componentName, ActionContext context, object args = null) - { - IViewComponentHelper viewComponentHelper = context.HttpContext.RequestServices.GetRequiredService(); - - using StringWriter stringWriter = new(); - - ViewContext viewContext = BuildViewContext(context, stringWriter); - - (viewComponentHelper as IViewContextAware)?.Contextualize(viewContext); - IHtmlContent result = await viewComponentHelper.InvokeAsync(componentName, args); - - result.WriteTo(stringWriter, HtmlEncoder.Default); - await stringWriter.FlushAsync(); - return stringWriter.ToString(); - } - - - /// - /// Renders a razor page to a string - /// - public async Task PageAsync(T model) where T : PageModel - { - IRazorPage pageResult = FindPage(model.PageContext, model.PageContext.ActionDescriptor.RelativePath); - - using StringWriter stringWriter = new(); - - RazorView view = new RazorView(ViewEngine, PageActivator, new List(), pageResult, HtmlEncoder.Default, new DiagnosticListener("ViewRenderService")); - - ViewContext viewContext = BuildViewContext(model.PageContext, stringWriter, view); - viewContext.RouteData = model.RouteData; - viewContext.ViewData = model.ViewData; - - Microsoft.AspNetCore.Mvc.RazorPages.Page razorPage = (Microsoft.AspNetCore.Mvc.RazorPages.Page)pageResult; - razorPage.PageContext = model.PageContext; - razorPage.ViewContext = viewContext; - - PageActivator.Activate(razorPage, viewContext); - - await razorPage.ExecuteAsync(); - await stringWriter.FlushAsync(); - - return stringWriter.ToString(); - } - - - /// - /// Renders a razor view to a string - /// - public async Task ViewAsync(string view, object model = null) - { - return await ViewAsync(BuildActionContext(), view, model); - } - - - /// - /// Renders a razor view to a string - /// - public async Task ViewAsync(ActionContext context, string view, object model = null) - { - IView viewResult = FindView(context, view); - - using StringWriter stringWriter = new(); - - ViewContext viewContext = BuildViewContext(context, stringWriter, viewResult); - viewContext.RouteData = context.RouteData; - viewContext.ViewData.Model = model; - - await viewResult.RenderAsync(viewContext); - await stringWriter.FlushAsync(); - - return stringWriter.ToString(); - } - - - /// - public void Dispose() - { - ServiceScope?.Dispose(); - } - - - /// - /// Build the view context - /// - protected virtual ViewContext BuildViewContext(ActionContext context, StringWriter writer, IView view = null) - { - ViewDataDictionary viewData = new(ModelMetadataProvider, context.ModelState); // result.ViewData ?? new ViewData...; - ITempDataDictionary tempData = TempDataDictionaryFactory.GetTempData(context.HttpContext); // result.TempData ?? TempData...; - - return new ViewContext(context, view ?? NullView.Instance, viewData, tempData, writer, HtmlHelperOptions); - } - - - /// - /// Builds a new view context - /// - protected virtual ActionContext BuildActionContext() - { - HttpContext context = GetHttpContext(); - RouteData routeData = context.GetRouteData(); - return new ActionContext(context, routeData, new ActionDescriptor()); - } - - - /// - /// Get HTTP context or mock one - /// - protected virtual HttpContext GetHttpContext() - { - HttpContext context = HttpContextAccessor.HttpContext; - context ??= new DefaultHttpContext() - { - RequestServices = ServiceScope.ServiceProvider - }; - - return context; - } - - - /// - /// Tries to find a view - /// - protected virtual IView FindView(ActionContext actionContext, string viewName) - { - ViewEngineResult getViewResult = ViewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: false); - if (getViewResult.Success) - { - return getViewResult.View; - } - - ViewEngineResult findViewResult = ViewEngine.FindView(actionContext, viewName, isMainPage: false); - if (findViewResult.Success) - { - return findViewResult.View; - } - - IEnumerable searchedLocations = getViewResult.SearchedLocations.Concat(findViewResult.SearchedLocations); - string errorMessage = String.Join(Environment.NewLine, new[] { $"Unable to find view '{viewName}'. The following locations were searched:" }.Concat(searchedLocations)); - - throw new InvalidOperationException(errorMessage); - } - - - /// - /// Tries to find a page - /// - protected virtual IRazorPage FindPage(ActionContext actionContext, string pageName) - { - RazorPageResult getPageResult = ViewEngine.GetPage(executingFilePath: null, pagePath: pageName); - if (getPageResult.Page != null) - { - return getPageResult.Page; - } - - RazorPageResult findPageResult = ViewEngine.FindPage(actionContext, pageName: pageName); - if (findPageResult.Page != null) - { - return findPageResult.Page; - } - - IEnumerable searchedLocations = getPageResult.SearchedLocations.Concat(findPageResult.SearchedLocations); - string errorMessage = String.Join(Environment.NewLine, new[] { $"Unable to find page '{pageName}'. The following locations were searched:" }.Concat(searchedLocations)); - - throw new InvalidOperationException(errorMessage); - } - - - class GenericController : ControllerBase { } - - - class NullView : IView - { - public static readonly NullView Instance = new NullView(); - - public string Path => string.Empty; - - public Task RenderAsync(ViewContext context) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - return Task.CompletedTask; - } - } - -} - - -public interface IRazorRenderer -{ - /// - /// Renders a razor component to a string - /// - Task ComponentAsync(object args = null) where T : ViewComponent; - - /// - /// Renders a razor component to a string - /// - Task ComponentAsync(Type componentType, object args = null); - - /// - /// Renders a razor component to a string - /// - Task ComponentAsync(string componentName, object args = null); - - /// - /// Renders a razor component to a string - /// - Task ComponentAsync(ActionContext context, object args = null) where T : ViewComponent; - - /// - /// Renders a razor component to a string - /// - Task ComponentAsync(Type componentType, ActionContext context, object args = null); - - /// - /// Renders a razor component to a string - /// - Task ComponentAsync(string componentName, ActionContext context, object args = null); - - /// - /// Renders a razor page to a string - /// - Task PageAsync(T model) where T : PageModel; - - /// - /// Renders a razor view to a string - /// - Task ViewAsync(string view, object model = null); - - /// - /// Renders a razor view to a string - /// - Task ViewAsync(ActionContext context, string view, object model = null); -} \ No newline at end of file diff --git a/zero.Core/Rendering/ZeroRenderingModule.cs b/zero.Core/Rendering/ZeroRenderingModule.cs deleted file mode 100644 index 5d975059..00000000 --- a/zero.Core/Rendering/ZeroRenderingModule.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Rendering; - -public class ZeroRenderingModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - } -} \ No newline at end of file diff --git a/zero.Core/Routing/ApplicationBuilderExtensions.cs b/zero.Core/Routing/ApplicationBuilderExtensions.cs deleted file mode 100644 index 20be284d..00000000 --- a/zero.Core/Routing/ApplicationBuilderExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.AspNetCore.Builder; - -namespace zero.Routing; - -public static class ApplicationBuilderExtensions -{ - public static IApplicationBuilder UseZeroRouting(this IApplicationBuilder app) - { - app.UseStaticFiles(); - app.UseRouting(); - return app; - } -} diff --git a/zero.Core/Routing/Configuration/RoutingEndpointOptions.cs b/zero.Core/Routing/Configuration/RoutingEndpointOptions.cs deleted file mode 100644 index e6ce1da7..00000000 --- a/zero.Core/Routing/Configuration/RoutingEndpointOptions.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace zero.Routing; - -public class RoutingEndpointOptions -{ - HashSet Resolvers { get; set; } = new(); - - class ResolverMap - { - public Type Type { get; set; } - public Func Impl { get; set; } - } - - - public void Add(Func resolver) where T : class, IRouteModel - { - Resolvers.Add(new() - { - Type = typeof(T), - Impl = obj => resolver(obj as T) - }); - } - - public void Replace(Func resolver) where T : class, IRouteModel - { - Remove(); - Add(resolver); - } - - public void Remove() where T : IRouteModel - { - Type type = typeof(T); - Resolvers.RemoveWhere(x => x.Type == type); - } - - public Func Get() where T : IRouteModel => Get(typeof(T)); - - public IEnumerable> GetAll() where T : IRouteModel => GetAll(typeof(T)); - - public Func Get(Type type) - { - ResolverMap map = Resolvers.LastOrDefault(x => x.Type == type); - return map?.Impl; - } - - public IEnumerable> GetAll(Type type) - { - IEnumerable maps = Resolvers.Where(x => x.Type == type); - return maps.Select(map => map.Impl).Where(x => x != null); - } -} diff --git a/zero.Core/Routing/Configuration/RoutingOptions.cs b/zero.Core/Routing/Configuration/RoutingOptions.cs deleted file mode 100644 index 22589514..00000000 --- a/zero.Core/Routing/Configuration/RoutingOptions.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace zero.Routing; - -public class RoutingOptions -{ - public RoutingOptions() - { - PageRouteIdBuilder = new PageRouteIdBuilder(); - DefaultEndpoint = new ControllerRouteEndpoint("ZeroFrontend", "Index"); - EndpointResolvers = new(); - PageResolvers = new(); - //ErrorReexecutionPath = "/error"; - NotFoundEndpoint = null; - } - - - public IPageRouteIdBuilder PageRouteIdBuilder { get; set; } - - - public IRouteEndpoint NotFoundEndpoint { get; set; } - - - public IRouteEndpoint DefaultEndpoint { get; set; } - - /// - /// - /// - public RoutingEndpointOptions EndpointResolvers { get; set; } - - /// - /// - /// - public RoutingPageResolverOptions PageResolvers { get; set; } - - /// - /// - /// - public string ErrorReexecutionPath { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Routing/Configuration/RoutingPageResolverOptions.cs b/zero.Core/Routing/Configuration/RoutingPageResolverOptions.cs deleted file mode 100644 index da0db725..00000000 --- a/zero.Core/Routing/Configuration/RoutingPageResolverOptions.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Linq.Expressions; - -namespace zero.Routing; - -public class RoutingPageResolverOptions -{ - HashSet Resolvers { get; set; } = new(); - - class ResolverMap - { - public Type Type { get; set; } - public Expression> Impl { get; set; } - } - - - public void Add(Expression> resolver) - { - Resolvers.Add(new() - { - Type = typeof(T), - Impl = resolver - }); - } - - public void Replace(Expression> resolver) - { - Remove(); - Add(resolver); - } - - public void Remove() - { - Type type = typeof(T); - Resolvers.RemoveWhere(x => x.Type == type); - } - - public Expression> Get() => Get(typeof(T)); - - public IEnumerable>> GetAll() => GetAll(typeof(T)); - - public Expression> Get(Type type) - { - ResolverMap map = Resolvers.LastOrDefault(x => x.Type == type); - return map?.Impl; - } - - public IEnumerable>> GetAll(Type type) - { - IEnumerable maps = Resolvers.Where(x => x.Type == type); - return maps.Select(map => map.Impl).Where(x => x != null); - } -} diff --git a/zero.Core/Routing/Indexes/RouteRedirects_ByUrl.cs b/zero.Core/Routing/Indexes/RouteRedirects_ByUrl.cs deleted file mode 100644 index 7c8ddbfc..00000000 --- a/zero.Core/Routing/Indexes/RouteRedirects_ByUrl.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace zero.Routing; - -public class RouteRedirects_ByUrl : ZeroIndex -{ - protected override void Create() - { - Map = items => items - .Select(x => new - { - IsAutomated = x.IsAutomated, - SourceUrl = x.SourceUrl, - TargetUrl = x.TargetUrl - }); - } -} \ No newline at end of file diff --git a/zero.Core/Routing/Indexes/Routes_ByDependencies.cs b/zero.Core/Routing/Indexes/Routes_ByDependencies.cs deleted file mode 100644 index c0ddb5fb..00000000 --- a/zero.Core/Routing/Indexes/Routes_ByDependencies.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace zero.Routing; - -public class Routes_ByDependencies : ZeroIndex -{ - protected override void Create() - { - Map = items => items - .Select(x => new - { - Dependencies = x.Dependencies - }); - } -} \ No newline at end of file diff --git a/zero.Core/Routing/Indexes/Routes_ForResolver.cs b/zero.Core/Routing/Indexes/Routes_ForResolver.cs deleted file mode 100644 index 6e2ca920..00000000 --- a/zero.Core/Routing/Indexes/Routes_ForResolver.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace zero.Routing; - -public class Routes_ForResolver : ZeroIndex -{ - protected override void Create() - { - Map = items => items - .Select(x => new - { - Url = x.Url, - AllowSuffix = x.AllowSuffix - }); - } -} \ No newline at end of file diff --git a/zero.Core/Routing/Links/ILinkProvider.cs b/zero.Core/Routing/Links/ILinkProvider.cs deleted file mode 100644 index 2c6dfc6b..00000000 --- a/zero.Core/Routing/Links/ILinkProvider.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Raven.Client.Documents.Session; - -namespace zero.Routing; - -public interface ILinkProvider -{ - /// - /// - /// - bool CanProcess(Link link); - - /// - /// - /// - Task Resolve(Link link); - - /// - /// - /// - Task Preview(IAsyncDocumentSession session, Link link); -} \ No newline at end of file diff --git a/zero.Core/Routing/Links/LinkTargetExtensions.cs b/zero.Core/Routing/Links/LinkTargetExtensions.cs deleted file mode 100644 index 63a62f7a..00000000 --- a/zero.Core/Routing/Links/LinkTargetExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace zero.Routing; - -public static class LinkTargetExtensions -{ - public static string ToTarget(this LinkTarget target) - { - return target switch - { - LinkTarget.Blank => "_blank", - _ => "_self" - }; - } -} diff --git a/zero.Core/Routing/Links/Links.cs b/zero.Core/Routing/Links/Links.cs deleted file mode 100644 index 18b964e0..00000000 --- a/zero.Core/Routing/Links/Links.cs +++ /dev/null @@ -1,177 +0,0 @@ -using Microsoft.Extensions.Logging; -using System.Net; -using System.Text.Json; -using System.Text.RegularExpressions; - -namespace zero.Routing; - -public class Links : ILinks -{ - protected IZeroStore Store { get; set; } - protected ILogger Logger { get; set; } - protected IEnumerable Providers { get; set; } - - public Links(IZeroStore store, ILogger logger, IEnumerable providers) - { - Store = store; - Logger = logger; - Providers = providers; - } - - - /// - public T GetProvider() where T : class, ILinkProvider - { - Type type = typeof(T); - return Providers.FirstOrDefault(x => x.GetType().IsAssignableFrom(type)) as T; - } - - - /// - public async Task GetUrl(Link link) - { - ILinkProvider provider = Providers.LastOrDefault(x => x.CanProcess(link)); - - if (provider == null) - { - Logger.LogWarning("Could not find provider for link with area {area}", link.Area); - return null; - } - - return await provider.Resolve(link); - } - - - /// - public async Task> GetUrls(params Link[] links) - { - Dictionary result = new(); - - foreach (Link link in links) - { - result.Add(link, await GetUrl(link)); - } - - return result; - } - - - /// - public async Task Resolve(Link link) - { - ILinkProvider provider = Providers.LastOrDefault(x => x.CanProcess(link)); - - if (provider == null) - { - Logger.LogWarning("Could not find provider for link with area {area}", link.Area); - return null; - } - - await provider.Resolve(link); - - return link; - } - - - /// - public async Task> Resolve(params Link[] links) - { - Dictionary result = new(); - - foreach (Link link in links) - { - Link resolved = await Resolve(link); - result.Add(link, resolved); - } - - return result; - } - - - /// - public ILinkProvider GetProvider(Link link) - { - return Providers.LastOrDefault(x => x.CanProcess(link)); - } - - - /// - public async Task Parse(string html) - { - MatchCollection matches = Regex.Matches(html, "zero-link=\"(.+?)\"", RegexOptions.IgnoreCase); - - foreach (Match match in matches) - { - if (!match.Success) - { - continue; - } - - string valueJson = WebUtility.HtmlDecode(match.Groups[1].Value); - LinkFromRTE link = JsonSerializer.Deserialize(valueJson, new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true - }); - - string url = await GetUrl(link.ToLink()); - string href = url.HasValue() ? "href=\"" + url + "\"" : string.Empty; - - html = html.Replace(match.Value, href); - } - - return html; - } - - - class LinkFromRTE - { - public string Area { get; set; } - public string Suffix { get; set; } - public Dictionary Values { get; set; } = new(); - - public Link ToLink() => new() - { - Area = Area, - UrlSuffix = Suffix, - Values = Values - }; - } -} - -public interface ILinks -{ - /// - /// Get URL from a link object by finding a provider which can resolve the link - /// - Task GetUrl(Link link); - - /// - /// Get URLs from link objects by finding matching providers - /// - Task> GetUrls(params Link[] links); - - /// - /// Get resolved Link from a link object by finding a provider which can resolve the link - /// - Task Resolve(Link link); - - /// - /// Get resolved Link from link objects by finding matching providers - /// - Task> Resolve(params Link[] links); - - /// - /// Get the provider for a specific link - /// - ILinkProvider GetProvider(Link link); - - /// - /// Find a provider by a specific type - /// - T GetProvider() where T : class, ILinkProvider; - - /// - /// Parses HTML which comes from the rich-text-editor and converts link references. - /// - Task Parse(string html); -} diff --git a/zero.Core/Routing/Links/RawLinkProvider.cs b/zero.Core/Routing/Links/RawLinkProvider.cs deleted file mode 100644 index dfe747e9..00000000 --- a/zero.Core/Routing/Links/RawLinkProvider.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Raven.Client.Documents.Session; - -namespace zero.Routing; - -public class RawLinkProvider : ILinkProvider -{ - public const string AREA = "zero.url"; - - - /// - /// Creates a new link object from an url - /// - public Link Create(string url, LinkTarget target = LinkTarget.Default, string title = null) - { - return new Link() - { - Area = AREA, - Target = target, - Title = title, - Values = new() - { - { "url", url } - } - }; - } - - - /// - public bool CanProcess(Link link) => link.Area is AREA or "zero.raw"; - - - /// - public Task Resolve(Link link) - { - link.Url = link.Values.GetValueOrDefault("url"); - link.IsActive = true; - return Task.FromResult(link.Url); - } - - - /// - public Task Preview(IAsyncDocumentSession session, Link link) - { - string url = link.Values.GetValueOrDefault("url"); - - if (url.IsNullOrEmpty()) - { - return Task.FromResult(default); - } - - return Task.FromResult(new LinkPreview() - { - Id = url, - Icon = "fth-link", - Name = link.Title.Or("@ui.link"), - IsActive = true, - Text = url - }); ; - } -} diff --git a/zero.Core/Routing/Models/IRouteModel.cs b/zero.Core/Routing/Models/IRouteModel.cs deleted file mode 100644 index e190d3c6..00000000 --- a/zero.Core/Routing/Models/IRouteModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace zero.Routing; - -public interface IRouteModel -{ - Route Route { get; set; } -} - -public class RouteModel : IRouteModel -{ - public Route Route { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Routing/Models/Link.cs b/zero.Core/Routing/Models/Link.cs deleted file mode 100644 index da7248a8..00000000 --- a/zero.Core/Routing/Models/Link.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Newtonsoft.Json; - -namespace zero.Routing; - -public class Link -{ - public string Area { get; set; } - - public LinkTarget Target { get; set; } - - public string UrlSuffix { get; set; } - - public string Title { get; set; } - - public Dictionary Values { get; set; } = new(); - - public bool IsActive { get; set; } = true; - - /// - /// [Warning] This field is always empty when bound to the database. - /// It is only filled in the app-code for routing. - /// - [JsonIgnore] - public string Url { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Routing/Models/LinkPreview.cs b/zero.Core/Routing/Models/LinkPreview.cs deleted file mode 100644 index d5c8dfd0..00000000 --- a/zero.Core/Routing/Models/LinkPreview.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace zero.Routing; - -public class LinkPreview -{ - public string Id { get; set; } - - public string Icon { get; set; } - - public string Text { get; set; } - - public string Name { get; set; } - - public bool HasError { get; set; } - - public bool IsActive { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Routing/Models/LinkTarget.cs b/zero.Core/Routing/Models/LinkTarget.cs deleted file mode 100644 index 90f4fc9d..00000000 --- a/zero.Core/Routing/Models/LinkTarget.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace zero.Routing; - -public enum LinkTarget -{ - Default = 0, - Self = 1, - Blank = 2 -} diff --git a/zero.Core/Routing/Models/Route.cs b/zero.Core/Routing/Models/Route.cs deleted file mode 100644 index 8cd0e2d4..00000000 --- a/zero.Core/Routing/Models/Route.cs +++ /dev/null @@ -1,72 +0,0 @@ -namespace zero.Routing; - -[RavenCollection("Routes")] -public class Route : ZeroIdEntity -{ - public Route() { } - - public Route(string providerAlias) - { - ProviderAlias = providerAlias; - } - - public Route(string id, string url, string providerAlias) - { - Id = id; - Url = url; - ProviderAlias = providerAlias; - } - - /// - /// Id of the referenced entity - /// - public string ReferenceId { get; set; } - - /// - /// Generated URL based on the URL provider - /// - public string Url { get; set; } - - /// - /// Alias of the URL provider which generated this route - /// - public string ProviderAlias { get; set; } - - /// - /// Enable this property so all routes are catched which start with this Url - /// - public bool AllowSuffix { get; set; } - - /// - /// Additional parameters - /// - public Dictionary Params { get; set; } = new(); - - /// - /// Route dependencies can be used for cache busting - /// - public List Dependencies { get; set; } = new(); - - - /// - /// Update an existing route with regenerated data - /// - public virtual Route Update(Route newRoute) - { - ReferenceId = newRoute.ReferenceId; - Url = newRoute.Url; - AllowSuffix = newRoute.AllowSuffix; - Params = newRoute.Params; - Dependencies = newRoute.Dependencies; - return this; - } - - - /// - /// Create a new route - /// - public static Route Create(string id, string providerAlias) - { - return new(providerAlias) { Id = id }; - } -} diff --git a/zero.Core/Routing/Models/RouteEndpoint.cs b/zero.Core/Routing/Models/RouteEndpoint.cs deleted file mode 100644 index acf2cf2b..00000000 --- a/zero.Core/Routing/Models/RouteEndpoint.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.AspNetCore.Routing; - -namespace zero.Routing; - -public class ControllerRouteEndpoint : IRouteEndpoint -{ - public string Controller { get; set; } - - public string Action { get; set; } - - public ControllerRouteEndpoint(string controller, string action = "Index") - { - Controller = controller; - Action = action; - } - - public virtual void Apply(RouteValueDictionary values) - { - values["controller"] = Controller; - values["action"] = Action; - } -} - - -public class PageRouteEndpoint : IRouteEndpoint -{ - public string Page { get; set; } - - public PageRouteEndpoint(string page) - { - Page = page; - } - - public virtual void Apply(RouteValueDictionary values) - { - values["page"] = Page; - } -} - - -public interface IRouteEndpoint -{ - /// - /// Append values to the RouteValueDictionary - /// - void Apply(RouteValueDictionary values); -} \ No newline at end of file diff --git a/zero.Core/Routing/Models/RouteRedirect.cs b/zero.Core/Routing/Models/RouteRedirect.cs deleted file mode 100644 index ab8d7835..00000000 --- a/zero.Core/Routing/Models/RouteRedirect.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace zero.Routing; - -[RavenCollection("RouteRedirects")] -public class RouteRedirect : ZeroEntity -{ - /// - /// Redirect type - /// - public RouteRedirectType RedirectType { get; set; } - - /// - /// URL which is redirected - /// - public string SourceUrl { get; set; } - - /// - /// Destination URL for automated (non-custom) redirects - /// - public string TargetUrl { get; set; } - - /// - /// Target link - /// - public Link Target { get; set; } - - /// - /// Whether this redirect is automated (from route interceptor updates) or custom-created. - /// non-custom redirects are automatically populated and updated. - /// - public bool IsAutomated { get; set; } -} - - -public enum RouteRedirectType -{ - Permanent = 301, - Temporary = 302 -} \ No newline at end of file diff --git a/zero.Core/Routing/Models/RouteResponse.cs b/zero.Core/Routing/Models/RouteResponse.cs deleted file mode 100644 index d9e4a777..00000000 --- a/zero.Core/Routing/Models/RouteResponse.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace zero.Routing; - -public class RouteResponse -{ - public Route Route { get; set; } - - public HttpContext HttpContext { get; set; } - - public Application App { get; set; } - - public string Path { get; set; } -} diff --git a/zero.Core/Routing/Models/RoutingContext.cs b/zero.Core/Routing/Models/RoutingContext.cs deleted file mode 100644 index 824825bf..00000000 --- a/zero.Core/Routing/Models/RoutingContext.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace zero.Routing; - -public class RoutingContext : IRoutingContext -{ - public IZeroStore Store { get; private set; } - - public IZeroContext Context { get; private set; } - - public IZeroDocumentSession Session { get; private set; } - - - public RoutingContext(IZeroStore store, IZeroContext context, IZeroDocumentSession session) - { - Store = store; - Context = context; - Session = session; - } -} - - -public interface IRoutingContext -{ - IZeroStore Store { get; } - - IZeroContext Context { get; } - - IZeroDocumentSession Session { get; } -} \ No newline at end of file diff --git a/zero.Core/Routing/NotFoundMatcherPolicy.cs b/zero.Core/Routing/NotFoundMatcherPolicy.cs deleted file mode 100644 index 08978142..00000000 --- a/zero.Core/Routing/NotFoundMatcherPolicy.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Matching; - -namespace zero.Routing; - -// see https://github.com/umbraco/Umbraco-CMS/blob/5bfab13dc5a268714aad2426a2b68ab5561a6407/src/Umbraco.Web.Website/Routing/NotFoundSelectorPolicy.cs - -/// -/// Used to handle 404 routes that haven't been handled by the end user -/// -public class NotFoundSelectorPolicy : MatcherPolicy, IEndpointSelectorPolicy -{ - //private readonly Lazy _notFound; - private readonly EndpointDataSource _endpointDataSource; - - public NotFoundSelectorPolicy(EndpointDataSource endpointDataSource) - { - //_notFound = new Lazy(GetNotFoundEndpoint); - _endpointDataSource = endpointDataSource; - } - - // return the endpoint for the RenderController.Index action. - //private Endpoint GetNotFoundEndpoint() - //{ - // Endpoint e = _endpointDataSource.Endpoints.First(x => - // { - // // return the endpoint for the RenderController.Index action. - // ControllerActionDescriptor descriptor = x.Metadata?.GetMetadata(); - // return descriptor?.ControllerTypeInfo == typeof(RenderController) - // && descriptor?.ActionName == nameof(RenderController.Index); - // }); - // return e; - //} - - public override int Order => 0; - - public bool AppliesToEndpoints(IReadOnlyList endpoints) - { - // Don't apply this filter to any endpoint group that is a controller route - // i.e. only dynamic routes. - foreach (Endpoint endpoint in endpoints) - { - ControllerAttribute controller = endpoint.Metadata?.GetMetadata(); - if (controller != null) - { - return false; - } - } - - // then ensure this is only applied if all endpoints are IDynamicEndpointMetadata - return endpoints.All(x => x.Metadata?.GetMetadata() != null); - } - - public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates) - { - if (AllInvalid(candidates)) - { - //UmbracoRouteValues umbracoRouteValues = httpContext.Features.Get(); - //if (umbracoRouteValues?.PublishedRequest != null - // && !umbracoRouteValues.PublishedRequest.HasPublishedContent() - // && umbracoRouteValues.PublishedRequest.ResponseStatusCode == StatusCodes.Status404NotFound) - //{ - // // not found/404 - // httpContext.SetEndpoint(_notFound.Value); - //} - } - - return Task.CompletedTask; - } - - private static bool AllInvalid(CandidateSet candidates) - { - for (int i = 0; i < candidates.Count; i++) - { - if (candidates.IsValidCandidate(i)) - { - return false; - } - } - return true; - } -} diff --git a/zero.Core/Routing/PageRouteProvider/BasePageRoute.cs b/zero.Core/Routing/PageRouteProvider/BasePageRoute.cs deleted file mode 100644 index 3114ebed..00000000 --- a/zero.Core/Routing/PageRouteProvider/BasePageRoute.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace zero.Routing; - -public abstract class BasePageRoute : IRouteModel -{ - public BasePageRoute() { } - - public BasePageRoute(Route route) - { - Route = route; - } - - public Page Page { get; set; } - - public Route Route { get; set; } -} diff --git a/zero.Core/Routing/PageRouteProvider/PageLinkProvider.cs b/zero.Core/Routing/PageRouteProvider/PageLinkProvider.cs deleted file mode 100644 index e406248f..00000000 --- a/zero.Core/Routing/PageRouteProvider/PageLinkProvider.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Raven.Client.Documents.Session; - -namespace zero.Routing; - -public class PageLinkProvider : ILinkProvider -{ - public const string AREA = "zero.pages"; - - protected IRoutes Routes { get; set; } - protected IPageTypeService PageTypeService { get; set; } - protected IPagesStore Pages { get; set; } - - public PageLinkProvider(IRoutes routes, IPageTypeService pageTypeService, IPagesStore pages) - { - Routes = routes; - PageTypeService = pageTypeService; - Pages = pages; - } - - - /// - /// Creates a new link object from a page - /// - public Link Create(Page page, LinkTarget target = LinkTarget.Default, string title = null) - { - return new Link() - { - Area = AREA, - Target = target, - Title = title, - Values = new() - { - { "id", page.Id } - } - }; - } - - - /// - public bool CanProcess(Link link) => link.Area == AREA; - - - /// - public async Task Resolve(Link link) - { - Page page = await Pages.Load(link.Values.GetValueOrDefault("id")); - - if (page == null) - { - return null; - } - - link.IsActive = page.IsActive; - link.Url = await Routes.GetUrl(page) + (link.UrlSuffix ?? string.Empty); - return link.Url; - } - - - /// - public async Task Preview(IAsyncDocumentSession session, Link link) - { - string id = link.Values.GetValueOrDefault("id"); - - if (id.IsNullOrEmpty()) - { - return null; - } - - Page page = await session.LoadAsync(id); - - if (page == null) - { - return null; - } - - FlavorConfig pageType = PageTypeService.GetByAlias(page.Flavor); - - string url = await Routes.GetUrl(page); - - return new() - { - Id = page.Id, - Icon = pageType?.Icon ?? "fth-folder", - Name = page.Name, - IsActive = page.IsActive, - Text = url - }; - } -} diff --git a/zero.Core/Routing/PageRouteProvider/PageRoute.cs b/zero.Core/Routing/PageRouteProvider/PageRoute.cs deleted file mode 100644 index 5a91e856..00000000 --- a/zero.Core/Routing/PageRouteProvider/PageRoute.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace zero.Routing; - -public class PageRoute : BasePageRoute -{ - public PageRoute() { } - public PageRoute(Route route) : base(route) { } - - public IList Parents { get; set; } - - public string PageType { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Routing/PageRouteProvider/PageRouteIdBuilder.cs b/zero.Core/Routing/PageRouteProvider/PageRouteIdBuilder.cs deleted file mode 100644 index b9ba367f..00000000 --- a/zero.Core/Routing/PageRouteProvider/PageRouteIdBuilder.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace zero.Routing; - -public class PageRouteIdBuilder : IPageRouteIdBuilder -{ - /// - public string Generate(Page page) - { - return "routes." + Constants.Pages.PageRouteProviderAlias + "." + page.Hash; - } -} - - -public interface IPageRouteIdBuilder -{ - /// - /// Generate ID for a page - /// - string Generate(Page page); -} diff --git a/zero.Core/Routing/PageRouteProvider/PageRouteProvider.cs b/zero.Core/Routing/PageRouteProvider/PageRouteProvider.cs deleted file mode 100644 index 08220c56..00000000 --- a/zero.Core/Routing/PageRouteProvider/PageRouteProvider.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; - -namespace zero.Routing; - -public class PageRouteProvider : ZeroRouteProvider -{ - public static string PAGE_TYPE_PARAM = "pageType"; - public static string PAGE_ID_PARAM = "pageId"; - public static string PAGE_IS_FOLDER = "pageIsFolder"; - - protected IPageUrlBuilder UrlBuilder { get; set; } - - - public PageRouteProvider(IPageUrlBuilder urlBuilder) : base(Constants.Pages.PageRouteProviderAlias) - { - Priority = 10; - UrlBuilder = urlBuilder; - } - - - /// - public override Task IsRouteStale(RoutingContext context, Page previous, Page current) - { - bool compareUrl = UrlBuilder.GetUrlPart(previous) == UrlBuilder.GetUrlPart(current); - bool compareParent = previous != null && previous.ParentId == current.ParentId; - return Task.FromResult(previous == null || !compareUrl || !compareParent); - } - - - /// - public override async Task Create(RoutingContext context, Page model) - { - IEnumerable parents = await GetParents(context, model); - - Route route = await base.Create(context, model); - route.Url = UrlBuilder.GetUrl(model, parents); - route.DependsOn(model.Id); - route.DependsOn(parents.Select(x => x.Id).ToArray()); - route.Param(PAGE_TYPE_PARAM, model.Flavor); - route.Param(PAGE_ID_PARAM, model.Id); - route.Param(PAGE_IS_FOLDER, model is PageFolder); - - return route; - } - - - /// - public override async Task Model(RoutingContext context, RouteResponse response) - { - Route route = response.Route; - - if (route.Param(PAGE_IS_FOLDER)) - { - return null; - } - - Page page = await context.Session.LoadAsync(route.ReferenceId); - PageRoute resolved = new(route); - resolved.Page = page; - resolved.Parents = await GetParents(context, page); - resolved.PageType = route.Param(PAGE_TYPE_PARAM); - - return resolved; - } - - - /// - public override async IAsyncEnumerable Seed(RoutingContext context) - { - var stream = await context.Session.Advanced.StreamAsync(context.Session.Query()); - while (await stream.MoveNextAsync()) - { - yield return await Create(context, stream.Current.Document); - } - } - - - /// - public override async IAsyncEnumerable SeedOnUpdate(RoutingContext context, T model) - { - if (model is Page) - { - string id = model.Id; - IList children = await context.Session.Query() - .Where(x => x.PathIds.In(new[] { id })).ProjectInto().ToListAsync(); - - foreach (Page child in children) - { - yield return await Create(context, child); - } - } - } - - - /// - /// Get parents for a page - /// - protected virtual async Task> GetParents(RoutingContext context, Page model) - { - if (model == null) - { - return new(); - } - zero_Pages_ByHierarchy.Result result = await context.Session.Query() - .ProjectInto() - .Include(x => x.Path.Select(p => p.Id)) - .FirstOrDefaultAsync(x => x.Id == model.Id); - - if (result == null) - { - return new(); - } - - return (await context.Session.LoadAsync(result.Path.Select(x => x.Id))).Select(x => x.Value).ToList(); - } -} diff --git a/zero.Core/Routing/PageRouteProvider/PageRouteResolverHelper.cs b/zero.Core/Routing/PageRouteProvider/PageRouteResolverHelper.cs deleted file mode 100644 index 11486abc..00000000 --- a/zero.Core/Routing/PageRouteProvider/PageRouteResolverHelper.cs +++ /dev/null @@ -1,181 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; -using System.Collections.Concurrent; -using System.Linq.Expressions; - -namespace zero.Routing; - -public class PageRouteResolverHelper -{ - protected ConcurrentDictionary> PageRouteCache { get; set; } = new(); - - - public async Task ResolveFor(RoutingContext context, Func predicate = null) - { - return (await ResolveAllFor(context, predicate)).FirstOrDefault(); - } - - - public async Task ResolvePageFor(RoutingContext context, Func predicate = null) - { - return (await ResolveAllPagesFor(context, predicate)).FirstOrDefault(); - } - - - public async Task> ResolveAllFor(RoutingContext context, Func predicate = null) - { - RoutingOptions options = context.Context.Options.For(); - - string fullKey = typeof(T).Name.ToString().ToLower() + ":" + context.Context.AppId; - - if (!PageRouteCache.TryGetValue(fullKey, out HashSet<(Page, Route)> routes)) - { - List pages = new(); - IEnumerable>> resolvers = options.PageResolvers.GetAll(typeof(T)); - - foreach (Expression> resolver in resolvers.Reverse()) - { - pages.AddRange(await context.Session.Query().Where(resolver).ToListAsync()); - } - - Dictionary idToPage = pages.ToDictionary(x => options.PageRouteIdBuilder.Generate(x), x => x); - Dictionary idToRoute = await context.Session.LoadAsync(idToPage.Select(x => x.Key)); - - routes = new(); - - foreach ((string id, Page page) in idToPage) - { - if (idToRoute.TryGetValue(id, out Route route) && route != null) - { - routes.Add((page, route)); - } - } - - PageRouteCache.TryAdd(fullKey, routes); - } - - if (predicate != null) - { - return routes.Where(x => predicate(x.Item1)).Select(x => x.Item2).ToHashSet(); - } - - return routes.Select(x => x.Item2).ToHashSet(); - } - - - public async Task> ResolveAllPagesFor(RoutingContext context, Func predicate = null) - { - RoutingOptions options = context.Context.Options.For(); - string fullKey = typeof(T).Name.ToString().ToLower() + ":" + context.Context.AppId; - - if (!PageRouteCache.TryGetValue(fullKey, out HashSet<(Page, Route)> routes)) - { - List pages = new(); - IEnumerable>> resolvers = options.PageResolvers.GetAll(typeof(T)); - - foreach (Expression> resolver in resolvers.Reverse()) - { - pages.AddRange(await context.Session.Query().Where(resolver).ToListAsync()); - } - - Dictionary idToPage = pages.ToDictionary(x => options.PageRouteIdBuilder.Generate(x), x => x); - Dictionary idToRoute = await context.Session.LoadAsync(idToPage.Select(x => x.Key)); - - routes = new(); - - foreach ((string id, Page page) in idToPage) - { - if (idToRoute.TryGetValue(id, out Route route) && route != null) - { - routes.Add((page, route)); - } - } - - PageRouteCache.TryAdd(fullKey, routes); - } - - if (predicate != null) - { - return routes.Where(x => predicate(x.Item1)).Select(x => x.Item1).ToHashSet(); - } - - return routes.Select(x => x.Item1).ToHashSet(); - } - - - public async Task> ResolveAllForAsDictionary(RoutingContext context, Func<(Page, Route), bool> predicate = null) - { - RoutingOptions options = context.Context.Options.For(); - string fullKey = typeof(T).Name.ToString().ToLower() + ":" + context.Context.AppId; - - if (!PageRouteCache.TryGetValue(fullKey, out HashSet<(Page, Route)> routes)) - { - List pages = new(); - IEnumerable>> resolvers = options.PageResolvers.GetAll(typeof(T)); - - foreach (Expression> resolver in resolvers.Reverse()) - { - pages.AddRange(await context.Session.Query().Where(resolver).ToListAsync()); - } - - Dictionary idToPage = pages.ToDictionary(x => options.PageRouteIdBuilder.Generate(x), x => x); - Dictionary idToRoute = await context.Session.LoadAsync(idToPage.Select(x => x.Key)); - - routes = new(); - - foreach ((string id, Page page) in idToPage) - { - if (idToRoute.TryGetValue(id, out Route route) && route != null) - { - routes.Add((page, route)); - } - } - - PageRouteCache.TryAdd(fullKey, routes); - } - - if (predicate != null) - { - return routes.Where(x => predicate(x)).ToDictionary(x => x.Item1, x => x.Item2); - } - - return routes.ToDictionary(x => x.Item1, x => x.Item2); - } - - - public async Task IsRelevantFor(RoutingContext context, Page page) - { - HashSet pages = await ResolveAllPagesFor(context); - - if (pages.Any(x => x.Id == page.Id)) - { - return true; - } - - string[] childIds = await GetChildIds(context, page.Id); - return pages.Any(x => childIds.Contains(x.Id)); - } - - - /// - /// Get relevant pages for a page - /// - public virtual async Task> GetRelevantPagesFor(RoutingContext context, Page page) - { - HashSet pages = await ResolveAllPagesFor(context); - string[] childIds = await GetChildIds(context, page.Id); - return pages.Where(x => childIds.Contains(x.Id) || x.Id == page.Id).ToList(); - } - - - /// - /// Get parents for a page - /// - protected virtual async Task GetChildIds(RoutingContext context, params string[] pageIds) - { - return await context.Session.Query() - .Where(x => x.PathIds.In(pageIds)) - .Select(x => x.Id) - .ToArrayAsync(); - } -} diff --git a/zero.Core/Routing/PageRouteProvider/PageUrlBuilder.cs b/zero.Core/Routing/PageRouteProvider/PageUrlBuilder.cs deleted file mode 100644 index 921687f0..00000000 --- a/zero.Core/Routing/PageRouteProvider/PageUrlBuilder.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Text; - -namespace zero.Routing; - -public class PageUrlBuilder : IPageUrlBuilder -{ - const char PATH_SEPARATOR = '/'; - - const bool TRAILING_SLASH = false; - - protected IZeroStore Store { get; private set; } - - protected PageOptions Options { get; private set; } - - - public PageUrlBuilder(IZeroStore store, IZeroOptions options) - { - Store = store; - Options = options.For(); - } - - - /// - public string GetUrl(Page page, IEnumerable parents) - { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.Append(PATH_SEPARATOR); - - void AddPart(Page page) - { - string part = GetUrlPart(page); - - if (!part.IsNullOrEmpty()) - { - stringBuilder.Append(part); - stringBuilder.Append(PATH_SEPARATOR); - } - } - - if (parents != null) - { - foreach (Page parent in parents) - { - AddPart(parent); - } - } - - AddPart(page); - - if (!TRAILING_SLASH && stringBuilder.Length > 1) - { - stringBuilder.Remove(stringBuilder.Length - 1, 1); - } - - return stringBuilder.ToString(); - } - - - /// - public virtual string GetUrlPart(Page page) - { - string alias; - - if (page == null) - { - return null; - } - - if (page is PageFolder && !((PageFolder)page).IsPartOfRoute) - { - return null; - } - - if (!page.UrlAlias.IsNullOrWhiteSpace()) - { - alias = page.UrlAlias; - } - else if (page.Flavor == Options.Root) - { - return String.Empty; - } - else if (!page.Alias.IsNullOrWhiteSpace()) - { - alias = page.Alias; - } - else - { - alias = page.Name; - } - - return Safenames.Alias(alias).Trim().Trim(PATH_SEPARATOR); - } -} - - -public interface IPageUrlBuilder -{ - /// - /// Get URL for a page - /// - string GetUrl(Page page, IEnumerable parents); - - /// - /// Get the part of the URL (by querying UrlAlias and Alias) for this page - /// - string GetUrlPart(Page page); -} diff --git a/zero.Core/Routing/RedirectAutomation.cs b/zero.Core/Routing/RedirectAutomation.cs deleted file mode 100644 index 180f3c88..00000000 --- a/zero.Core/Routing/RedirectAutomation.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Raven.Client.Documents; - -namespace zero.Routing; - -public class RedirectAutomation : IRedirectAutomation -{ - protected IZeroStore Store { get; set; } - - - public RedirectAutomation(IZeroStore store) - { - Store = store; - } - - - /// - public async Task AddForRoute(string oldUrl, string newUrl, RouteRedirectType redirectType = RouteRedirectType.Permanent) - { - List redirects = await Store.Session().Query().ProjectInto().Where(x => x.SourceUrl == oldUrl || x.TargetUrl == oldUrl).ToListAsync(); - - RouteRedirect redirect = redirects.FirstOrDefault(x => x.SourceUrl == oldUrl) ?? new(); - redirect.IsActive = true; - redirect.IsAutomated = true; - redirect.RedirectType = redirectType; - redirect.SourceUrl = oldUrl; - redirect.TargetUrl = newUrl; - - //await Collection.Save(redirect); // TODO we need generic collection save data - - // TODO see https://statamic.com/addons/rias/redirect - - // TODO this does not work as expected yet - //foreach (RouteRedirect oldTargetRedirect in redirects) - //{ - // if (oldTargetRedirect.TargetUrl == oldUrl) - // { - // oldTargetRedirect.IsActive = true; - // oldTargetRedirect.IsAutomated = true; - // oldTargetRedirect.RedirectType = redirectType; - // oldTargetRedirect.TargetUrl = newUrl; - - // if (oldTargetRedirect.TargetUrl != oldTargetRedirect.SourceUrl) - // { - // await Collection.Save(oldTargetRedirect); - // } - // else - // { - // await Collection.Delete(oldTargetRedirect); - // } - // } - //} - - return redirect; - } - - - /// - public async Task DeleteForRoutes(params Route[] routes) - { - await Task.Delay(0); - - if (routes.Length < 1) - { - return; - } - - //await Collection.Purge($"where c.{nameof(RouteRedirect.IsAutomated)} = $automated and c.{nameof(RouteRedirect.TargetUrl)} IN ($urls)", new Raven.Client.Parameters() - //{ - // { "automated", true }, - // { "urls", routes.Select(x => x.Url).ToArray() } - //}); - } -} - - - -public interface IRedirectAutomation -{ - /// - /// Add or update all affected redirects for a route - /// - Task AddForRoute(string sourceUrl, string targetUrl, RouteRedirectType redirectType = RouteRedirectType.Permanent); - - /// - /// Delete all redirects which have the passed route URLs as their target. - /// This is only done for automated redirects and not for redirects which are custom-created. - /// - Task DeleteForRoutes(params Route[] routes); -} diff --git a/zero.Core/Routing/RequestUrlResolver.cs b/zero.Core/Routing/RequestUrlResolver.cs deleted file mode 100644 index ff359f19..00000000 --- a/zero.Core/Routing/RequestUrlResolver.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; - -namespace zero.Routing; - -public class RequestUrlResolver : IRequestUrlResolver -{ - public IRequestUrlResolver Backoffice { get; private set; } - - protected IHttpContextAccessor HttpContextAccessor { get; private set; } - - protected IZeroOptions Options { get; private set; } - - protected ILogger Logger { get; private set; } - - static string[] Protocols = new[] { "http://", "https://", "ftp://", "ftps://", "sftp://", "udp://" }; - - bool IsBackoffice = false; - - - public RequestUrlResolver(IHttpContextAccessor httpContextAccessor, ILogger logger, IZeroOptions options, bool isBackoffice = false) - { - HttpContextAccessor = httpContextAccessor; - Logger = logger; - Options = options; - IsBackoffice = isBackoffice; - if (!isBackoffice) - { - Backoffice = new RequestUrlResolver(httpContextAccessor, logger, options, true); - } - } - - - /// - public bool IsAbsolute(string path) - { - if (!CanResolve()) - { - return false; - } - - if (String.IsNullOrWhiteSpace(path)) - { - return false; - } - - if (Protocols.Any(p => path.StartsWith(p, StringComparison.InvariantCultureIgnoreCase))) - { - return true; - } - - return false; - } - - - /// - public string ToAbsolute(string path) - { - if (!CanResolve()) - { - return null; - } - - if (String.IsNullOrWhiteSpace(path)) - { - return GetRoot(); - } - - if (Protocols.Any(p => path.StartsWith(p, StringComparison.InvariantCultureIgnoreCase))) - { - return path; - } - - path = path.Trim().TrimEnd('/'); - - if (path.StartsWith("~/")) - { - path = path.Substring(1); - } - if (!path.StartsWith("/")) - { - return GetRoot(includePath: true) + '/' + path; - } - - return GetRoot() + path; - } - - - /// - public string GetRoot(bool includePath = false) - { - if (!CanResolve()) - { - Logger.LogWarning("Tried to resolve an URL but no HttpRequest is available"); - return null; - } - - HttpRequest request = HttpContextAccessor.HttpContext.Request; - - string suffix = IsBackoffice ? Options.ZeroPath : String.Empty; - - return $"{request.Scheme}://{request.Host.Host}{GetPortUrlPart(request)}{suffix}{(includePath ? request.PathBase.ToUriComponent() : String.Empty)}"; - } - - - /// - /// Require a request in order to resolve the URL - /// - protected bool CanResolve() => HttpContextAccessor?.HttpContext?.Request != null; - - - /// - /// Resolves the port as part of the URL - /// - protected string GetPortUrlPart(HttpRequest request) - { - if (!request.Host.Port.HasValue || request.Host.Port == 80 || (request.Host.Port == 443 && request.IsHttps)) - { - return String.Empty; - } - - return ":" + request.Host.Port; - } -} - - -public interface IRequestUrlResolver -{ - /// - /// URL resolver for backoffice links - /// - IRequestUrlResolver Backoffice { get; } - - /// - /// Checks whether a path is absolute or relative - /// - bool IsAbsolute(string path); - - /// - /// Converts a relative to an absolute path for the currently used domain - /// - string ToAbsolute(string path); - - /// - /// Get protocol + domain and optional port - /// - string GetRoot(bool includePath = false); -} diff --git a/zero.Core/Routing/RouteBulkRefresher.cs b/zero.Core/Routing/RouteBulkRefresher.cs deleted file mode 100644 index d31bc021..00000000 --- a/zero.Core/Routing/RouteBulkRefresher.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.BulkInsert; - -namespace zero.Routing; - -public class RouteBulkRefresher -{ - protected IZeroStore Store { get; set; } - protected IZeroContext Context { get; set; } - protected IEnumerable Providers { get; set; } - - - public RouteBulkRefresher(IZeroStore store, IZeroContext context, IEnumerable providers) - { - Store = store; - Context = context; - Providers = providers; - } - - - /// - public async Task RebuildAllRoutes() - { - int count = 0; - - List apps = await Store.Session(global: true).Query().ToListAsync(); - - foreach (Application app in apps) - { - using ZeroContextScope scope = Context.CreateScope(app); - using IZeroDocumentSession session = scope.Store.Session(scope.Database, ZeroSessionResolution.Create); - session.Advanced.MaxNumberOfRequestsPerSession = 100_000_000; - - await scope.Store.Raven.PurgeAsync(scope.Database); - - RoutingContext context = new(scope.Store, Context, session); - - foreach (IRouteProvider provider in Providers.OrderByDescending(x => x.Priority)) - { - using BulkInsertOperation bulkInsert = scope.Store.Raven.BulkInsert(scope.Database); - - await foreach (Route route in provider.Seed(context)) - { - if (route != null) - { - route.ProviderAlias = provider.Alias; - await bulkInsert.StoreAsync(route, route.Id); - count += 1; - } - } - } - } - - return count; - } -} diff --git a/zero.Core/Routing/RouteExtensions.cs b/zero.Core/Routing/RouteExtensions.cs deleted file mode 100644 index 5ce8d5c1..00000000 --- a/zero.Core/Routing/RouteExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace zero.Routing; - -public static class RouteExtensions -{ - public static Route DependsOn(this Route route, params string[] ids) - { - route.Dependencies.AddRange(ids); - return route; - } - - public static Route DependsOn(this Route route, Route anotherRoute) - { - route.Dependencies.AddRange(anotherRoute.Dependencies); - - foreach ((string key, object value) in anotherRoute.Params) - { - route.Params[key] = value; - } - - return route; - } - - public static Route Param(this Route route, string key, object value) - { - route.Params[key] = value; - return route; - } - - public static object Param(this Route route, string key) - { - return route.Params.TryGetValue(key, out object val) ? val : default; - } - - public static T Param(this Route route, string key) - { - return route.Params.GetValueOrDefault(key); - } -} diff --git a/zero.Core/Routing/RouteResolver.cs b/zero.Core/Routing/RouteResolver.cs deleted file mode 100644 index 57cffe0e..00000000 --- a/zero.Core/Routing/RouteResolver.cs +++ /dev/null @@ -1,156 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; -using Raven.Client.Exceptions.Documents.Indexes; - -namespace zero.Routing; - -public class RouteResolver : Routes, IRouteResolver -{ - protected IApplicationResolver AppResolver { get; set; } - protected IZeroOptions Options { get; set; } - - - public RouteResolver(IZeroContext context, IZeroStore store, ILogger logger, IEnumerable providers, IApplicationResolver appResolver) : base(context, store, logger, providers) - { - AppResolver = appResolver; - Options = context.Options; - } - - - /// - public async Task ResolveUrl(HttpContext context, Application application, string path) - { - IZeroDocumentSession session = Store.Session(application.Database); - - path = path.Length > 1 ? path.TrimEnd(PATH_SEPERATOR) : path; - - string[] pathParts = path.Trim(PATH_SEPERATOR).Split(PATH_SEPERATOR); - string[] parts = new string[pathParts.Length]; - - int min = parts.Length; - foreach (string pathPart in pathParts) - { - for (int i = 0; i < min; i++) - { - parts[i] += PATH_SEPERATOR + pathPart; - } - min -= 1; - } - - IList routes = null; - - try - { - routes = await session.Query() - .Where(x => (!x.AllowSuffix && x.Url == path) || (x.AllowSuffix && x.Url.In(parts))) - .Include("References[].Id") - .Include("Dependencies") - .ToListAsync(); - - routes = routes.Where(x => !x.Param(PageRouteProvider.PAGE_IS_FOLDER)).ToList(); - } - catch (IndexDoesNotExistException ex) - { - Logger.LogError(ex, "Indexes have not been created yet"); - return null; - } - - Route route = routes.FirstOrDefault(); - - // try to get the best matching path when multiple routes are found - // our assumption is that the best path is those with the longest path parts separated by a slash - // if we still get multiple routes with equal part counts then we prefer those which do not allow a suffix - if (routes.Count > 1) - { - int maxPathParts = routes.Max(x => x.Url.Count(u => u == PATH_SEPERATOR)); - List longestRoutes = routes.Where(x => maxPathParts == x.Url.Count(u => u == PATH_SEPERATOR)).OrderBy(x => x.AllowSuffix).ToList(); - - if (longestRoutes.Count > 1) - { - Logger.LogInformation("Multiple routes {routes} were found for {path}", longestRoutes.Select(x => x.Id), path); - } - - route = longestRoutes.FirstOrDefault(); - } - - if (route == null) - { - return null; - } - - return await ResolveRouteInternal(new RouteResponse() - { - Route = route, - App = application, - HttpContext = context, - Path = path - }); - } - - - /// - public async Task ResolveUrl(HttpContext context) - { - if (!Options.Initialized || context.IsBackofficeRequest(Options.ZeroPath)) - { - return null; - } - - Application app = await AppResolver.Resolve(context, Context.BackofficeUser); - string path = context.Request.Path; - - if (app == null) - { - return null; - } - - return await ResolveUrl(context, app, path); - } - - - /// - public IRouteEndpoint MapEndpoint(IRouteModel route) - { - if (TryGetProvider(route.Route.ProviderAlias, out IRouteProvider provider)) - { - return provider.Map(GetContext(), route); - } - - return null; - } - - - /// - /// Call the provider which can resolve the route - /// - async Task ResolveRouteInternal(RouteResponse response) - { - if (TryGetProvider(response.Route.ProviderAlias, out IRouteProvider provider)) - { - return await provider.Model(GetContext(), response); - } - - return null; - } -} - - -public interface IRouteResolver -{ - /// - /// Resolve an URL from the specified app and the path - /// - Task ResolveUrl(HttpContext context, Application application, string path); - - /// - /// Resolve an URL from an http context - /// - Task ResolveUrl(HttpContext context); - - /// - /// Get endpoint the route maps to - /// - IRouteEndpoint MapEndpoint(IRouteModel route); -} \ No newline at end of file diff --git a/zero.Core/Routing/Routes.cs b/zero.Core/Routing/Routes.cs deleted file mode 100644 index 9b332873..00000000 --- a/zero.Core/Routing/Routes.cs +++ /dev/null @@ -1,199 +0,0 @@ -using Microsoft.Extensions.Logging; -using Raven.Client.Documents.Linq; - -namespace zero.Routing; - -public class Routes : IRoutes -{ - public const char PATH_SEPERATOR = '/'; - - protected IZeroContext Context { get; set; } - protected IZeroStore Store { get; set; } - protected ILogger Logger { get; set; } - protected IEnumerable Providers { get; set; } - - - public Routes(IZeroContext context, IZeroStore store, ILogger logger, IEnumerable providers) - { - Context = context; - Store = store; - Logger = logger; - Providers = providers; - } - - - /// - public async Task GetUrl(T model) where T : ISupportsRouting => (await GetRoute(model))?.Url; - - - /// - public async Task GetUrl(string id) where T : ISupportsRouting => (await GetRoute(id))?.Url; - - - /// - public async Task> GetUrls(params T[] models) where T : ISupportsRouting => (await GetRoutes(models)).ToDictionary(x => x.Key, x => x.Value?.Url); - - - /// - public async Task GetRoute(string id) where T : ISupportsRouting - { - T model = await GetContext().Session.LoadAsync(id); - return await GetRoute(model); - } - - - /// - public async Task GetRoute(T model) where T : ISupportsRouting - { - if (model == null) - { - return null; - } - - if (!TryGetProvider(model, out IRouteProvider routeProvider)) - { - return null; - } - - RoutingContext context = GetContext(); - return await FindRoute(routeProvider, context, model); - } - - - /// - public async Task> GetRoutes(params T[] models) where T : ISupportsRouting - { - if (models.Length < 1) - { - return new(); - } - - T firstExistingModel = models.FirstOrDefault(x => x != null); - - if (firstExistingModel == null) - { - return new(); - } - - if (!TryGetProvider(firstExistingModel, out IRouteProvider routeProvider)) - { - return null; - } - - RoutingContext context = GetContext(); - return (await routeProvider.Find(context, models.Select(x => (ISupportsRouting)x).ToArray())).ToDictionary(x => (T)x.Key, x => x.Value); - } - - - /// - public virtual bool TryGetProvider(T model, out IRouteProvider provider) where T : ISupportsRouting - { - Type type = model.GetType(); - provider = Providers.OrderByDescending(x => x.Priority).FirstOrDefault(x => x.CanHandle(type)); - return provider != null; - } - - - /// - public virtual bool TryGetProvider(T model, bool forSeeding, out IRouteProvider provider) where T : ISupportsRouting - { - Type type = model.GetType(); - provider = Providers.OrderByDescending(x => x.Priority).FirstOrDefault(x => x.CanHandle(type) && (!forSeeding || x.CanSeed(type))); - return provider != null; - } - - - /// - public virtual bool TryGetProvider(string alias, out IRouteProvider provider) - { - provider = Providers.OrderByDescending(x => x.Priority).FirstOrDefault(x => x.Alias.Equals(alias, StringComparison.InvariantCultureIgnoreCase)); - return provider != null; - } - - - /// - /// Map a route to an MVC endpoint - /// - //public virtual RouteProviderEndpoint MapEndpoint(IResolvedRoute route) - //{ - // IEnumerable> resolvers = Options.Routing.EndpointResolvers.GetAll(route.GetType()); - - // foreach (Func resolver in resolvers.Reverse()) - // { - // RouteProviderEndpoint endpoint = resolver(route); - - // if (endpoint != null) - // { - // return endpoint; - // } - // } - - // return Options.Routing.DefaultEndpoint; - //} - - - /// - /// Find a persisted route for an entity - /// - protected virtual async Task FindRoute(IRouteProvider provider, RoutingContext context, ISupportsRouting model) - { - return await provider.Find(context, model); - } - - - /// - /// Build a new routing context - /// - protected virtual RoutingContext GetContext() - { - return new(Store, Context, Store.Session()); - } -} - -public interface IRoutes -{ - /// - /// Get the URL for an entity - /// - Task GetUrl(T model) where T : ISupportsRouting; - - /// - /// Get the URL for an entity - /// - Task GetUrl(string id) where T : ISupportsRouting; - - /// - /// Get URLs for multiple entities - /// - Task> GetUrls(params T[] models) where T : ISupportsRouting; - - /// - /// Get the route object for an entity - /// - Task GetRoute(T model) where T : ISupportsRouting; - - /// - /// Get the route object for an entity - /// - Task GetRoute(string id) where T : ISupportsRouting; - - /// - /// Get routes for multiple entities - /// - Task> GetRoutes(params T[] models) where T : ISupportsRouting; - - /// - /// Find a provider for a certain entity - /// - bool TryGetProvider(T model, out IRouteProvider provider) where T : ISupportsRouting; - - /// - /// Find a provider for a certain entity - /// - bool TryGetProvider(T model, bool forSeeding, out IRouteProvider provider) where T : ISupportsRouting; - - /// - /// Find a provider by alias - /// - bool TryGetProvider(string alias, out IRouteProvider provider); -} diff --git a/zero.Core/Routing/ZeroEndpointRouteBuilder.cs b/zero.Core/Routing/ZeroEndpointRouteBuilder.cs deleted file mode 100644 index f67fbc56..00000000 --- a/zero.Core/Routing/ZeroEndpointRouteBuilder.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Routing; - -namespace zero.Routing; - -public class ZeroEndpointRouteBuilder : IZeroEndpointRouteBuilder -{ - readonly IEndpointRouteBuilder _builder; - - public IServiceProvider ServiceProvider => _builder.ServiceProvider; - - public ICollection DataSources => _builder.DataSources; - - internal ZeroEndpointRouteBuilder(IEndpointRouteBuilder builder) - { - _builder = builder; - } - - public IApplicationBuilder CreateApplicationBuilder() => _builder.CreateApplicationBuilder(); -} - - -public interface IZeroEndpointRouteBuilder : IEndpointRouteBuilder -{ - -} \ No newline at end of file diff --git a/zero.Core/Routing/ZeroEnpointRouteBuilderExtensions.cs b/zero.Core/Routing/ZeroEnpointRouteBuilderExtensions.cs deleted file mode 100644 index b02560e7..00000000 --- a/zero.Core/Routing/ZeroEnpointRouteBuilderExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.AspNetCore.Builder; - -namespace zero.Routing; - -public static class ZeroEndpointRouteBuilderExtensions -{ - public static void UseZeroBackoffice(this IApplicationBuilder app, string path = "/zero") - { - //app.UseWhen(ctx => ctx.Request.Path.StartsWithSegments(path + "/api"), app1 => - //{ - // app1.UseEndpoints(endpoints => - // { - // //IZeroOptions options = app.ApplicationServices.GetService(); // TODO oO - // // see https://our.umbraco.com/documentation/reference/routing/custom-routes#where-to-put-your-routing-logic - // //string path = options.BackofficePath.EnsureStartsWith('/').TrimEnd('/'); - // //endpoints.MapFallbackToController(path + "/{**path}", "Index", "ZeroIndex"); - - // //endpoints.MapControllers(); - // }); - //}); - } - - - public static IZeroEndpointRouteBuilder MapZeroRoutes(this IZeroEndpointRouteBuilder endpoints) - { - endpoints.MapDynamicControllerRoute("/{**url}", state: ZeroRoutesTransformerType.Controller, order: 10); - endpoints.MapDynamicPageRoute("/{**url}", state: ZeroRoutesTransformerType.Page, order: 11); - return endpoints; - } -} diff --git a/zero.Core/Routing/ZeroEntityRouteInterceptor.cs b/zero.Core/Routing/ZeroEntityRouteInterceptor.cs deleted file mode 100644 index e6eb4d50..00000000 --- a/zero.Core/Routing/ZeroEntityRouteInterceptor.cs +++ /dev/null @@ -1,199 +0,0 @@ -using Microsoft.Extensions.Logging; -using Raven.Client.Documents; -using Raven.Client.Documents.Linq; - -namespace zero.Routing; - -public class ZeroEntityRouteInterceptor : Interceptor -{ - protected IZeroContext Context { get; set; } - - protected IZeroStore Store { get; set; } - - protected IEnumerable Providers { get; set; } - - protected IRoutes Routes { get; set; } - - protected IRedirectAutomation RedirectAutomation { get; set; } - - protected ILogger Logger { get; set; } - - - public ZeroEntityRouteInterceptor(IZeroContext context, IZeroStore store, IRoutes routes, ILogger logger, IEnumerable providers, IRedirectAutomation redirectAutomation) - { - Gravity = 100; - Context = context; - Store = store; - Routes = routes; - Providers = providers; - Logger = logger; - RedirectAutomation = redirectAutomation; - } - - - /// - public override Task Created(InterceptorParameters args, ZeroEntity model) => Saved(args, model, false); - - - /// - public override Task Updated(InterceptorParameters args, ZeroEntity model) => Saved(args, model, true); - - - /// - /// Remove all dependent routes and redirects on route deletion - /// - public override async Task Deleted(InterceptorParameters args, ZeroEntity model) - { - RoutingContext context = GetContext(); - - string id = model.Id; - List dependencies = await GetDependencies(context, model); - - if (!dependencies.Any()) - { - return; - } - - await context.Store.Raven.PurgeAsync(context.Context.Application.Database, $"where c.Dependencies IN ($id)", new Raven.Client.Parameters() - { - { "id", id } - }); - - // delete associated redirects for obsolete routes - await RedirectAutomation.DeleteForRoutes(dependencies.ToArray()); - - Logger.LogInformation("Route deletes completed (-{removed}) for {model} (id: {id})", dependencies.Count, model.Name, model.Id); - } - - - /// - /// Create or update a route when an entity changes and rebuild all dependent routes - /// - protected async Task Saved(InterceptorParameters args, ZeroEntity model, bool update = false) - { - // DONE [1] assume we have an update for a Product: - // this will not trigger a new seeding as the ProductRouteProvider handles entities, but not products itself - // we could overwrite CanHandle() to support types, but this will lead to errors when other methods are called on this provider - - // DONE [2] some providers have dependencies on entities which are not part of a standalone route provider. - // Example: The ProductRouteProvider has a dependency on . Channel has no route provider for itself. - // Therefore updates to Channels will not trigger seeding. - // For deletes it's easy as we can just delete all routes with the dependency by ID (and we won't need a corresponding route provider) - - // DONE [3] we have to find all routes which have become obsolete after update seeding. - - // TODO [4] if the entity is part of a route provider it can be checked for stale-ness. - // The problem is if we skip this part and only update dependencies. - // E.g. ProductRouteProvider has a dependency on . As Channel has no route provider it can not be checked for stale-ness. - // This has to be done in ProductRouteProvider as only the provider knows which properties of the entity it is using (therefore which properties need to be compared). - - RoutingContext context = GetContext(); - context.Session.Advanced.MaxNumberOfRequestsPerSession = 100_000_000; - - Dictionary urlUpdates = new(); - List obsoleteRoutes = await GetDependencies(context, model); - int countObsoleteRoutes = obsoleteRoutes.Count; - int countRoutes = 0; - - - // routine to store a new or updated route - async Task StoreRoute(Route route) - { - if (route != null) - { - countRoutes += 1; - - Route obsoleteRoute = obsoleteRoutes.FirstOrDefault(x => x.Id == route.Id); - - if (obsoleteRoute != null) - { - obsoleteRoutes.Remove(obsoleteRoute); - urlUpdates.Add(obsoleteRoute.Url, route.Url); - route = obsoleteRoute.Update(route); - } - - await context.Session.StoreAsync(route); - } - } - - - // find provider for this model - if (Routes.TryGetProvider(model, true, out IRouteProvider provider)) - { - // return if the route is not stale - if (args.PreviousModel is ZeroEntity previousZeroModel && !(await provider.IsRouteStale(context, previousZeroModel, model))) - { - return; - } - - // build and save new route - Route route = await provider.Create(context, model); - await StoreRoute(route); - } - - // find all providers which need to be informed that this route has changed - // e.g. a Page has changed: - // 1. Inform PageRouteProvider to update children - // 2. Inform ProductRouteProvider to update product routes which are part of this page or of a child page - // 3. ... - - // in a dependent route provider: - // 1. subscribe to updates - // 2. determine if this Page is relevant for the provider - // 3. gather all updated routes from the provider - - foreach (IRouteProvider dependentProvider in Providers.OrderByDescending(x => x.Priority)) - { - await foreach (Route dependentRoute in dependentProvider.SeedOnUpdate(context, model)) - { - await StoreRoute(dependentRoute); - } - } - - // at first we have gathered all routes which are dependent on the entity (via GetDependencies()) - // every time we insert a new route in the route table we remove this route from the gathered routes above. - // what is left are routes which are obsolete and have not been updated - foreach (Route obsoleteRoute in obsoleteRoutes) - { - context.Session.Delete(obsoleteRoute); - } - - await context.Session.SaveChangesAsync(); - - // delete associated redirects for obsolete routes - await RedirectAutomation.DeleteForRoutes(obsoleteRoutes.ToArray()); - - // update associated redirects for routes which have a new URL - foreach (var kvp in urlUpdates) - { - await RedirectAutomation.AddForRoute(kvp.Key, kvp.Value); - } - - if (countRoutes < 1 && countObsoleteRoutes < 1) - { - return; - } - - int countUpdatedRoutes = countObsoleteRoutes - obsoleteRoutes.Count; - Logger.LogInformation("Route updates completed (+{added}/~{updated}/-{removed}) for {model} (id: {id})", countRoutes - countUpdatedRoutes, countUpdatedRoutes, obsoleteRoutes.Count, model.Name, model.Id); - } - - - /// - /// Get route dependencies for an entity - /// - protected async Task> GetDependencies(RoutingContext context, T model) where T : ISupportsRouting - { - string[] ids = new[] { model.Id }; - return await context.Session.Query().Where(x => x.Dependencies.ContainsAny(ids)).ToListAsync(); - } - - - /// - /// Build a new routing context - /// - protected virtual RoutingContext GetContext() - { - return new(Store, Context, Store.Session()); - } -} \ No newline at end of file diff --git a/zero.Core/Routing/ZeroRouteProvider.cs b/zero.Core/Routing/ZeroRouteProvider.cs deleted file mode 100644 index c9ae3c89..00000000 --- a/zero.Core/Routing/ZeroRouteProvider.cs +++ /dev/null @@ -1,257 +0,0 @@ -namespace zero.Routing; - -public abstract class ZeroRouteProvider : ZeroRouteProvider, IRouteProvider where T : ISupportsRouting -{ - public ZeroRouteProvider(string alias) : base(alias) { } - - /// - public override bool CanHandle(Type type) => typeof(T).IsAssignableFrom(type); - - /// - public override bool CanSeed(Type type) => CanHandle(type); - - /// - public virtual Task Create(RoutingContext context, T model) => base.Create(context, model); - - /// - public sealed override Task Create(RoutingContext context, ISupportsRouting model) => Create(context, (T)model); - - /// - public virtual string Key(T model) => model.Hash; - - /// - public sealed override string Key(ISupportsRouting model) => Key((T)model); - - /// - public virtual Task IsRouteStale(RoutingContext context, T previous, T current) => Task.FromResult(true); - - /// - public sealed override Task IsRouteStale(RoutingContext context, ISupportsRouting previous, ISupportsRouting current) => IsRouteStale(context, (T)previous, (T)current); - - /// - public virtual string Id(T model) => base.Id(model); - - /// - public sealed override string Id(ISupportsRouting model) => base.Id(model); - - /// - public virtual async Task Find(RoutingContext context, T model) => await context.Session.LoadAsync(Id(model)); - - /// - public sealed override Task Find(RoutingContext context, ISupportsRouting model) => Find(context, (T)model); - - /// - public virtual async Task> Find(RoutingContext context, params T[] models) - { - Dictionary idMap = models.ToDistinctDictionary(x => Id(x), x => x); - Dictionary routes = await context.Session.LoadAsync(idMap.Select(x => x.Key)); - Dictionary result = new(); - - foreach ((string id, T model) in idMap) - { - result.Add(model, routes.GetValueOrDefault(id)); - } - - return result; - } - - /// - public sealed override async Task> Find(RoutingContext context, params ISupportsRouting[] models) - { - return (await Find(context, models.Select(x => (T)x).ToArray())).ToDictionary(x => (ISupportsRouting)x.Key, x => x.Value); - } -} - - -public abstract class ZeroRouteProvider : IRouteProvider -{ - protected static string ID_PARAM = "id"; - - protected PageRouteResolverHelper PageResolver { get; set; } = new(); - - - public ZeroRouteProvider(string alias) - { - Alias = alias; - } - - public virtual string Alias { get; protected set; } - - /// - public uint Priority { get; protected set; } = 0; - - /// - public virtual bool CanHandle(Type type) => false; - - /// - public virtual bool CanSeed(Type type) => false; - - /// - public virtual Task Create(RoutingContext context, ISupportsRouting model) => Task.FromResult(new Route() - { - Id = Id(model), - ProviderAlias = Alias, - ReferenceId = model.Id - }); - - /// - public virtual string Key(ISupportsRouting model) => model.Hash; - - /// - public virtual IAsyncEnumerable Seed(RoutingContext context) => AsyncEnumerable.Empty(); - - /// - public virtual IAsyncEnumerable SeedOnUpdate(RoutingContext context, T model) where T : ISupportsRouting => AsyncEnumerable.Empty(); - - /// - public virtual Task Model(RoutingContext context, RouteResponse response) => Task.FromResult((IRouteModel)new RouteModel() { Route = response.Route }); - - /// - public virtual Task IsRouteStale(RoutingContext context, ISupportsRouting previous, ISupportsRouting current) => Task.FromResult(true); - - /// - public virtual string Id(ISupportsRouting model) => "routes." + Alias + "." + Key(model); - - /// - public virtual IRouteEndpoint Map(RoutingContext context, IRouteModel route) - { - RoutingOptions options = context.Context.Options.For(); - - IEnumerable> resolvers = options.EndpointResolvers.GetAll(route.GetType()); - - foreach (Func resolver in resolvers.Reverse()) - { - IRouteEndpoint endpoint = resolver(route); - - if (endpoint != null) - { - return endpoint; - } - } - - return options.DefaultEndpoint; - } - - /// - public virtual async Task Find(RoutingContext context, ISupportsRouting model) => await context.Session.LoadAsync(Id(model)); - - /// - public virtual async Task> Find(RoutingContext context, params ISupportsRouting[] models) - { - Dictionary idMap = models.ToDistinctDictionary(x => Id(x), x => x); - Dictionary routes = await context.Session.LoadAsync(idMap.Select(x => x.Key)); - Dictionary result = new(); - - foreach ((string id, ISupportsRouting model) in idMap) - { - result.Add(model, routes.GetValueOrDefault(id)); - } - - return result; - } -} - - -public interface IRouteProvider : IRouteProvider where T : ISupportsRouting -{ - /// - /// Generate unique route key for a model - /// - string Key(T model); - - /// - /// Generate unique ID for a model - /// - string Id(T model); - - /// - /// Create route entity from a model - /// - Task Create(RoutingContext context, T model); - - /// - /// Determines whether the route for previous is stale and needs to be refreshed - /// based on comparison with the previous version - /// - Task IsRouteStale(RoutingContext context, T previous, T current); - - /// - /// Find a persisted route for an entity - /// - Task Find(RoutingContext context, T model); -} - - -public interface IRouteProvider -{ - /// - /// Alias of this route provider - /// - string Alias { get; } - - /// - /// Providers with higher priority will run before other providers when seeding - /// - uint Priority { get; } - - /// - /// Whether this provider can handle a certain entity type - /// - bool CanHandle(Type type); - - /// - /// Whether this provider can handle seeding for a certain entity type - /// - bool CanSeed(Type type); - - /// - /// Generate unique route key for a model - /// - string Key(ISupportsRouting model); - - /// - /// Generate unique ID for a model - /// - string Id(ISupportsRouting model); - - /// - /// Create route entity from a model - /// - Task Create(RoutingContext context, ISupportsRouting model); - - /// - /// Get all models which should be provided and handled by this instance - /// - IAsyncEnumerable Seed(RoutingContext context); - - /// - /// Get all models which should be updated when an entity changes - /// - IAsyncEnumerable SeedOnUpdate(RoutingContext context, T model) where T : ISupportsRouting; - - /// - /// Converts a route to a model which is passed to the endpoint - /// - Task Model(RoutingContext context, RouteResponse response); - - /// - /// Determines whether the route for previous is stale and needs to be refreshed - /// based on comparison with the previous version - /// - Task IsRouteStale(RoutingContext context, ISupportsRouting previous, ISupportsRouting current); - - /// - /// Map a route model to an endpoint - /// - IRouteEndpoint Map(RoutingContext context, IRouteModel route); - - /// - /// Find a persisted route for an entity - /// - Task Find(RoutingContext context, ISupportsRouting model); - - /// - /// Find persisted routes for entities - /// - Task> Find(RoutingContext context, params ISupportsRouting[] models); -} \ No newline at end of file diff --git a/zero.Core/Routing/ZeroRoutesTransformer.cs b/zero.Core/Routing/ZeroRoutesTransformer.cs deleted file mode 100644 index c6e63188..00000000 --- a/zero.Core/Routing/ZeroRoutesTransformer.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Logging; - -namespace zero.Routing; - -public class ZeroRoutesTransformer : DynamicRouteValueTransformer -{ - readonly IRouteResolver RouteResolver; - readonly IMediaFileSystem MediaFileSystem; - readonly ILogger Logger; - - public ZeroRoutesTransformer(IRouteResolver routeResolver, IMediaFileSystem mediaFileSystem, ILogger logger) - { - RouteResolver = routeResolver; - MediaFileSystem = mediaFileSystem; - Logger = logger; - } - - - public override async ValueTask TransformAsync(HttpContext httpContext, RouteValueDictionary values) - { - if (State == null || State is not ZeroRoutesTransformerType) - { - throw new ArgumentNullException(nameof(State), "Pass a ZeroRoutesTransformerType instance to the state parameter in the dynamic route transformer"); - } - - ZeroRoutesTransformerType type = (ZeroRoutesTransformerType)State; - - IRouteEndpoint endpoint = httpContext.Features.Get(); - IRouteModel route = httpContext.Features.Get(); - - // check if a resolved endpoint routes to a Razor Page. - // if the Page transformer can't find an endpoint it exits as the Controller transformer already ran before. - if (type == ZeroRoutesTransformerType.Page) - { - if (endpoint is PageRouteEndpoint) - { - return Apply(httpContext, values, endpoint, type); - } - - return null; - } - - // a route was already set, therefore we prevent another execution - if (route != null) - { - return null; - } - - // do not continue if it is a media path - if (MediaFileSystem.IsMediaPath(httpContext.Request.Path)) - { - return null; - } - - // resolve route from URL - route = await RouteResolver.ResolveUrl(httpContext); - - if (route == null) - { - return null; - } - - httpContext.Features.Set(route); - - // map the route to an endpoint - // an endpoint can be a controller action or a Razor page. - // in case it is a Razor page we exit here and let the transformer run again within the page context (MapDynamicPageRoute). - endpoint = RouteResolver.MapEndpoint(route); - - if (endpoint == null) - { - return null; - } - - httpContext.Features.Set(endpoint); - - if (endpoint is PageRouteEndpoint) - { - return null; - } - - return Apply(httpContext, values, endpoint, type); - } - - - protected virtual RouteValueDictionary Apply(HttpContext httpContext, RouteValueDictionary values, IRouteEndpoint endpoint, ZeroRoutesTransformerType type) - { - endpoint.Apply(values); - - HashSet logValues = new(); - - foreach ((string key, object value) in values) - { - if (key != "url" && value != null && (value is not string || ((string)value).HasValue())) - { - logValues.Add(key + ": " + value); - } - } - - Logger.LogDebug("Routed {url} to [{values}] (transformer: {type})", httpContext.Request.Path, logValues, type); - - return values; - } -} - - -public enum ZeroRoutesTransformerType -{ - Controller = 0, - Page = 1 -} \ No newline at end of file diff --git a/zero.Core/Routing/ZeroRoutingModule.cs b/zero.Core/Routing/ZeroRoutingModule.cs deleted file mode 100644 index e324162b..00000000 --- a/zero.Core/Routing/ZeroRoutingModule.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace zero.Routing; - -internal class ZeroRoutingModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddTransient(); - services.TryAddEnumerable(ServiceDescriptor.Singleton()); - services.AddScoped(); - - services.AddOptions().Bind(configuration.GetSection("Zero:Routing")); - - services.Configure(opts => - { - RavenOptions raven = opts.For(); - raven.Indexes.Add(); - raven.Indexes.Add(); - raven.Indexes.Add(); - }); - } -} \ No newline at end of file diff --git a/zero.Core/Search/Indexes/zero_Search.cs b/zero.Core/Search/Indexes/zero_Search.cs deleted file mode 100644 index df05089c..00000000 --- a/zero.Core/Search/Indexes/zero_Search.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Raven.Client.Documents; - -namespace zero.Search; - -public class zero_Search : ZeroJavascriptIndex -{ - public override void Setup(IZeroOptions options, IDocumentStore store) - { - // TODO index.Conventions is null, but needed for collection name retrieval - - foreach (var map in options.For()) - { - Maps.Add(map.BuildInstruction(store)); - } - - - //Index(nameof(SearchIndexResult.Name), FieldIndexing.Search); - //Index(nameof(SearchIndexResult.Fields), FieldIndexing.Search); - } -} \ No newline at end of file diff --git a/zero.Core/Search/Models/SearchIndexMap.cs b/zero.Core/Search/Models/SearchIndexMap.cs deleted file mode 100644 index 5ee0c775..00000000 --- a/zero.Core/Search/Models/SearchIndexMap.cs +++ /dev/null @@ -1,120 +0,0 @@ -using Raven.Client.Documents; - -namespace zero.Search; - -public class SearchIndexMap -{ - internal Type Type; - internal string _Icon; - protected string _group; - protected string[] _fields; - protected float _boost = 0; - protected Func _modify; - - const string mapTemplate = @"map('{collection}', function (x) { - return { - Id: x.Id, - Group: '{group}', - Name: boost(x.Name, {boost}), - IsActive: x.IsActive, - Fields: [{fields}] - }; - });"; - - internal SearchIndexMap(Type type, string icon = null) - { - Type = type; - _group = "__TODO"; - _Icon = icon; - } - - internal string BuildInstruction(IDocumentStore store) - { - return TokenReplacement.Apply(mapTemplate, new() - { - { "collection", store.Conventions.GetCollectionName(Type) }, - { "group", _group }, - { "fields", BuildFieldArray(_fields) }, - { "boost", _boost.ToString() } - }); - } - - internal string BuildFieldArray(string[] fields) - { - if (fields == null || !fields.Any()) - { - return String.Empty; - } - - return String.Join(", ", fields.Select(x => $"x.{x}")); //$"boost(x.{x}, {_boost})")); - } - - internal bool CanModify(Type type) - { - return Type.IsAssignableFrom(type); - } - - internal async Task Modify(ZeroEntity entity, SearchResult result, IZeroContext context) - { - if (_modify != null) - { - await _modify(entity, result, context); - } - } -} - - -public class SearchIndexMap : SearchIndexMap where T : ZeroEntity -{ - internal SearchIndexMap(string icon = null) : base(typeof(T), icon) { } - - public virtual SearchIndexMap Icon(string icon) - { - _Icon = icon; - return this; - } - - public SearchIndexMap Display(Action modify = null) - { - _modify = (x, res, opts) => - { - modify?.Invoke(x as T, res); - return Task.CompletedTask; - }; - return this; - } - - public SearchIndexMap Display(Func modify = null) - { - _modify = (x, res, ctx) => modify?.Invoke(x as T, res); - return this; - } - - public SearchIndexMap Display(Func modify = null) - { - _modify = (x, res, ctx) => modify?.Invoke(x as T, res, ctx); - return this; - } - - public SearchIndexMap Display(Action modify = null) - { - _modify = (x, res, ctx) => - { - modify?.Invoke(x as T, res, ctx); - return Task.CompletedTask; - }; - return this; - } - - public SearchIndexMap Fields(params string[] fieldNames) - { - _fields = fieldNames; - return this; - } - - public SearchIndexMap Boost(float boostValue) - { - _boost = boostValue; - return this; - } -} diff --git a/zero.Core/Search/Models/SearchResult.cs b/zero.Core/Search/Models/SearchResult.cs deleted file mode 100644 index 7f4cd64f..00000000 --- a/zero.Core/Search/Models/SearchResult.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace zero.Search; - -public class SearchResult -{ - public string Id { get; set; } - - public string Name { get; set; } - - public string Description { get; set; } - - public string Icon { get; set; } - - public string Image { get; set; } - - public string Url { get; set; } - - public string Group { get; set; } - - public bool IsActive { get; set; } -} - - -public class SearchIndexResult -{ - public string Id { get; set; } - - public string Group { get; set; } - - public string Name { get; set; } - - public bool IsActive { get; set; } - - public List Fields { get; set; } = new(); -} \ No newline at end of file diff --git a/zero.Core/Search/SearchService.cs b/zero.Core/Search/SearchService.cs deleted file mode 100644 index 830778c5..00000000 --- a/zero.Core/Search/SearchService.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Queries; -using Raven.Client.Documents.Session; - -namespace zero.Search; - -public class SearchService : ISearchService -{ - protected IZeroStore Store { get; private set; } - - protected IZeroOptions Options { get; private set; } - - protected IZeroContext Context { get; private set; } - - - public SearchService(IZeroStore store, IZeroOptions options, IZeroContext context) - { - Store = store; - Options = options; - Context = context; - } - - - public async Task> Query(string searchTerm, int page = 1, int pageSize = 10) - { - string[] searchParts = searchTerm.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries).Select(x => - { - return "*" + x + "*"; - }).ToArray(); - - List results = await Store.Session().Query() - .Statistics(out QueryStatistics stats) - .Search(x => x.Name, searchParts, 2, @operator: SearchOperator.And) - .Search(x => x.Fields, searchParts, 1, Raven.Client.Documents.SearchOptions.Or, @operator: SearchOperator.And) - .OrderByScoreDescending() - .Paging(page, pageSize) - .As() - .ToListAsync(); - - List items = new(); - - IEnumerable maps = Options.For(); - - foreach (ZeroEntity result in results) - { - Type type = result.GetType(); - SearchIndexMap map = maps.FirstOrDefault(x => x.CanModify(type)); - - SearchResult searchResult = new() - { - Id = result.Id, - Icon = map._Icon.Or("fth-folder"), - Name = result.Name, - IsActive = result.IsActive, - Group = GetGroupName(map.Type), - Url = "/" - }; - - await map.Modify(result, searchResult, Context); - - items.Add(searchResult); - } - - return new Paged(items, stats.TotalResults, 1, 20); - } - - - protected string GetGroupName(Type type) - { - return "@search.collection." + type.Name.ToCamelCase(); - } -} - -public interface ISearchService -{ - Task> Query(string searchTerm, int page = 1, int pageSize = 10); -} \ No newline at end of file diff --git a/zero.Core/Search/ZeroSearchModule.cs b/zero.Core/Search/ZeroSearchModule.cs deleted file mode 100644 index c424dca6..00000000 --- a/zero.Core/Search/ZeroSearchModule.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Search; - -internal class ZeroSearchModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - - services.Configure(opts => - { - opts.Indexes.Add(); - }); - - services.AddOptions().Bind(configuration.GetSection("Zero:Search")); - } -} \ No newline at end of file diff --git a/zero.Core/Search/ZeroSearchOptions.cs b/zero.Core/Search/ZeroSearchOptions.cs deleted file mode 100644 index 01a93ae8..00000000 --- a/zero.Core/Search/ZeroSearchOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace zero.Search; - -public class ZeroSearchOptions : List -{ - public bool Enabled { get; set; } - - public SearchIndexMap Map(string icon = null) where T : ZeroEntity, new() - { - SearchIndexMap map = new(icon); - Add(map); - return map; - } -} \ No newline at end of file diff --git a/zero.Core/ServiceCollectionExtensions.cs b/zero.Core/ServiceCollectionExtensions.cs deleted file mode 100644 index f854e89b..00000000 --- a/zero.Core/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero; - -public static class ServiceCollectionExtensions -{ - public static ZeroBuilder AddZero(this IServiceCollection services, IConfiguration configuration) - { - return new ZeroBuilder(services, configuration, null); - } - - public static ZeroBuilder AddZero(this IServiceCollection services, IConfiguration configuration, Action setupAction) - { - return new ZeroBuilder(services, configuration, setupAction); - } -} \ No newline at end of file diff --git a/zero.Core/Setup/SetupApi.cs b/zero.Core/Setup/SetupApi.cs deleted file mode 100644 index 0eefe3d1..00000000 --- a/zero.Core/Setup/SetupApi.cs +++ /dev/null @@ -1,339 +0,0 @@ -//using FluentValidation.Results; -//using Microsoft.AspNetCore.Identity; -//using Newtonsoft.Json; -//using Raven.Client.Documents; -//using Raven.Client.Documents.Session; -//using System.IO; -//using System.Security.Cryptography; -//using zero.Setup; - -//namespace zero.Core.Api -//{ -// public class SetupApi : ISetupApi -// { -// protected IZeroOptions Options { get; private set; } - -// protected IPasswordHasher PasswordHasher { get; private set; } - -// protected IZeroDocumentConventionsBuilder ConventionsBuilder { get; private set; } - - -// public SetupApi(IZeroOptions options, IPasswordHasher passwordHasher, IZeroDocumentConventionsBuilder conventionsBuilder) -// { -// Options = options; -// PasswordHasher = passwordHasher; -// ConventionsBuilder = conventionsBuilder; -// } - - -// public async Task> Install(SetupModel model) -// { -// ValidationResult validation = await new SetupModelValidator().ValidateAsync(model); - -// if (!validation.IsValid) -// { -// return EntityResult.Fail(validation); -// } - -// DocumentStore raven = null; - -// try -// { -// // test read & write permissions on folders -// // TODO - -// // create temporary instance of database -// raven = new DocumentStore() -// { -// Urls = model.Database.Url.Split(','), -// Database = model.Database.Name -// }; - -// ConventionsBuilder.Run(raven.Conventions); -// raven.Initialize(); - -// // create application -// Application app = Prepare(new Application() -// { -// Id = "app." + Safenames.Alias(model.AppName), -// CreatedDate = DateTimeOffset.Now, -// IsActive = true, -// Name = model.AppName, -// Alias = Safenames.Alias(model.AppName), -// Database = model.Database.Name -// }); - -// // create user -// ZeroUser user = Prepare(new ZeroUser() -// { -// IsSuper = true, -// CreatedDate = DateTimeOffset.Now, -// Email = model.User.Email, -// Username = model.User.Email, -// Name = model.User.Name, -// IsActive = true, -// LanguageId = Options.DefaultLanguage, -// Alias = Safenames.Alias(model.User.Name), -// IsEmailConfirmed = true, -// SecurityStamp = NewSecurityStamp() -// }); - -// user.PasswordHash = PasswordHasher.HashPassword(user, model.User.Password); - -// // create default language -// Language language = Prepare(new Language() // TODO get default language selection from setup UI -// { -// Name = "English", -// Alias = Safenames.Alias("English"), -// CreatedDate = DateTimeOffset.Now, -// Code = "en-US", -// IsActive = true, -// IsDefault = true -// }); - -// using IAsyncDocumentSession session = raven.OpenAsyncSession(); - -// await session.StoreAsync(user); - -// // user creation failed -// //if (!result.Succeeded) -// //{ -// // EntityResult entityResult = EntityResult.Fail(); - -// // foreach (IdentityError error in result.Errors) -// // { -// // entityResult.AddError(error.Code, error.Description); -// // } - -// // return entityResult; -// //} - - -// await session.StoreAsync(app); - -// // save default user roles -// IList roles = GetRoles(model); - -// foreach (ZeroUserRole role in roles) -// { -// await session.StoreAsync(role); -// } - -// // add admin role to super user -// // set app-id for user and store it -// user.AppId = session.Advanced.GetDocumentId(app); -// user.CurrentAppId = user.AppId; -// user.RoleIds.Add(roles.First(role => role.Name == "Standard").Id); -// user.RoleIds.Add(roles.First(role => role.Name == "Administrator").Id); -// await session.StoreAsync(user); - -// // create language -// await session.StoreAsync(language); - -// // set countries -// using (Raven.Client.Documents.BulkInsert.BulkInsertOperation bulkInsert = raven.BulkInsert()) -// { -// foreach (Country country in GetCountries(model, language)) -// { -// await bulkInsert.StoreAsync(country); -// } -// } - -// // update settings file. if this fails the changes won't be stored -// UpdateSettingsFile(model); - -// await session.SaveChangesAsync(); -// } -// catch (Exception) -// { -// // TODO revert -// throw; -// } -// finally -// { -// raven?.Dispose(); -// } - -// return EntityResult.Success(model); -// } - - -// /// -// /// Updates the settings file with the new data (database connection and version) -// /// -// void UpdateSettingsFile(SetupModel model) -// { -// // TODO should we write this into appSettings.json now? -// // or let the user do it in the code editor? - -// //var filePath = Path.Combine(model.ContentRootPath, "zeroSettings.json"); -// //string json = File.ReadAllText(filePath); - -// //ZeroConfiguration config = JsonConvert.DeserializeObject(json); - -// //config.Raven = new RavenOptions() -// //{ -// // Database = model.Database.Name, -// // Url = model.Database.Url -// //}; - -// //config.ZeroVersion = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString(); - -// //json = JsonConvert.SerializeObject(config, Formatting.Indented); - -// //File.WriteAllText(filePath, json); -// } - - -// /// -// /// Get countries for supported backoffice languages -// /// -// IList GetCountries(SetupModel model, Language defaultLanguage) -// { -// List countries = new List(); - -// //string[] isoCodes = Config.SupportedLanguages; -// string[] isoCodes = new string[1] { "en-US" }; - -// foreach (string languageISO in isoCodes) -// { -// // country list from: https://github.com/umpirsky/country-list/tree/master/data -// var filePath = Path.Combine(model.ContentRootPath, "Resources", "Countries", "countries." + languageISO.ToLowerInvariant() + ".json"); -// string json = File.ReadAllText(filePath); - - -// countries.AddRange(JsonConvert.DeserializeObject>(json).Select(country => Prepare(new Country() -// { -// CreatedDate = DateTimeOffset.Now, -// IsActive = true, -// //AppId = Constants.Database.SharedAppId, // TODO appx fix -// Alias = Safenames.Alias(country.Value), -// //LanguageId = defaultLanguage.Id, -// Code = country.Key.ToLowerInvariant(), -// Name = country.Value -// })).ToList()); -// } - -// return countries; -// } - - -// /// -// /// Create default roles -// /// -// IList GetRoles(SetupModel model) -// { -// string type = Constants.Auth.Claims.Permission; - -// ZeroUserRole adminRole = Prepare(new ZeroUserRole() -// { -// Name = "Administrator", -// Alias = Safenames.Alias("Administrator"), -// Sort = 0, -// Icon = "fth-award", -// CreatedDate = DateTimeOffset.Now, -// IsActive = true, -// Claims = new List() -// { -// //new UserClaim(type, Permissions.Applications, PermissionsValue.Write), -// new UserClaim(type, Permissions.Sections.Dashboard, PermissionsValue.True), -// new UserClaim(type, Permissions.Sections.Spaces, PermissionsValue.True), -// new UserClaim(type, Permissions.Sections.Pages, PermissionsValue.True), -// new UserClaim(type, Permissions.Sections.Media, PermissionsValue.True), -// new UserClaim(type, Permissions.Sections.Settings, PermissionsValue.True), -// new UserClaim(type, Permissions.Settings.Applications, PermissionsValue.None), -// new UserClaim(type, Permissions.Settings.Countries, PermissionsValue.Update), -// new UserClaim(type, Permissions.Settings.Logging, PermissionsValue.Update), -// new UserClaim(type, Permissions.Settings.Translations, PermissionsValue.Update), -// new UserClaim(type, Permissions.Settings.Updates, PermissionsValue.Update), -// new UserClaim(type, Permissions.Settings.Users, PermissionsValue.Update), -// }, -// }); - -// ZeroUserRole editorRole = Prepare(new ZeroUserRole() -// { -// Name = "Editor", -// Alias = Safenames.Alias("Editor"), -// Sort = 1, -// Icon = "fth-feather", -// CreatedDate = DateTimeOffset.Now, -// IsActive = true, -// Claims = new List() -// { -// new UserClaim(type, Permissions.Sections.Dashboard, PermissionsValue.True), -// new UserClaim(type, Permissions.Sections.Spaces, PermissionsValue.True), -// new UserClaim(type, Permissions.Sections.Pages, PermissionsValue.True), -// new UserClaim(type, Permissions.Sections.Media, PermissionsValue.True), -// new UserClaim(type, Permissions.Sections.Settings, PermissionsValue.True), -// new UserClaim(type, Permissions.Settings.Translations, PermissionsValue.True) -// } -// }); - -// ZeroUserRole defaultRole = Prepare(new ZeroUserRole() -// { -// Name = "Standard", -// Alias = Safenames.Alias("Standard"), -// Sort = 2, -// Icon = "fth-users", -// CreatedDate = DateTimeOffset.Now, -// IsActive = true, -// Claims = new List() -// { -// new UserClaim(type, Permissions.Sections.Dashboard, PermissionsValue.True) -// } -// }); - -// return new List() { adminRole, editorRole, defaultRole }; -// } - - -// T Prepare(T model, string languageId = null) where T : ZeroIdEntity -// { -// ZeroEntity zeroEntity = model as ZeroEntity; - -// // set default properties -// if (zeroEntity != null && zeroEntity.CreatedDate == default) -// { -// zeroEntity.CreatedDate = DateTimeOffset.Now; -// } -// if (zeroEntity != null && zeroEntity.CreatedById == default) -// { -// zeroEntity.CreatedById = Constants.Auth.SystemUser; -// } -// if (zeroEntity != null && zeroEntity.LanguageId == default) -// { -// zeroEntity.LanguageId = languageId; -// } - -// // update name alias and last modified -// if (zeroEntity != null) -// { -// zeroEntity.Alias = Safenames.Alias(zeroEntity.Name); -// zeroEntity.LastModifiedById = Constants.Auth.SystemUser; -// zeroEntity.LastModifiedDate = DateTimeOffset.Now; -// zeroEntity.Hash ??= IdGenerator.Create(); -// zeroEntity.IsActive = true; -// } - -// return model; -// } - - -// /// -// /// Creates a new security stamp -// /// -// string NewSecurityStamp() -// { -// byte[] bytes = new byte[20]; -// RandomNumberGenerator.Fill(bytes); -// return Base32.ToBase32(bytes); -// } -// } - - - -// public interface ISetupApi -// { -// Task> Install(SetupModel model); -// } -//} diff --git a/zero.Core/Setup/SetupModel.cs b/zero.Core/Setup/SetupModel.cs deleted file mode 100644 index 967d50ac..00000000 --- a/zero.Core/Setup/SetupModel.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace zero.Setup; - -public sealed class SetupModel -{ - public string AppName { get; set; } - - public SetupUserModel User { get; set; } - - public SetupDatabaseModel Database { get; set; } - - public string ContentRootPath { get; set; } -} - - -public sealed class SetupUserModel -{ - public string Name { get; set; } - - public string Email { get; set; } - - public string Password { get; set; } -} - -public sealed class SetupDatabaseModel -{ - public string Url { get; set; } - - public string Name { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Setup/SetupModelValidator.cs b/zero.Core/Setup/SetupModelValidator.cs deleted file mode 100644 index d0f708a1..00000000 --- a/zero.Core/Setup/SetupModelValidator.cs +++ /dev/null @@ -1,19 +0,0 @@ -using FluentValidation; - -namespace zero.Setup; - -public class SetupModelValidator : ZeroValidator -{ - public SetupModelValidator() - { - RuleFor(x => x.User).NotNull(); - RuleFor(x => x.User.Email).NotEmpty().Email(); - RuleFor(x => x.User.Name).MaximumLength(40).NotEmpty(); - RuleFor(x => x.User.Password).MaximumLength(1024); // TODO password policy - - RuleFor(x => x.AppName).MaximumLength(40).NotEmpty(); - - RuleFor(x => x.Database.Url).NotEmpty().Url(); - RuleFor(x => x.Database.Name).NotEmpty(); - } -} \ No newline at end of file diff --git a/zero.Core/Spaces/FlavorOptionsExtensions.cs b/zero.Core/Spaces/FlavorOptionsExtensions.cs deleted file mode 100644 index df2d15c8..00000000 --- a/zero.Core/Spaces/FlavorOptionsExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace zero.Spaces; - -public static class FlavorOptionsExtensions -{ - public static void AddSpace(this FlavorOptions options) where T : SpaceType, new() where TEntity : Space, new() - { - options.Add(new T()); - } - - public static void AddSpaceList(this FlavorOptions options, string alias, string name, string description, string icon, string editorAlias = null) where T : Space, new() - { - options.Add(new SpaceType(typeof(T)) - { - Alias = alias, - EditorAlias = editorAlias.Or(alias), - View = SpaceView.List, - Construct = _ => new T(), - Name = name, - Description = description, - Icon = icon - }); - } - - - public static void AddSpaceEditor(this FlavorOptions options, string alias, string name, string description, string icon, string editorAlias = null) where T : Space, new() - { - options.Add(new SpaceType(typeof(T)) - { - Alias = alias, - EditorAlias = editorAlias.Or(alias), - View = SpaceView.Editor, - Construct = _ => new T(), - Name = name, - Description = description, - Icon = icon - }); - } - - public static void AddSpaceCustom(this FlavorOptions options, string componentPath, string alias, string name, string description, string icon, string editorAlias = null) where T : Space, new() - { - options.Add(new SpaceType(typeof(T)) - { - Alias = alias, - EditorAlias = editorAlias.Or(alias), - View = SpaceView.Custom, - Construct = _ => new T(), - ComponentPath = componentPath, - Name = name, - Description = description, - Icon = icon - }); - } -} diff --git a/zero.Core/Spaces/Models/Space.cs b/zero.Core/Spaces/Models/Space.cs deleted file mode 100644 index 59f2b952..00000000 --- a/zero.Core/Spaces/Models/Space.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace zero.Spaces; - -/// -/// -/// -[RavenCollection("Spaces")] -public class Space : ZeroEntity -{ - -} \ No newline at end of file diff --git a/zero.Core/Spaces/Models/SpaceType.cs b/zero.Core/Spaces/Models/SpaceType.cs deleted file mode 100644 index 4474f1f7..00000000 --- a/zero.Core/Spaces/Models/SpaceType.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace zero.Spaces; - -public class SpaceType : FlavorConfig -{ - public SpaceType(Type type) : base(type) { } - - public string EditorAlias { get; set; } - - public SpaceView View { get; set; } - - public string ComponentPath { get; set; } - - public bool LineBelow { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Spaces/Models/SpaceView.cs b/zero.Core/Spaces/Models/SpaceView.cs deleted file mode 100644 index f6fbcf67..00000000 --- a/zero.Core/Spaces/Models/SpaceView.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace zero.Spaces; - -public enum SpaceView -{ - List, - Editor, - Custom -} \ No newline at end of file diff --git a/zero.Core/Spaces/SpaceService.cs b/zero.Core/Spaces/SpaceService.cs deleted file mode 100644 index a3bd8d7f..00000000 --- a/zero.Core/Spaces/SpaceService.cs +++ /dev/null @@ -1,104 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Indexes; -using Raven.Client.Documents.Linq; -using Raven.Client.Documents.Session; - -namespace zero.Spaces; - -public class SpaceService : SpaceStore, ISpaceService -{ - /// - public ISpaceTypeService Types { get; protected set; } - - - public SpaceService(IStoreContext store, ISpaceTypeService spaceTypeService) : base(store) - { - Types = spaceTypeService; - } - - - /// - public async Task Load(string id, string changeVector = null) where T : Space, new() - { - return await Load(id, changeVector) as T; - } - - - /// - public async Task> Load(string spaceType, IEnumerable ids) where T : Space, new() - { - ids = ids.Distinct().ToArray(); - - Dictionary models = await Session.LoadAsync(ids); - Dictionary result = new(); - - foreach (string id in ids) - { - models.TryGetValue(id, out T model); - - if (!typeof(T).IsAssignableFrom(model.GetType())) - { - result.Add(id, null); - } - else - { - result.Add(id, WhenActive(model) as T); - } - } - - return result; - } - - - /// - public async Task> Load(string spaceType, int pageNumber, int pageSize, Func, IQueryable> expression = null) where T : Space, new() - { - IRavenQueryable queryable = Session.Query().Where(x => x.Flavor == spaceType).Statistics(out QueryStatistics statistics); - expression ??= x => x; - - List result = await expression(queryable).Paging(pageNumber, pageSize).ToListAsync(); - return new Paged(result, statistics.TotalResults, pageNumber, pageSize); - } - - - /// - public async Task> Load(string spaceType, int pageNumber, int pageSize, Func, IQueryable> expression = null) - where T : Space, new() - where TIndex : AbstractCommonApiForIndexes, new() - { - IRavenQueryable queryable = Session.Query().Where(x => x.Flavor == spaceType).Statistics(out QueryStatistics statistics); - expression ??= x => x; - - List result = await expression(queryable).Paging(pageNumber, pageSize).ToListAsync(); - return new Paged(result, statistics.TotalResults, pageNumber, pageSize); - } -} - - -public interface ISpaceService : ISpaceStore -{ - /// - /// Service for retrieving space types - /// - ISpaceTypeService Types { get; } - - /// - /// Get editor item for a space - /// - Task Load(string id, string changeVector = null) where T : Space, new(); - - /// - /// Get editor items for a space - /// - Task> Load(string spaceType, IEnumerable ids) where T : Space, new(); - - /// - /// Get space items by query - /// - Task> Load(string spaceType, int pageNumber, int pageSize, Func, IQueryable> expression = default) where T : Space, new(); - - /// - /// Get space items by query (by using the specified index) - /// - Task> Load(string spaceType, int pageNumber, int pageSize, Func, IQueryable> expression = default) where T : Space, new() where TIndex : AbstractCommonApiForIndexes, new(); -} \ No newline at end of file diff --git a/zero.Core/Spaces/SpaceStore.cs b/zero.Core/Spaces/SpaceStore.cs deleted file mode 100644 index 998ca1ee..00000000 --- a/zero.Core/Spaces/SpaceStore.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace zero.Spaces; - -public class SpaceStore : EntityStore, ISpaceStore -{ - public SpaceStore(IStoreContext context) : base(context) - { - - } -} - - -public interface ISpaceStore : IEntityStore -{ - -} \ No newline at end of file diff --git a/zero.Core/Spaces/SpaceTypeService.cs b/zero.Core/Spaces/SpaceTypeService.cs deleted file mode 100644 index 456354c1..00000000 --- a/zero.Core/Spaces/SpaceTypeService.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace zero.Spaces; - -public class SpaceTypeService : ISpaceTypeService -{ - protected IZeroContext Context { get; private set; } - - protected IHandlerHolder Handler { get; private set; } - - protected FlavorOptions Flavors { get; set; } - - - public SpaceTypeService(IZeroContext context, IZeroOptions options, IHandlerHolder handler) - { - Context = context; - Handler = handler; - Flavors = options.For(); - } - - - /// - public IEnumerable GetAll() - { - return Flavors.GetAll().Select(x => (SpaceType)x); - } - - - /// - public SpaceType GetByAlias(string spaceTypeAlias) - { - return Flavors.Get(spaceTypeAlias) as SpaceType; - } - - - /// - public SpaceType GetByType() where T : Space - { - return Flavors.Get() as SpaceType; - } -} - - -public interface ISpaceTypeService -{ - /// - /// Get all available space types - /// - IEnumerable GetAll(); - - /// - /// Get a specific space type by alias - /// - SpaceType GetByAlias(string spaceTypeAlias); - - /// - /// Get a specific space type by space type. funny, I know. - /// - SpaceType GetByType() where T : Space; -} diff --git a/zero.Core/Spaces/ZeroSpaceModule.cs b/zero.Core/Spaces/ZeroSpaceModule.cs deleted file mode 100644 index 436c4065..00000000 --- a/zero.Core/Spaces/ZeroSpaceModule.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Spaces; - -internal class ZeroSpaceModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - services.Configure(opts => - { - opts.Configure(cfg => - { - cfg.CanUseWithoutFlavors = false; - }); - }); - } -} \ No newline at end of file diff --git a/zero.Core/Stores/EntityStore.cs b/zero.Core/Stores/EntityStore.cs deleted file mode 100644 index 199c00da..00000000 --- a/zero.Core/Stores/EntityStore.cs +++ /dev/null @@ -1,190 +0,0 @@ -using FluentValidation.Results; -using Raven.Client.Documents.Indexes; -using Raven.Client.Documents.Linq; - -namespace zero.Stores; - -public abstract class EntityStore : IEntityStore where T : ZeroIdEntity, ISupportsFlavors, ISupportsSorting, new() -{ - /// - public Guid Guid { get; private set; } = Guid.NewGuid(); - - /// - public IZeroDocumentSession Session => Operations.Session; - - /// - public StoreConfig Config => Operations.Config; - - /// - public IStoreOperations Operations { get; private set; } - - protected IZeroContext Context { get; private set; } - - - protected IInterceptors Interceptors { get; private set; } - - protected IZeroOptions Options { get; private set; } - - protected IMessageAggregator Messages { get; private set; } - - - public EntityStore(IStoreContext ctx) - { - Context = ctx.Context; - Interceptors = ctx.Interceptors; - Options = ctx.Options; - Operations = new StoreOperations(ctx); - Messages = ctx.Messages; - - } - - - /// - public virtual Task Empty(string flavorAlias = null) => Operations.Empty(flavorAlias); - - /// - public virtual Task Empty(string flavorAlias = null) where TFlavor : T, new() => Operations.Empty(flavorAlias); - - /// - public virtual Task Load(string id, string changeVector = null) => Operations.Load(id, changeVector); - - /// - public virtual Task> Load(IEnumerable ids) => Operations.Load(ids); - - /// - public virtual Task> Load(int pageNumber, int pageSize, Func, IQueryable> querySelector = default) => Operations.Load(pageNumber, pageSize, querySelector); - - /// - public virtual Task> Load(int pageNumber, int pageSize, Func, IQueryable> querySelector = default) where TIndex : AbstractCommonApiForIndexes, new() => Operations.Load(pageNumber, pageSize, querySelector); - - /// - public virtual Task> LoadAll() => Operations.LoadAll(); - - /// - public virtual IAsyncEnumerable Stream(Func, IQueryable> expression) => Operations.Stream(expression); - - /// - public virtual string GetChangeToken(T model) => Operations.GetChangeToken(model); - - /// - public virtual Task> Create(T model, Action onAfterStore = null) => Operations.Create(model, async m => await Validate(m), onAfterStore); - - /// - public virtual Task> Update(T model, Action onAfterStore = null) => Operations.Update(model, async m => await Validate(m), onAfterStore); - - /// - public virtual Task>> Sort(string[] sortedIds) => Operations.Sort(sortedIds); - - /// - public virtual Task> Delete(T model) => Operations.Delete(model); - - /// - public virtual Task Validate(T model) => Operations.Validate(model); - - /// - /// Create rules for validation - /// - [Obsolete] - protected virtual void ValidationRules(ZeroValidator validator) { } - - /// - /// Do only return the model when it is set to active or inactive entities are included with IncludeInactive() - /// - protected virtual T WhenActive(T model) => Operations.WhenActive(model); -} - - - -public interface IEntityStore where T : ZeroIdEntity, ISupportsFlavors, ISupportsSorting, new() -{ - /// - /// Id for this store - /// - Guid Guid { get; } - - /// - /// Access the current document session - /// - IZeroDocumentSession Session { get; } - - /// - /// Configure the store - /// - StoreConfig Config { get; } - - /// - /// Access to underlying operations helper - /// - IStoreOperations Operations { get; } - - /// - /// Get new instance of an entity (with an optional flavor) - /// - Task Empty(string flavorAlias = null); - - /// - /// Get new instance of an entity with a specific flavor - /// - Task Empty(string flavorAlias = null) where TFlavor : T, new(); - - /// - /// Get an entity by Id - /// - Task Load(string id, string changeVector = null); - - /// - /// Get entities by ids - /// - Task> Load(IEnumerable ids); - - /// - /// Get entities by query - /// - Task> Load(int pageNumber, int pageSize, Func, IQueryable> querySelector = default); - - /// - /// Get entities by query (by using the specified index) - /// - Task> Load(int pageNumber, int pageSize, Func, IQueryable> querySelector = default) where TIndex : AbstractCommonApiForIndexes, new(); - - /// - /// Get all entities from this collection. - /// Warning: Don't use this method for large collections. Stream the results instead. - /// - Task> LoadAll(); - - /// - /// Stream the collection - /// - IAsyncEnumerable Stream(Func, IQueryable> expression); - - /// - /// Get the change vector for a model (Proxy to IAsyncDocumentSession.GetChangeVectorFor<>) - /// - string GetChangeToken(T model); - - /// - /// Validates an entity in this collection - /// - Task Validate(T model); - - /// - /// Creates an entity with an optional validator - /// - Task> Create(T model, Action onAfterStore = null); - - /// - /// Updates an entity with an optional validator - /// - Task> Update(T model, Action onAfterStore = null); - - /// - /// Update sorting of entities on a specific level - /// - Task>> Sort(string[] sortedIds); - - /// - /// Deletes an entity - /// - Task> Delete(T model); -} \ No newline at end of file diff --git a/zero.Core/Stores/EntityStoreExtensions.cs b/zero.Core/Stores/EntityStoreExtensions.cs deleted file mode 100644 index 97e5ea2c..00000000 --- a/zero.Core/Stores/EntityStoreExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Raven.Client.Documents.Linq; - -namespace zero.Stores; - -public static class EntityStoreExtensions -{ - /// - /// Stream the collection - /// - public static IAsyncEnumerable Stream(this IEntityStore store) where T : ZeroIdEntity, ISupportsFlavors, ISupportsSorting, new() => store.Stream(null); - - - /// - /// Deletes an entity by Id - /// - public static async Task> Delete(this IEntityStore store, string id) where T : ZeroIdEntity, ISupportsFlavors, ISupportsSorting, new() => await store.Delete(await store.Load(id)); - - - /// - /// Deletes entities by Id - /// - public static async Task Delete(this IEntityStore store, IEnumerable ids) where T : ZeroIdEntity, ISupportsFlavors, ISupportsSorting, new() => await store.Delete((await store.Load(ids)).Select(x => x.Value)); - - - /// - /// Deletes entities - /// - public static async Task Delete(this IEntityStore store, IEnumerable models) where T : ZeroIdEntity, ISupportsFlavors, ISupportsSorting, new() - { - int successCount = 0; - - foreach (T model in models) - { - Result result = await store.Delete(model); - successCount += result.IsSuccess ? 1 : 0; - } - - return successCount; - } - - - /// - /// Deletes an entity by Id with all descendents - /// - public static async Task> DeleteWithDescendants(this ITreeEntityStore store, string id) where T : ZeroIdEntity, ISupportsTrees, ISupportsFlavors, ISupportsSorting, new() => await store.DeleteWithDescendants(await store.Load(id)); -} \ No newline at end of file diff --git a/zero.Core/Stores/Flavors/ConfigureFlavorJsonOptions.cs b/zero.Core/Stores/Flavors/ConfigureFlavorJsonOptions.cs deleted file mode 100644 index 32daa811..00000000 --- a/zero.Core/Stores/Flavors/ConfigureFlavorJsonOptions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; - -namespace zero.Stores; - - -public class ConfigureFlavorJsonOptions : IConfigureOptions -{ - private readonly IZeroOptions zeroOptions; - - public ConfigureFlavorJsonOptions(IZeroOptions options) - { - zeroOptions = options; - } - - public void Configure(JsonOptions options) - { - options.JsonSerializerOptions.Converters.Add(new JsonFlavorVariantConverterFactory(zeroOptions)); - } -} \ No newline at end of file diff --git a/zero.Core/Stores/Flavors/FlavorConfig.cs b/zero.Core/Stores/Flavors/FlavorConfig.cs deleted file mode 100644 index a0139203..00000000 --- a/zero.Core/Stores/Flavors/FlavorConfig.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Text.Json.Serialization; - -namespace zero.Stores; - -/// -/// A flavor config holds information about a flavor implementation -/// -public class FlavorConfig -{ - /// - /// Type of the associated entity - /// - [JsonIgnore] - public Type FlavorType { get; private set; } - - /// - /// Alias for querying - /// - public string Alias { get; set; } - - /// - /// Name of the flavor - /// - public string Name { get; set; } - - /// - /// Optional description - /// - public string Description { get; set; } - - /// - /// Icon of the flavor - /// - public string Icon { get; set; } - - - [JsonIgnore] - public Func Construct { get; set; } - - - public FlavorConfig(Type type) - { - FlavorType = type; - } -} \ No newline at end of file diff --git a/zero.Core/Stores/Flavors/FlavorOptions.cs b/zero.Core/Stores/Flavors/FlavorOptions.cs deleted file mode 100644 index dfcb5521..00000000 --- a/zero.Core/Stores/Flavors/FlavorOptions.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System.Collections.Concurrent; - -namespace zero.Stores; - -public class FlavorOptions -{ - public ConcurrentDictionary Providers { get; private set; } = new(); - - public void Configure(Action> configure) where TEntity : class, ISupportsFlavors, new() - { - Type type = typeof(TEntity); - FlavorProvider provider = Providers.GetOrAdd(type, _ => CreateProvider()); - configure(new FlavorProviderOptions(this, provider)); - } - - - public bool CanUseWithoutFlavors() where TEntity : class, ISupportsFlavors, new() - { - return Providers.GetValueOrDefault(typeof(TEntity), new()).CanUseWithoutFlavors; - } - - public string DefaultFlavorFor() where TEntity : class, ISupportsFlavors, new() - { - FlavorProvider provider = Providers.GetValueOrDefault(typeof(TEntity), new()); - - if (!provider.Flavors.Any()) - { - return null; - } - - if (provider.DefaultFlavor.HasValue() && provider.Flavors.Any(x => x.Alias == provider.DefaultFlavor)) - { - return provider.DefaultFlavor; - } - - return null; - } - - - public TFlavor Construct(string alias = null) - where TEntity : class, ISupportsFlavors, new() - where TFlavor : class, TEntity, new() - { - // if no flavor provider is registered we return the base entity - if (!Providers.TryGetValue(typeof(TEntity), out FlavorProvider provider)) - { - return new TFlavor(); - } - - // throw if this entity is not allowed to be created without a flavor - if (alias.IsNullOrEmpty() && !provider.CanUseWithoutFlavors) - { - string defaultFlavorAlias = provider.DefaultFlavor; - - if (defaultFlavorAlias.IsNullOrEmpty() || !provider.Flavors.Any(x => x.Alias == defaultFlavorAlias)) - { - throw new ArgumentException("Can not create instance of an entity which is configured to to be only used as a flavor", nameof(alias)); - } - - alias = defaultFlavorAlias; - } - - // return default instance if no flavor is required - if (alias.IsNullOrEmpty()) - { - return provider.FlavorlessConstruct() as TFlavor; - } - - // try to load and construct a specific flavor - FlavorConfig config = provider.Flavors.FirstOrDefault(x => x.Alias == alias); - TFlavor result = config?.Construct(config) as TFlavor; - - if (result == null) - { - return default; - } - - result.Flavor = alias; - return result; - } - - - public IEnumerable GetAll() => GetAll(typeof(TEntity)); - - - public IEnumerable GetAll(Type type) => Providers.GetValueOrDefault(type, new()).Flavors; - - - public FlavorConfig Get() => Get(typeof(TEntity), typeof(TFlavor)); - - - public FlavorConfig Get(string alias) => Get(typeof(TEntity), typeof(TFlavor), alias); - - - public FlavorConfig Get(string alias) => Get(typeof(TEntity), alias); - - - public FlavorConfig Get(Type baseType, Type flavorType) - { - FlavorProvider provider = Providers.GetValueOrDefault(baseType, new()); - return provider.Flavors.FirstOrDefault(x => x.FlavorType == flavorType); - } - - - public FlavorConfig Get(Type baseType, Type flavorType, string alias) - { - FlavorProvider provider = Providers.GetValueOrDefault(baseType, new()); - return provider.Flavors.FirstOrDefault(x => x.FlavorType == flavorType && x.Alias == alias); - } - - - public FlavorConfig Get(Type baseType, string alias) - { - FlavorProvider provider = Providers.GetValueOrDefault(baseType, new()); - return provider.Flavors.FirstOrDefault(x => x.Alias == alias); - } - - - public bool Exists(string alias) - { - FlavorProvider provider = Providers.GetValueOrDefault(typeof(TEntity), new()); - return provider.Flavors.Any(x => x.Alias == alias); - } - - - public void Add(string alias, string name, string description = null, string icon = null) - where TEntity : class, ISupportsFlavors, new() - where TFlavor : TEntity, new() - { - Add(typeof(TFlavor), _ => new TFlavor(), alias, name, description, icon); - } - - - public void Add(Func construct, string alias, string name, string description = null, string icon = null) - where TEntity : class, ISupportsFlavors, new() - where TFlavor : TEntity, new() - { - Add(typeof(TFlavor), construct, alias, name, description, icon); - } - - - public void Add(Type flavorType, Func construct, string alias, string name, string description = null, string icon = null) - where TEntity : class, ISupportsFlavors, new() - where TFlavor : TEntity, new() - { - Add(new(flavorType) - { - Alias = alias, - Name = name, - Description = description, - Icon = icon, - Construct = _ => construct(_) - }); - } - - - public void Add(FlavorConfig config) - where TEntity : class, ISupportsFlavors, new() - where TFlavor : TEntity, new() - { - Type baseEntityType = typeof(TEntity); - FlavorProvider provider = Providers.GetOrAdd(baseEntityType, _ => CreateProvider()); - provider.Flavors.Add(config); - } - - - public void Implement() - where TEntity : class, ISupportsFlavors, new() - where TDefaultImplementation : TEntity, new() - { - Type baseEntityType = typeof(TEntity); - FlavorProvider provider = Providers.GetOrAdd(baseEntityType, _ => CreateProvider()); - - provider.FlavorlessConstruct = () => new TDefaultImplementation(); - provider.FlavorlessType = typeof(TDefaultImplementation); - } - - - FlavorProvider CreateProvider() - where TEntity : class, ISupportsFlavors, new() - { - return new FlavorProvider() - { - BaseType = typeof(TEntity), - FlavorlessConstruct = () => new TEntity(), - FlavorlessType = typeof(TEntity), - ConverterCreator = cfg => new JsonFlavorVariantConverter(cfg) - }; - } -} diff --git a/zero.Core/Stores/Flavors/FlavorProvider.cs b/zero.Core/Stores/Flavors/FlavorProvider.cs deleted file mode 100644 index c8ace2b1..00000000 --- a/zero.Core/Stores/Flavors/FlavorProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Text.Json.Serialization; - -namespace zero.Stores; - -/// -/// A flavor provider is attached to an entity (which has ISupportsFlavors) and contains all flavors -/// -public class FlavorProvider -{ - public bool CanUseWithoutFlavors { get; set; } = true; - - public string DefaultFlavor { get; set; } - - [JsonIgnore] - public Type BaseType { get; set; } - - [JsonIgnore] - public Type FlavorlessType { get; set; } - - [JsonIgnore] - public Func FlavorlessConstruct { get; set; } - - public List Flavors { get; set; } = new(); - - /// - /// Flavor discriminator converter - /// - [JsonIgnore] - public Func ConverterCreator { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Stores/Flavors/FlavorProviderOptions.cs b/zero.Core/Stores/Flavors/FlavorProviderOptions.cs deleted file mode 100644 index 35686027..00000000 --- a/zero.Core/Stores/Flavors/FlavorProviderOptions.cs +++ /dev/null @@ -1,82 +0,0 @@ -namespace zero.Stores; - -public class FlavorProviderOptions where TEntity : class, ISupportsFlavors, new() -{ - readonly Type _baseType; - readonly FlavorOptions _options; - readonly FlavorProvider _provider; - - internal FlavorProviderOptions(FlavorOptions options, FlavorProvider provider) - { - _baseType = typeof(TEntity); - _options = options; - _provider = provider; - } - - public bool CanUseWithoutFlavors - { - get => _provider.CanUseWithoutFlavors; - set => _provider.CanUseWithoutFlavors = value; - } - - public string DefaultFlavor - { - get => _provider.DefaultFlavor; - set => _provider.DefaultFlavor = value; - } - - public Type BaseType => _provider.BaseType; - - public Func FlavorlessConstruct - { - get => _provider.FlavorlessConstruct; - set => _provider.FlavorlessConstruct = value; - } - - public Type FlavorlessType - { - get => _provider.FlavorlessType; - set => _provider.FlavorlessType = value; - } - - public IEnumerable GetAll() => _options.GetAll(); - - public FlavorConfig Get() => _options.Get(typeof(TEntity), typeof(TFlavor)); - - public FlavorConfig Get(string alias) => _options.Get(typeof(TEntity), alias); - - public FlavorConfig Get(Type flavorType) => _options.Get(typeof(TEntity), flavorType); - - public FlavorConfig Get(string alias) => _options.Get(typeof(TEntity), alias); - - public bool Exists(string alias) => _options.Exists(alias); - - public void Add(string alias, string name, string description = null, string icon = null) - where TFlavor : TEntity, new() => _options.Add(alias, name, description, icon); - - public void Add(Func construct, string alias, string name, string description = null, string icon = null) - where TFlavor : TEntity, new() => _options.Add(construct, alias, name, description, icon); - - public void Add(Type flavorType, Func construct, string alias, string name, string description = null, string icon = null) - where TFlavor : TEntity, new() => _options.Add(flavorType, construct, alias, name, description, icon); - - public void Add(FlavorConfig config) - where TFlavor : TEntity, new() => _options.Add(config); - - public void Remove(string alias) - { - FlavorProvider provider = _options.Providers.GetValueOrDefault(_baseType, new()); - FlavorConfig flavor = provider.Flavors.FirstOrDefault(x => x.Alias == alias); - - if (flavor != null) - { - provider.Flavors.Remove(flavor); - } - } - - public void RemoveAll() - { - FlavorProvider provider = _options.Providers.GetValueOrDefault(_baseType, new()); - provider.Flavors.Clear(); - } -} diff --git a/zero.Core/Stores/Flavors/JsonFlavorVariantConverter.cs b/zero.Core/Stores/Flavors/JsonFlavorVariantConverter.cs deleted file mode 100644 index 7224a194..00000000 --- a/zero.Core/Stores/Flavors/JsonFlavorVariantConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace zero.Stores; - -internal class JsonFlavorVariantConverter : JsonDiscriminatorConverter where T : class, ISupportsFlavors, new() -{ - protected FlavorProvider Provider { get; private set; } - - Type _factoryType = typeof(JsonFlavorVariantConverterFactory); - - - public JsonFlavorVariantConverter(FlavorProvider provider) : base("flavor") - { - Provider = provider; - } - - - protected override Type GetTypeFromDiscriminator(Type requestedType, string discriminator) - { - FlavorConfig config = Provider.Flavors.FirstOrDefault(x => x.Alias.Equals(discriminator, StringComparison.InvariantCultureIgnoreCase)); - return config?.FlavorType ?? Provider.FlavorlessType ?? requestedType; - } - - - protected override JsonSerializerOptions CreateOptions(JsonSerializerOptions baseOptions) - { - JsonSerializerOptions newOptions = base.CreateOptions(baseOptions); - JsonConverter toRemove = newOptions.Converters.FirstOrDefault(x => x.GetType() == _factoryType); - newOptions.Converters.Remove(toRemove); - return newOptions; - } -} \ No newline at end of file diff --git a/zero.Core/Stores/Flavors/JsonFlavorVariantConverterFactory.cs b/zero.Core/Stores/Flavors/JsonFlavorVariantConverterFactory.cs deleted file mode 100644 index 10df7099..00000000 --- a/zero.Core/Stores/Flavors/JsonFlavorVariantConverterFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace zero.Stores; - -public class JsonFlavorVariantConverterFactory : JsonConverterFactory -{ - readonly IZeroOptions _options; - - public JsonFlavorVariantConverterFactory(IZeroOptions options) - { - _options = options; - } - - public override bool CanConvert(Type typeToConvert) - { - if (!typeof(ISupportsFlavors).IsAssignableFrom(typeToConvert)) - { - return false; - } - - if (!_options.For().Providers.TryGetValue(typeToConvert, out FlavorProvider provider) || provider.ConverterCreator == null) - { - return false; - } - - return true; - } - - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) - { - FlavorProvider provider = _options.For().Providers[typeToConvert]; - return provider.ConverterCreator(provider); - } -} \ No newline at end of file diff --git a/zero.Core/Stores/StoreCache.cs b/zero.Core/Stores/StoreCache.cs deleted file mode 100644 index 9221d2a8..00000000 --- a/zero.Core/Stores/StoreCache.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Concurrent; - -namespace zero.Stores; - -public class StoreCache : IStoreCache -{ - protected ConcurrentDictionary _cache = new(); - - - public StoreCache() - { - - } - - public bool TryGetValue(string id, out T model) - { - if (_cache.TryGetValue(id, out object modelObj)) - { - model = (T)modelObj; - return true; - } - - model = default; - return false; - } - - //public IStoreCache For(CachableEntityStoreOptions options) - //{ - // return this; - //} -} - - -public interface IStoreCache -{ - //IStoreCache For(CachableEntityStoreOptions options); - - bool TryGetValue(string id, out T model); -} \ No newline at end of file diff --git a/zero.Core/Stores/StoreConfig.cs b/zero.Core/Stores/StoreConfig.cs deleted file mode 100644 index dad6d999..00000000 --- a/zero.Core/Stores/StoreConfig.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace zero.Stores; - -public class StoreConfig -{ - /// - /// Whether to include entities with IsActive=false in queries. Defaults to false. - /// - public bool IncludeInactive { get; set; } = false; - - /// - /// Override the database which is resolved automatically by default (based on IZeroContext and IApplicationResolver). - /// - public string Database { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Stores/StoreContext.cs b/zero.Core/Stores/StoreContext.cs deleted file mode 100644 index 40dbfe42..00000000 --- a/zero.Core/Stores/StoreContext.cs +++ /dev/null @@ -1,48 +0,0 @@ -namespace zero.Stores; - -public class StoreContext : IStoreContext -{ - public IZeroStore Store { get; private set; } - - public IZeroContext Context { get; private set; } - - public IZeroOptions Options { get; private set; } - - public IInterceptors Interceptors { get; private set; } - - public IStoreCache Cache { get; private set; } - - public IServiceProvider Services { get; private set; } - - public IMessageAggregator Messages { get; private set; } - - - public StoreContext(IZeroContext context, IInterceptors interceptors, IStoreCache cache, IServiceProvider serviceProvider, IMessageAggregator messages) - { - Store = context.Store; - Options = context.Options; - Context = context; - Interceptors = interceptors; - Cache = cache; - Services = serviceProvider; - Messages = messages; - } -} - - -public interface IStoreContext -{ - IZeroStore Store { get; } - - IZeroContext Context { get; } - - IZeroOptions Options { get; } - - IInterceptors Interceptors { get; } - - IStoreCache Cache { get; } - - IServiceProvider Services { get; } - - IMessageAggregator Messages { get; } -} \ No newline at end of file diff --git a/zero.Core/Stores/StoreOperations.Delete.cs b/zero.Core/Stores/StoreOperations.Delete.cs deleted file mode 100644 index 465d23c8..00000000 --- a/zero.Core/Stores/StoreOperations.Delete.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace zero.Stores; - -public partial class StoreOperations : IStoreOperations -{ - /// - public virtual async Task> Delete(T model) where T : ZeroIdEntity, new() - { - if (model == null) - { - return Result.Fail("@errors.ondelete.idnotfound"); - } - - InterceptorInstruction instruction = Interceptors.ForDelete(model); - - if (InterceptorBlocker == null && !await instruction.Start(this)) - { - return instruction.Result; - } - - Session.Delete(model); - await Session.SaveChangesAsync(); - if (InterceptorBlocker == null) - { - await instruction.Complete(); - } - await Session.SaveChangesAsync(); - - return Result.Success(); - } -} \ No newline at end of file diff --git a/zero.Core/Stores/StoreOperations.Empty.cs b/zero.Core/Stores/StoreOperations.Empty.cs deleted file mode 100644 index 903d5e6c..00000000 --- a/zero.Core/Stores/StoreOperations.Empty.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace zero.Stores; - -public partial class StoreOperations : IStoreOperations -{ - /// - public virtual Task Empty(string flavorAlias = null) where T : ZeroIdEntity, ISupportsFlavors, new() => Empty(flavorAlias); - - - /// - public virtual Task Empty(string flavorAlias = null) - where T : ZeroIdEntity, ISupportsFlavors, new() - where TFlavor : T, new() - { - return Task.FromResult(Flavors.Construct(flavorAlias)); - } -} \ No newline at end of file diff --git a/zero.Core/Stores/StoreOperations.Read.cs b/zero.Core/Stores/StoreOperations.Read.cs deleted file mode 100644 index e8c81090..00000000 --- a/zero.Core/Stores/StoreOperations.Read.cs +++ /dev/null @@ -1,121 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Indexes; -using Raven.Client.Documents.Linq; -using Raven.Client.Documents.Session; - -namespace zero.Stores; - -public partial class StoreOperations : IStoreOperations -{ - /// - public virtual async Task Load(string id, string changeVector = null) where T : ZeroIdEntity, new() - { - if (id.IsNullOrWhiteSpace()) - { - return default; - } - if (!changeVector.IsNullOrEmpty()) - { - //return WhenActive(await GetRevision(changeVector)); // TODO - } - - return WhenActive(await Session.LoadAsync(id)); - } - - - /// - public virtual async Task> Load(IEnumerable ids) where T : ZeroIdEntity, new() - { - ids = ids.Distinct().ToArray(); - - Dictionary models = await Session.LoadAsync(ids); - Dictionary result = new(); - - foreach (string id in ids) - { - models.TryGetValue(id, out T model); - result.Add(id, WhenActive(model)); - } - - return result; - } - - - /// - public virtual async Task Any(Func, IQueryable> querySelector = default) where T : ZeroIdEntity, new() - { - querySelector ??= x => x; - return await querySelector(Session.Query()).AnyAsync(); - } - - - /// - public virtual async Task> Load(int pageNumber, int pageSize, Func, IQueryable> querySelector = default) where T : ZeroIdEntity, new() - { - IRavenQueryable queryable = Session.Query().Statistics(out QueryStatistics statistics); - querySelector ??= x => x; - - List result = await querySelector(queryable).Paging(pageNumber, pageSize).ToListAsync(); - return new Paged(result, statistics.TotalResults, pageNumber, pageSize); - } - - - /// - public virtual async Task> Load(int pageNumber, int pageSize, Func, IQueryable> querySelector = default) - where T : ZeroIdEntity, new() - where TIndex : AbstractCommonApiForIndexes, new() - { - IRavenQueryable queryable = Session.Query().Statistics(out QueryStatistics statistics); - querySelector ??= x => x; - - List result = await querySelector(queryable).Paging(pageNumber, pageSize).ToListAsync(); - return new Paged(result, statistics.TotalResults, pageNumber, pageSize); - } - - - /// - public virtual async Task> LoadAll() where T : ZeroIdEntity, new() - { - List items = new(); - - await foreach (T item in Stream(null)) - { - items.Add(item); - } - - return items; - } - - - /// - public virtual async IAsyncEnumerable Stream(Func, IQueryable> expression) where T : ZeroIdEntity, new() - { - IRavenQueryable query = Session.Query(); - IQueryable queryable = query; - - if (expression != null) - { - queryable = expression(query); - } - - var stream = await Session.Advanced.StreamAsync(queryable); - - while (await stream.MoveNextAsync()) - { - if (WhenActive(stream.Current.Document) == default) - { - continue; - } - - yield return stream.Current.Document; - } - } - - - /// - public virtual string GetChangeToken(T model) where T : ZeroIdEntity, new() - { - string changeVector = Session.Advanced.GetChangeVectorFor(model); - return IdGenerator.HashString(changeVector); - } -} \ No newline at end of file diff --git a/zero.Core/Stores/StoreOperations.Tree.cs b/zero.Core/Stores/StoreOperations.Tree.cs deleted file mode 100644 index 964e6730..00000000 --- a/zero.Core/Stores/StoreOperations.Tree.cs +++ /dev/null @@ -1,192 +0,0 @@ -using Raven.Client.Documents; -using Raven.Client.Documents.Indexes; -using Raven.Client.Documents.Linq; -using Raven.Client.Documents.Session; - -namespace zero.Stores; - -public partial class StoreOperations : IStoreOperations -{ - /// - public virtual Task IsAllowedAsChild(T model, string parentId) where T : ZeroIdEntity, ISupportsTrees, new() - { - return Task.FromResult(true); - } - - - /// - public async Task GetHierarchy(string id) - where T : ZeroIdEntity, ISupportsTrees, new() - where TIndex : ZeroTreeHierarchyIndex, new() - { - ZeroTreeHierarchyIndexResult result = await Session.Query() - .ProjectInto() - .Include(x => x.Path) - .Include(x => x.Id) - .FirstOrDefaultAsync(x => x.Id == id); - - if (result == null) - { - return Array.Empty(); - } - - List ids = result.Path ?? new(); - ids.Add(id); - - return (await Session.LoadAsync(ids)).Select(x => x.Value).ToArray(); - } - - - /// - public async Task> LoadChildren(string parentId, int pageNumber, int pageSize, Func, IQueryable> querySelector = default) where T : ZeroIdEntity, ISupportsTrees, new() - { - IRavenQueryable queryable = Session.Query().Where(x => x.ParentId == parentId).Statistics(out QueryStatistics statistics); - querySelector ??= x => x.OrderBy(x => x.Sort); - - List result = await querySelector(queryable).Paging(pageNumber, pageSize).ToListAsync(); - return new Paged(result, statistics.TotalResults, pageNumber, pageSize); - } - - - /// - public async Task> LoadChildren(string parentId, int pageNumber, int pageSize, Func, IQueryable> querySelector = default) - where T : ZeroIdEntity, ISupportsTrees, new() - where TIndex : AbstractCommonApiForIndexes, new() - { - IRavenQueryable queryable = Session.Query().Where(x => x.ParentId == parentId).Statistics(out QueryStatistics statistics); - querySelector ??= x => x.OrderBy(x => x.Sort); - - var query = querySelector(queryable).Paging(pageNumber, pageSize); - - List result = await query.ToListAsync(); - return new Paged(result, statistics.TotalResults, pageNumber, pageSize); - } - - - /// - public async Task> Move(string id, string newParentId, Func> isParentAllowed = null) where T : ZeroIdEntity, ISupportsTrees, new() - { - T model = await Load(id); - T parent = await Load(newParentId); - - if (model == null || (!newParentId.IsNullOrEmpty() && parent == null)) - { - return Result.Fail("@errors.idnotfound"); - } - - if (isParentAllowed != null && !await isParentAllowed(model, newParentId)) - { - return Result.Fail("@errors.treeentity.parentnotallowed"); - } - - model.ParentId = parent?.Id; - - return await Update(model); - } - - - /// - public Task> Copy(string id, string newParentId, Func> isParentAllowed = null) where T : ZeroIdEntity, ISupportsTrees, new() => Copy(id, newParentId, false, isParentAllowed); - - - /// - public Task> CopyWithDescendants(string id, string newParentId, Func> isParentAllowed = null) where T : ZeroIdEntity, ISupportsTrees, new() => Copy(id, newParentId, true, isParentAllowed); - - - /// - /// Copies an entity (with optional descendants) to a new location - /// - protected async Task> Copy(string id, string newParentId, bool includeDescendants, Func> isParentAllowed = null, bool isDescendant = false) where T : ZeroIdEntity, ISupportsTrees, new() - { - T originalModel = await Load(id); - T model = ObjectCopycat.Clone(originalModel); - T parent = await Load(newParentId); - - if (model == null || (!newParentId.IsNullOrEmpty() && parent == null)) - { - return Result.Fail("@errors.idnotfound"); - } - - string baseId = model.Id; - string originalParentId = model.ParentId; - - // update new page properties - model.Id = null; - model.ParentId = parent?.Id; - - if (model is ZeroEntity zeroEntity) - { - zeroEntity.IsActive = !isDescendant ? false : (originalModel as ZeroEntity).IsActive; - zeroEntity.CreatedDate = DateTime.Now; - zeroEntity.Hash = null; - - // update page name in case they are on the same level - string name = zeroEntity.Name; - if (originalParentId == parent?.Id || await Session.Query().AnyAsync(x => x.Name == name && x.ParentId == newParentId)) - { - zeroEntity.Name += " (copy #" + IdGenerator.Create(4) + ")"; - } - } - - // check if new parent is allowed - if (isParentAllowed != null && !await isParentAllowed(model, newParentId)) - { - return Result.Fail("@errors.treeentity.parentnotallowed"); - } - - Result result = await Create(model); - - // recursive function to store descendants - if (includeDescendants) - { - List children = await Session.Query().Where(x => x.ParentId == baseId).ToListAsync(); - - foreach (T child in children) - { - await Copy(child.Id, model.Id, true, isParentAllowed, true); - } - } - - return result; - } - - - /// - public async Task> DeleteWithDescendants(T model) where T : ZeroIdEntity, ISupportsTrees, new() - { - List pages = await GetDescendantsAndSelf(model); - - if (pages.Count < 1) - { - return Result.Fail("@errors.ondelete.idnotfound"); - } - - await this.Delete(pages.ToArray()); - return Result.Success(pages.Select(x => x.Id).ToArray()); - } - - - /// - /// Get an entity with all its descendants - /// - async Task> GetDescendantsAndSelf(T model) where T : ZeroIdEntity, ISupportsTrees, new() - { - List items = new() { model }; - - async Task AddChildren(T parent) - { - string parentId = parent.Id; - List children = await Session.Query().Where(x => x.ParentId == parentId).ToListAsync(); - items.AddRange(children); - - foreach (T child in children) - { - await AddChildren(child); - } - } - - await AddChildren(model); - - return items; - } -} \ No newline at end of file diff --git a/zero.Core/Stores/StoreOperations.Write.cs b/zero.Core/Stores/StoreOperations.Write.cs deleted file mode 100644 index 9744c75e..00000000 --- a/zero.Core/Stores/StoreOperations.Write.cs +++ /dev/null @@ -1,112 +0,0 @@ -using FluentValidation.Results; - -namespace zero.Stores; - -public partial class StoreOperations : IStoreOperations -{ - /// - public virtual Task> Create(T model, Func> validate = null, Action onAfterStore = null) where T : ZeroIdEntity, new() => Save(model, validate, onAfterStore); - - /// - public virtual Task> Update(T model, Func> validate = null, Action onAfterStore = null) where T : ZeroIdEntity, new() => Save(model, validate, onAfterStore); - - /// - protected virtual async Task> Save(T model, Func> validate = null, Action onAfterStore = null) where T : ZeroIdEntity, new() - { - if (model == null) - { - return Result.Fail("@errors.onsave.empty"); - } - - T previousModel = null; - bool isUpdate = false; - - // check if the Id for a model already exists - if (!model.Id.IsNullOrEmpty()) - { - using (IZeroDocumentSession session = Context.Store.Session(resolution: ZeroSessionResolution.Create, options: new Raven.Client.Documents.Session.SessionOptions() { Database = Context.Store.ResolvedDatabase, NoCaching = true })) - { - previousModel = await session.LoadAsync(model.Id); - } - - if (previousModel == null) - { - return Result.Fail("@errors.onsave.noidmatch"); - } - - isUpdate = true; - } - - // validate flavor - if (model is ISupportsFlavors flavorModel && !flavorModel.Flavor.IsNullOrEmpty()) - { - if (!Flavors.Exists(flavorModel.Flavor)) - { - return Result.Fail("@errors.onsave.flavornotfound"); - } - } - - // prepare model - PrepareForSave(model); - - // run validator - if (validate != null) - { - ValidationResult validation = await validate(model); - - if (!validation.IsValid) - { - return Result.Fail(validation); - } - } - - // create ID before-hand so interceptors can use it - if (!isUpdate) - { - model.Id = await GenerateId(model); - } - - // run interceptor - InterceptorInstruction instruction = isUpdate ? Interceptors.ForUpdate(model, previousModel) : Interceptors.ForCreate(model); - - if (InterceptorBlocker == null && !await instruction.Start(this)) - { - return instruction.Result; - } - - // store our model - await Session.StoreAsync(model); - onAfterStore?.Invoke(Session); - await Session.SaveChangesAsync(); - if (InterceptorBlocker == null) - { - await instruction.Complete(); - } - await Session.SaveChangesAsync(); - - return Result.Success(model); - } - - - /// - public async Task>> Sort(string[] sortedIds) where T : ZeroIdEntity, ISupportsSorting, new() - { - Dictionary items = await Load(sortedIds); - uint index = 10; - - // contains multiple parents, therefore fail - if (typeof(ISupportsTrees).IsAssignableFrom(typeof(T)) && items.Select(x => (x.Value as ISupportsTrees)?.ParentId).Distinct().Count() > 1) - { - return Result>.Fail("@errors.treeentity.sortingmultipleparents"); - } - - foreach (var item in items) - { - item.Value.Sort = index; - index += 10; - await Update(item.Value); - } - - return Result>.Success(items.Select(x => x.Value).OrderByDescending(x => x.Sort)); - } -} \ No newline at end of file diff --git a/zero.Core/Stores/StoreOperations.cs b/zero.Core/Stores/StoreOperations.cs deleted file mode 100644 index 238fba0d..00000000 --- a/zero.Core/Stores/StoreOperations.cs +++ /dev/null @@ -1,301 +0,0 @@ -using FluentValidation.Results; -using Microsoft.Extensions.DependencyInjection; -using Raven.Client.Documents.Indexes; -using Raven.Client.Documents.Linq; -using System.Security.Claims; - -namespace zero.Stores; - -public partial class StoreOperations : - IStoreOperations, - ISharedStoreOperations, - IStoreOperationsWithInactive, - ISharedStoreOperationsWithInactive -{ - /// - public IZeroDocumentSession Session => Context.Store.Session(_overrideDatabase ?? Config.Database); - - /// - public StoreConfig Config { get; set; } - - protected record OperationOptions(bool IncludeInactive); - - protected IZeroContext Context { get; private set; } - - protected IInterceptors Interceptors { get; private set; } - - protected FlavorOptions Flavors { get; private set; } - - protected IServiceProvider Services { get; private set; } - - protected StoreInterceptorBlocker InterceptorBlocker { get; private set; } - - string _overrideDatabase = null; - - - public StoreOperations(IStoreContext context, StoreConfig config = null) - { - Context = context.Context; - Interceptors = context.Interceptors; - Services = context.Services; - Flavors = context.Options.For(); - Config = config ?? new(); - } - - - /// - public async Task GenerateId(T model) where T : ZeroIdEntity - { - IZeroDocumentSession session = Session; - return await session.Advanced.DocumentStore.Conventions.GenerateDocumentIdAsync(session.Advanced.DocumentStore.Database, model); - } - - - /// - public T AutoSetIds(T model) - { - return IdGenerator.Autofill(model); - } - - - /// - public T PrepareForSave(T model) where T : ZeroIdEntity - { - // set IDs - AutoSetIds(model); - - if (model is ZeroEntity zeroModel) - { - // get current user - string userId = Context.BackofficeUser.FindFirstValue(Constants.Auth.Claims.UserId).Or(Constants.Auth.SystemUser); - - // set default properties - if (zeroModel.CreatedDate == default) - { - zeroModel.CreatedDate = DateTimeOffset.Now; - } - if (zeroModel.CreatedById.IsNullOrEmpty()) - { - zeroModel.CreatedById = userId; - } - if (zeroModel.LanguageId.IsNullOrEmpty()) - { - zeroModel.LanguageId ??= "languages.1-A"; // TODO correct language id - } - - // update name alias and last modified - zeroModel.Alias = Safenames.Alias(zeroModel.Name); - zeroModel.LastModifiedById = userId; - zeroModel.LastModifiedDate = DateTimeOffset.Now; - zeroModel.CreatedById ??= userId; - zeroModel.Hash ??= IdGenerator.Create(); - } - - if (model is IAlwaysActive activeModel) - { - activeModel.IsActive = true; - } - - return model; - } - - - /// - public async Task Validate(T model) where T : ZeroIdEntity, new() - { - IZeroMergedValidator validator = Services.GetService>(); - - if (validator == null) - { - return new(); - } - - return await validator.ValidateAsync(model); - } - - - /// - public StoreInterceptorBlocker WithoutInterceptors() - { - return InterceptorBlocker ?? (InterceptorBlocker = new(() => InterceptorBlocker = null)); - } - - - /// - public virtual T WhenActive(T model) where T : ZeroIdEntity, new() - { - return model != null && (Context.IsPreview || Config.IncludeInactive || model is IAlwaysActive || model is not ZeroEntity || (model as ZeroEntity).IsActive) ? model : default; - } -} - - -public class StoreInterceptorBlocker : IDisposable -{ - Action _onRelease; - - internal StoreInterceptorBlocker(Action onRelease) - { - _onRelease = onRelease; - } - - public void Dispose() - { - _onRelease(); - } -} - - -public interface IStoreOperations -{ - /// - /// Access to the current session - /// - IZeroDocumentSession Session { get; } - - /// - /// Configure operations - /// - StoreConfig Config { get; set; } - - /// - /// Get new instance of an entity (with an optional flavor) - /// - Task Empty(string flavorAlias = null) where T : ZeroIdEntity, ISupportsFlavors, new(); - - /// - /// Get new instance of an entity with a specific flavor - /// - /// Optional alias. If left out the default flavor is used (if configured) - Task Empty(string flavorAlias = null) - where T : ZeroIdEntity, ISupportsFlavors, new() - where TFlavor : T, new(); - - /// - /// Generate model Id by using configured document store conventions - /// - Task GenerateId(T model) where T : ZeroIdEntity; - - /// - /// Generate values for all properties (incl. nested) which contain the [GenerateId] attribute - /// - T AutoSetIds(T model); - - /// - /// Automatically fill base properties of a ZeroEntity - /// - T PrepareForSave(T model) where T : ZeroIdEntity; - - /// - /// Check if any items exist in this collection (with optional query) - /// - Task Any(Func, IQueryable> querySelector = default) where T : ZeroIdEntity, new(); - - /// - /// Get an entity by Id - /// - Task Load(string id, string changeVector = null) where T : ZeroIdEntity, new(); - - /// - /// Get entities by ids - /// - Task> Load(IEnumerable ids) where T : ZeroIdEntity, new(); - - /// - /// Get entities by query - /// - Task> Load(int pageNumber, int pageSize, Func, IQueryable> expression = default) where T : ZeroIdEntity, new(); - - /// - /// Get entities by query (by using the specified index) - /// - Task> Load(int pageNumber, int pageSize, Func, IQueryable> expression = default) where T : ZeroIdEntity, new() where TIndex : AbstractCommonApiForIndexes, new(); - - /// - /// Get all entities from this collection. - /// Warning: Don't use this method for large collections. Stream the results instead. - /// - Task> LoadAll() where T : ZeroIdEntity, new(); - - /// - /// Stream the collection - /// - IAsyncEnumerable Stream(Func, IQueryable> expression) where T : ZeroIdEntity, new(); - - /// - /// Get the change vector for a model - /// - string GetChangeToken(T model) where T : ZeroIdEntity, new(); - - /// - /// Validates an entity - /// - Task Validate(T model) where T : ZeroIdEntity, new(); - - /// - /// Do not run interceptors for create/update/delete operations while this disposable is active - /// - StoreInterceptorBlocker WithoutInterceptors(); - - /// - /// Do only return the model when it is set to active or inactive entities are included with IncludeInactive() - /// - T WhenActive(T model) where T : ZeroIdEntity, new(); - - /// - /// Creates an entity with an optional validator - /// - Task> Create(T model, Func> validate = null, Action onAfterStore = null) where T : ZeroIdEntity, new(); - - /// - /// Updates an entity with an optional validator - /// - Task> Update(T model, Func> validate = null, Action onAfterStore = null) where T : ZeroIdEntity, new(); - - /// - Task>> Sort(string[] sortedIds) where T : ZeroIdEntity, ISupportsSorting, new(); - - /// - /// Deletes an entity - /// - Task> Delete(T model) where T : ZeroIdEntity, new(); - - /// - /// Loads all children for an entity - /// - Task> LoadChildren(string parentId, int pageNumber, int pageSize, Func, IQueryable> querySelector = default) where T : ZeroIdEntity, ISupportsTrees, new(); - - /// - /// Get descendants by query (by using the specified index) - /// - Task> LoadChildren(string parentId, int pageNumber, int pageSize, Func, IQueryable> querySelector = default) where T : ZeroIdEntity, ISupportsTrees, new() where TIndex : AbstractCommonApiForIndexes, new(); - - /// - /// Get tree hierarchy for an entity - /// - Task GetHierarchy(string id) where T : ZeroIdEntity, ISupportsTrees, new() where TIndex : ZeroTreeHierarchyIndex, new(); - - /// - /// Move an entity to a new parent - /// - Task> Move(string id, string newParentId, Func> isParentAllowed = null) where T : ZeroIdEntity, ISupportsTrees, new(); - - /// - /// Copies an entity to a new location - /// - Task> Copy(string id, string newParentId, Func> isParentAllowed = null) where T : ZeroIdEntity, ISupportsTrees, new(); - - /// - /// Copies an entity with descendants to a new location - /// - Task> CopyWithDescendants(string id, string newParentId, Func> isParentAllowed = null) where T : ZeroIdEntity, ISupportsTrees, new(); - - /// - /// Deletes an entity with all descendents - /// - Task> DeleteWithDescendants(T model) where T : ZeroIdEntity, ISupportsTrees, new(); -} - - -public interface ISharedStoreOperations : IStoreOperations { } -public interface IStoreOperationsWithInactive : IStoreOperations { } -public interface ISharedStoreOperationsWithInactive : IStoreOperations { } \ No newline at end of file diff --git a/zero.Core/Stores/StoreOperationsExtensions.cs b/zero.Core/Stores/StoreOperationsExtensions.cs deleted file mode 100644 index 5039d029..00000000 --- a/zero.Core/Stores/StoreOperationsExtensions.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Raven.Client.Documents.Linq; - -namespace zero.Stores; - -public static class StoreOperationsExtensions -{ - /// - /// Stream the collection - /// - public static IAsyncEnumerable Stream(this IStoreOperations ops) where T : ZeroIdEntity, new() => ops.Stream(null); - - - /// - /// Deletes an entity by Id - /// - public static async Task> Delete(this IStoreOperations ops, string id) where T : ZeroIdEntity, new() => await ops.Delete(await ops.Load(id)); - - - /// - /// Deletes entities by Id - /// - public static async Task Delete(this IStoreOperations ops, IEnumerable ids) where T : ZeroIdEntity, new() => await ops.Delete((await ops.Load(ids)).Select(x => x.Value)); - - - /// - /// Deletes entities - /// - public static async Task Delete(this IStoreOperations ops, IEnumerable models) where T : ZeroIdEntity, new() - { - int successCount = 0; - - foreach (T model in models) - { - Result result = await ops.Delete(model); - successCount += result.IsSuccess ? 1 : 0; - } - - return successCount; - } - - /// - /// Deletes an entity by Id with all descendents - /// - public static async Task> DeleteWithDescendants(this IStoreOperations ops, string id) where T : ZeroIdEntity, ISupportsTrees, new() => await ops.DeleteWithDescendants(await ops.Load(id)); -} \ No newline at end of file diff --git a/zero.Core/Stores/TreeEntityStore.cs b/zero.Core/Stores/TreeEntityStore.cs deleted file mode 100644 index 45ef40ee..00000000 --- a/zero.Core/Stores/TreeEntityStore.cs +++ /dev/null @@ -1,110 +0,0 @@ -using Raven.Client.Documents.Indexes; -using Raven.Client.Documents.Linq; - -namespace zero.Stores; - -public abstract class TreeEntityStore : EntityStore, ITreeEntityStore where T : ZeroIdEntity, ISupportsTrees, ISupportsFlavors, ISupportsSorting, new() -{ - public TreeEntityStore(IStoreContext collectionContext) : base(collectionContext) { } - - - /// - public override async Task> Create(T model, Action onAfterStore = null) - { - if (!await IsAllowedAsChild(model, model.ParentId)) - { - return Result.Fail("@errors.treeentity.parentnotallowed"); - } - return await base.Create(model, onAfterStore); - } - - /// - public override async Task> Update(T model, Action onAfterStore = null) - { - if (!await IsAllowedAsChild(model, model.ParentId)) - { - return Result.Fail("@errors.treeentity.parentnotallowed"); - } - return await base.Update(model, onAfterStore); - } - - /// - public virtual Task IsAllowedAsChild(T model, string parentId) => Task.FromResult(true); - - /// - public virtual Task> LoadChildren(string parentId, int pageNumber, int pageSize, Func, IQueryable> querySelector = null) - => Operations.LoadChildren(parentId, pageNumber, pageSize, querySelector); - - /// - public virtual Task> LoadChildren(string parentId, int pageNumber, int pageSize, Func, IQueryable> querySelector = null) where TIndex : AbstractCommonApiForIndexes, new() - => Operations.LoadChildren(parentId, pageNumber, pageSize, querySelector); - - /// - public virtual Task GetHierarchy(string id) where TIndex : ZeroTreeHierarchyIndex, new() => Operations.GetHierarchy(id); - - /// - public virtual Task> Copy(string id, string newParentId) => Operations.Copy(id, newParentId, async (model, parentId) => await IsAllowedAsChild(model, parentId)); - - /// - public virtual Task> CopyWithDescendants(string id, string newParentId) => Operations.CopyWithDescendants(id, newParentId, async (model, parentId) => await IsAllowedAsChild(model, parentId)); - - /// - public virtual Task> Move(string id, string newParentId) => Operations.Move(id, newParentId, async (model, parentId) => await IsAllowedAsChild(model, parentId)); - - /// - public virtual Task> DeleteWithDescendants(T model) => Operations.DeleteWithDescendants(model); - - /// - public virtual Task> DeleteWithDescendants(string id) => Operations.DeleteWithDescendants(id); -} - - - -public interface ITreeEntityStore : IEntityStore where T : ZeroIdEntity, ISupportsTrees, ISupportsFlavors, ISupportsSorting, new() -{ - /// - /// Determines whether a model is allowed as a child for a new parent. - /// This is primarily used by copy/move operations - /// - Task IsAllowedAsChild(T model, string parentId); - - /// - /// Loads all children for an entity - /// - Task> LoadChildren(string parentId, int pageNumber, int pageSize, Func, IQueryable> querySelector = default); - - /// - /// Get descendants by query (by using the specified index) - /// - Task> LoadChildren(string parentId, int pageNumber, int pageSize, Func, IQueryable> querySelector = default) where TIndex : AbstractCommonApiForIndexes, new(); - - /// - /// Get tree hierarchy for an entity - /// - Task GetHierarchy(string id) where TIndex : ZeroTreeHierarchyIndex, new(); - - /// - /// Move an entity to a new parent - /// - Task> Move(string id, string newParentId); - - /// - /// Copies an entity to a new location - /// - Task> Copy(string id, string newParentId); - - /// - /// Copies an entity with descendants to a new location - /// - Task> CopyWithDescendants(string id, string newParentId); - - /// - /// Deletes an entity with all descendents - /// - Task> DeleteWithDescendants(T model); - - /// - /// Deletes an entity by Id with all descendents - /// - Task> DeleteWithDescendants(string id); -} \ No newline at end of file diff --git a/zero.Core/Stores/ZeroStoreModule.cs b/zero.Core/Stores/ZeroStoreModule.cs deleted file mode 100644 index eae99457..00000000 --- a/zero.Core/Stores/ZeroStoreModule.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Stores; - -internal class ZeroStoreModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(); - services.AddSingleton(); - - services.AddTransient(); - services.AddTransient(x => new StoreOperations(x.GetRequiredService(), new() { Database = x.GetRequiredService().For().Database })); - services.AddTransient(x => new StoreOperations(x.GetRequiredService(), new() { IncludeInactive = true })); - services.AddTransient(x => new StoreOperations(x.GetRequiredService(), new() { Database = x.GetRequiredService().For().Database, IncludeInactive = true })); - - services.AddOptions(); - - services.ConfigureOptions(); - } -} \ No newline at end of file diff --git a/zero.Core/Usings.cs b/zero.Core/Usings.cs deleted file mode 100644 index a1685b06..00000000 --- a/zero.Core/Usings.cs +++ /dev/null @@ -1,31 +0,0 @@ - -global using System; -global using System.Collections; -global using System.Collections.Generic; -global using System.Linq; -global using System.Threading; -global using System.Threading.Tasks; - -global using zero.Applications; -global using zero.Architecture; -global using zero.Communication; -global using zero.Configuration; -global using zero.Context; -global using zero.Extensions; -global using zero.FileStorage; -global using zero.Identity; -global using zero.Localization; -global using zero.Mails; -global using zero.Mapper; -global using zero.Media; -global using zero.Models; -global using zero.Pages; -global using zero.Persistence; -global using zero.Preview; -global using zero.Rendering; -global using zero.Routing; -global using zero.Stores; -global using zero.Spaces; -global using zero.Utils; -global using zero.Validation; -global using zero.Search; \ No newline at end of file diff --git a/zero.Core/Utils/DateRange.cs b/zero.Core/Utils/DateRange.cs deleted file mode 100644 index 3650671f..00000000 --- a/zero.Core/Utils/DateRange.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace zero.Utils; - -public class DateRange -{ - public DateTimeOffset? From { get; set; } - - public DateTimeOffset? To { get; set; } - - - public bool IsWithin(DateTimeOffset date) - { - if (From.HasValue && date < From) - { - return false; - } - if (To.HasValue && date > To) - { - return false; - } - return true; - } - - - public string Format(string format) - { - if (!From.HasValue && !To.HasValue) - { - return null; - } - if (!From.HasValue && To.HasValue) - { - return "≤ " + To.Value.ToLocalTime().ToString(format); - } - if (From.HasValue && !To.HasValue) - { - return "≥ " + From.Value.ToLocalTime().ToString(format); - } - return From.Value.ToLocalTime().ToString(format) + " – " + To.Value.ToLocalTime().ToString(format); - } -} \ No newline at end of file diff --git a/zero.Core/Utils/Gender.cs b/zero.Core/Utils/Gender.cs deleted file mode 100644 index acfb9b4c..00000000 --- a/zero.Core/Utils/Gender.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace zero.Utils; - -public enum Gender -{ - Undisclosed = 0, - Female = 1, - Male = 2, - NonBinary = 3 -} \ No newline at end of file diff --git a/zero.Core/Utils/JsonDiscriminatorConverter.cs b/zero.Core/Utils/JsonDiscriminatorConverter.cs deleted file mode 100644 index 935cc5e6..00000000 --- a/zero.Core/Utils/JsonDiscriminatorConverter.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace zero.Stores; - -public abstract class JsonDiscriminatorConverter : JsonConverter where T : class, new() -{ - Type _type; - string _discriminatorPropertyName; - - public JsonDiscriminatorConverter(string discriminatorPropertyName) : base() - { - _discriminatorPropertyName = discriminatorPropertyName; - _type = GetType(); - } - - - protected abstract Type GetTypeFromDiscriminator(Type requestedType, string discriminator); - - - /// - public override bool CanConvert(Type typeToConvert) => typeof(T).IsAssignableFrom(typeToConvert); - - - /// - public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - Utf8JsonReader variantReader = reader; - string discriminatorValue = null; - - while (variantReader.Read()) - { - if (variantReader.TokenType == JsonTokenType.PropertyName) - { - string property = variantReader.GetString(); - - if (property != null && property.Equals(_discriminatorPropertyName, StringComparison.InvariantCultureIgnoreCase)) - { - variantReader.Read(); - discriminatorValue = variantReader.GetString(); - break; - } - } - } - - Type childType = GetTypeFromDiscriminator(typeToConvert, discriminatorValue); - Type resolvedType = childType ?? typeToConvert; - - return JsonSerializer.Deserialize(ref reader, resolvedType, CreateOptions(options)) as T; - } - - - /// - public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, value, value.GetType(), CreateOptions(options)); - } - - - protected virtual JsonSerializerOptions CreateOptions(JsonSerializerOptions baseOptions) - { - JsonSerializerOptions newOptions = new(baseOptions); - JsonConverter toRemove = newOptions.Converters.FirstOrDefault(x => x.GetType() == _type); - newOptions.Converters.Remove(toRemove); - return newOptions; - } -} diff --git a/zero.Core/Utils/ObjectCopycat.cs b/zero.Core/Utils/ObjectCopycat.cs deleted file mode 100644 index cf13f02d..00000000 --- a/zero.Core/Utils/ObjectCopycat.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Concurrent; -using System.Reflection; - -namespace zero.Utils; - -public class ObjectCopycat -{ - const string SEPARATOR = "."; - - static readonly Type STRING_TYPE = typeof(string); - - static ConcurrentDictionary> PublicPropertiesPerType { get; set; } = new(); - - - public static T Clone(T obj) - { - Type type = obj.GetType(); - return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj), type); - } - - - public static T CloneSpecific(T obj) - { - Type type = obj.GetType(); - return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); - } - - - public static bool ContentEquals(T obj1, T obj2) - { - return (obj1 == null && obj2 == null) || (obj1 != null && obj2 != null && JsonConvert.SerializeObject(obj1) == JsonConvert.SerializeObject(obj2)); - } - - - public static T CopyProperties(T source, T target, params string[] exceptions) - { - return CopyPropertiesInternal(source, target, null, exceptions); - } - - - static T CopyPropertiesInternal(T source, T target, string keyPrefix = null, params string[] exceptions) - { - Type type = source.GetType(); - - if (!PublicPropertiesPerType.TryGetValue(type.FullName, out IEnumerable properties)) - { - properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(x => x.CanWrite); - PublicPropertiesPerType.TryAdd(type.FullName, properties); - } - - foreach (PropertyInfo property in properties) - { - string propertyNameWithPrefix = CombineKey(SEPARATOR, keyPrefix, property.Name); - - if (exceptions.Contains(propertyNameWithPrefix, StringComparer.InvariantCultureIgnoreCase)) - { - continue; - } - - object propertyValue = property.GetValue(source, null); - - if (property.PropertyType.IsValueType || property.PropertyType.IsEnum || property.PropertyType.Equals(STRING_TYPE) || propertyValue == null || propertyValue is IEnumerable) - { - property.SetValue(target, propertyValue, null); - } - else if (property.PropertyType.IsClass) - { - string[] nestedExceptions = exceptions.Where(x => x.StartsWith(propertyNameWithPrefix, StringComparison.InvariantCultureIgnoreCase)).ToArray(); - property.SetValue(target, CopyPropertiesInternal(propertyValue, property.GetValue(target, null), propertyNameWithPrefix, nestedExceptions), null); - } - } - - return target; - } - - - private static string CombineKey(string sep, string key1, string key2) - { - if (String.IsNullOrEmpty(key1)) - { - return key2; - } - return String.Join(sep, key1, key2); - } -} diff --git a/zero.Core/Utils/ObjectTraverser.cs b/zero.Core/Utils/ObjectTraverser.cs deleted file mode 100644 index f5de7b9c..00000000 --- a/zero.Core/Utils/ObjectTraverser.cs +++ /dev/null @@ -1,273 +0,0 @@ -using System.Reflection; - -namespace zero.Utils; - -public class ObjectTraverser -{ - private const string SEPARATOR = "."; - - private const string SQBKT_OPEN = "["; - - private const string SQBKT_CLOSE = "]"; - - private const string NEWTONSOFT_NS = "Newtonsoft.Json"; - - - /// - /// Find all instances of a given type in an object (features nested + enumerable content) - /// - public static List> Find(object value) where T : class - { - HashSet exploredObjects = new HashSet(); - List> found = new List>(); - - Find(value, null, String.Empty, null, exploredObjects, found); - - return found; - } - - - /// - /// Find all instances of a given type in an object (features nested + enumerable content) - /// - public static List Find(Type type, object value) - { - HashSet exploredObjects = new HashSet(); - List found = new List(); - - Find(type, value, null, String.Empty, null, exploredObjects, found); - - return found; - } - - - /// - /// Find all instances of a given type in an object (features nested + enumerable content) - /// - public static List> FindAttribute(object value) where T : Attribute - { - HashSet exploredObjects = new HashSet(); - List> found = new List>(); - - FindAttribute(value, null, String.Empty, null, exploredObjects, found); - - return found; - } - - - /// - /// Recursively ind all instances of a given type in an object - /// - private static void Find(object value, object parent, string key, PropertyInfo property, HashSet exploredObjects, List> found) where T : class - { - if (value == null || value is string || exploredObjects.Contains(value) || value.GetType().FullName.StartsWith(NEWTONSOFT_NS)) - { - return; - } - - exploredObjects.Add(value); - - System.Collections.ICollection enumerable = value as System.Collections.ICollection; - - // this property is a list - if (enumerable != null) - { - int idx = 0; - foreach (object item in enumerable) - { - Find(item, value, CombineKey(String.Empty, key, SQBKT_OPEN + idx + SQBKT_CLOSE), property, exploredObjects, found); - idx += 1; - } - return; - } - - // get type of value - Type type = typeof(T); - Type valueType = value.GetType(); - Type genericValueType = valueType.IsGenericType ? valueType.GetGenericTypeDefinition() : null; - - // check if the current property is our searched type - if (valueType == type || genericValueType == type || type.IsAssignableFrom(valueType) || (genericValueType != null && type.IsAssignableFrom(genericValueType))) - { - found.Add(new Result() - { - Path = key, - Item = value as T, - Parent = parent, - Property = property - }); - return; - } - - // check if our property is a primitive type - if (value == null || valueType.IsPrimitive || value is string || value is DateTime || value is DateTimeOffset || value is Uri) - { - return; - } - - // iterate over nested properties - PropertyInfo[] properties = valueType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty); - foreach (PropertyInfo nestedProperty in properties) - { - object propertyValue = nestedProperty.GetValue(value, null); - Find(propertyValue, value, CombineKey(SEPARATOR, key, nestedProperty.Name), nestedProperty, exploredObjects, found); - } - } - - - /// - /// Recursively ind all instances of a given type in an object - /// - private static void Find(Type type, object value, object parent, string key, PropertyInfo property, HashSet exploredObjects, List found) - { - if (value == null || value is string || exploredObjects.Contains(value) || value.GetType().FullName.StartsWith(NEWTONSOFT_NS)) - { - return; - } - - exploredObjects.Add(value); - - System.Collections.ICollection enumerable = value as System.Collections.ICollection; - - // this property is a list - if (enumerable != null) - { - int idx = 0; - foreach (object item in enumerable) - { - Find(type, item, value, CombineKey(String.Empty, key, SQBKT_OPEN + idx + SQBKT_CLOSE), property, exploredObjects, found); - idx += 1; - } - return; - } - - // get type of value - Type valueType = value.GetType(); - Type genericValueType = valueType.IsGenericType ? valueType.GetGenericTypeDefinition() : null; - - // check if the current property is our searched type - if (valueType == type || genericValueType == type || type.IsAssignableFrom(valueType) || (genericValueType != null && type.IsAssignableFrom(genericValueType))) - { - found.Add(new Result() - { - Path = key, - Item = value, - Type = valueType, - Parent = parent, - Property = property - }); - return; - } - - // check if our property is a primitive type - if (value == null || valueType.IsPrimitive || value is string || value is DateTime || value is DateTimeOffset || value is Uri) - { - return; - } - - // iterate over nested properties - PropertyInfo[] properties = valueType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty); - foreach (PropertyInfo nestedProperty in properties) - { - object propertyValue = nestedProperty.GetValue(value, null); - Find(type, propertyValue, value, CombineKey(SEPARATOR, key, nestedProperty.Name), nestedProperty, exploredObjects, found); - } - } - - - /// - /// Recursively find all instances with the attribute of a given type in an object - /// - private static void FindAttribute(object value, object parent, string key, PropertyInfo property, HashSet exploredObjects, List> found) where T : Attribute - { - if (exploredObjects.Contains(key) || (value != null && value.GetType().FullName.StartsWith(NEWTONSOFT_NS))) - { - return; - } - - exploredObjects.Add(key); - - // check if the current property contains the attribute - if (property != null) - { - T attribute = property.GetCustomAttribute(true); - - if (attribute != null) - { - found.Add(new Result() - { - Path = key, - Item = attribute, - Parent = parent, - Property = property //parent.GetType().GetProperty(key) - }); - return; - } - } - - System.Collections.ICollection enumerable = value as System.Collections.ICollection; - - // this property is a list - if (enumerable != null && !(value is string)) - { - int idx = 0; - foreach (object item in enumerable) - { - FindAttribute(item, value, CombineKey(String.Empty, key, SQBKT_OPEN + idx + SQBKT_CLOSE), null, exploredObjects, found); - idx += 1; - } - return; - } - - // check if our property is a primitive type - if (value == null || value.GetType().IsPrimitive || value is string || value is DateTime || value is DateTimeOffset || value is Uri || value is System.Text.Json.JsonElement) - { - return; - } - - // iterate over nested properties - PropertyInfo[] properties = value.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty); - foreach (PropertyInfo nestedProperty in properties) - { - object propertyValue = nestedProperty.GetValue(value, null); - FindAttribute(propertyValue, value, CombineKey(SEPARATOR, key, nestedProperty.Name), nestedProperty, exploredObjects, found); - } - } - - - private static string CombineKey(string sep, string key1, string key2) - { - if (String.IsNullOrEmpty(key1)) - { - return key2; - } - return String.Join(sep, key1, key2); - } - - - public class Result - { - public string Path { get; set; } - - public PropertyInfo Property { get; set; } - - public object Parent { get; set; } - - public T Item { get; set; } - } - - public class Result - { - public string Path { get; set; } - - public PropertyInfo Property { get; set; } - - public object Parent { get; set; } - - public object Item { get; set; } - - public Type Type { get; set; } - - public int Index { get; set; } - } -} diff --git a/zero.Core/Utils/PriceRange.cs b/zero.Core/Utils/PriceRange.cs deleted file mode 100644 index 1d764d94..00000000 --- a/zero.Core/Utils/PriceRange.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace zero.Utils; - -public class PriceRange -{ - public decimal? From { get; set; } - - public decimal? To { get; set; } -} diff --git a/zero.Core/Utils/PrimitiveTypeCollection.cs b/zero.Core/Utils/PrimitiveTypeCollection.cs deleted file mode 100644 index 05ae8f4b..00000000 --- a/zero.Core/Utils/PrimitiveTypeCollection.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Concurrent; - -namespace zero.Utils; - -public class PrimitiveTypeCollection : ConcurrentDictionary, IPrimitiveTypeCollection -{ - /// - /// Initializes a new instance of . - /// - public PrimitiveTypeCollection() { } - - /// - public TValue Get() => (TValue)this[typeof(TValue)]; - - /// - public void Set(TValue instance) => this[typeof(TValue)] = instance; - - /// - public void Remove() => TryRemove(typeof(TValue), out _); -} - - -/// -/// Represents a simple collection based on type. -/// -public interface IPrimitiveTypeCollection : ICollection> -{ - /// - /// Retrieves the requested value from the collection. - /// - /// The value key. - /// The requested value, or null if it is not present. - TValue Get(); - - /// - /// Sets the given value in the collection. - /// - /// The value key. - /// The value value. - void Set(TValue instance); - - /// - /// Removes the given value from the collection. - /// - /// The value key. - /// The value value. - void Remove(); -} diff --git a/zero.Core/Utils/TableBuilder/CsvCreator.cs b/zero.Core/Utils/TableBuilder/CsvCreator.cs deleted file mode 100644 index 8e528acd..00000000 --- a/zero.Core/Utils/TableBuilder/CsvCreator.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System.Globalization; -using System.IO; -using System.Text; - -namespace zero.Utils; - -public class CsvCreator : ITableCreator -{ - private MemoryStream Stream { get; set; } - - private StreamWriter StreamWriter { get; set; } - - private IEnumerable> Columns { get; set; } - - private const char CHAR_DBL_QUOTE = '"'; - - private const char CHAR_QUOTE = '\''; - - private const char CHAR_COLON = ','; - - private const char CHAR_SEMICOLON = ';'; - - private const string DATE_FORMAT = "dd. MM. yyyy"; // TODO date format from culture - - - private ILocalizer Localizer = null; - - private string YesText = null; - private string NoText = null; - - - public CsvCreator(ILocalizer localizer, IEnumerable> columns) - { - Localizer = localizer; - Columns = columns; - YesText = "yes"; // localizer.Text("ui.yes"); - NoText = "no"; // localizer.Text("ui.no"); - } - - - /// - /// Create a readable stream from a source enumerable - /// - public MemoryStream CreateStream(IEnumerable source, CultureInfo culture) - { - StringBuilder builder = Create(true); - foreach (T item in source) - { - List values = Columns.Select(c => c.FieldSelector != null && c.CanRender(item) ? c.FieldSelector(item) : String.Empty).ToList(); - AppendLine(builder, values); - } - Stream = new MemoryStream(); - StreamWriter = new StreamWriter(Stream, Encoding.UTF8); - - StreamWriter.Write(builder); - StreamWriter.Flush(); - Stream.Position = 0; - - return Stream; - } - - - /// - /// Create a readable stream from a source enumerable - /// - public async Task CreateStream(IAsyncEnumerable source, CultureInfo culture, CancellationToken token = default) - { - StringBuilder builder = Create(true); - await foreach (T item in source.WithCancellation(token)) - { - List values = Columns.Select(c => c.FieldSelector != null && c.CanRender(item) ? c.FieldSelector(item) : String.Empty).ToList(); - AppendLine(builder, values); - } - Stream = new MemoryStream(); - StreamWriter = new StreamWriter(Stream, Encoding.UTF8); - - StreamWriter.Write(builder); - StreamWriter.Flush(); - Stream.Position = 0; - - return Stream; - } - - - /// - /// Create a CSV from a source enumerable - /// - protected StringBuilder Create(bool headerRow = true) - { - StringBuilder csv = new StringBuilder(); - - // add header - if (headerRow) - { - AppendLine(csv, Columns.Select(c => c.Name)); - } - - return csv; - } - - - /// - /// Appends a line to the CSV - /// - protected void AppendLine(StringBuilder builder, IEnumerable values) - { - int index = 0; - int length = values.Count(); - - foreach (object value in values) - { - string property = MapProperty(value); - - if (!String.IsNullOrWhiteSpace(property)) - { - builder.Append(CHAR_DBL_QUOTE + property + CHAR_DBL_QUOTE); - } - if (++index < length) - { - builder.Append(CHAR_COLON); - } - } - - builder.AppendLine(); - } - - - /// - /// Map a property to a string based on its type - /// - protected string MapProperty(object value) - { - string property = String.Empty; - - if (value == null) - { - return property; - } - - if (value is string) - { - property = Localizer.Maybe((string)value); - } - else if (value is bool) - { - property = (bool)value ? YesText : NoText; - } - else if (value is decimal || value is double || value is float) - { - property = value.ToString().Replace(",", "."); - } - else if (value is DateTime) - { - property = ((DateTime)value).ToString(DATE_FORMAT); - } - else if (value is DateTime?) - { - var val = (DateTime?)value; - property = val.HasValue ? val.Value.ToString(DATE_FORMAT) : String.Empty; - } - else if (value is DateTimeOffset) - { - property = ((DateTimeOffset)value).ToString(DATE_FORMAT); - } - else if (value is DateTimeOffset?) - { - var val = (DateTimeOffset?)value; - property = val.HasValue ? val.Value.ToString(DATE_FORMAT) : String.Empty; - } - else - { - property = value.ToString(); - } - - return property.Replace(CHAR_DBL_QUOTE, CHAR_QUOTE).Replace(CHAR_COLON, CHAR_SEMICOLON); - } - - - public void Dispose() - { - Stream?.Dispose(); - StreamWriter?.Dispose(); - } -} diff --git a/zero.Core/Utils/TableBuilder/ExcelCreator.cs b/zero.Core/Utils/TableBuilder/ExcelCreator.cs deleted file mode 100644 index 4dc1fe7a..00000000 --- a/zero.Core/Utils/TableBuilder/ExcelCreator.cs +++ /dev/null @@ -1,261 +0,0 @@ -using ClosedXML.Excel; -using System.Globalization; -using System.IO; - -namespace zero.Utils; - -public class ExcelCreator : ITableCreator -{ - private MemoryStream Stream { get; set; } - - private XLWorkbook Workbook { get; set; } - - private IEnumerable> Columns { get; set; } - - private const string DATE_FORMAT = "dd. MM. yyyy"; - - private const string DATETIME_FORMAT = "dd. MM. yyyy, HH:ii"; - - public const string CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; - - private CultureInfo Culture = null; - - private ILocalizer Localizer = null; - - private string LinkText = null; - private string YesText = null; - private string NoText = null; - - - public ExcelCreator(ILocalizer localizer, IEnumerable> columns) - { - Localizer = localizer; - Columns = columns; - LinkText = "Link"; // localizer.Text("ui.link"); - YesText = "yes"; // localizer.Text("ui.yes"); - NoText = "no"; // localizer.Text("ui.no"); - } - - - /// - /// Create a readable XLSX stream from a source enumerable - /// - public MemoryStream CreateStream(IEnumerable source, CultureInfo culture) - { - Culture = culture; - Workbook = Create(true, out int lineNumber); - IXLWorksheet sheet = Workbook.Worksheets.First(); - foreach (T item in source) - { - AddRow(sheet, lineNumber++, Columns, item); - } - Stream = new MemoryStream(); - Workbook.SaveAs(Stream); - Stream.Position = 0; - return Stream; - } - - - /// - /// Create a readable XLSX stream from a source enumerable - /// - public async Task CreateStream(IAsyncEnumerable source, CultureInfo culture, CancellationToken token = default) - { - Culture = culture; - Workbook = Create(true, out int lineNumber); - IXLWorksheet sheet = Workbook.Worksheets.First(); - await foreach (T item in source.WithCancellation(token)) - { - AddRow(sheet, lineNumber++, Columns, item); - } - Stream = new MemoryStream(); - Workbook.SaveAs(Stream); - Stream.Position = 0; - return Stream; - } - - - /// - /// Create a XLSX from a source enumerable - /// - protected XLWorkbook Create(IEnumerable source, bool headerRow = true) - { - XLWorkbook workbook = new XLWorkbook(); - IXLWorksheet worksheet = workbook.Worksheets.Add("Data"); - - int lineNumber = 1; - - // add metadata + defaults - workbook.RowHeight = 20; - - // add header - if (headerRow) - { - AddRow(worksheet, lineNumber++, Columns, default, true); - worksheet.Row(1).Height = 30; - } - - // set column widths - for (int i = 0; i < Columns.Count(); i++) - { - TableColumn column = Columns.ElementAt(i); - worksheet.Column(i + 1).Width = column.Width; - } - - // add rows - foreach (T item in source) - { - AddRow(worksheet, lineNumber++, Columns, item); - } - - return workbook; - } - - - /// - /// Create a XLSX from a source enumerable - /// - protected XLWorkbook Create(bool headerRow, out int lineNumber) - { - XLWorkbook workbook = new XLWorkbook(); - IXLWorksheet worksheet = workbook.Worksheets.Add("Data"); - - lineNumber = 1; - - // add metadata + defaults - workbook.RowHeight = 20; - - // add header - if (headerRow) - { - AddRow(worksheet, lineNumber++, Columns, default, true); - worksheet.Row(1).Height = 30; - } - - // set column widths - for (int i = 0; i < Columns.Count(); i++) - { - TableColumn column = Columns.ElementAt(i); - worksheet.Column(i + 1).Width = column.Width; - } - - return workbook; - } - - - /// - /// Appends a line to the XLSX - /// - protected void AddRow(IXLWorksheet builder, int lineNumber, IEnumerable> columns, T item, bool isHeader = false) - { - int index = 0; - - foreach (TableColumn column in columns) - { - object value = isHeader ? column.Name : (column.FieldSelector != null && column.CanRender(item) ? column.FieldSelector(item) : String.Empty); - object property = MapProperty(value, column, out XLDataType dataType); - - IXLCell cell = builder.Cell(lineNumber, index + 1) - .SetValue(property) - .SetDataType(dataType); - - if (isHeader) - { - cell.Style.Font.SetBold(); - cell.Style.Fill.BackgroundColor = XLColor.WhiteSmoke; - } - - if (column.IsBold) - { - cell.Style.Font.SetBold(); - } - - cell.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center; - - if (!isHeader && column.ColumnType == TableColumnType.Link) - { - cell.SetValue(LinkText); - cell.SetHyperlink(new XLHyperlink(value.ToString())); - } - if (column.ColumnType == TableColumnType.Currency) - { - cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Right; - } - - index += 1; - } - - if (!isHeader) - { - builder.Row(lineNumber).Height = 20; - } - } - - - /// - /// Map a property based on its type - /// - protected object MapProperty(object value, TableColumn column, out XLDataType dataType) - { - dataType = XLDataType.Text; - - if (value == null) - { - return String.Empty; - } - - if (value is bool) - { - return (bool)value ? YesText : NoText; - } - else if (value is decimal || value is float || value is double) - { - dataType = XLDataType.Number; - decimal result = Decimal.Round(Convert.ToDecimal(value), 2); - - if (column.ColumnType == TableColumnType.Currency) - { - dataType = XLDataType.Text; - return result.ToString("C2", Culture); - } - - return result; - } - else if (value is int) - { - dataType = XLDataType.Number; - return value; - } - else if (value is DateTime) - { - return ((DateTime)value).ToString(DATE_FORMAT); - } - else if (value is DateTime?) - { - var val = (DateTime?)value; - return val.HasValue ? val.Value.ToString(DATE_FORMAT) : String.Empty; - } - else if (value is DateTimeOffset) - { - return ((DateTimeOffset)value).ToString(DATE_FORMAT); - } - else if (value is DateTimeOffset?) - { - var val = (DateTimeOffset?)value; - return val.HasValue ? val.Value.ToString(DATE_FORMAT) : String.Empty; - } - else if (value is string) - { - return Localizer.Maybe((string)value, new()); - } - - return value; - } - - - public void Dispose() - { - Stream?.Dispose(); - Workbook?.Dispose(); - } -} diff --git a/zero.Core/Utils/TableBuilder/ITableCreator.cs b/zero.Core/Utils/TableBuilder/ITableCreator.cs deleted file mode 100644 index c051ca80..00000000 --- a/zero.Core/Utils/TableBuilder/ITableCreator.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Globalization; -using System.IO; - -namespace zero.Utils; - -public interface ITableCreator : IDisposable -{ - /// - /// Create a readable stream from a source enumerable - /// - Task CreateStream(IAsyncEnumerable source, CultureInfo culture, CancellationToken token = default); - - /// - /// Create a readable stream from a source enumerable - /// - MemoryStream CreateStream(IEnumerable source, CultureInfo culture); -} diff --git a/zero.Core/Utils/TableBuilder/TableBuilder.cs b/zero.Core/Utils/TableBuilder/TableBuilder.cs deleted file mode 100644 index 3f4c2466..00000000 --- a/zero.Core/Utils/TableBuilder/TableBuilder.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System.Globalization; -using System.IO; -using System.Linq.Expressions; - -namespace zero.Utils; - -public class TableBuilder : ITableBuilder, IDisposable -{ - protected List> Columns { get; set; } - - protected TableFormat Format { get; set; } - - protected CultureInfo Culture { get; set; } - - protected ILocalizer Localizer { get; set; } - - - public TableBuilder(ILocalizer localizer, TableFormat format, CultureInfo culture = null) - { - Localizer = localizer; - Format = format; - - Columns = new List>(); - - Culture = culture ?? new CultureInfo("de-DE"); // TODO get culture from zero config - Culture.NumberFormat.CurrencyDecimalSeparator = ","; - Culture.NumberFormat.CurrencyGroupSeparator = " "; - } - - - /// - public string GetExtension() - { - return Format switch - { - TableFormat.Csv => ".csv", - TableFormat.Excel => ".xlsx", - _ => throw new NotSupportedException($"The table format {Format} is not supported") - }; - } - - - /// - public MemoryStream ToStream(IEnumerable source) - { - ITableCreator creator = Format switch - { - TableFormat.Csv => new CsvCreator(Localizer, Columns), - TableFormat.Excel => new ExcelCreator(Localizer, Columns), - _ => throw new NotSupportedException($"The table format {Format} is not supported") - }; - - return creator.CreateStream(source, Culture); - } - - - /// - public async Task ToStream(IAsyncEnumerable source) - { - ITableCreator creator = Format switch - { - TableFormat.Csv => new CsvCreator(Localizer, Columns), - TableFormat.Excel => new ExcelCreator(Localizer, Columns), - _ => throw new NotSupportedException($"The table format {Format} is not supported") - }; - - return await creator.CreateStream(source, Culture); - } - - - /// - public TableColumn Column(string name, Expression> fieldSelector = null, double width = 15, TableColumnType type = TableColumnType.Default, int? position = null) - { - TableColumn column = new() - { - Name = name, - FieldSelector = fieldSelector?.Compile(), - Width = width, - ColumnType = type - }; - - if (position.HasValue && Columns.Count > position.Value) - { - Columns.Insert(position.Value, column); - } - else - { - Columns.Add(column); - } - - return column; - } - - - /// - public void Scope(Action action) - { - action(default); - } - - - /// - public TableColumn Separator() - { - TableColumn column = new() - { - IsSeparator = true, - Width = 4 - }; - - Columns.Add(column); - - return column; - } - - - /// - public void Dispose() - { - //Stream?.Dispose(); - //Workbook?.Dispose(); - } -} - - -public interface ITableBuilder -{ - /// - /// Get file extension calculated by TableFormat - /// - string GetExtension(); - - /// - /// Add a new column to the table - /// - TableColumn Column(string name, Expression> fieldSelector = null, double width = 12, TableColumnType type = TableColumnType.Default, int? position = null); - - /// - /// - /// - void Scope(Action action); - - /// - /// Adds an empty column which acts as a separator - /// - TableColumn Separator(); - - /// - /// Creates a memory stream from the provided source - /// - MemoryStream ToStream(IEnumerable source); - - /// - /// Creates a memory stream from the provided source - /// - Task ToStream(IAsyncEnumerable source); - - /// - /// Disposes the underlying table creator - /// - void Dispose(); -} diff --git a/zero.Core/Utils/TableBuilder/TableColumn.cs b/zero.Core/Utils/TableBuilder/TableColumn.cs deleted file mode 100644 index 553004a4..00000000 --- a/zero.Core/Utils/TableBuilder/TableColumn.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Linq.Expressions; - -namespace zero.Utils; - -public class TableColumn -{ - public string Name { get; set; } - - public bool IsSeparator { get; set; } - - public Func FieldSelector { get; set; } - - public Func CanRender { get; set; } = x => true; - - public double Width { get; set; } - - public bool IsBold { get; set; } - - public TableColumnType ColumnType { get; set; } - - public TableColumn Type(TableColumnType type) - { - ColumnType = type; - return this; - } - - public TableColumn Size(double width) - { - Width = width; - return this; - } - - public TableColumn For(Expression> fieldSelector, Expression> canRender = null) - { - FieldSelector = fieldSelector.Compile(); - CanRender = canRender != null ? canRender.Compile() : CanRender; - return this; - } - - public TableColumn Currency() - { - ColumnType = TableColumnType.Currency; - return this; - } - - public TableColumn Link() - { - ColumnType = TableColumnType.Link; - return this; - } - - public TableColumn Bold() - { - IsBold = true; - return this; - } -} - - -public enum TableColumnType -{ - Default, - Currency, - Link -} diff --git a/zero.Core/Utils/TableBuilder/TableExport.cs b/zero.Core/Utils/TableBuilder/TableExport.cs deleted file mode 100644 index 47010b1f..00000000 --- a/zero.Core/Utils/TableBuilder/TableExport.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.IO; - -namespace zero.Utils; - -public abstract class TableExport : ITableExport where TEntity : ZeroIdEntity -{ - protected ILocalizer Localizer { get; private set; } - - public TableExport(ILocalizer localizer) - { - Localizer = localizer; - } - - - public virtual async Task Export(TableFormat format = TableFormat.Excel) - { - ITableBuilder builder = new TableBuilder(Localizer, format); - - await Warmup(); - - Build(builder); - - IAsyncEnumerable items = Load(); - - MemoryStream stream = await builder.ToStream(items); - - return stream; - } - - - protected virtual Task Warmup() => Task.CompletedTask; - - - protected virtual async IAsyncEnumerable Load() - { - await Task.Delay(0); - yield break; - } - - - protected virtual void Build(ITableBuilder builder) - { - builder.Column("@ui.id").For(c => c.Id).Size(40); - } -} - - -public interface ITableExport where TEntity : ZeroIdEntity -{ - Task Export(TableFormat format = TableFormat.Excel); -} \ No newline at end of file diff --git a/zero.Core/Utils/TableBuilder/TableFormat.cs b/zero.Core/Utils/TableBuilder/TableFormat.cs deleted file mode 100644 index f0eae05a..00000000 --- a/zero.Core/Utils/TableBuilder/TableFormat.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace zero.Utils; - -public enum TableFormat -{ - Csv = 0, - Excel = 1 -} diff --git a/zero.Core/Utils/TableBuilder/TableResult.cs b/zero.Core/Utils/TableBuilder/TableResult.cs deleted file mode 100644 index 62fbcef7..00000000 --- a/zero.Core/Utils/TableBuilder/TableResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.IO; - -namespace zero.Utils; - -public class TableResult -{ - public MemoryStream Stream { get; set; } - - public string Filename { get; set; } -} \ No newline at end of file diff --git a/zero.Core/Utils/TokenReplacement.cs b/zero.Core/Utils/TokenReplacement.cs deleted file mode 100644 index 1b3a7e3f..00000000 --- a/zero.Core/Utils/TokenReplacement.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Text.RegularExpressions; - -namespace zero.Utils; - -public class TokenReplacement -{ - static Regex TokenRegex; - - - static TokenReplacement() - { - TokenRegex = new Regex("{([\\w-_.]+)}", RegexOptions.IgnoreCase); - } - - - public static string Apply(string text, Dictionary tokens) - { - if (text.IsNullOrWhiteSpace()) - { - return text; - } - - MatchCollection matches = TokenRegex.Matches(text); - - foreach (Match match in matches) - { - if (!match.Success) - { - continue; - } - - string original = match.Value; - string token = match.Groups[1].Value; - - if (tokens.ContainsKey(token)) - { - text = text.Replace(original, tokens[token]); - } - } - - return text; - - //foreach ((string key, string value) in tokens) - //{ - // string tokenKey = key.EnsureStartsWith(BEGIN).EnsureEndsWith(END); - // string tokenValue = value; // TODO escape for HTML? - - // text = text.Replace(tokenKey, value); - //} - - //return text; - } -} diff --git a/zero.Core/Validation/ValidatorCamelCasePropertyResolver.cs b/zero.Core/Validation/ValidatorCamelCasePropertyResolver.cs deleted file mode 100644 index 52c87e72..00000000 --- a/zero.Core/Validation/ValidatorCamelCasePropertyResolver.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FluentValidation.Internal; -using System.Linq.Expressions; -using System.Reflection; - -namespace zero.Validation; - -public class ValidatorCamelCasePropertyResolver -{ - public static string ResolvePropertyName(Type type, MemberInfo memberInfo, LambdaExpression expression) - { - return DefaultPropertyNameResolver(type, memberInfo, expression).ToCamelCaseId(); - } - - private static string DefaultPropertyNameResolver(Type type, MemberInfo memberInfo, LambdaExpression expression) - { - if (expression != null) - { - var chain = PropertyChain.FromExpression(expression); - if (chain.Count > 0) return chain.ToString(); - } - - if (memberInfo != null) - { - return memberInfo.Name; - } - - return null; - } -} \ No newline at end of file diff --git a/zero.Core/Validation/ValidatorExtensions.cs b/zero.Core/Validation/ValidatorExtensions.cs deleted file mode 100644 index 68dfe4f3..00000000 --- a/zero.Core/Validation/ValidatorExtensions.cs +++ /dev/null @@ -1,177 +0,0 @@ -using FluentValidation; -using Raven.Client.Documents; -using System.Globalization; - -namespace zero.Validation; - -public static class ValidatorExtensions -{ - private const char DOT = '.'; - - private const char KLAMMERAFFE = '@'; - - private static string HEX_REGEX = "(^$)|(#[0-9a-fA-F]{3,8})"; - - /// - /// Validate a color input as HEX (#aabbccdd or #aabbcc or #abc) - /// - public static IRuleBuilderOptions Hex(this IRuleBuilder ruleBuilder) - { - return ruleBuilder.Matches(HEX_REGEX).WithMessage("@errors.forms.hex_format"); - } - - - /// - /// Validate an email - /// - public static IRuleBuilderOptions Url(this IRuleBuilder ruleBuilder) - { - return ruleBuilder.Must((root, value, context) => - { - return value.IsNullOrWhiteSpace() || Uri.IsWellFormedUriString(value, UriKind.Absolute); - }).WithMessage("@errors.forms.url_format"); - } - - - /// - /// Validate an email - /// - public static IRuleBuilderOptions Email(this IRuleBuilder ruleBuilder) - { - return ruleBuilder.Must((root, value, context) => - { - if (value.IsNullOrWhiteSpace()) - { - return true; - } - - int index = value.IndexOf(KLAMMERAFFE); - - if (index < 0 || index == value.Length - 1 || index != value.LastIndexOf(KLAMMERAFFE)) - { - return false; - } - - return true; - }).WithMessage("@errors.forms.email_invalid"); - } - - - /// - /// Validate one or multiple emails - /// - public static IRuleBuilderOptions Emails(this IRuleBuilder ruleBuilder) - { - return ruleBuilder.Must((root, value, context) => - { - if (value.IsNullOrWhiteSpace()) - { - return true; - } - - string[] mails = value.Split(',', ';').Select(x => x.Trim()).Where(x => !x.IsNullOrWhiteSpace()).ToArray(); - - if (!mails.Any()) - { - return false; - } - - foreach (string mail in mails) - { - int index = mail.IndexOf(KLAMMERAFFE); - - if (index < 0 || index == mail.Length - 1 || index != mail.LastIndexOf(KLAMMERAFFE)) - { - return false; - } - } - - return true; - }).WithMessage("@errors.forms.emails_invalid"); - } - - - /// - /// Validates a culture identifier - /// - public static IRuleBuilderOptions Culture(this IRuleBuilder ruleBuilder) - { - return ruleBuilder.Must((root, value, context) => - { - if (value.IsNullOrWhiteSpace()) - { - return true; - } - - try - { - CultureInfo info = CultureInfo.GetCultureInfo(value); - return info != null && !info.EnglishName.Equals(value, StringComparison.InvariantCultureIgnoreCase); - } - catch - { - return false; - } - }).WithMessage("@errors.forms.culture"); - } - - - - - /// - /// Check if this value is unique within a collection - /// - public static IRuleBuilderOptions Unique(this IRuleBuilder ruleBuilder, IZeroStore store) where T : ZeroEntity - { - return ruleBuilder.MustAsync(async (entity, value, context, cancellation) => - { - bool any = await store.Session().Advanced.AsyncDocumentQuery() - .WhereNotEquals(nameof(ZeroIdEntity.Id), entity.Id) - .WhereEquals(context.PropertyName.ToPascalCaseId(), value) - .AnyAsync(cancellation); - - return !any; - }).WithMessage("@errors.forms.not_unique"); - } - - - /// - /// Check if this value is at least set once to the expected value within a collection - /// - public static IRuleBuilderOptions ExpectAnyUnique(this IRuleBuilder ruleBuilder, IZeroStore store, TProperty expectedValue) where T : ZeroEntity - { - return ruleBuilder.MustAsync(async (entity, value, context, cancellation) => - { - return await store.Session().Advanced.AsyncDocumentQuery() - .WhereNotEquals(nameof(ZeroIdEntity.Id), entity.Id) - .WhereEquals(context.PropertyName.ToPascalCaseId(), expectedValue) - .AnyAsync(cancellation); - }).WithMessage("@errors.forms.not_unique_alone"); - } - - - /// - /// Check if this reference exists and is an entity which can be referenced (appId = shared for shareable entities or appId = current) - /// - public static IRuleBuilderOptions Exists(this IRuleBuilder ruleBuilder, IZeroStore store) where T : ZeroEntity - { - return ruleBuilder.Exists(store); - } - - - /// - /// Check if this reference exists and is an entity which can be referenced (appId = shared for shareable entities or appId = current) - /// - public static IRuleBuilderOptions Exists(this IRuleBuilder ruleBuilder, IZeroStore store) where T : ZeroEntity where TReference : ZeroEntity - { - return ruleBuilder.MustAsync(async (entity, id, context, cancellation) => - { - if (id.IsNullOrWhiteSpace()) - { - return true; - } - - return await store.Session().Query().AnyAsync(x => x.Id == id); - }).WithMessage("@errors.forms.reference_notfound"); - } -} diff --git a/zero.Core/Validation/ZeroMergedValidator.cs b/zero.Core/Validation/ZeroMergedValidator.cs deleted file mode 100644 index a33e5fe0..00000000 --- a/zero.Core/Validation/ZeroMergedValidator.cs +++ /dev/null @@ -1,98 +0,0 @@ -using FluentValidation; -using FluentValidation.Results; -using System.Collections.Concurrent; - -namespace zero.Validation; - -public class ZeroMergedValidator : IZeroMergedValidator -{ - protected IEnumerable> Validators { get; private set; } - - ConcurrentDictionary>> TypeCache = new(); - - - public ZeroMergedValidator(IEnumerable> validators) - { - Validators = validators; - } - - - /// - public async Task ValidateAsync(T model) - { - ValidationResult result = new(); - - foreach (IValidator validator in ResolveFor(model)) - { - ValidationResult innerResult = await validator.ValidateAsync(model); - result.Errors.AddRange(innerResult.Errors); - } - - return result; - } - - - /// - public IEnumerable> ResolveFor(T model) - { - Type type = model.GetType(); - - if (!TypeCache.TryGetValue(type, out IEnumerable> validators)) - { - validators = Validators.Where(validator => CanHandle(validator, type)).ToList(); - TypeCache.TryAdd(type, validators); - } - - return validators; - } - - - /// - /// Checks whether a certain validator can handle the give type - /// - bool CanHandle(IValidator validator, Type modelType) - { - Type validatorType = validator.GetType(); - Type typeToFind = typeof(ZeroValidator<,>); - - Type findValidatorBase(Type type) - { - if (type.BaseType == null) - { - return null; - } - - if (type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeToFind) - { - return type.BaseType; - } - - return findValidatorBase(type.BaseType); - } - - Type zeroValidatorType = findValidatorBase(validatorType); - - if (zeroValidatorType == null) - { - return false; - } - - Type implementationType = zeroValidatorType.GenericTypeArguments[1]; - - return implementationType.IsAssignableFrom(modelType); - } -} - - -public interface IZeroMergedValidator -{ - /// - /// Get all validators which can run for the given model - /// - IEnumerable> ResolveFor(T model); - - /// - /// Validates a model by using all registered ZeroValidators for this entity type - /// - Task ValidateAsync(T model); -} \ No newline at end of file diff --git a/zero.Core/Validation/ZeroValidationModule.cs b/zero.Core/Validation/ZeroValidationModule.cs deleted file mode 100644 index a33d66cd..00000000 --- a/zero.Core/Validation/ZeroValidationModule.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero.Validation; - -internal class ZeroValidationModule : ZeroModule -{ - public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) - { - services.AddScoped(typeof(IZeroMergedValidator<>), typeof(ZeroMergedValidator<>)); - } -} \ No newline at end of file diff --git a/zero.Core/Validation/ZeroValidator.cs b/zero.Core/Validation/ZeroValidator.cs deleted file mode 100644 index acdcf9e3..00000000 --- a/zero.Core/Validation/ZeroValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FluentValidation; -using FluentValidation.Results; - -namespace zero.Validation; - -public class ZeroValidator : ZeroValidator -{ -} - -public abstract class ZeroValidator : AbstractValidator, IValidator where TImplementation : TInterface -{ - public ValidationResult Validate(TInterface instance) - { - if (!(instance is TImplementation)) - { - throw new ArgumentException($"Parameter {nameof(instance)} has to be of type {typeof(TImplementation)}."); - } - return base.Validate((TImplementation)instance); - } - - public Task ValidateAsync(TInterface instance, CancellationToken cancellation = default) - { - if (!(instance is TImplementation)) - { - throw new ArgumentException($"Parameter {nameof(instance)} has to be of type {typeof(TImplementation)}."); - } - return base.ValidateAsync((TImplementation)instance, cancellation); - } -} \ No newline at end of file diff --git a/zero.Core/ZeroApplicationBuilder.cs b/zero.Core/ZeroApplicationBuilder.cs deleted file mode 100644 index 8d61db74..00000000 --- a/zero.Core/ZeroApplicationBuilder.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Microsoft.AspNetCore.Builder; - -namespace zero; - -public class ZeroApplicationBuilder : IZeroApplicationBuilder -{ - protected IApplicationBuilder App { get; private set; } - - protected IZeroOptions Options { get; private set; } - - - internal ZeroApplicationBuilder(IApplicationBuilder app) - { - App = app; - App.UseStaticFiles(); - //App.UseExceptionHandler("/zero/api/error"); - App.UseMiddleware(); - App.UseRouting(); - App.UseAuthentication(); - App.UseAuthorization(); - - ZeroBuilder.Modules.Configure(app, null, app.ApplicationServices); - - foreach (IZeroPlugin plugin in ZeroBuilder.Plugins) - { - plugin.Configure(app, null, app.ApplicationServices); - } - - - //ZeroModuleInitializer.ConfigureAll(app.ApplicationServices.GetRequiredService()); - } - - - public void WithEndpoints(Action configure) - { - App.UseEndpoints(e => - { - ZeroEndpointRouteBuilder builder = new(e); - configure(builder); - }); - } - - - public IZeroApplicationBuilder WithMiddleware(Action configure) - { - configure(App); - return this; - } -} - - -public interface IZeroApplicationBuilder -{ - void WithEndpoints(Action endpoints); - - IZeroApplicationBuilder WithMiddleware(Action configure); -} \ No newline at end of file diff --git a/zero.Core/ZeroBuilder.cs b/zero.Core/ZeroBuilder.cs deleted file mode 100644 index 620b28ca..00000000 --- a/zero.Core/ZeroBuilder.cs +++ /dev/null @@ -1,91 +0,0 @@ -using FluentValidation; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace zero; - -// TODO maybe use a middleware like Hangfire does: https://github.com/HangfireIO/Hangfire/blob/master/src/Hangfire.AspNetCore/HangfireEndpointRouteBuilderExtensions.cs - -public class ZeroBuilder -{ - public virtual IServiceCollection Services { get; } - - public virtual IMvcBuilder Mvc { get; } - - internal static ZeroModuleCollection Modules { get; private set; } = new(); - - readonly IConfiguration Configuration; - readonly IZeroStartupOptions StartupOptions; - - internal static HashSet Plugins { get; private set; } = new(); - - - public ZeroBuilder(IServiceCollection services, IConfiguration configuration, Action setupAction) - { - Services = services; - Mvc = services.AddMvc(); - Configuration = configuration; - - // create startup options - StartupOptions = new ZeroStartupOptions(Mvc); - StartupOptions.AssemblyDiscoveryRules.Add(new ZeroAssemblyDiscoveryRule()); - setupAction?.Invoke(StartupOptions); - - services.AddControllers(); - services.AddRazorPages(); - - // adds and discovers additional and built-in assemblies - new AssemblyDiscovery(Mvc).Execute(StartupOptions.AssemblyDiscoveryRules); - - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - Modules.Add(); - - Modules.ConfigureServices(services, configuration); - - // configure FluentValidation - ValidatorOptions.Global.PropertyNameResolver = ValidatorCamelCasePropertyResolver.ResolvePropertyName; - } - - - /// - /// Use specified options - /// - public ZeroBuilder WithOptions(Action configureOptions) - { - Services.Configure(configureOptions); - return this; - } - - - /// - /// Adds a zero plugin - /// - public ZeroBuilder AddPlugin() where T : class, IZeroPlugin, new() - { - T plugin = new T(); - plugin.ConfigureServices(Services, Configuration); - AssemblyDiscovery.Current.AddAssembly(typeof(T).Assembly); - Services.AddSingleton(); - Plugins.Add(plugin); - return this; - } -} diff --git a/zero.Core/zero.Core.csproj b/zero.Core/zero.Core.csproj deleted file mode 100644 index 50bd3fca..00000000 --- a/zero.Core/zero.Core.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - zero.Core - 0.1.0 - preview - net8.0 - true - zero - embedded - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/zero.sln b/zero.sln index c010669a..13cf3f69 100644 --- a/zero.sln +++ b/zero.sln @@ -3,12 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31912.275 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero.Core", "zero.Core\zero.Core.csproj", "{CED2A7FA-8D35-4A2A-AF57-8B95307547CB}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero", "zero\zero.csproj", "{33CD6E46-CD81-42C3-9019-C0EAD557EE99}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "legacy", "legacy", "{93A1CCEB-A323-445E-8116-4575C5939B46}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero.Raven", "zero.Raven\zero.Raven.csproj", "{DC79D7EA-FAA1-4701-9C36-8295CD1C45D0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zero.Raven.IdentityDemo", "zero.Raven.IdentityDemo\zero.Raven.IdentityDemo.csproj", "{05C829D6-3052-40DB-9282-3BDFCDAE0F05}" @@ -19,10 +15,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CED2A7FA-8D35-4A2A-AF57-8B95307547CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CED2A7FA-8D35-4A2A-AF57-8B95307547CB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CED2A7FA-8D35-4A2A-AF57-8B95307547CB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CED2A7FA-8D35-4A2A-AF57-8B95307547CB}.Release|Any CPU.Build.0 = Release|Any CPU {33CD6E46-CD81-42C3-9019-C0EAD557EE99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {33CD6E46-CD81-42C3-9019-C0EAD557EE99}.Debug|Any CPU.Build.0 = Debug|Any CPU {33CD6E46-CD81-42C3-9019-C0EAD557EE99}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -39,9 +31,6 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {CED2A7FA-8D35-4A2A-AF57-8B95307547CB} = {93A1CCEB-A323-445E-8116-4575C5939B46} - EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AD3F31B9-8BBB-4334-9571-F556B9E632C9} EndGlobalSection