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;