diff --git a/LiteCharms.Features.MidrandBooks.Tests/AuthorServiceFeatureTests.cs b/LiteCharms.Features.MidrandBooks.Tests/AuthorServiceFeatureTests.cs index d74aac1..8116065 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/AuthorServiceFeatureTests.cs +++ b/LiteCharms.Features.MidrandBooks.Tests/AuthorServiceFeatureTests.cs @@ -1,7 +1,7 @@ using LiteCharms.Features.MidrandBooks.Authors; using LiteCharms.Features.MidrandBooks.Authors.Models; -using LiteCharms.Features.MidrandBooks.Tests.Common; using LiteCharms.Features.Models; +using LiteCharms.Features.Tests.Common; namespace LiteCharms.Features.MidrandBooks.Tests; diff --git a/LiteCharms.Features.MidrandBooks.Tests/BooksServiceFeatureTests.cs b/LiteCharms.Features.MidrandBooks.Tests/BooksServiceFeatureTests.cs index d65e0d6..57e4474 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/BooksServiceFeatureTests.cs +++ b/LiteCharms.Features.MidrandBooks.Tests/BooksServiceFeatureTests.cs @@ -1,5 +1,5 @@ using LiteCharms.Features.MidrandBooks.AuthorBooks; -using LiteCharms.Features.MidrandBooks.Tests.Common; +using LiteCharms.Features.Tests.Common; namespace LiteCharms.Features.MidrandBooks.Tests; diff --git a/LiteCharms.Features.MidrandBooks.Tests/CategoryServiceFeatureTests.cs b/LiteCharms.Features.MidrandBooks.Tests/CategoryServiceFeatureTests.cs index c1f103c..f84b3f2 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/CategoryServiceFeatureTests.cs +++ b/LiteCharms.Features.MidrandBooks.Tests/CategoryServiceFeatureTests.cs @@ -1,5 +1,5 @@ using LiteCharms.Features.MidrandBooks.Categories; -using LiteCharms.Features.MidrandBooks.Tests.Common; +using LiteCharms.Features.Tests.Common; namespace LiteCharms.Features.MidrandBooks.Tests; diff --git a/LiteCharms.Features.MidrandBooks.Tests/CustomerServiceFeatureTests.cs b/LiteCharms.Features.MidrandBooks.Tests/CustomerServiceFeatureTests.cs index 5edea77..f102a78 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/CustomerServiceFeatureTests.cs +++ b/LiteCharms.Features.MidrandBooks.Tests/CustomerServiceFeatureTests.cs @@ -1,6 +1,6 @@ using LiteCharms.Features.MidrandBooks.Customers; using LiteCharms.Features.MidrandBooks.Customers.Models; -using LiteCharms.Features.MidrandBooks.Tests.Common; +using LiteCharms.Features.Tests.Common; namespace LiteCharms.Features.MidrandBooks.Tests; diff --git a/LiteCharms.Features.MidrandBooks.Tests/LiteCharms.Features.MidrandBooks.Tests.csproj b/LiteCharms.Features.MidrandBooks.Tests/LiteCharms.Features.MidrandBooks.Tests.csproj index a4b86f0..b8084c2 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/LiteCharms.Features.MidrandBooks.Tests.csproj +++ b/LiteCharms.Features.MidrandBooks.Tests/LiteCharms.Features.MidrandBooks.Tests.csproj @@ -35,6 +35,7 @@ + @@ -45,10 +46,4 @@ - - - Always - - - \ No newline at end of file diff --git a/LiteCharms.Features.MidrandBooks.Tests/OrderServiceFeatureTests.cs b/LiteCharms.Features.MidrandBooks.Tests/OrderServiceFeatureTests.cs index ef09044..1e01faa 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/OrderServiceFeatureTests.cs +++ b/LiteCharms.Features.MidrandBooks.Tests/OrderServiceFeatureTests.cs @@ -1,7 +1,7 @@ using LiteCharms.Features.MidrandBooks.Orders; using LiteCharms.Features.MidrandBooks.Orders.Models; -using LiteCharms.Features.MidrandBooks.Tests.Common; using LiteCharms.Features.Models; +using LiteCharms.Features.Tests.Common; namespace LiteCharms.Features.MidrandBooks.Tests; diff --git a/LiteCharms.Features.MidrandBooks.Tests/PageServiceFeatureTests.cs b/LiteCharms.Features.MidrandBooks.Tests/PageServiceFeatureTests.cs index a98c368..afecd71 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/PageServiceFeatureTests.cs +++ b/LiteCharms.Features.MidrandBooks.Tests/PageServiceFeatureTests.cs @@ -1,5 +1,5 @@ using LiteCharms.Features.MidrandBooks.Pages; -using LiteCharms.Features.MidrandBooks.Tests.Common; +using LiteCharms.Features.Tests.Common; namespace LiteCharms.Features.MidrandBooks.Tests; diff --git a/LiteCharms.Features.MidrandBooks.Tests/PayfastServiceFeatureTests.cs b/LiteCharms.Features.MidrandBooks.Tests/PayfastServiceFeatureTests.cs index 65f4eed..7923a6b 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/PayfastServiceFeatureTests.cs +++ b/LiteCharms.Features.MidrandBooks.Tests/PayfastServiceFeatureTests.cs @@ -1,6 +1,6 @@ using LiteCharms.Features.MidrandBooks.Payments; using LiteCharms.Features.MidrandBooks.Payments.Models; -using LiteCharms.Features.MidrandBooks.Tests.Common; +using LiteCharms.Features.Tests.Common; namespace LiteCharms.Features.MidrandBooks.Tests; diff --git a/LiteCharms.Features.MidrandBooks.Tests/PaymentServiceFeatureTests.cs b/LiteCharms.Features.MidrandBooks.Tests/PaymentServiceFeatureTests.cs index 3681116..c1514b1 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/PaymentServiceFeatureTests.cs +++ b/LiteCharms.Features.MidrandBooks.Tests/PaymentServiceFeatureTests.cs @@ -1,6 +1,6 @@ using LiteCharms.Features.MidrandBooks.Payments; using LiteCharms.Features.MidrandBooks.Payments.Models; -using LiteCharms.Features.MidrandBooks.Tests.Common; +using LiteCharms.Features.Tests.Common; namespace LiteCharms.Features.MidrandBooks.Tests; diff --git a/LiteCharms.Features.MidrandBooks.Tests/ProductServiceFeatureTests.cs b/LiteCharms.Features.MidrandBooks.Tests/ProductServiceFeatureTests.cs index 827025f..860f059 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/ProductServiceFeatureTests.cs +++ b/LiteCharms.Features.MidrandBooks.Tests/ProductServiceFeatureTests.cs @@ -1,7 +1,7 @@ using LiteCharms.Features.MidrandBooks.Products; using LiteCharms.Features.MidrandBooks.Products.Models; -using LiteCharms.Features.MidrandBooks.Tests.Common; using LiteCharms.Features.Models; +using LiteCharms.Features.Tests.Common; namespace LiteCharms.Features.MidrandBooks.Tests; diff --git a/LiteCharms.Features.MidrandBooks.Tests/Common/Fixture.cs b/LiteCharms.Features.Tests.Common/Fixture.cs similarity index 85% rename from LiteCharms.Features.MidrandBooks.Tests/Common/Fixture.cs rename to LiteCharms.Features.Tests.Common/Fixture.cs index a5a759f..507d4e1 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/Common/Fixture.cs +++ b/LiteCharms.Features.Tests.Common/Fixture.cs @@ -1,9 +1,7 @@ using LiteCharms.Features.Extensions; -using LiteCharms.Features.MidrandBooks.Abstractions; using LiteCharms.Features.MidrandBooks.Extensions; -using Microsoft.VisualStudio.TestPlatform.TestHost; -namespace LiteCharms.Features.MidrandBooks.Tests.Common; +namespace LiteCharms.Features.Tests.Common; public class Fixture : IDisposable { @@ -27,9 +25,8 @@ public class Fixture : IDisposable .Build(); Services = new ServiceCollection() - .AddHttpClient() - .AddMediator() .AddLogging() + .AddMediator() .AddEmailServiceBus() .AddGarageS3(Configuration) .AddMidrandShopDatabase(Configuration) @@ -37,7 +34,9 @@ public class Fixture : IDisposable .AddSingleton(Configuration) .AddShopServices() .AddHashServices(Configuration) - .BuildServiceProvider(); + .AddLiteCharmsApiSecurity(Configuration) + .AddSecurityApiSdk(Configuration) + .BuildServiceProvider(); ; Mediator = Services.GetRequiredService(); } diff --git a/LiteCharms.Features.MidrandBooks.Tests/Common/IntegrationFactAttribute.cs b/LiteCharms.Features.Tests.Common/IntegrationFactAttribute.cs similarity index 77% rename from LiteCharms.Features.MidrandBooks.Tests/Common/IntegrationFactAttribute.cs rename to LiteCharms.Features.Tests.Common/IntegrationFactAttribute.cs index 304f78e..f62577c 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/Common/IntegrationFactAttribute.cs +++ b/LiteCharms.Features.Tests.Common/IntegrationFactAttribute.cs @@ -1,4 +1,4 @@ -namespace LiteCharms.Features.MidrandBooks.Tests.Common; +namespace LiteCharms.Features.Tests.Common; public class IntegrationFactAttribute : FactAttribute { diff --git a/LiteCharms.Features.Tests.Common/LiteCharms.Features.Tests.Common.csproj b/LiteCharms.Features.Tests.Common/LiteCharms.Features.Tests.Common.csproj new file mode 100644 index 0000000..d90376e --- /dev/null +++ b/LiteCharms.Features.Tests.Common/LiteCharms.Features.Tests.Common.csproj @@ -0,0 +1,80 @@ + + + + net10.0 + enable + enable + 0521f45a-eba0-457f-bb5e-c3680f65d8b1 + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/LiteCharms.Features.MidrandBooks.Tests/appsettings.json b/LiteCharms.Features.Tests.Common/appsettings.json similarity index 75% rename from LiteCharms.Features.MidrandBooks.Tests/appsettings.json rename to LiteCharms.Features.Tests.Common/appsettings.json index b6f6ba7..634da64 100644 --- a/LiteCharms.Features.MidrandBooks.Tests/appsettings.json +++ b/LiteCharms.Features.Tests.Common/appsettings.json @@ -1,4 +1,13 @@ { + "LiteCharmsSettings": { + "Authority": "https://sts.security.khongisa.co.za", + "Audience": "midrandbooks-api" + }, + "LiteCharmsClientSettings": { + "Authority": "https://sts.security.khongisa.co.za", + "GrantType": "client_credentials", + "Scope": "midrandbooks-api" + }, "ValidPayfastHosts": [ "www.payfast.co.za", "sandbox.payfast.co.za", @@ -26,7 +35,6 @@ "UseSsl": true }, "Monitoring": { - "ApiKey": "", "Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889", "ServiceName": "LiteCharms.LeadGenerator" }, diff --git a/LiteCharms.Features.Tests/Fixture.cs b/LiteCharms.Features.Tests/Fixture.cs deleted file mode 100644 index 1ad8e4a..0000000 --- a/LiteCharms.Features.Tests/Fixture.cs +++ /dev/null @@ -1,36 +0,0 @@ -using LiteCharms.Features.Extensions; - -namespace LiteCharms.Features.Tests; - -public class Fixture : IDisposable -{ - public IConfiguration Configuration { get; set; } - - public IServiceProvider Services { get; set; } - - public IMediator Mediator { get; set; } - - public Fixture() - { - Configuration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddUserSecrets() - .AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"), optional: true, reloadOnChange: true) - .AddEnvironmentVariables() - .Build(); - - Services = new ServiceCollection() - .AddMediator() - .AddLogging() - .AddEmailServiceBus() - .AddGarageS3(Configuration) - .AddEmailServices(Configuration) - .AddSingleton(Configuration) - .AddHashServices(Configuration) - .BuildServiceProvider(); - - Mediator = Services.GetRequiredService(); - } - - public void Dispose() { } -} diff --git a/LiteCharms.Features.Tests/HashServiceFeatureTests.cs b/LiteCharms.Features.Tests/HashServiceFeatureTests.cs index 844c7fb..8f2a331 100644 --- a/LiteCharms.Features.Tests/HashServiceFeatureTests.cs +++ b/LiteCharms.Features.Tests/HashServiceFeatureTests.cs @@ -1,4 +1,5 @@ using LiteCharms.Features.Hasher; +using LiteCharms.Features.Tests.Common; namespace LiteCharms.Features.Tests; diff --git a/LiteCharms.Features.Tests/LiteCharms.Features.Tests.csproj b/LiteCharms.Features.Tests/LiteCharms.Features.Tests.csproj index 2540fae..ef7e0dc 100644 --- a/LiteCharms.Features.Tests/LiteCharms.Features.Tests.csproj +++ b/LiteCharms.Features.Tests/LiteCharms.Features.Tests.csproj @@ -36,6 +36,7 @@ + @@ -43,10 +44,4 @@ - - - Always - - - \ No newline at end of file diff --git a/LiteCharms.Features.Tests/LiteCharmsApiFeatureTests.cs b/LiteCharms.Features.Tests/LiteCharmsApiFeatureTests.cs new file mode 100644 index 0000000..d33f897 --- /dev/null +++ b/LiteCharms.Features.Tests/LiteCharmsApiFeatureTests.cs @@ -0,0 +1,19 @@ +using LiteCharms.Features.Api; +using LiteCharms.Features.Tests.Common; + +namespace LiteCharms.Features.Tests; + +public sealed class LiteCharmsApiFeatureTests(Fixture fixture) : IClassFixture +{ + private readonly TokenService tokenService = fixture.Services.GetRequiredService(); + + [IntegrationFact] + public async Task TokenService_GenerateTokenAsync_ShouldReturn_TokenInResult() + { + var result = await tokenService.GenerateAsync(fixture.CancellationToken); + + Assert.True(result.IsSuccess); + Assert.NotNull(result.Value); + Assert.NotEmpty(result.Value.AccessToken!); + } +} diff --git a/LiteCharms.Features.Tests/S3ServiceFeatureTests.cs b/LiteCharms.Features.Tests/S3ServiceFeatureTests.cs index 8af9a34..b355eac 100644 --- a/LiteCharms.Features.Tests/S3ServiceFeatureTests.cs +++ b/LiteCharms.Features.Tests/S3ServiceFeatureTests.cs @@ -1,4 +1,5 @@ using LiteCharms.Features.S3.Abstractions; +using LiteCharms.Features.Tests.Common; namespace LiteCharms.Features.Tests; diff --git a/LiteCharms.Features.Tests/appsettings.json b/LiteCharms.Features.Tests/appsettings.json deleted file mode 100644 index 1066af9..0000000 --- a/LiteCharms.Features.Tests/appsettings.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "BookshopS3Settings": { - "ServiceUrl": "http://192.168.1.177:30900", - "Region": "garage", - "BucketName": "bookshop", - "CdnBaseUrl": "https://bookshop.cdn.khongisa.co.za" - }, - "BookshopQuotesS3Settings": { - "ServiceUrl": "http://192.168.1.177:30900", - "Region": "garage", - "BucketName": "bookshop.quotes", - "CdnBaseUrl": "https://bookshop.quotes.cdn.khongisa.co.za" - }, - "Email": { - "Credentials": { - "Username": "shop@litecharms.co.za" - }, - "Port": 465, - "Host": "mail.litecharms.co.za", - "UseSsl": true - }, - "Monitoring": { - "ApiKey": "", - "Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889", - "ServiceName": "LiteCharms.LeadGenerator" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/LiteCharms.Features/Api/Configuration/LiteCharmsClientSettings.cs b/LiteCharms.Features/Api/Configuration/LiteCharmsClientSettings.cs new file mode 100644 index 0000000..aff2931 --- /dev/null +++ b/LiteCharms.Features/Api/Configuration/LiteCharmsClientSettings.cs @@ -0,0 +1,14 @@ +namespace LiteCharms.Features.Api.Configuration; + +public sealed class LiteCharmsClientSettings +{ + public string? Authority { get; set; } + + public string? GrantType { get; set; } + + public string? ClientId { get; set; } + + public string? ClientSecret { get; set; } + + public string? Scope { get; set; } +} diff --git a/LiteCharms.Features/Api/Models/TokenErrorResponse.cs b/LiteCharms.Features/Api/Models/TokenErrorResponse.cs new file mode 100644 index 0000000..0d31195 --- /dev/null +++ b/LiteCharms.Features/Api/Models/TokenErrorResponse.cs @@ -0,0 +1,13 @@ +namespace LiteCharms.Features.Api.Models; + +public sealed class TokenErrorResponse +{ + [JsonPropertyName("error")] + public string? Error { get; set; } + + [JsonPropertyName("error_description")] + public string? ErrorDescription { get; set; } + + [JsonPropertyName("error_uri")] + public string? ErrorUri { get; set; } +} diff --git a/LiteCharms.Features/Api/Models/TokenRequest.cs b/LiteCharms.Features/Api/Models/TokenRequest.cs new file mode 100644 index 0000000..68b1abc --- /dev/null +++ b/LiteCharms.Features/Api/Models/TokenRequest.cs @@ -0,0 +1,20 @@ +namespace LiteCharms.Features.Api.Models; + +public sealed class TokenRequest +{ + [JsonPropertyName("grant_type")] + [AliasAs("grant_type")] + public string? GrantType { get; set; } + + [JsonPropertyName("client_id")] + [AliasAs("client_id")] + public string? ClientId { get; set; } + + [JsonPropertyName("client_secret")] + [AliasAs("client_secret")] + public string? ClientSecret { get; set; } + + [JsonPropertyName("scope")] + [AliasAs("scope")] + public string? Scope { get; set; } +} diff --git a/LiteCharms.Features/Api/Models/TokenResponse.cs b/LiteCharms.Features/Api/Models/TokenResponse.cs new file mode 100644 index 0000000..3fb090a --- /dev/null +++ b/LiteCharms.Features/Api/Models/TokenResponse.cs @@ -0,0 +1,17 @@ +namespace LiteCharms.Features.Api.Models; + +public sealed class TokenResponse +{ + [JsonPropertyName("access_token")] + public string? AccessToken { get; set; } + + [JsonPropertyName("expires_in")] + public int ExpiresIn { get; set; } + + [JsonPropertyName("token_type")] + public string? TokenType { get; set; } + + [JsonPropertyName("scope")] + public string? Scope { get; set; } +} + diff --git a/LiteCharms.Features/Api/Sdk/IConnectApi.cs b/LiteCharms.Features/Api/Sdk/IConnectApi.cs new file mode 100644 index 0000000..b7d3430 --- /dev/null +++ b/LiteCharms.Features/Api/Sdk/IConnectApi.cs @@ -0,0 +1,10 @@ +using LiteCharms.Features.Api.Models; + +namespace LiteCharms.Features.Api.Sdk; + +public interface IConnectApi +{ + [Post("/connect/token")] + ValueTask GetToken([Body(BodySerializationMethod.UrlEncoded)] TokenRequest request, + CancellationToken cancellationToken = default); +} diff --git a/LiteCharms.Features/Api/TokenService.cs b/LiteCharms.Features/Api/TokenService.cs new file mode 100644 index 0000000..c2dcdfc --- /dev/null +++ b/LiteCharms.Features/Api/TokenService.cs @@ -0,0 +1,67 @@ +using LiteCharms.Features.Abstractions; +using LiteCharms.Features.Api.Configuration; +using LiteCharms.Features.Api.Models; +using LiteCharms.Features.Api.Sdk; + +namespace LiteCharms.Features.Api; + +public sealed class TokenService(IConnectApi connectApi, IOptions clientOptions) : IService +{ + private readonly LiteCharmsClientSettings clientSettings = clientOptions.Value; + + public async Task> GenerateAsync(CancellationToken cancellationToken = default) + { + try + { + var request = new TokenRequest + { + ClientId = clientSettings.ClientId, + ClientSecret = clientSettings.ClientSecret, + GrantType = clientSettings.GrantType, + Scope = clientSettings.Scope, + }; + + using var response = await connectApi.GetToken(request, cancellationToken); + + var contentRaw = await response.Content.ReadAsStringAsync(cancellationToken); + + if (string.IsNullOrWhiteSpace(contentRaw)) + return Result.Fail(new Error($"The authentication endpoint returned an empty payload. Status code: {response.StatusCode}")); + + if (response.IsSuccessStatusCode) + { + var tokenResponse = JsonSerializer.Deserialize(contentRaw); + + return !string.IsNullOrWhiteSpace(tokenResponse?.AccessToken) + ? Result.Ok(tokenResponse) + : Result.Fail(new Error("Authentication succeeded, but no access token was found in the response payload.")); + } + + try + { + var errorResult = JsonSerializer.Deserialize(contentRaw); + + if (errorResult != null) + { + string summary = $"{errorResult.Error}: {errorResult.ErrorDescription}"; + + return Result.Fail(new Error(summary)); + } + } + catch + { + return Result.Fail(new Error($"Authentication failed: {contentRaw}")); + } + + return Result.Fail(new Error($"Authentication failed with status code: {response.StatusCode}")); + } + catch (OperationCanceledException ex) + { + return Result.Fail(new Error("The token generation request was canceled.").CausedBy(ex)); + } + catch (Exception ex) + { + return Result.Fail(new Error(ex.Message).CausedBy(ex)); + } + } +} \ No newline at end of file diff --git a/LiteCharms.Features/Extensions/Api.cs b/LiteCharms.Features/Extensions/Api.cs index 1e60834..9fe6af4 100644 --- a/LiteCharms.Features/Extensions/Api.cs +++ b/LiteCharms.Features/Extensions/Api.cs @@ -1,7 +1,7 @@ using LiteCharms.Features.Abstractions; using LiteCharms.Features.Api; using LiteCharms.Features.Api.Configuration; -using Microsoft.AspNetCore.Authentication.JwtBearer; +using LiteCharms.Features.Api.Sdk; namespace LiteCharms.Features.Extensions; @@ -9,6 +9,36 @@ public static class Api { public const string Books = nameof(Books); public const string Payments = nameof(Payments); + + public static IServiceCollection AddSecurityApiSdk(this IServiceCollection services, IConfiguration configuration) + { + var configSection = configuration.GetSection(nameof(LiteCharmsClientSettings)); + + var authOptions = new LiteCharmsClientSettings(); + configSection.Bind(authOptions); + + services.Configure(configSection); + + if (string.IsNullOrWhiteSpace(authOptions.Authority)) + return services; + + if (!authOptions.Authority.EndsWith("/", StringComparison.Ordinal)) authOptions.Authority += "/"; + + services.AddRefitClient() + .ConfigureHttpClient(config => + { + config.BaseAddress = new Uri(authOptions.Authority); + config.Timeout = TimeSpan.FromSeconds(15); + }) + .AddStandardResilienceHandler(options => + { + options.Retry.MaxRetryAttempts = 3; + options.Retry.Delay = TimeSpan.FromSeconds(1); + options.Retry.BackoffType = Polly.DelayBackoffType.Exponential; + }); + + return services; + } public static IServiceCollection AddLiteCharmsWebSecurity(this IServiceCollection services, IConfiguration configuration) { diff --git a/LiteCharms.Features/LiteCharms.Features.csproj b/LiteCharms.Features/LiteCharms.Features.csproj index b2374c2..f0ee577 100644 --- a/LiteCharms.Features/LiteCharms.Features.csproj +++ b/LiteCharms.Features/LiteCharms.Features.csproj @@ -37,11 +37,23 @@ + + + + + + + + + + + + @@ -183,6 +195,7 @@ + diff --git a/LiteCharmsShared.slnx b/LiteCharmsShared.slnx index be7cb5c..de05e99 100644 --- a/LiteCharmsShared.slnx +++ b/LiteCharmsShared.slnx @@ -12,6 +12,7 @@ +