diff --git a/src/RocketInsights.DXP.Providers.Contentful.Tests/IntegrationTests.cs b/src/RocketInsights.DXP.Providers.Contentful.Tests/IntegrationTests.cs new file mode 100644 index 0000000..de528e9 --- /dev/null +++ b/src/RocketInsights.DXP.Providers.Contentful.Tests/IntegrationTests.cs @@ -0,0 +1,56 @@ + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RocketInsights.Contextual; +using RocketInsights.Contextual.Models; +using RocketInsights.DXP.Providers.Contentful.Extensions; +using RocketInsights.DXP.Services; +using System.Globalization; +using System.Reflection; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace RocketInsights.DXP.Providers.Contentful.Tests +{ + [TestClass] + public class IntegrationTests + { + private static IServiceCollection ServiceCollection { get; set; } + + public IntegrationTests() + { + IConfigurationRoot configurationRoot = new ConfigurationBuilder() + .AddUserSecrets(Assembly.GetExecutingAssembly()) + .Build(); + + ServiceCollection = new ServiceCollection() + .AddLogging(logging => logging.SetMinimumLevel(LogLevel.Trace)) + .AddHttpContextAccessor() + .AddContextual() + .AddSingleton() + .AddDXP() + .AddContentful(configurationRoot); + + } + + //[TestMethod] + public async Task TestRetrievingAContentFragmentFromContentful() + { + var provider = ServiceCollection.BuildServiceProvider(); + + var contextStore = provider.GetRequiredService(); + contextStore.Set("context", new Context() + { + Culture = new CultureInfo("en-US"), + Identity = new ClaimsIdentity() + }); + + var experienceService = provider.GetRequiredService(); + + var fragment = await experienceService.GetFragmentAsync("3EK9sDbFFm69x1azZdd6MJ"); + Assert.AreEqual("3EK9sDbFFm69x1azZdd6MJ", fragment.Id); + } + } +} diff --git a/src/RocketInsights.DXP.Providers.Contentful.Tests/README.md b/src/RocketInsights.DXP.Providers.Contentful.Tests/README.md new file mode 100644 index 0000000..d7e894a --- /dev/null +++ b/src/RocketInsights.DXP.Providers.Contentful.Tests/README.md @@ -0,0 +1 @@ +Please see the readme in `RocketInsights.DXP.Providers.Contentful` project for details about required config settings needed to run this. \ No newline at end of file diff --git a/src/RocketInsights.DXP.Providers.Contentful.Tests/RocketInsights.DXP.Providers.Contentful.Tests.csproj b/src/RocketInsights.DXP.Providers.Contentful.Tests/RocketInsights.DXP.Providers.Contentful.Tests.csproj new file mode 100644 index 0000000..dceaa4c --- /dev/null +++ b/src/RocketInsights.DXP.Providers.Contentful.Tests/RocketInsights.DXP.Providers.Contentful.Tests.csproj @@ -0,0 +1,35 @@ + + + + net5.0 + + false + + 130d7bc1-de18-4c07-9e18-fc49e3ce9cb5 + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/src/RocketInsights.DXP.Providers.Contentful/ContentService.cs b/src/RocketInsights.DXP.Providers.Contentful/ContentService.cs new file mode 100644 index 0000000..1314db4 --- /dev/null +++ b/src/RocketInsights.DXP.Providers.Contentful/ContentService.cs @@ -0,0 +1,48 @@ +using RocketInsights.Common.Models; +using RocketInsights.Contextual.Services; +using RocketInsights.DXP.Models; +using RocketInsights.DXP.Providers.Contentful.Proxy; +using RocketInsights.DXP.Services; +using System; +using System.Threading.Tasks; + +namespace RocketInsights.DXP.Providers.Contentful +{ + public class ContentService : IContentService + { + private IContextService ContextService { get; } + private IContentfulProxy ContentfulProxy { get; } + + public ContentService(IContextService contextService, IContentfulProxy contentfulProxy) + { + ContextService = contextService; + ContentfulProxy = contentfulProxy; + } + + public Task GetFragmentAsync(string id) + { + if (ContextService.TryGetContext(out var context)) + { + var fragmentEntry = ContentfulProxy.GetEntryByIdAsync(id, context.Culture?.Name).Result; + if (fragmentEntry != null) + { + var fragment = new Fragment() + { + Id = fragmentEntry.SystemProperties.Id, + ContentType = fragmentEntry.SystemProperties.ContentType.Name, + Content = fragmentEntry.Fields + }; + return Task.FromResult(fragment); + } + else + { + throw new Exception($"Unable to fetch Fragment: {id}"); + } + } + else + { + throw new Exception("Unable to fetch context"); + } + } + } +} diff --git a/src/RocketInsights.DXP.Providers.Contentful/Extensions/DependencyInjectionExtensions.cs b/src/RocketInsights.DXP.Providers.Contentful/Extensions/DependencyInjectionExtensions.cs new file mode 100644 index 0000000..748bc92 --- /dev/null +++ b/src/RocketInsights.DXP.Providers.Contentful/Extensions/DependencyInjectionExtensions.cs @@ -0,0 +1,32 @@ +using Contentful.Core; +using Contentful.Core.Configuration; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using RocketInsights.DXP.Providers.Contentful.Proxy; +using RocketInsights.DXP.Services; +using System.Net.Http; + +namespace RocketInsights.DXP.Providers.Contentful.Extensions +{ + public static class DependencyInjectionExtensions + { + public static IServiceCollection AddContentful (this IServiceCollection services, IConfiguration configuration) + { + services.AddSingleton(s => InitContentfulClient(configuration)); + + services.AddSingleton(); + services.AddSingleton(); + + return services; + } + + private static ContentfulClient InitContentfulClient (IConfiguration configuration) + { + var httpClient = new HttpClient(); + ContentfulOptions contentfulOptions = configuration.GetSection(nameof(ContentfulOptions)).Get(); + + ContentfulClient contentfulClient = new ContentfulClient(httpClient, contentfulOptions); + return contentfulClient; + } + } +} diff --git a/src/RocketInsights.DXP.Providers.Contentful/LayoutService.cs b/src/RocketInsights.DXP.Providers.Contentful/LayoutService.cs new file mode 100644 index 0000000..43e0178 --- /dev/null +++ b/src/RocketInsights.DXP.Providers.Contentful/LayoutService.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace RocketInsights.DXP.Providers.Contentful +{ + public class LayoutService + { + } +} diff --git a/src/RocketInsights.DXP.Providers.Contentful/Proxy/ContentfulProxy.cs b/src/RocketInsights.DXP.Providers.Contentful/Proxy/ContentfulProxy.cs new file mode 100644 index 0000000..defca7b --- /dev/null +++ b/src/RocketInsights.DXP.Providers.Contentful/Proxy/ContentfulProxy.cs @@ -0,0 +1,33 @@ +using Contentful.Core; +using Contentful.Core.Models; +using Contentful.Core.Search; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace RocketInsights.DXP.Providers.Contentful.Proxy +{ + public class ContentfulProxy : IContentfulProxy + { + private IContentfulClient ContentfulClient { get; } + + public ContentfulProxy(IContentfulClient contentfulClient) + { + ContentfulClient = contentfulClient; + } + + //TODO: Handle API errors, including rate limiting + + public async Task> GetEntryByIdAsync(string entryId, string locale = null) + { + var query = QueryBuilder>.New; + if (!string.IsNullOrEmpty(locale)) + { + query.LocaleIs(locale); + } + var entry = await ContentfulClient.GetEntry>(entryId, query); + return entry; + } + } +} diff --git a/src/RocketInsights.DXP.Providers.Contentful/Proxy/IContentfulProxy.cs b/src/RocketInsights.DXP.Providers.Contentful/Proxy/IContentfulProxy.cs new file mode 100644 index 0000000..f90528d --- /dev/null +++ b/src/RocketInsights.DXP.Providers.Contentful/Proxy/IContentfulProxy.cs @@ -0,0 +1,13 @@ +using Contentful.Core.Models; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace RocketInsights.DXP.Providers.Contentful.Proxy +{ + public interface IContentfulProxy + { + Task> GetEntryByIdAsync(string entryId, string locale = "*"); + } +} diff --git a/src/RocketInsights.DXP.Providers.Contentful/README.md b/src/RocketInsights.DXP.Providers.Contentful/README.md new file mode 100644 index 0000000..04c4db0 --- /dev/null +++ b/src/RocketInsights.DXP.Providers.Contentful/README.md @@ -0,0 +1,12 @@ +This project relies on configuration settings to be set. These are essentially API keys and related info needed to access the Contentful site. + +While the `Environment` and `SpaceId` are not secret and can be stored elsewhere, greater care should be taken for the `API Key` as it should be kept secure. + +For Development purpose, it can be simpler to just add the block to `User Secrets` (right click on project and select `Manage User Secrets`) +```JS +{ + "ContentfulOptions:DeliveryApiKey": "", + "ContentfulOptions:Environment": "", + "ContentfulOptions:SpaceId": "" +} +``` \ No newline at end of file diff --git a/src/RocketInsights.DXP.Providers.Contentful/RocketInsights.DXP.Providers.Contentful.csproj b/src/RocketInsights.DXP.Providers.Contentful/RocketInsights.DXP.Providers.Contentful.csproj new file mode 100644 index 0000000..46c571e --- /dev/null +++ b/src/RocketInsights.DXP.Providers.Contentful/RocketInsights.DXP.Providers.Contentful.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0 + 0085a9fb-bf75-4b62-a03f-9d9680f176c9 + + + + + + + + + + + + + + diff --git a/src/RocketInsights.sln b/src/RocketInsights.sln index 89555be..cb6ea99 100644 --- a/src/RocketInsights.sln +++ b/src/RocketInsights.sln @@ -25,7 +25,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RocketInsights.DXP.AspNetCo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RocketInsights.DXP.Providers.Kontent", "RocketInsights.DXP.Providers.Kontent\RocketInsights.DXP.Providers.Kontent.csproj", "{CB498A41-AB48-40E1-9E59-D25F4FA35305}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RocketInsights.DXP.Providers.Kontent.Tests", "RocketInsights.DXP.Providers.Kontent.Tests\RocketInsights.DXP.Providers.Kontent.Tests.csproj", "{3FB84C80-6D85-4307-BC03-B83304F86400}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RocketInsights.DXP.Providers.Kontent.Tests", "RocketInsights.DXP.Providers.Kontent.Tests\RocketInsights.DXP.Providers.Kontent.Tests.csproj", "{3FB84C80-6D85-4307-BC03-B83304F86400}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RocketInsights.DXP.Providers.Contentful", "RocketInsights.DXP.Providers.Contentful\RocketInsights.DXP.Providers.Contentful.csproj", "{FF78627C-13D2-4093-B06D-10EC46114339}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RocketInsights.DXP.Providers.Contentful.Tests", "RocketInsights.DXP.Providers.Contentful.Tests\RocketInsights.DXP.Providers.Contentful.Tests.csproj", "{C348A0CB-8618-4686-A281-F59F874CE04C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -69,6 +73,14 @@ Global {3FB84C80-6D85-4307-BC03-B83304F86400}.Debug|Any CPU.Build.0 = Debug|Any CPU {3FB84C80-6D85-4307-BC03-B83304F86400}.Release|Any CPU.ActiveCfg = Release|Any CPU {3FB84C80-6D85-4307-BC03-B83304F86400}.Release|Any CPU.Build.0 = Release|Any CPU + {FF78627C-13D2-4093-B06D-10EC46114339}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF78627C-13D2-4093-B06D-10EC46114339}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF78627C-13D2-4093-B06D-10EC46114339}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF78627C-13D2-4093-B06D-10EC46114339}.Release|Any CPU.Build.0 = Release|Any CPU + {C348A0CB-8618-4686-A281-F59F874CE04C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C348A0CB-8618-4686-A281-F59F874CE04C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C348A0CB-8618-4686-A281-F59F874CE04C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C348A0CB-8618-4686-A281-F59F874CE04C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -82,7 +94,8 @@ Global {A96BA359-7830-4148-916A-10A889ECE470} = {EEBF50A2-9A78-4A59-8D58-FD6EA92FC08E} {19CC8707-2540-4CFB-9265-D9D774300906} = {EEBF50A2-9A78-4A59-8D58-FD6EA92FC08E} {CB498A41-AB48-40E1-9E59-D25F4FA35305} = {EEBF50A2-9A78-4A59-8D58-FD6EA92FC08E} - {3FB84C80-6D85-4307-BC03-B83304F86400} = {EEBF50A2-9A78-4A59-8D58-FD6EA92FC08E} + {FF78627C-13D2-4093-B06D-10EC46114339} = {EEBF50A2-9A78-4A59-8D58-FD6EA92FC08E} + {C348A0CB-8618-4686-A281-F59F874CE04C} = {EEBF50A2-9A78-4A59-8D58-FD6EA92FC08E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0904E15D-0535-4207-B6B6-83A605BB2590}