refactor(infra): migrate data access and identity impelementations to infrastructure

This commit is contained in:
Stevan Freeborn
2026-02-03 04:38:40 -06:00
parent 7e67e02a68
commit c79136fb26
16 changed files with 396 additions and 191 deletions
+64
View File
@@ -0,0 +1,64 @@
using FiscalOS.Core.Data;
namespace FiscalOS.Infra.Data;
public sealed class AppDbContext(IOptions<AppDbContextOptions> ctxOptions) : DbContext
{
private const string DataSourceKey = "Data Source=";
private readonly AppDbContextOptions _ctxOptions = ctxOptions.Value;
public DbSet<User> Users => Set<User>();
public DbSet<RefreshToken> RefreshTokens => Set<RefreshToken>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var dbPath = _ctxOptions.GetFullyQualifiedDatabasePath();
var dbDirectory = Path.GetDirectoryName(dbPath) ?? throw new InvalidOperationException("Database directory path could not be determined.");
if (Directory.Exists(dbDirectory) is false)
{
Directory.CreateDirectory(dbDirectory);
}
var connectionString = $"{DataSourceKey}{dbPath}";
optionsBuilder.UseSqlite(connectionString)
.AddInterceptors(new TimestampInterceptor());
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var entityTypes = modelBuilder.Model.GetEntityTypes()
.Where(static e => typeof(Entity).IsAssignableFrom(e.ClrType));
foreach (var entityType in entityTypes)
{
modelBuilder.Entity(entityType.ClrType)
.HasKey(nameof(Entity.Id));
modelBuilder.Entity(entityType.ClrType)
.Property(nameof(Entity.CreatedAt));
modelBuilder.Entity(entityType.ClrType)
.Property(nameof(Entity.UpdatedAt));
}
modelBuilder.Entity<User>(static eb =>
{
eb.HasMany(static u => u.RefreshTokens)
.WithOne(static t => t.User)
.HasForeignKey(static t => t.UserId)
.OnDelete(DeleteBehavior.Cascade);
eb.Property(static u => u.Username);
eb.Property(static u => u.HashedPassword);
});
modelBuilder.Entity<RefreshToken>(static eb =>
{
eb.Property(static t => t.Id);
});
}
}
@@ -0,0 +1,26 @@
namespace FiscalOS.Infra.Data;
public sealed record AppDbContextOptions
{
public string DatabaseFilePath { get; init; } = string.Empty;
public string GetFullyQualifiedDatabasePath()
{
return Path.GetFullPath(DatabaseFilePath, AppContext.BaseDirectory);
}
}
public sealed record AppDbContextOptionsSetup : IConfigureOptions<AppDbContextOptions>
{
private const string SectionName = nameof(AppDbContextOptions);
private readonly IConfiguration _configuration;
public AppDbContextOptionsSetup(IConfiguration configuration)
{
_configuration = configuration;
}
public void Configure(AppDbContextOptions options)
{
_configuration.GetSection(SectionName).Bind(options);
}
}
@@ -0,0 +1,25 @@
namespace FiscalOS.Infra.Data;
internal sealed class MigrationService(
IServiceProvider serviceProvider,
ILogger<MigrationService> logger
) : IHostedService
{
private readonly IServiceProvider _serviceProvider = serviceProvider;
public async Task StartAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Applying database migrations...");
using var scope = _serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await context.Database.MigrateAsync(cancellationToken).ConfigureAwait(false);
logger.LogInformation("Database migrations applied.");
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}