Compare commits

...

7 Commits

Author SHA1 Message Date
khwezi db6c77ce6a Merge pull request 'taskcancellation' (#131) from taskcancellation into master
Reviewed-on: #131
2026-06-15 12:08:45 +02:00
Khwezi Mngoma 516062ed5d Refactored order item retrieval error message
continuous-integration/drone/pr Build is passing
2026-06-15 12:08:19 +02:00
Khwezi Mngoma 16832ec214 Added GetOrderItems to OrderService 2026-06-15 12:06:33 +02:00
khwezi db431e1682 Merge pull request 'Ensure uninherited types are sealed' (#130) from taskcancellation into master
Reviewed-on: #130
2026-06-15 10:28:41 +02:00
Khwezi Mngoma cf439c5006 Ensure uninherited types are sealed
continuous-integration/drone/pr Build is passing
Resolved mediator source geenrator conflict with tests
2026-06-15 10:27:44 +02:00
khwezi ad2ea48592 Merge pull request 'Hardened certificate loading' (#129) from dataprotection into master
Reviewed-on: #129
2026-06-14 23:34:59 +02:00
Khwezi Mngoma bf36bb6bbc Hardened certificate loading
continuous-integration/drone/pr Build is passing
2026-06-14 23:34:25 +02:00
23 changed files with 87 additions and 41 deletions
@@ -11,7 +11,7 @@
<!-- Quartz Scheduler-->
<ItemGroup>
<PackageReference Include="Bogus" Version="35.6.5" />
<PackageReference Include="Meziantou.Analyzer" Version="3.0.103">
<PackageReference Include="Meziantou.Analyzer" Version="3.0.104">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -13,10 +13,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Mediator.SourceGenerator" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
@@ -32,7 +32,7 @@
<!-- Quartz Scheduler-->
<ItemGroup>
<PackageReference Include="Humanizer" Version="3.0.10" />
<PackageReference Include="Meziantou.Analyzer" Version="3.0.103">
<PackageReference Include="Meziantou.Analyzer" Version="3.0.104">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -203,6 +203,26 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
}
}
public async ValueTask<Result<OrderItem[]>> GetOrderItemsAsync(long orderId, CancellationToken cancellationToken = default)
{
try
{
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var orderItems = await context.OrderItems
.Where(o => o.OrderId == orderId)
.ToListAsync(cancellationToken);
return orderItems.Count > 0
? Result.Ok(orderItems.Select(i => i.ToModel()).ToArray())
: Result.Fail<OrderItem[]>($"Order items not found for order ID {orderId}");
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result<Order[]>> GetOrdersByCustomerAsync(long customerId, CancellationToken cancellationToken = default)
{
try
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.MidrandBooks.Payments.Entities;
[EntityTypeConfiguration<PaymentGatewayConfiguration, PaymentGateway>]
public class PaymentGateway : Models.PaymentGateway;
public sealed class PaymentGateway : Models.PaymentGateway;
@@ -7,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{
/// <inheritdoc />
public partial class Init : Migration
public sealed partial class Init : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -6,7 +6,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{
/// <inheritdoc />
public partial class AddedCategories : Migration
public sealed partial class AddedCategories : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -6,7 +6,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{
/// <inheritdoc />
public partial class AddedProductCategories : Migration
public sealed partial class AddedProductCategories : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -7,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{
/// <inheritdoc />
public partial class AddedPaymentObjects : Migration
public sealed partial class AddedPaymentObjects : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -5,7 +5,7 @@
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{
/// <inheritdoc />
public partial class RemovedPassphraseFromPaymentGateway : Migration
public sealed partial class RemovedPassphraseFromPaymentGateway : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -7,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{
/// <inheritdoc />
public partial class AddedPaymentGatewayLedger : Migration
public sealed partial class AddedPaymentGatewayLedger : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -5,7 +5,7 @@
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{
/// <inheritdoc />
public partial class AddedPayfastPaymentIdToPaymentGatewayLedger : Migration
public sealed partial class AddedPayfastPaymentIdToPaymentGatewayLedger : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -5,7 +5,7 @@
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{
/// <inheritdoc />
public partial class OnlyEmailIsMandatoryOnCustomer : Migration
public sealed partial class OnlyEmailIsMandatoryOnCustomer : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -25,7 +25,6 @@
<!-- Mediator -->
<ItemGroup>
<PackageReference Include="Mediator.Abstractions" Version="3.0.2" />
<PackageReference Include="Mediator.SourceGenerator" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -13,10 +13,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Mediator.SourceGenerator" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
@@ -0,0 +1,16 @@
namespace LiteCharms.Features;
public sealed class CancellationTokenProvider : IDisposable
{
private readonly CancellationTokenSource source = new();
public CancellationToken Token => source.Token;
public void Dispose()
{
source.Cancel();
source.Dispose();
GC.SuppressFinalize(this);
}
}
+8 -2
View File
@@ -54,7 +54,13 @@ public static class Api
public static IServiceCollection AddLiteCharmsWebSecurity(this IServiceCollection services, IConfiguration configuration)
{
var certificate = X509CertificateLoader.LoadPkcs12(Convert.FromBase64String(configuration["DataProtection:Certificate"]!), configuration["DataProtection:Password"]);
var certString = configuration["DataProtection:Certificate"] ?? configuration["DataProtection__Certificate"];
var certPassword = configuration["DataProtection:Password"] ?? configuration["DataProtection__Password"];
if (string.IsNullOrEmpty(certString))
throw new InvalidOperationException("Data Protection Certificate configuration is missing.");
var certificate = X509CertificateLoader.LoadPkcs12(Convert.FromBase64String(certString), certPassword);
services.AddDataProtection().PersistKeysToDbContext<DataProtectionDbContext>()
.ProtectKeysWithCertificate(certificate)
@@ -235,7 +241,7 @@ public static class Api
if (!string.IsNullOrWhiteSpace(urls))
{
string firstUrl = urls.Split(';').FirstOrDefault(s => s.Contains("http://"))!
string firstUrl = urls.Split(';').FirstOrDefault(s => s.Contains("http://", StringComparison.InvariantCultureIgnoreCase))!
.Replace("0.0.0.0", "localhost")
.Replace("*", "localhost")
.Replace("+", "localhost");
+3 -3
View File
@@ -19,7 +19,7 @@ public static class S3
AuthenticationRegion = configuration.GetSection($"{BookshopS3SettingsSection}:Region").Value,
ForcePathStyle = true,
EndpointDiscoveryEnabled = true,
UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://")
UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://", StringComparison.InvariantCultureIgnoreCase),
}));
services.AddKeyedScoped<IS3Service, BookshopS3Service>(BookshopBucketName);
@@ -36,7 +36,7 @@ public static class S3
AuthenticationRegion = configuration.GetSection($"{BookshopInvoicesS3SettingsSection}:Region").Value,
ForcePathStyle = true,
EndpointDiscoveryEnabled = true,
UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://")
UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://", StringComparison.InvariantCultureIgnoreCase),
}));
services.AddKeyedScoped<IS3Service, BookshopInvoicesS3Service>(BookshopInvoicesBucketName);
@@ -53,7 +53,7 @@ public static class S3
AuthenticationRegion = configuration.GetSection($"{BookshopQuotesS3SettingsSection}:Region").Value,
ForcePathStyle = true,
EndpointDiscoveryEnabled = true,
UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://")
UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://", StringComparison.InvariantCultureIgnoreCase),
}));
services.AddKeyedScoped<IS3Service, BookshopQuotesS3Service>(BookshopQuotesBucketName);
@@ -0,0 +1,13 @@
namespace LiteCharms.Features.Extensions;
public static class TaskCancellation
{
public static IServiceCollection AddCancellationToken(this IServiceCollection services)
{
services.AddScoped<CancellationTokenProvider>();
services.AddScoped(typeof(CancellationToken),
provider => provider.GetRequiredService<CancellationTokenProvider>().Token);
return services;
}
}
@@ -79,7 +79,7 @@
<!-- Quartz Scheduler-->
<ItemGroup>
<PackageReference Include="Hashids.net" Version="1.7.0" />
<PackageReference Include="Meziantou.Analyzer" Version="3.0.103">
<PackageReference Include="Meziantou.Analyzer" Version="3.0.104">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Postgres;
public class DataProtectionDbContext(DbContextOptions<DataProtectionDbContext> options) : DbContext(options), IDataProtectionKeyContext
public sealed class DataProtectionDbContext(DbContextOptions<DataProtectionDbContext> options) : DbContext(options), IDataProtectionKeyContext
{
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
@@ -2,7 +2,7 @@
namespace LiteCharms.Features.Postgres;
public class DataProtectionDbContextFactory : IDesignTimeDbContextFactory<DataProtectionDbContext>
public sealed class DataProtectionDbContextFactory : IDesignTimeDbContextFactory<DataProtectionDbContext>
{
public DataProtectionDbContext CreateDbContext(string[] args)
{
@@ -6,7 +6,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace LiteCharms.Features.Postgres.Migrations
{
/// <inheritdoc />
public partial class Init : Migration
public sealed partial class Init : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)