Compare commits

..

11 Commits

Author SHA1 Message Date
khwezi 0a9a459892 Merge pull request 'Refactored AddLiteCharmsWebSecurity to force a session recycling of keys on other pods' (#128) from dataprotection into master
Reviewed-on: #128
2026-06-14 23:10:42 +02:00
khwezi f5a69de0a0 Merge pull request 'Refactored CheckSameSite' (#127) from dataprotection into master
Reviewed-on: #127
2026-06-14 22:52:05 +02:00
khwezi fc4db32f20 Merge pull request 'dataprotection' (#126) from dataprotection into master
Reviewed-on: #126
2026-06-14 13:12:20 +02:00
khwezi a9aa0a675a Merge pull request 'Added cookie policies on AddLiteCharmsWebSecurity' (#125) from dataprotection into master
Reviewed-on: #125
2026-06-14 12:51:25 +02:00
khwezi a763e5e40e Merge pull request 'Added certificate protected data protection keys' (#124) from dataprotection into master
Reviewed-on: #124
2026-06-14 11:27:32 +02:00
khwezi 1ef5e52ed9 Merge pull request 'Refactored AddDataProtectionDatabase' (#123) from dataprotection into master
Reviewed-on: #123
2026-06-14 10:12:08 +02:00
khwezi c06cf13add Merge pull request 'Added data protection database based support' (#122) from dataprotection into master
Reviewed-on: #122
2026-06-14 09:58:17 +02:00
khwezi 92c60e6616 Merge pull request 'Refactored AddLiteCharmsWebSecurity to be OS aware when it handles data protection keys' (#121) from dataprotection into master
Reviewed-on: #121
2026-06-13 23:41:30 +02:00
khwezi 21788c66a3 Merge pull request 'Added data protection keys to web iodc middleware regirtration method' (#120) from dataprotection into master
Reviewed-on: #120
2026-06-13 23:34:35 +02:00
khwezi 54ef7a6e5f Merge pull request 'Fixed cookie and header collision issue on signout' (#119) from logout-fix into master
Reviewed-on: #119
2026-06-13 23:07:22 +02:00
khwezi 6594e0aecd Merge pull request 'Fixed the redirect URI on logout so its passed by the caller' (#118) from logout-fix into master
Reviewed-on: #118
2026-06-13 22:51:48 +02:00
23 changed files with 41 additions and 87 deletions
@@ -11,7 +11,7 @@
<!-- Quartz Scheduler--> <!-- Quartz Scheduler-->
<ItemGroup> <ItemGroup>
<PackageReference Include="Bogus" Version="35.6.5" /> <PackageReference Include="Bogus" Version="35.6.5" />
<PackageReference Include="Meziantou.Analyzer" Version="3.0.104"> <PackageReference Include="Meziantou.Analyzer" Version="3.0.103">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -13,6 +13,10 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </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="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5"> <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
@@ -32,7 +32,7 @@
<!-- Quartz Scheduler--> <!-- Quartz Scheduler-->
<ItemGroup> <ItemGroup>
<PackageReference Include="Humanizer" Version="3.0.10" /> <PackageReference Include="Humanizer" Version="3.0.10" />
<PackageReference Include="Meziantou.Analyzer" Version="3.0.104"> <PackageReference Include="Meziantou.Analyzer" Version="3.0.103">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -43,7 +43,7 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken)) if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
return Result.Fail<long>("Order not found."); return Result.Fail<long>("Order not found.");
if (!await context.Books.AnyAsync(ab => ab.Id == request.AuthorBookId, cancellationToken)) if(!await context.Books.AnyAsync(ab => ab.Id == request.AuthorBookId, cancellationToken))
return Result.Fail<long>("Author book not found."); return Result.Fail<long>("Author book not found.");
if (!await context.Prices.AnyAsync(pp => pp.Id == request.ProductPriceId, cancellationToken)) if (!await context.Prices.AnyAsync(pp => pp.Id == request.ProductPriceId, cancellationToken))
@@ -51,7 +51,7 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
var existingItem = await context.OrderItems.FirstOrDefaultAsync(i => i.ProductPriceId == request.ProductPriceId && i.OrderId == orderId, cancellationToken); var existingItem = await context.OrderItems.FirstOrDefaultAsync(i => i.ProductPriceId == request.ProductPriceId && i.OrderId == orderId, cancellationToken);
if (existingItem is not null) if(existingItem is not null)
{ {
existingItem.Quantity += request.Quantity; existingItem.Quantity += request.Quantity;
@@ -82,7 +82,7 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
{ {
try try
{ {
if (items.Length == 0) if(items.Length == 0)
return Result.Fail("No items to add."); return Result.Fail("No items to add.");
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
@@ -203,33 +203,13 @@ 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) public async ValueTask<Result<Order[]>> GetOrdersByCustomerAsync(long customerId, CancellationToken cancellationToken = default)
{ {
try try
{ {
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken)) if(!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
return Result.Fail<Order[]>("Customer not found."); return Result.Fail<Order[]>("Customer not found.");
var orders = await context.Orders var orders = await context.Orders
@@ -296,16 +276,16 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
{ {
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken)) if(!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken))
return Result.Fail("Order not found."); return Result.Fail("Order not found.");
if (!await context.Addresses.AnyAsync(a => a.Id == request.AddressId, cancellationToken)) if(!await context.Addresses.AnyAsync(a => a.Id == request.AddressId, cancellationToken))
return Result.Fail("Address not found."); return Result.Fail("Address not found.");
if (!await context.ShippingProviders.AnyAsync(sp => sp.Id == request.ShippingProviderId && sp.Enabled, cancellationToken)) if(!await context.ShippingProviders.AnyAsync(sp => sp.Id == request.ShippingProviderId && sp.Enabled, cancellationToken))
return Result.Fail("Shipping provider not found or disabled."); return Result.Fail("Shipping provider not found or disabled.");
if (await context.Shippings.AnyAsync(s => s.OrderId == orderId, cancellationToken)) if(await context.Shippings.AnyAsync(s => s.OrderId == orderId, cancellationToken))
return Result.Fail("Shipping already exists for this order."); return Result.Fail("Shipping already exists for this order.");
var shipping = context.Shippings.Add(new Entities.Shipping var shipping = context.Shippings.Add(new Entities.Shipping
@@ -418,7 +398,7 @@ public sealed class OrderService(IDbContextFactory<MidrandBooksDbContext> contex
{ {
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (await context.ShippingProviders.AnyAsync(sp => sp.Type == request.Type, cancellationToken)) if(await context.ShippingProviders.AnyAsync(sp => sp.Type == request.Type, cancellationToken))
return Result.Fail("Shipping provider with the same type already exists."); return Result.Fail("Shipping provider with the same type already exists.");
var shippingProvider = context.ShippingProviders.Add(new Entities.ShippingProvider var shippingProvider = context.ShippingProviders.Add(new Entities.ShippingProvider
@@ -1,4 +1,4 @@
namespace LiteCharms.Features.MidrandBooks.Payments.Entities; namespace LiteCharms.Features.MidrandBooks.Payments.Entities;
[EntityTypeConfiguration<PaymentGatewayConfiguration, PaymentGateway>] [EntityTypeConfiguration<PaymentGatewayConfiguration, PaymentGateway>]
public sealed class PaymentGateway : Models.PaymentGateway; public class PaymentGateway : Models.PaymentGateway;
@@ -7,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public sealed partial class Init : Migration public partial class Init : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
@@ -6,7 +6,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public sealed partial class AddedCategories : Migration public partial class AddedCategories : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
@@ -6,7 +6,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public sealed partial class AddedProductCategories : Migration public partial class AddedProductCategories : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
@@ -7,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public sealed partial class AddedPaymentObjects : Migration public partial class AddedPaymentObjects : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
@@ -5,7 +5,7 @@
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public sealed partial class RemovedPassphraseFromPaymentGateway : Migration public partial class RemovedPassphraseFromPaymentGateway : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
@@ -7,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public sealed partial class AddedPaymentGatewayLedger : Migration public partial class AddedPaymentGatewayLedger : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
@@ -5,7 +5,7 @@
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public sealed partial class AddedPayfastPaymentIdToPaymentGatewayLedger : Migration public partial class AddedPayfastPaymentIdToPaymentGatewayLedger : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
@@ -5,7 +5,7 @@
namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations namespace LiteCharms.Features.MidrandBooks.Postgres.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public sealed partial class OnlyEmailIsMandatoryOnCustomer : Migration public partial class OnlyEmailIsMandatoryOnCustomer : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
@@ -25,6 +25,7 @@
<!-- Mediator --> <!-- Mediator -->
<ItemGroup> <ItemGroup>
<PackageReference Include="Mediator.Abstractions" Version="3.0.2" />
<PackageReference Include="Mediator.SourceGenerator" Version="3.0.2"> <PackageReference Include="Mediator.SourceGenerator" Version="3.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -13,6 +13,10 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </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="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5"> <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
@@ -1,16 +0,0 @@
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);
}
}
+2 -8
View File
@@ -54,13 +54,7 @@ public static class Api
public static IServiceCollection AddLiteCharmsWebSecurity(this IServiceCollection services, IConfiguration configuration) public static IServiceCollection AddLiteCharmsWebSecurity(this IServiceCollection services, IConfiguration configuration)
{ {
var certString = configuration["DataProtection:Certificate"] ?? configuration["DataProtection__Certificate"]; var certificate = X509CertificateLoader.LoadPkcs12(Convert.FromBase64String(configuration["DataProtection:Certificate"]!), configuration["DataProtection:Password"]);
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>() services.AddDataProtection().PersistKeysToDbContext<DataProtectionDbContext>()
.ProtectKeysWithCertificate(certificate) .ProtectKeysWithCertificate(certificate)
@@ -241,7 +235,7 @@ public static class Api
if (!string.IsNullOrWhiteSpace(urls)) if (!string.IsNullOrWhiteSpace(urls))
{ {
string firstUrl = urls.Split(';').FirstOrDefault(s => s.Contains("http://", StringComparison.InvariantCultureIgnoreCase))! string firstUrl = urls.Split(';').FirstOrDefault(s => s.Contains("http://"))!
.Replace("0.0.0.0", "localhost") .Replace("0.0.0.0", "localhost")
.Replace("*", "localhost") .Replace("*", "localhost")
.Replace("+", "localhost"); .Replace("+", "localhost");
+3 -3
View File
@@ -19,7 +19,7 @@ public static class S3
AuthenticationRegion = configuration.GetSection($"{BookshopS3SettingsSection}:Region").Value, AuthenticationRegion = configuration.GetSection($"{BookshopS3SettingsSection}:Region").Value,
ForcePathStyle = true, ForcePathStyle = true,
EndpointDiscoveryEnabled = true, EndpointDiscoveryEnabled = true,
UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://", StringComparison.InvariantCultureIgnoreCase), UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://")
})); }));
services.AddKeyedScoped<IS3Service, BookshopS3Service>(BookshopBucketName); services.AddKeyedScoped<IS3Service, BookshopS3Service>(BookshopBucketName);
@@ -36,7 +36,7 @@ public static class S3
AuthenticationRegion = configuration.GetSection($"{BookshopInvoicesS3SettingsSection}:Region").Value, AuthenticationRegion = configuration.GetSection($"{BookshopInvoicesS3SettingsSection}:Region").Value,
ForcePathStyle = true, ForcePathStyle = true,
EndpointDiscoveryEnabled = true, EndpointDiscoveryEnabled = true,
UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://", StringComparison.InvariantCultureIgnoreCase), UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://")
})); }));
services.AddKeyedScoped<IS3Service, BookshopInvoicesS3Service>(BookshopInvoicesBucketName); services.AddKeyedScoped<IS3Service, BookshopInvoicesS3Service>(BookshopInvoicesBucketName);
@@ -53,7 +53,7 @@ public static class S3
AuthenticationRegion = configuration.GetSection($"{BookshopQuotesS3SettingsSection}:Region").Value, AuthenticationRegion = configuration.GetSection($"{BookshopQuotesS3SettingsSection}:Region").Value,
ForcePathStyle = true, ForcePathStyle = true,
EndpointDiscoveryEnabled = true, EndpointDiscoveryEnabled = true,
UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://", StringComparison.InvariantCultureIgnoreCase), UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://")
})); }));
services.AddKeyedScoped<IS3Service, BookshopQuotesS3Service>(BookshopQuotesBucketName); services.AddKeyedScoped<IS3Service, BookshopQuotesS3Service>(BookshopQuotesBucketName);
@@ -1,13 +0,0 @@
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--> <!-- Quartz Scheduler-->
<ItemGroup> <ItemGroup>
<PackageReference Include="Hashids.net" Version="1.7.0" /> <PackageReference Include="Hashids.net" Version="1.7.0" />
<PackageReference Include="Meziantou.Analyzer" Version="3.0.104"> <PackageReference Include="Meziantou.Analyzer" Version="3.0.103">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@@ -1,6 +1,6 @@
namespace LiteCharms.Features.Postgres; namespace LiteCharms.Features.Postgres;
public sealed class DataProtectionDbContext(DbContextOptions<DataProtectionDbContext> options) : DbContext(options), IDataProtectionKeyContext public class DataProtectionDbContext(DbContextOptions<DataProtectionDbContext> options) : DbContext(options), IDataProtectionKeyContext
{ {
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; } public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
@@ -2,7 +2,7 @@
namespace LiteCharms.Features.Postgres; namespace LiteCharms.Features.Postgres;
public sealed class DataProtectionDbContextFactory : IDesignTimeDbContextFactory<DataProtectionDbContext> public class DataProtectionDbContextFactory : IDesignTimeDbContextFactory<DataProtectionDbContext>
{ {
public DataProtectionDbContext CreateDbContext(string[] args) public DataProtectionDbContext CreateDbContext(string[] args)
{ {
@@ -6,7 +6,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace LiteCharms.Features.Postgres.Migrations namespace LiteCharms.Features.Postgres.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public sealed partial class Init : Migration public partial class Init : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)