From 3051d86a70a8732faa905d8c3708a26d3f8b0920 Mon Sep 17 00:00:00 2001 From: Tobias Klika Date: Wed, 22 Dec 2021 00:14:48 +0100 Subject: [PATCH] output integration types --- .../Integrations/IntegrationModule.cs | 17 ++ .../Integrations/IntegrationsController.cs | 60 +++---- .../Maps/IntegrationMapperProfile.cs | 20 +++ .../Maps/IntegrationTypeDisplay.cs | 22 +++ zero.Api/Endpoints/Spaces/SpacesController.cs | 2 +- zero.Api/Plugin.cs | 1 + zero.Backoffice.UI/app/core/zeroRuntime.ts | 4 +- zero.Backoffice.UI/app/modules/index.ts | 3 +- .../app/modules/integrations/api.ts | 12 +- .../integrations/integrations-item.vue | 149 ++++++++++++++++++ .../app/modules/integrations/integrations.vue | 73 +++++++-- .../Configuration/ConfigurationModule.cs | 2 + .../Configuration/Integrations/Integration.cs | 2 +- .../Integrations/IntegrationOptions.cs | 18 +-- .../Integrations/IntegrationType.cs | 58 ++++--- .../Integrations/IntegrationTypeService.cs | 59 +++++++ zero.Core/Mapper/MapperExtensions.cs | 6 + zero.Demo/FathomAnalyticsIntegration.cs | 17 ++ zero.Demo/GoogleAnalyticsIntegration.cs | 18 +++ zero.Demo/Program.cs | 7 + 20 files changed, 455 insertions(+), 95 deletions(-) create mode 100644 zero.Api/Endpoints/Integrations/IntegrationModule.cs create mode 100644 zero.Api/Endpoints/Integrations/Maps/IntegrationMapperProfile.cs create mode 100644 zero.Api/Endpoints/Integrations/Maps/IntegrationTypeDisplay.cs create mode 100644 zero.Backoffice.UI/app/modules/integrations/integrations-item.vue create mode 100644 zero.Core/Configuration/Integrations/IntegrationTypeService.cs create mode 100644 zero.Demo/FathomAnalyticsIntegration.cs create mode 100644 zero.Demo/GoogleAnalyticsIntegration.cs diff --git a/zero.Api/Endpoints/Integrations/IntegrationModule.cs b/zero.Api/Endpoints/Integrations/IntegrationModule.cs new file mode 100644 index 00000000..d23a7ff6 --- /dev/null +++ b/zero.Api/Endpoints/Integrations/IntegrationModule.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace zero.Api.Endpoints.Integrations; + +public class IntegrationModule : ZeroModule +{ + public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + services.AddSingleton(); + + //services.Configure(opts => + //{ + // opts.Indexes.Add(); + //}); + } +} \ No newline at end of file diff --git a/zero.Api/Endpoints/Integrations/IntegrationsController.cs b/zero.Api/Endpoints/Integrations/IntegrationsController.cs index 89611c5f..cc86de71 100644 --- a/zero.Api/Endpoints/Integrations/IntegrationsController.cs +++ b/zero.Api/Endpoints/Integrations/IntegrationsController.cs @@ -1,41 +1,31 @@ -//using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; +using System.Collections; -//namespace zero.Api.Endpoints.Integrations; +namespace zero.Api.Endpoints.Integrations; -//public class IntegrationsController : ZeroApiController -//{ -// public IntegrationsController(IIntegrationService store) -// { +public class IntegrationsController : ZeroApiController +{ + readonly IIntegrationTypeService IntegrationTypes; -// } + public IntegrationsController(IIntegrationTypeService integrationTypes) + { + IntegrationTypes = integrationTypes; + } -// [HttpGet("empty")] -// [ZeroAuthorize(LanguagePermissions.Create)] -// public virtual Task> Empty(string flavor = null) => EmptyModel(flavor); + [HttpGet("types")] + //[ZeroAuthorize(SpacePermissions.Read)] + public virtual ActionResult GetTypes() + { + IEnumerable result = Mapper.Map(IntegrationTypes.GetAll()); + return Ok(result); + } - -// [HttpGet("{id}")] -// [ZeroAuthorize(LanguagePermissions.Read)] -// public virtual Task> Get(string id, string changeVector = null) => GetModel(id, changeVector); - - -// [HttpGet("")] -// [ZeroAuthorize(LanguagePermissions.Read)] -// public virtual Task> Get([FromQuery] ListQuery query) => GetModelsByIndex(query); - - -// [HttpPost("")] -// [ZeroAuthorize(LanguagePermissions.Create)] -// public virtual Task> Create(Language saveModel) => CreateModel(saveModel); - - -// [HttpPut("{id}")] -// [ZeroAuthorize(LanguagePermissions.Update)] -// public virtual Task> Update(string id, Language updateModel, [FromQuery] string changeToken = null) => UpdateModel(id, updateModel, changeToken); - - -// [HttpDelete("{id}")] -// [ZeroAuthorize(LanguagePermissions.Delete)] -// public virtual Task> Delete(string id) => DeleteModel(id); -//} \ No newline at end of file + [HttpGet("types/{alias}")] + //[ZeroAuthorize(SpacePermissions.Read)] + public virtual ActionResult GetTypes(string alias) + { + IntegrationTypeDisplay result = Mapper.Map(IntegrationTypes.GetByAlias(alias)); + return Ok(result); + } +} \ No newline at end of file diff --git a/zero.Api/Endpoints/Integrations/Maps/IntegrationMapperProfile.cs b/zero.Api/Endpoints/Integrations/Maps/IntegrationMapperProfile.cs new file mode 100644 index 00000000..fdfb0dcf --- /dev/null +++ b/zero.Api/Endpoints/Integrations/Maps/IntegrationMapperProfile.cs @@ -0,0 +1,20 @@ +namespace zero.Api.Endpoints.Integrations; + +public class IntegrationMapperProfile : ZeroMapperProfile +{ + public override void Configure(IZeroMapper mapper) + { + mapper.Define((source, ctx) => new(), Map); + } + + + protected virtual void Map(IntegrationType source, IntegrationTypeDisplay target, IZeroMapperContext ctx) + { + target.Description = source.Description; + target.Name = source.Name; + target.Alias = source.Alias; + target.EditorAlias = source.EditorAlias; + target.ImagePath = source.ImagePath; + target.Tags = source.Tags; + } +} \ No newline at end of file diff --git a/zero.Api/Endpoints/Integrations/Maps/IntegrationTypeDisplay.cs b/zero.Api/Endpoints/Integrations/Maps/IntegrationTypeDisplay.cs new file mode 100644 index 00000000..ff069ea1 --- /dev/null +++ b/zero.Api/Endpoints/Integrations/Maps/IntegrationTypeDisplay.cs @@ -0,0 +1,22 @@ +namespace zero.Api.Endpoints.Integrations; + +public class IntegrationTypeDisplay +{ + public string EditorAlias { get; set; } + + public string Alias { get; set; } + + public string Name { get; set; } + + public List Tags { get; set; } = new(); + + public string Description { get; set; } + + public string ImagePath { get; set; } + + public bool IsActivated { get; set; } + + public bool IsConfigured { get; set; } + + public string ModelId { get; set; } +} diff --git a/zero.Api/Endpoints/Spaces/SpacesController.cs b/zero.Api/Endpoints/Spaces/SpacesController.cs index 91976767..f14f34af 100644 --- a/zero.Api/Endpoints/Spaces/SpacesController.cs +++ b/zero.Api/Endpoints/Spaces/SpacesController.cs @@ -19,7 +19,7 @@ public class SpacesController : ZeroApiEntityStoreController [HttpGet("types/{alias}")] [ZeroAuthorize(SpacePermissions.Read)] - public virtual ActionResult GetTypes(string alias) => Ok(SpaceTypes.GetByAlias(alias)); + public virtual ActionResult GetType(string alias) => Ok(SpaceTypes.GetByAlias(alias)); [HttpGet("{alias}")] [ZeroAuthorize(SpacePermissions.Read)] diff --git a/zero.Api/Plugin.cs b/zero.Api/Plugin.cs index 15193696..36c51f58 100644 --- a/zero.Api/Plugin.cs +++ b/zero.Api/Plugin.cs @@ -37,6 +37,7 @@ public class ZeroApiPlugin : ZeroPlugin modules.Add(); modules.Add(); modules.Add(); + modules.Add(); modules.ConfigureServices(services, configuration); diff --git a/zero.Backoffice.UI/app/core/zeroRuntime.ts b/zero.Backoffice.UI/app/core/zeroRuntime.ts index a1d76fbb..401bbfb4 100644 --- a/zero.Backoffice.UI/app/core/zeroRuntime.ts +++ b/zero.Backoffice.UI/app/core/zeroRuntime.ts @@ -19,7 +19,8 @@ import spacePlugin, pagePlugin, mailTemplatePlugin, - translationPlugin + translationPlugin, + integrationPlugin } from '../modules'; import editorPlugin from '../editor/plugin'; import { ZeroSchema } from 'zero/schemas'; @@ -112,6 +113,7 @@ export class ZeroRuntime implements Zero pagePlugin.install(pluginOptions); mailTemplatePlugin.install(pluginOptions); translationPlugin.install(pluginOptions); + integrationPlugin.install(pluginOptions); } diff --git a/zero.Backoffice.UI/app/modules/index.ts b/zero.Backoffice.UI/app/modules/index.ts index 5b238da0..f8ced67b 100644 --- a/zero.Backoffice.UI/app/modules/index.ts +++ b/zero.Backoffice.UI/app/modules/index.ts @@ -7,4 +7,5 @@ export * from './media'; export * from './spaces'; export * from './pages'; export * from './mails'; -export * from './translations'; \ No newline at end of file +export * from './translations'; +export * from './integrations'; \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/integrations/api.ts b/zero.Backoffice.UI/app/modules/integrations/api.ts index 0dbc0644..26f59c48 100644 --- a/zero.Backoffice.UI/app/modules/integrations/api.ts +++ b/zero.Backoffice.UI/app/modules/integrations/api.ts @@ -2,17 +2,9 @@ import { get, post, put, del, ApiRequestConfig, ApiRequestQuery } from '../../se export default { - getEmpty: (flavor?: string, config?: ApiRequestConfig) => get("translations/empty", { ...config, params: { flavor } }), + getTypes: (config?: ApiRequestConfig) => get('integrations/types', { ...config }), - getById: (id: string, changeVector?: string, config?: ApiRequestConfig) => get('translations/' + id, { ...config, params: { changeVector } }), - - getByQuery: (query: ApiRequestQuery, config?: ApiRequestConfig) => get('translations', { ...config, params: { ...query } }), - - create: (model: any, config?: ApiRequestConfig) => post('translations', model, config), - - update: (model: any, config?: ApiRequestConfig) => put('translations/' + model.id, model, config), - - delete: (id: string, config?: ApiRequestConfig) => del('translations/' + id, null, config), + getType: (alias: string, config?: ApiRequestConfig) => get('integrations/types/' + alias, { ...config }), //getEmpty: async alias => await get(base + 'getEmpty', { params: { alias } }), //getTypes: async () => await get(base + 'getTypes'), diff --git a/zero.Backoffice.UI/app/modules/integrations/integrations-item.vue b/zero.Backoffice.UI/app/modules/integrations/integrations-item.vue new file mode 100644 index 00000000..c5cda726 --- /dev/null +++ b/zero.Backoffice.UI/app/modules/integrations/integrations-item.vue @@ -0,0 +1,149 @@ + + + + + + \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/integrations/integrations.vue b/zero.Backoffice.UI/app/modules/integrations/integrations.vue index ee6166a0..3bbe3996 100644 --- a/zero.Backoffice.UI/app/modules/integrations/integrations.vue +++ b/zero.Backoffice.UI/app/modules/integrations/integrations.vue @@ -1,22 +1,71 @@  - \ No newline at end of file + + + \ No newline at end of file diff --git a/zero.Core/Configuration/ConfigurationModule.cs b/zero.Core/Configuration/ConfigurationModule.cs index b8c80c48..f0b10e7a 100644 --- a/zero.Core/Configuration/ConfigurationModule.cs +++ b/zero.Core/Configuration/ConfigurationModule.cs @@ -19,7 +19,9 @@ public class ConfigurationModule : ZeroModule }).Bind(configuration.GetSection("Zero")); services.AddTransient(factory => factory.GetService>().Value); + services.AddScoped(); services.AddOptions().Bind(configuration.GetSection("Zero:Features")); + services.AddOptions(); } } \ No newline at end of file diff --git a/zero.Core/Configuration/Integrations/Integration.cs b/zero.Core/Configuration/Integrations/Integration.cs index 6405a828..44221bd6 100644 --- a/zero.Core/Configuration/Integrations/Integration.cs +++ b/zero.Core/Configuration/Integrations/Integration.cs @@ -5,7 +5,7 @@ /// It's up to the user to provide functionality. /// [RavenCollection("Integrations")] -public class Integration : ZeroEntity +public class IntegrationModel : ZeroEntity { /// public string TypeAlias { get; set; } diff --git a/zero.Core/Configuration/Integrations/IntegrationOptions.cs b/zero.Core/Configuration/Integrations/IntegrationOptions.cs index 2d8373fe..901ee118 100644 --- a/zero.Core/Configuration/Integrations/IntegrationOptions.cs +++ b/zero.Core/Configuration/Integrations/IntegrationOptions.cs @@ -4,13 +4,7 @@ namespace zero.Configuration; public class IntegrationOptions : List { - public void Add(IntegrationType integration) where T : Integration, new() - { - Add(IntegrationType.Convert(integration)); - } - - - public void Add(string alias, string name, string description, List tags = default, string imagePath = null, IValidator validator = null) where T : Integration, new() + public void Add(string alias, string name, string description, string editorAlias = null, List tags = default, string imagePath = null, IValidator validator = null) where T : IntegrationModel, new() { Add(new IntegrationType(typeof(T)) { @@ -19,12 +13,14 @@ public class IntegrationOptions : List Description = description, ImagePath = imagePath, Tags = tags, - Validator = validator + Validator = validator, + Construct = cfg => new T(), + EditorAlias = editorAlias }); } - public void Add(Type type, string alias, string name, string description, List tags = default, string imagePath = null, IValidator validator = null) + public void Add(Type type, string alias, string name, string description, string editorAlias = null, List tags = default, string imagePath = null, IValidator validator = null) { Add(new IntegrationType(type) { @@ -33,7 +29,9 @@ public class IntegrationOptions : List Description = description, ImagePath = imagePath, Tags = tags, - Validator = validator + Validator = validator, + Construct = cfg => Activator.CreateInstance(type), + EditorAlias = editorAlias }); } } diff --git a/zero.Core/Configuration/Integrations/IntegrationType.cs b/zero.Core/Configuration/Integrations/IntegrationType.cs index 20a25590..22a5ea33 100644 --- a/zero.Core/Configuration/Integrations/IntegrationType.cs +++ b/zero.Core/Configuration/Integrations/IntegrationType.cs @@ -1,20 +1,35 @@ -namespace zero.Configuration; +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 : IntegrationType where T : Integration, new() +public class IntegrationType { - public IntegrationType() : base(typeof(T)) { } -} + /// + /// Type of the associated entity + /// + [JsonIgnore] + public Type ModelType { get; private set; } + + /// + /// Alias to find the editor schema + /// + public string EditorAlias { get; set; } + + /// + /// Alias for querying + /// + public string Alias { get; set; } + + /// + /// Name of the flavor + /// + public string Name { get; set; } -/// -/// 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 : OptionsType -{ /// /// Group integrations by tags /// @@ -30,22 +45,17 @@ public class IntegrationType : OptionsType /// public string ImagePath { get; set; } + /// + /// Set a validator for this integration + /// + [JsonIgnore] + public IValidator Validator { get; set; } + + [JsonIgnore] + public Func Construct { get; set; } public IntegrationType(Type type) { - ContentType = type; - } - - public static IntegrationType Convert(IntegrationType model) where T : Integration, new() - { - return new(model.ContentType) - { - Alias = model.Alias, - Name = model.Name, - Description = model.Description, - ImagePath = model.ImagePath, - Tags = model.Tags, - Validator = model.Validator - }; + ModelType = type; } } diff --git a/zero.Core/Configuration/Integrations/IntegrationTypeService.cs b/zero.Core/Configuration/Integrations/IntegrationTypeService.cs new file mode 100644 index 00000000..b378aabe --- /dev/null +++ b/zero.Core/Configuration/Integrations/IntegrationTypeService.cs @@ -0,0 +1,59 @@ +namespace zero.Configuration; + +public class IntegrationTypeService : IIntegrationTypeService +{ + protected IZeroContext Context { get; private set; } + + protected IHandlerHolder Handler { get; private set; } + + protected IntegrationOptions Types { get; set; } + + + public IntegrationTypeService(IZeroContext context, IZeroOptions options, IHandlerHolder handler) + { + Context = context; + Handler = handler; + Types = options.For(); + } + + + /// + public IEnumerable GetAll() + { + return Types; + } + + + /// + public IntegrationType GetByAlias(string integrationTypeAlias) + { + return Types.FirstOrDefault(x => x.Alias == integrationTypeAlias); + } + + + /// + public IntegrationType GetByType() where T : IntegrationModel + { + Type type = typeof(T); + return Types.FirstOrDefault(x => x.ModelType == type); + } +} + + +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 : IntegrationModel; +} diff --git a/zero.Core/Mapper/MapperExtensions.cs b/zero.Core/Mapper/MapperExtensions.cs index 38df094e..d5e8160b 100644 --- a/zero.Core/Mapper/MapperExtensions.cs +++ b/zero.Core/Mapper/MapperExtensions.cs @@ -52,4 +52,10 @@ public static class MapperExtensions return model; } + + + public static IEnumerable Map(this IZeroMapper mapper, IEnumerable source) + { + return source.Select(x => mapper.Map(x)); + } } diff --git a/zero.Demo/FathomAnalyticsIntegration.cs b/zero.Demo/FathomAnalyticsIntegration.cs new file mode 100644 index 00000000..450b009e --- /dev/null +++ b/zero.Demo/FathomAnalyticsIntegration.cs @@ -0,0 +1,17 @@ +using zero.Configuration; + +namespace zero.Demo +{ + public class FathomAnalyticsIntegration : IntegrationModel + { + /// + /// ID of the site in Fathom + /// + public string SiteId { get; set; } + + /// + /// Custom domain URL (without /script.js) + /// + public string CustomDomain { get; set; } + } +} \ No newline at end of file diff --git a/zero.Demo/GoogleAnalyticsIntegration.cs b/zero.Demo/GoogleAnalyticsIntegration.cs new file mode 100644 index 00000000..cbb160c4 --- /dev/null +++ b/zero.Demo/GoogleAnalyticsIntegration.cs @@ -0,0 +1,18 @@ +using zero.Configuration; + +namespace zero.Demo +{ + public class GoogleAnalyticsIntegration : IntegrationModel + { + /// + /// Provided tracking ID from Google + /// + public string TrackingId { get; set; } + + /// + /// Verifying ownership of the site (via Google Search Console) with this id + /// Found in verification method -> HTML Tag + /// + public string SiteVerificationId { get; set; } + } +} \ No newline at end of file diff --git a/zero.Demo/Program.cs b/zero.Demo/Program.cs index 4723c720..a163a2a4 100644 --- a/zero.Demo/Program.cs +++ b/zero.Demo/Program.cs @@ -4,6 +4,7 @@ using zero.Applications; using zero.Architecture; using zero.Backoffice; using zero.Backoffice.DevServer; +using zero.Configuration; using zero.Demo; using zero.Localization; using zero.Routing; @@ -41,6 +42,12 @@ builder.Services.Configure(opts => opts.Add(); }); +builder.Services.Configure(opts => +{ + opts.Add("analytics.fathom", "Fathom Analytics", "Connect your website to Fathom and track page views", tags: new() { "analytics" }, imagePath: "/assets/fathom.png"); + opts.Add("analytics.google", "Google Analytics", "Connect your website to google analytics", tags: new() { "analytics" }, imagePath: "/assets/googleanalytics.png"); +}); + var app = builder.Build(); // Configure the HTTP request pipeline.