From 116906729b421ddfbd38d139207dfc5882319b8e Mon Sep 17 00:00:00 2001 From: austinchima Date: Sun, 28 Jun 2026 01:17:44 -0300 Subject: [PATCH 1/3] fix(ci): use CI-provisioned Postgres for test database Tests were starting their own Testcontainers Postgres container, which failed or connected to an un-migrated DB in GitHub Actions. Make the shared PostgresContainerFixture respect ConnectionStrings__PreceptDb when present, so CI uses the action-setup-postgres service. - PostgresContainerFixture falls back to Testcontainers locally - Per-class isolated databases are still created on the chosen server - Workflow sets the env var only for the test step --- .github/workflows/ci.yml | 6 +- .../PostgresContainerFixture.cs | 100 +++++++++++++++--- .../PreceptWebApplicationFactory.cs | 3 +- 3 files changed, 89 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7678df0..e7784a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,17 +39,15 @@ jobs: - name: Restore dependencies run: dotnet restore - env: - ConnectionStrings__PreceptDb: "Host=localhost;Port=5432;Username=${{ env.TEST_DB_USER }};Password=${{ steps.db-password.outputs.password }};Database=${{ env.TEST_DB_NAME }}" - name: Build run: dotnet build --configuration Release --no-restore - env: - ConnectionStrings__PreceptDb: "Host=localhost;Port=5432;Username=${{ env.TEST_DB_USER }};Password=${{ steps.db-password.outputs.password }};Database=${{ env.TEST_DB_NAME }}" - name: Test run: dotnet test --configuration Release --no-build --logger "trx" --results-directory "TestResults" env: + # Tell the shared test fixture to use the CI-provisioned Postgres + # server instead of starting a Testcontainers container. ConnectionStrings__PreceptDb: "Host=localhost;Port=5432;Username=${{ env.TEST_DB_USER }};Password=${{ steps.db-password.outputs.password }};Database=${{ env.TEST_DB_NAME }}" # OWASP A06: Scan for vulnerable NuGet packages diff --git a/Precept.Tests/Infrastructure/PostgresContainerFixture.cs b/Precept.Tests/Infrastructure/PostgresContainerFixture.cs index ba40caa..e44a9b0 100644 --- a/Precept.Tests/Infrastructure/PostgresContainerFixture.cs +++ b/Precept.Tests/Infrastructure/PostgresContainerFixture.cs @@ -1,33 +1,103 @@ +using Npgsql; using Testcontainers.PostgreSql; namespace Precept.Tests.Infrastructure; /// -/// Manages a single PostgreSQL Testcontainer shared across the entire test session. -/// Each test CLASS creates its own isolated GUID-named database inside this container. +/// Manages the PostgreSQL test database backing store. +/// +/// By default a Testcontainers Postgres container is started. When the +/// ConnectionStrings__PreceptDb (or PRECEPT_TEST_DB_CONNECTION) +/// environment variable is set, the fixture uses that externally provisioned +/// Postgres server instead. This allows CI workflows that start Postgres via a +/// service container to be used without Docker-in-Docker support. +/// +/// Each test CLASS still creates its own isolated GUID-named database on the +/// chosen server. /// public class PostgresContainerFixture : IAsyncLifetime { - private readonly PostgreSqlContainer _container = new PostgreSqlBuilder() - .WithImage("postgres:16-alpine") - .WithDatabase("precept_test_root") - .WithUsername("precept_test") - .WithPassword("precept_test_password") - .Build(); + private readonly PostgreSqlContainer? _container; + private readonly string? _externalConnectionString; + private readonly bool _useExternal; - public string GetConnectionString(string databaseName) => - $"Host={_container.Hostname};Port={_container.GetMappedPublicPort(5432)};" + - $"Database={databaseName};Username=precept_test;Password=precept_test_password;Include Error Detail=true"; + public PostgresContainerFixture() + { + _externalConnectionString = Environment.GetEnvironmentVariable("ConnectionStrings__PreceptDb") + ?? Environment.GetEnvironmentVariable("PRECEPT_TEST_DB_CONNECTION"); - public string RootConnectionString => _container.GetConnectionString(); + _useExternal = !string.IsNullOrWhiteSpace(_externalConnectionString); + if (!_useExternal) + { + _container = new PostgreSqlBuilder() + .WithImage("postgres:16-alpine") + .WithDatabase("precept_test_root") + .WithUsername("precept_test") + .WithPassword("precept_test_password") + .Build(); + } + } - public async Task InitializeAsync() => await _container.StartAsync(); + public string GetConnectionString(string databaseName) + { + if (_useExternal) + { + var builder = new NpgsqlConnectionStringBuilder(_externalConnectionString!) + { + Database = databaseName + }; + return builder.ConnectionString; + } - public async Task DisposeAsync() => await _container.DisposeAsync(); + return $"Host={_container!.Hostname};Port={_container.GetMappedPublicPort(5432)};" + + $"Database={databaseName};Username=precept_test;Password=precept_test_password;Include Error Detail=true"; + } + + /// + /// Connection string to a database suitable for issuing CREATE/DROP DATABASE + /// commands. For Testcontainers this is the container's default database; for + /// an external server it points to the postgres maintenance database. + /// + public string RootConnectionString + { + get + { + if (_useExternal) + { + var builder = new NpgsqlConnectionStringBuilder(_externalConnectionString!) + { + Database = "postgres" + }; + return builder.ConnectionString; + } + + return _container!.GetConnectionString(); + } + } + + public Task InitializeAsync() + { + if (!_useExternal) + { + return _container!.StartAsync(); + } + + return Task.CompletedTask; + } + + public Task DisposeAsync() + { + if (!_useExternal) + { + return _container!.DisposeAsync().AsTask(); + } + + return Task.CompletedTask; + } } /// -/// xUnit collection definition — one container shared across all integration test classes. +/// xUnit collection definition — one backing store shared across all database test classes. /// [CollectionDefinition("Integration")] public class IntegrationCollection : ICollectionFixture { } diff --git a/Precept.Tests/Infrastructure/PreceptWebApplicationFactory.cs b/Precept.Tests/Infrastructure/PreceptWebApplicationFactory.cs index dcbc824..8e09164 100644 --- a/Precept.Tests/Infrastructure/PreceptWebApplicationFactory.cs +++ b/Precept.Tests/Infrastructure/PreceptWebApplicationFactory.cs @@ -13,7 +13,8 @@ namespace Precept.Tests.Infrastructure; /// /// WebApplicationFactory that boots a test instance of the Precept API -/// against a real PostgreSQL Testcontainer database. +/// against a real PostgreSQL database (Testcontainer by default, or the +/// server pointed to by ConnectionStrings__PreceptDb in CI). /// /// Per-class isolation: each instance creates a uniquely-named database, /// so xUnit's parallel test class execution never sees shared state. From bd9fe174921f30c5ee465bc9c60d70683585d251 Mon Sep 17 00:00:00 2001 From: austinchima Date: Sun, 28 Jun 2026 16:27:28 -0300 Subject: [PATCH 2/3] fix(ci): track EF Core migrations in git MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Migrations/ folder was gitignored, so the migration classes never reached GitHub. In CI the fresh checkout compiled Precept.Api with zero Migration classes, so MigrateAsync() found nothing to apply and silently no-op'd — leaving every per-test-class database schemaless. All 52 DB-backed tests failed with 42P01 "relation does not exist" (unit) or 503 Service Unavailable (integration). Remove the Migrations/ ignore rule and commit the existing migrations so CI provisions the schema the same way local runs do. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 2 - ...20021952_InitialPostgresCreate.Designer.cs | 684 ++++++++++++++++ .../20260620021952_InitialPostgresCreate.cs | 454 ++++++++++ ...260621222255_SyncModelSnapshot.Designer.cs | 726 ++++++++++++++++ .../20260621222255_SyncModelSnapshot.cs | 48 ++ ...3_AddConcurrencyToRefreshToken.Designer.cs | 727 ++++++++++++++++ ...0622004143_AddConcurrencyToRefreshToken.cs | 22 + ...20260623003812_AddTestimonials.Designer.cs | 775 ++++++++++++++++++ .../20260623003812_AddTestimonials.cs | 51 ++ .../PreceptDbContextModelSnapshot.cs | 772 +++++++++++++++++ 10 files changed, 4259 insertions(+), 2 deletions(-) create mode 100644 Precept.Api/Migrations/20260620021952_InitialPostgresCreate.Designer.cs create mode 100644 Precept.Api/Migrations/20260620021952_InitialPostgresCreate.cs create mode 100644 Precept.Api/Migrations/20260621222255_SyncModelSnapshot.Designer.cs create mode 100644 Precept.Api/Migrations/20260621222255_SyncModelSnapshot.cs create mode 100644 Precept.Api/Migrations/20260622004143_AddConcurrencyToRefreshToken.Designer.cs create mode 100644 Precept.Api/Migrations/20260622004143_AddConcurrencyToRefreshToken.cs create mode 100644 Precept.Api/Migrations/20260623003812_AddTestimonials.Designer.cs create mode 100644 Precept.Api/Migrations/20260623003812_AddTestimonials.cs create mode 100644 Precept.Api/Migrations/PreceptDbContextModelSnapshot.cs diff --git a/.gitignore b/.gitignore index 0ea80e8..f9a1a8f 100644 --- a/.gitignore +++ b/.gitignore @@ -54,8 +54,6 @@ secrets.json appsettings.*.json !appsettings.json -Migrations/ - .vscode/ node_modules/ diff --git a/Precept.Api/Migrations/20260620021952_InitialPostgresCreate.Designer.cs b/Precept.Api/Migrations/20260620021952_InitialPostgresCreate.Designer.cs new file mode 100644 index 0000000..5985c05 --- /dev/null +++ b/Precept.Api/Migrations/20260620021952_InitialPostgresCreate.Designer.cs @@ -0,0 +1,684 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Precept.Api.Data; + +#nullable disable + +namespace Precept.Api.Migrations +{ + [DbContext(typeof(PreceptDbContext))] + [Migration("20260620021952_InitialPostgresCreate")] + partial class InitialPostgresCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateApplied") + .HasColumnType("timestamp with time zone"); + + b.Property("DateLastContact") + .HasColumnType("timestamp with time zone"); + + b.Property("FollowUpDate") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRemote") + .HasColumnType("boolean"); + + b.Property("JobDescriptionId") + .HasColumnType("uuid"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResumeVersion") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoleTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("SalaryRange") + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("JobDescriptionId"); + + b.HasIndex("UserId"); + + b.ToTable("Applications"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Precept.Api.Models.BehavioralStory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Result") + .IsRequired() + .HasColumnType("text"); + + b.Property("Situation") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Task") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("BehavioralStories"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("text"); + + b.Property("DatePosted") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection>("ExtractedKeyWords") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("IsRemote") + .HasColumnType("boolean"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection>("MissingKeyWords") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("RoleTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("SalaryRange") + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.Property("YourMatchScore") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("JobDescriptions"); + }); + + modelBuilder.Entity("Precept.Api.Models.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceInfo") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RememberMe") + .HasColumnType("boolean"); + + b.Property("ReplacedByToken") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Precept.Api.Models.Skill", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("ProficiencyLevel") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Skills"); + }); + + modelBuilder.Entity("Precept.Api.Models.Story", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .HasColumnType("integer"); + + b.Property("CodeSnippet") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConfidenceLevel") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Explanation") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastReviewedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceProject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Stories"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.HasOne("Precept.Api.Models.JobDescription", "JobDescription") + .WithMany("Applications") + .HasForeignKey("JobDescriptionId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Applications") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDescription"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.BehavioralStory", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("BehavioralStories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("JobDescriptions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.RefreshToken", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Skill", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Skills") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Story", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Stories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationUser", b => + { + b.Navigation("Applications"); + + b.Navigation("BehavioralStories"); + + b.Navigation("JobDescriptions"); + + b.Navigation("Skills"); + + b.Navigation("Stories"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.Navigation("Applications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Precept.Api/Migrations/20260620021952_InitialPostgresCreate.cs b/Precept.Api/Migrations/20260620021952_InitialPostgresCreate.cs new file mode 100644 index 0000000..d937ee9 --- /dev/null +++ b/Precept.Api/Migrations/20260620021952_InitialPostgresCreate.cs @@ -0,0 +1,454 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Precept.Api.Migrations +{ + /// + public partial class InitialPostgresCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + FirstName = table.Column(type: "text", nullable: false), + LastName = table.Column(type: "text", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "boolean", nullable: false), + PasswordHash = table.Column(type: "text", nullable: true), + SecurityStamp = table.Column(type: "text", nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true), + PhoneNumber = table.Column(type: "text", nullable: true), + PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false), + TwoFactorEnabled = table.Column(type: "boolean", nullable: false), + LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true), + LockoutEnabled = table.Column(type: "boolean", nullable: false), + AccessFailedCount = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RoleId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "text", nullable: false), + ProviderKey = table.Column(type: "text", nullable: false), + ProviderDisplayName = table.Column(type: "text", nullable: true), + UserId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + RoleId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + LoginProvider = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Value = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BehavioralStories", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "text", nullable: false), + Title = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Situation = table.Column(type: "text", nullable: false), + Task = table.Column(type: "text", nullable: false), + Action = table.Column(type: "text", nullable: false), + Result = table.Column(type: "text", nullable: false), + Tags = table.Column(type: "text", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BehavioralStories", x => x.Id); + table.ForeignKey( + name: "FK_BehavioralStories_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "JobDescriptions", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "text", nullable: false), + CompanyName = table.Column(type: "text", nullable: false), + RoleTitle = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: false), + ExtractedKeyWords = table.Column>(type: "text[]", nullable: false), + MissingKeyWords = table.Column>(type: "text[]", nullable: false), + YourMatchScore = table.Column(type: "integer", nullable: true), + Url = table.Column(type: "text", nullable: false), + SalaryRange = table.Column(type: "text", nullable: true), + Location = table.Column(type: "text", nullable: false), + IsRemote = table.Column(type: "boolean", nullable: false), + Source = table.Column(type: "text", nullable: false), + DatePosted = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_JobDescriptions", x => x.Id); + table.ForeignKey( + name: "FK_JobDescriptions_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RefreshTokens", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Token = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + UserId = table.Column(type: "text", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + ExpiresAt = table.Column(type: "timestamp with time zone", nullable: false), + RevokedAt = table.Column(type: "timestamp with time zone", nullable: true), + ReplacedByToken = table.Column(type: "character varying(128)", maxLength: 128, nullable: true), + DeviceInfo = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + RememberMe = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RefreshTokens", x => x.Id); + table.ForeignKey( + name: "FK_RefreshTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Skills", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "text", nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Category = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + ProficiencyLevel = table.Column(type: "integer", nullable: false), + Notes = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Skills", x => x.Id); + table.ForeignKey( + name: "FK_Skills_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Stories", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "text", nullable: false), + Title = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + CodeSnippet = table.Column(type: "text", nullable: false), + Explanation = table.Column(type: "text", nullable: false), + SourceProject = table.Column(type: "text", nullable: false), + Category = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + LastReviewedAt = table.Column(type: "timestamp with time zone", nullable: true), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false), + ConfidenceLevel = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Stories", x => x.Id); + table.ForeignKey( + name: "FK_Stories_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Applications", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "text", nullable: false), + CompanyName = table.Column(type: "text", nullable: false), + RoleTitle = table.Column(type: "text", nullable: false), + Location = table.Column(type: "text", nullable: false), + SalaryRange = table.Column(type: "text", nullable: true), + Status = table.Column(type: "integer", nullable: false), + DateApplied = table.Column(type: "timestamp with time zone", nullable: true), + DateLastContact = table.Column(type: "timestamp with time zone", nullable: true), + FollowUpDate = table.Column(type: "timestamp with time zone", nullable: false), + ResumeVersion = table.Column(type: "text", nullable: false), + Notes = table.Column(type: "text", nullable: false), + IsRemote = table.Column(type: "boolean", nullable: false), + Source = table.Column(type: "text", nullable: false), + JobDescriptionId = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Applications", x => x.Id); + table.ForeignKey( + name: "FK_Applications_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Applications_JobDescriptions_JobDescriptionId", + column: x => x.JobDescriptionId, + principalTable: "JobDescriptions", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateIndex( + name: "IX_Applications_JobDescriptionId", + table: "Applications", + column: "JobDescriptionId"); + + migrationBuilder.CreateIndex( + name: "IX_Applications_UserId", + table: "Applications", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BehavioralStories_UserId", + table: "BehavioralStories", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_JobDescriptions_UserId", + table: "JobDescriptions", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_RefreshTokens_Token", + table: "RefreshTokens", + column: "Token"); + + migrationBuilder.CreateIndex( + name: "IX_RefreshTokens_UserId", + table: "RefreshTokens", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Skills_UserId", + table: "Skills", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Stories_UserId", + table: "Stories", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Applications"); + + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "BehavioralStories"); + + migrationBuilder.DropTable( + name: "RefreshTokens"); + + migrationBuilder.DropTable( + name: "Skills"); + + migrationBuilder.DropTable( + name: "Stories"); + + migrationBuilder.DropTable( + name: "JobDescriptions"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/Precept.Api/Migrations/20260621222255_SyncModelSnapshot.Designer.cs b/Precept.Api/Migrations/20260621222255_SyncModelSnapshot.Designer.cs new file mode 100644 index 0000000..c1cddae --- /dev/null +++ b/Precept.Api/Migrations/20260621222255_SyncModelSnapshot.Designer.cs @@ -0,0 +1,726 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Precept.Api.Data; + +#nullable disable + +namespace Precept.Api.Migrations +{ + [DbContext(typeof(PreceptDbContext))] + [Migration("20260621222255_SyncModelSnapshot")] + partial class SyncModelSnapshot + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateApplied") + .HasColumnType("timestamp with time zone"); + + b.Property("DateLastContact") + .HasColumnType("timestamp with time zone"); + + b.Property("FollowUpDate") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRemote") + .HasColumnType("boolean"); + + b.Property("JobDescriptionId") + .HasColumnType("uuid"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResumeVersion") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoleTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("SalaryRange") + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("JobDescriptionId"); + + b.HasIndex("UserId"); + + b.ToTable("Applications"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("DateOccurred") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationEvents"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Precept.Api.Models.BehavioralStory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Result") + .IsRequired() + .HasColumnType("text"); + + b.Property("Situation") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Task") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("BehavioralStories"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("text"); + + b.Property("DatePosted") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection>("ExtractedKeyWords") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("IsRemote") + .HasColumnType("boolean"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection>("MissingKeyWords") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("RoleTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("SalaryRange") + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.Property("YourMatchScore") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("JobDescriptions"); + }); + + modelBuilder.Entity("Precept.Api.Models.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceInfo") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RememberMe") + .HasColumnType("boolean"); + + b.Property("ReplacedByToken") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Precept.Api.Models.Skill", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("ProficiencyLevel") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Skills"); + }); + + modelBuilder.Entity("Precept.Api.Models.Story", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .HasColumnType("integer"); + + b.Property("CodeSnippet") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConfidenceLevel") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Explanation") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastReviewedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceProject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Stories"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.HasOne("Precept.Api.Models.JobDescription", "JobDescription") + .WithMany("Applications") + .HasForeignKey("JobDescriptionId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Applications") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDescription"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationEvent", b => + { + b.HasOne("Precept.Api.Models.Application", "Application") + .WithMany("Events") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Precept.Api.Models.BehavioralStory", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("BehavioralStories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("JobDescriptions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.RefreshToken", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Skill", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Skills") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Story", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Stories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.Navigation("Events"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationUser", b => + { + b.Navigation("Applications"); + + b.Navigation("BehavioralStories"); + + b.Navigation("JobDescriptions"); + + b.Navigation("Skills"); + + b.Navigation("Stories"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.Navigation("Applications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Precept.Api/Migrations/20260621222255_SyncModelSnapshot.cs b/Precept.Api/Migrations/20260621222255_SyncModelSnapshot.cs new file mode 100644 index 0000000..3129d84 --- /dev/null +++ b/Precept.Api/Migrations/20260621222255_SyncModelSnapshot.cs @@ -0,0 +1,48 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Precept.Api.Migrations +{ + /// + public partial class SyncModelSnapshot : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApplicationEvents", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ApplicationId = table.Column(type: "uuid", nullable: false), + Status = table.Column(type: "integer", nullable: false), + DateOccurred = table.Column(type: "timestamp with time zone", nullable: false), + Notes = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApplicationEvents", x => x.Id); + table.ForeignKey( + name: "FK_ApplicationEvents_Applications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "Applications", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApplicationEvents_ApplicationId", + table: "ApplicationEvents", + column: "ApplicationId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApplicationEvents"); + } + } +} diff --git a/Precept.Api/Migrations/20260622004143_AddConcurrencyToRefreshToken.Designer.cs b/Precept.Api/Migrations/20260622004143_AddConcurrencyToRefreshToken.Designer.cs new file mode 100644 index 0000000..7762ccc --- /dev/null +++ b/Precept.Api/Migrations/20260622004143_AddConcurrencyToRefreshToken.Designer.cs @@ -0,0 +1,727 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Precept.Api.Data; + +#nullable disable + +namespace Precept.Api.Migrations +{ + [DbContext(typeof(PreceptDbContext))] + [Migration("20260622004143_AddConcurrencyToRefreshToken")] + partial class AddConcurrencyToRefreshToken + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateApplied") + .HasColumnType("timestamp with time zone"); + + b.Property("DateLastContact") + .HasColumnType("timestamp with time zone"); + + b.Property("FollowUpDate") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRemote") + .HasColumnType("boolean"); + + b.Property("JobDescriptionId") + .HasColumnType("uuid"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResumeVersion") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoleTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("SalaryRange") + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("JobDescriptionId"); + + b.HasIndex("UserId"); + + b.ToTable("Applications"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("DateOccurred") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationEvents"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Precept.Api.Models.BehavioralStory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Result") + .IsRequired() + .HasColumnType("text"); + + b.Property("Situation") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Task") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("BehavioralStories"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("text"); + + b.Property("DatePosted") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection>("ExtractedKeyWords") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("IsRemote") + .HasColumnType("boolean"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection>("MissingKeyWords") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("RoleTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("SalaryRange") + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.Property("YourMatchScore") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("JobDescriptions"); + }); + + modelBuilder.Entity("Precept.Api.Models.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceInfo") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RememberMe") + .HasColumnType("boolean"); + + b.Property("ReplacedByToken") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("RevokedAt") + .IsConcurrencyToken() + .HasColumnType("timestamp with time zone"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Precept.Api.Models.Skill", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("ProficiencyLevel") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Skills"); + }); + + modelBuilder.Entity("Precept.Api.Models.Story", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .HasColumnType("integer"); + + b.Property("CodeSnippet") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConfidenceLevel") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Explanation") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastReviewedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceProject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Stories"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.HasOne("Precept.Api.Models.JobDescription", "JobDescription") + .WithMany("Applications") + .HasForeignKey("JobDescriptionId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Applications") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDescription"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationEvent", b => + { + b.HasOne("Precept.Api.Models.Application", "Application") + .WithMany("Events") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Precept.Api.Models.BehavioralStory", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("BehavioralStories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("JobDescriptions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.RefreshToken", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Skill", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Skills") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Story", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Stories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.Navigation("Events"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationUser", b => + { + b.Navigation("Applications"); + + b.Navigation("BehavioralStories"); + + b.Navigation("JobDescriptions"); + + b.Navigation("Skills"); + + b.Navigation("Stories"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.Navigation("Applications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Precept.Api/Migrations/20260622004143_AddConcurrencyToRefreshToken.cs b/Precept.Api/Migrations/20260622004143_AddConcurrencyToRefreshToken.cs new file mode 100644 index 0000000..929f035 --- /dev/null +++ b/Precept.Api/Migrations/20260622004143_AddConcurrencyToRefreshToken.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Precept.Api.Migrations +{ + /// + public partial class AddConcurrencyToRefreshToken : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Precept.Api/Migrations/20260623003812_AddTestimonials.Designer.cs b/Precept.Api/Migrations/20260623003812_AddTestimonials.Designer.cs new file mode 100644 index 0000000..f2c2130 --- /dev/null +++ b/Precept.Api/Migrations/20260623003812_AddTestimonials.Designer.cs @@ -0,0 +1,775 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Precept.Api.Data; + +#nullable disable + +namespace Precept.Api.Migrations +{ + [DbContext(typeof(PreceptDbContext))] + [Migration("20260623003812_AddTestimonials")] + partial class AddTestimonials + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateApplied") + .HasColumnType("timestamp with time zone"); + + b.Property("DateLastContact") + .HasColumnType("timestamp with time zone"); + + b.Property("FollowUpDate") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRemote") + .HasColumnType("boolean"); + + b.Property("JobDescriptionId") + .HasColumnType("uuid"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResumeVersion") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoleTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("SalaryRange") + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("JobDescriptionId"); + + b.HasIndex("UserId"); + + b.ToTable("Applications"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("DateOccurred") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationEvents"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Precept.Api.Models.BehavioralStory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Result") + .IsRequired() + .HasColumnType("text"); + + b.Property("Situation") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Task") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("BehavioralStories"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("text"); + + b.Property("DatePosted") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection>("ExtractedKeyWords") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("IsRemote") + .HasColumnType("boolean"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection>("MissingKeyWords") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("RoleTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("SalaryRange") + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.Property("YourMatchScore") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("JobDescriptions"); + }); + + modelBuilder.Entity("Precept.Api.Models.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceInfo") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RememberMe") + .HasColumnType("boolean"); + + b.Property("ReplacedByToken") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("RevokedAt") + .IsConcurrencyToken() + .HasColumnType("timestamp with time zone"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Precept.Api.Models.Skill", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("ProficiencyLevel") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Skills"); + }); + + modelBuilder.Entity("Precept.Api.Models.Story", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .HasColumnType("integer"); + + b.Property("CodeSnippet") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConfidenceLevel") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Explanation") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastReviewedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceProject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Stories"); + }); + + modelBuilder.Entity("Precept.Api.Models.Testimonial", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AvatarSrc") + .HasColumnType("text"); + + b.Property("DateSubmitted") + .HasColumnType("timestamp with time zone"); + + b.Property("Handle") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsApproved") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Testimonials"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.HasOne("Precept.Api.Models.JobDescription", "JobDescription") + .WithMany("Applications") + .HasForeignKey("JobDescriptionId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Applications") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDescription"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationEvent", b => + { + b.HasOne("Precept.Api.Models.Application", "Application") + .WithMany("Events") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Precept.Api.Models.BehavioralStory", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("BehavioralStories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("JobDescriptions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.RefreshToken", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Skill", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Skills") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Story", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Stories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Testimonial", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.Navigation("Events"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationUser", b => + { + b.Navigation("Applications"); + + b.Navigation("BehavioralStories"); + + b.Navigation("JobDescriptions"); + + b.Navigation("Skills"); + + b.Navigation("Stories"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.Navigation("Applications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Precept.Api/Migrations/20260623003812_AddTestimonials.cs b/Precept.Api/Migrations/20260623003812_AddTestimonials.cs new file mode 100644 index 0000000..bcf2bce --- /dev/null +++ b/Precept.Api/Migrations/20260623003812_AddTestimonials.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Precept.Api.Migrations +{ + /// + public partial class AddTestimonials : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Testimonials", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + UserId = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Handle = table.Column(type: "text", nullable: false), + Text = table.Column(type: "text", nullable: false), + AvatarSrc = table.Column(type: "text", nullable: true), + IsApproved = table.Column(type: "boolean", nullable: false), + DateSubmitted = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Testimonials", x => x.Id); + table.ForeignKey( + name: "FK_Testimonials_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Testimonials_UserId", + table: "Testimonials", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Testimonials"); + } + } +} diff --git a/Precept.Api/Migrations/PreceptDbContextModelSnapshot.cs b/Precept.Api/Migrations/PreceptDbContextModelSnapshot.cs new file mode 100644 index 0000000..a9519e2 --- /dev/null +++ b/Precept.Api/Migrations/PreceptDbContextModelSnapshot.cs @@ -0,0 +1,772 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Precept.Api.Data; + +#nullable disable + +namespace Precept.Api.Migrations +{ + [DbContext(typeof(PreceptDbContext))] + partial class PreceptDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("text"); + + b.Property("DateApplied") + .HasColumnType("timestamp with time zone"); + + b.Property("DateLastContact") + .HasColumnType("timestamp with time zone"); + + b.Property("FollowUpDate") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRemote") + .HasColumnType("boolean"); + + b.Property("JobDescriptionId") + .HasColumnType("uuid"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.Property("Notes") + .IsRequired() + .HasColumnType("text"); + + b.Property("ResumeVersion") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoleTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("SalaryRange") + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("JobDescriptionId"); + + b.HasIndex("UserId"); + + b.ToTable("Applications"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("DateOccurred") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.ToTable("ApplicationEvents"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Precept.Api.Models.BehavioralStory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Result") + .IsRequired() + .HasColumnType("text"); + + b.Property("Situation") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Task") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("BehavioralStories"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("text"); + + b.Property("DatePosted") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection>("ExtractedKeyWords") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("IsRemote") + .HasColumnType("boolean"); + + b.Property("Location") + .IsRequired() + .HasColumnType("text"); + + b.PrimitiveCollection>("MissingKeyWords") + .IsRequired() + .HasColumnType("text[]"); + + b.Property("RoleTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("SalaryRange") + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.Property("YourMatchScore") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("JobDescriptions"); + }); + + modelBuilder.Entity("Precept.Api.Models.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceInfo") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RememberMe") + .HasColumnType("boolean"); + + b.Property("ReplacedByToken") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("RevokedAt") + .IsConcurrencyToken() + .HasColumnType("timestamp with time zone"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Precept.Api.Models.Skill", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("ProficiencyLevel") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Skills"); + }); + + modelBuilder.Entity("Precept.Api.Models.Story", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .HasColumnType("integer"); + + b.Property("CodeSnippet") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConfidenceLevel") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Explanation") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastReviewedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceProject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Stories"); + }); + + modelBuilder.Entity("Precept.Api.Models.Testimonial", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AvatarSrc") + .HasColumnType("text"); + + b.Property("DateSubmitted") + .HasColumnType("timestamp with time zone"); + + b.Property("Handle") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsApproved") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Testimonials"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.HasOne("Precept.Api.Models.JobDescription", "JobDescription") + .WithMany("Applications") + .HasForeignKey("JobDescriptionId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Applications") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDescription"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationEvent", b => + { + b.HasOne("Precept.Api.Models.Application", "Application") + .WithMany("Events") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("Precept.Api.Models.BehavioralStory", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("BehavioralStories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("JobDescriptions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.RefreshToken", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Skill", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Skills") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Story", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany("Stories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Testimonial", b => + { + b.HasOne("Precept.Api.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Precept.Api.Models.Application", b => + { + b.Navigation("Events"); + }); + + modelBuilder.Entity("Precept.Api.Models.ApplicationUser", b => + { + b.Navigation("Applications"); + + b.Navigation("BehavioralStories"); + + b.Navigation("JobDescriptions"); + + b.Navigation("Skills"); + + b.Navigation("Stories"); + }); + + modelBuilder.Entity("Precept.Api.Models.JobDescription", b => + { + b.Navigation("Applications"); + }); +#pragma warning restore 612, 618 + } + } +} From b5bbc2721e1bfc7a300f139b5d34d1e803743a1d Mon Sep 17 00:00:00 2001 From: austinchima Date: Sun, 28 Jun 2026 16:30:15 -0300 Subject: [PATCH 3/3] fix(security): fail fast when JWT signing key is missing or too weak Previously a missing or short JwtSettings:SecretKey surfaced only as an obscure error on the first token-signing request. Validate at startup that the key is present and at least 32 bytes (256 bits, the HMAC-SHA256 minimum), throwing a clear InvalidOperationException at boot otherwise. Co-Authored-By: Claude Opus 4.8 --- Precept.Api/Program.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Precept.Api/Program.cs b/Precept.Api/Program.cs index 7161664..9a23a77 100644 --- a/Precept.Api/Program.cs +++ b/Precept.Api/Program.cs @@ -97,8 +97,26 @@ var jwtSettings = builder.Configuration.GetSection(JwtSettings.SectionName).Get() ?? throw new InvalidOperationException("JwtSettings configuration section is missing."); +// Fail fast at startup if the signing key is missing or too weak, instead of +// surfacing an obscure error on the first token-signing request. HMAC-SHA256 +// requires a key of at least 256 bits (32 bytes). +if (string.IsNullOrWhiteSpace(jwtSettings.SecretKey)) +{ + throw new InvalidOperationException( + "JwtSettings:SecretKey is not configured. Set it via the JWT_SECRET_KEY " + + "environment variable (or JwtSettings:SecretKey in configuration)."); +} + var secretKey = Encoding.UTF8.GetBytes(jwtSettings.SecretKey); +if (secretKey.Length < 32) +{ + throw new InvalidOperationException( + $"JwtSettings:SecretKey must be at least 32 bytes (256 bits) for HMAC-SHA256, " + + $"but the configured value is {secretKey.Length} bytes. Provide a stronger " + + "JWT_SECRET_KEY (e.g. `openssl rand -hex 32`)."); +} + // ───────────────────────────────────────────────────────────── // 4. JWT Authentication // ─────────────────────────────────────────────────────────────