From 725392227ec6fdf9cafde7a05e131202583e4af6 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sun, 1 Feb 2026 06:13:08 -0600 Subject: [PATCH] feat: setup db context with initial models --- src/Directory.Packages.props | 9 ++-- src/FiscalOS.API/Data/AppDbContext.cs | 38 +++++++++++++++ src/FiscalOS.API/Data/AppDbContextOptions.cs | 26 ++++++++++ src/FiscalOS.API/Data/MigrationService.cs | 25 ++++++++++ src/FiscalOS.API/FiscalOS.API.csproj | 5 ++ src/FiscalOS.API/Identity/RefreshToken.cs | 6 +++ src/FiscalOS.API/Identity/User.cs | 6 +++ ...15839_AddUsersAndRefreshTokens.Designer.cs | 47 +++++++++++++++++++ ...20260201115839_AddUsersAndRefreshTokens.cs | 47 +++++++++++++++++++ .../Migrations/AppDbContextModelSnapshot.cs | 44 +++++++++++++++++ src/FiscalOS.API/Program.cs | 6 +++ src/FiscalOS.API/Usings.cs | 3 ++ tests/FiscalOS.API.Tests/Usings.cs | 8 +++- 13 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 src/FiscalOS.API/Data/AppDbContext.cs create mode 100644 src/FiscalOS.API/Data/AppDbContextOptions.cs create mode 100644 src/FiscalOS.API/Data/MigrationService.cs create mode 100644 src/FiscalOS.API/Identity/RefreshToken.cs create mode 100644 src/FiscalOS.API/Identity/User.cs create mode 100644 src/FiscalOS.API/Migrations/20260201115839_AddUsersAndRefreshTokens.Designer.cs create mode 100644 src/FiscalOS.API/Migrations/20260201115839_AddUsersAndRefreshTokens.cs create mode 100644 src/FiscalOS.API/Migrations/AppDbContextModelSnapshot.cs diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 85154c0..c3d995a 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -1,10 +1,13 @@ - true - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - + \ No newline at end of file diff --git a/src/FiscalOS.API/Data/AppDbContext.cs b/src/FiscalOS.API/Data/AppDbContext.cs new file mode 100644 index 0000000..446b509 --- /dev/null +++ b/src/FiscalOS.API/Data/AppDbContext.cs @@ -0,0 +1,38 @@ +namespace FiscalOS.API.Data; + +internal sealed class AppDbContext(IOptions ctxOptions) : DbContext +{ + private const string DataSourceKey = "Data Source="; + private readonly AppDbContextOptions _ctxOptions = ctxOptions.Value; + + public DbSet Users => Set(); + public DbSet RefreshTokens => Set(); + + 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); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .Property(static u => u.Id) + .ValueGeneratedOnAdd(); + + modelBuilder.Entity() + .Property(static t => t.Id) + .ValueGeneratedOnAdd(); + } +} \ No newline at end of file diff --git a/src/FiscalOS.API/Data/AppDbContextOptions.cs b/src/FiscalOS.API/Data/AppDbContextOptions.cs new file mode 100644 index 0000000..786acc5 --- /dev/null +++ b/src/FiscalOS.API/Data/AppDbContextOptions.cs @@ -0,0 +1,26 @@ +namespace FiscalOS.API.Data; + +internal sealed record AppDbContextOptions +{ + public string DatabaseFilePath { get; init; } = string.Empty; + public string GetFullyQualifiedDatabasePath() + { + return Path.GetFullPath(DatabaseFilePath, AppContext.BaseDirectory); + } +} + +internal sealed record AppDbContextOptionsSetup : IConfigureOptions +{ + 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); + } +} \ No newline at end of file diff --git a/src/FiscalOS.API/Data/MigrationService.cs b/src/FiscalOS.API/Data/MigrationService.cs new file mode 100644 index 0000000..d236525 --- /dev/null +++ b/src/FiscalOS.API/Data/MigrationService.cs @@ -0,0 +1,25 @@ +namespace FiscalOS.API.Data; + +internal sealed class MigrationService( + IServiceProvider serviceProvider, + ILogger 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(); + await context.Database.MigrateAsync(cancellationToken); + + logger.LogInformation("Database migrations applied."); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/FiscalOS.API/FiscalOS.API.csproj b/src/FiscalOS.API/FiscalOS.API.csproj index d44aec3..cc3e03b 100644 --- a/src/FiscalOS.API/FiscalOS.API.csproj +++ b/src/FiscalOS.API/FiscalOS.API.csproj @@ -2,6 +2,11 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + diff --git a/src/FiscalOS.API/Identity/RefreshToken.cs b/src/FiscalOS.API/Identity/RefreshToken.cs new file mode 100644 index 0000000..4731515 --- /dev/null +++ b/src/FiscalOS.API/Identity/RefreshToken.cs @@ -0,0 +1,6 @@ +namespace FiscalOS.API.Identity; + +internal sealed class RefreshToken +{ + public Guid Id { get; init; } +} \ No newline at end of file diff --git a/src/FiscalOS.API/Identity/User.cs b/src/FiscalOS.API/Identity/User.cs new file mode 100644 index 0000000..c97b22b --- /dev/null +++ b/src/FiscalOS.API/Identity/User.cs @@ -0,0 +1,6 @@ +namespace FiscalOS.API.Identity; + +internal sealed class User +{ + public Guid Id { get; init; } +} \ No newline at end of file diff --git a/src/FiscalOS.API/Migrations/20260201115839_AddUsersAndRefreshTokens.Designer.cs b/src/FiscalOS.API/Migrations/20260201115839_AddUsersAndRefreshTokens.Designer.cs new file mode 100644 index 0000000..207a6db --- /dev/null +++ b/src/FiscalOS.API/Migrations/20260201115839_AddUsersAndRefreshTokens.Designer.cs @@ -0,0 +1,47 @@ +// +using System; +using FiscalOS.API.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace FiscalOS.API.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260201115839_AddUsersAndRefreshTokens")] + partial class AddUsersAndRefreshTokens + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.2"); + + modelBuilder.Entity("FiscalOS.API.Identity.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("FiscalOS.API.Identity.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/FiscalOS.API/Migrations/20260201115839_AddUsersAndRefreshTokens.cs b/src/FiscalOS.API/Migrations/20260201115839_AddUsersAndRefreshTokens.cs new file mode 100644 index 0000000..2e8d69e --- /dev/null +++ b/src/FiscalOS.API/Migrations/20260201115839_AddUsersAndRefreshTokens.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace FiscalOS.API.Migrations +{ + /// + public partial class AddUsersAndRefreshTokens : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RefreshTokens", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RefreshTokens", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RefreshTokens"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} \ No newline at end of file diff --git a/src/FiscalOS.API/Migrations/AppDbContextModelSnapshot.cs b/src/FiscalOS.API/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..e8930a9 --- /dev/null +++ b/src/FiscalOS.API/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,44 @@ +// +using System; +using FiscalOS.API.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace FiscalOS.API.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.2"); + + modelBuilder.Entity("FiscalOS.API.Identity.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("FiscalOS.API.Identity.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/FiscalOS.API/Program.cs b/src/FiscalOS.API/Program.cs index ed5ab3d..93745dd 100644 --- a/src/FiscalOS.API/Program.cs +++ b/src/FiscalOS.API/Program.cs @@ -1,8 +1,14 @@ +using FiscalOS.API.Data; + var builder = WebApplication.CreateBuilder(args); builder.Services.AddValidation(); builder.Services.AddOpenApi(); +builder.Services.ConfigureOptions(); +builder.Services.AddDbContext(); +builder.Services.AddHostedService(); + var app = builder.Build(); if (app.Environment.IsDevelopment()) diff --git a/src/FiscalOS.API/Usings.cs b/src/FiscalOS.API/Usings.cs index 9a3b651..8db0751 100644 --- a/src/FiscalOS.API/Usings.cs +++ b/src/FiscalOS.API/Usings.cs @@ -1,5 +1,8 @@ global using System.ComponentModel.DataAnnotations; +global using FiscalOS.API.Identity; global using FiscalOS.API.Login; global using Microsoft.AspNetCore.Mvc; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.Extensions.Options; \ No newline at end of file diff --git a/tests/FiscalOS.API.Tests/Usings.cs b/tests/FiscalOS.API.Tests/Usings.cs index 315d38f..a6a235e 100644 --- a/tests/FiscalOS.API.Tests/Usings.cs +++ b/tests/FiscalOS.API.Tests/Usings.cs @@ -1,5 +1,11 @@ +global using System.Net; +global using System.Net.Http.Json; + global using FiscalOS.API.Tests.Infra; global using Microsoft.AspNetCore.Hosting; +global using Microsoft.AspNetCore.Mvc; global using Microsoft.AspNetCore.Mvc.Testing; -global using Microsoft.Extensions.Logging; \ No newline at end of file +global using Microsoft.Extensions.Logging; + +global using Xunit.Sdk;