feat(discovery): add saved job searches

This commit is contained in:
cesnimda
2026-08-31 17:17:48 +02:00
parent 78ad0c66f9
commit c2181afaea
13 changed files with 461 additions and 80 deletions
@@ -0,0 +1,24 @@
using JobTrackerApi.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace JobTrackerApi.Migrations;
[DbContext(typeof(JobTrackerContext))]
[Migration("20260830137000_AdoptSavedJobSearchSchema")]
public sealed class AdoptSavedJobSearchSchema : Migration
{
protected override void Up(MigrationBuilder b) => b.Sql(ActiveProvider.Contains("MySql", StringComparison.OrdinalIgnoreCase) ? MySql : Sqlite);
protected override void Down(MigrationBuilder b) => b.Sql(ActiveProvider.Contains("MySql", StringComparison.OrdinalIgnoreCase) ? "DROP TABLE IF EXISTS `SavedJobSearchResults`; DROP TABLE IF EXISTS `SavedJobSearches`;" : "DROP TABLE IF EXISTS \"SavedJobSearchResults\"; DROP TABLE IF EXISTS \"SavedJobSearches\";");
private const string Sqlite = """
CREATE TABLE IF NOT EXISTS "SavedJobSearches" ("Id" INTEGER NOT NULL CONSTRAINT "PK_SavedJobSearches" PRIMARY KEY AUTOINCREMENT, "OwnerUserId" TEXT NOT NULL, "Name" TEXT NOT NULL, "Query" TEXT NOT NULL, "Location" TEXT NOT NULL, "IsActive" INTEGER NOT NULL, "LastRunAtUtc" TEXT NULL, "CreatedAtUtc" TEXT NOT NULL, "UpdatedAtUtc" TEXT NOT NULL);
CREATE INDEX IF NOT EXISTS "IX_SavedJobSearches_Owner_Updated" ON "SavedJobSearches" ("OwnerUserId", "UpdatedAtUtc");
CREATE TABLE IF NOT EXISTS "SavedJobSearchResults" ("Id" INTEGER NOT NULL CONSTRAINT "PK_SavedJobSearchResults" PRIMARY KEY AUTOINCREMENT, "SavedJobSearchId" INTEGER NOT NULL, "ExternalJobId" TEXT NOT NULL, "FirstSeenAtUtc" TEXT NOT NULL, "LastSeenAtUtc" TEXT NOT NULL, "IsDismissed" INTEGER NOT NULL, CONSTRAINT "FK_SavedJobSearchResults_Search" FOREIGN KEY ("SavedJobSearchId") REFERENCES "SavedJobSearches" ("Id") ON DELETE CASCADE);
CREATE UNIQUE INDEX IF NOT EXISTS "IX_SavedJobSearchResults_Search_External" ON "SavedJobSearchResults" ("SavedJobSearchId", "ExternalJobId");
""";
private const string MySql = """
CREATE TABLE IF NOT EXISTS `SavedJobSearches` (`Id` int NOT NULL AUTO_INCREMENT, `OwnerUserId` varchar(255) NOT NULL, `Name` varchar(255) NOT NULL, `Query` varchar(500) NOT NULL, `Location` varchar(255) NOT NULL, `IsActive` tinyint(1) NOT NULL, `LastRunAtUtc` datetime(6) NULL, `CreatedAtUtc` datetime(6) NOT NULL, `UpdatedAtUtc` datetime(6) NOT NULL, PRIMARY KEY (`Id`)) CHARACTER SET=utf8mb4;
CREATE INDEX IF NOT EXISTS `IX_SavedJobSearches_Owner_Updated` ON `SavedJobSearches` (`OwnerUserId`, `UpdatedAtUtc`);
CREATE TABLE IF NOT EXISTS `SavedJobSearchResults` (`Id` int NOT NULL AUTO_INCREMENT, `SavedJobSearchId` int NOT NULL, `ExternalJobId` varchar(128) NOT NULL, `FirstSeenAtUtc` datetime(6) NOT NULL, `LastSeenAtUtc` datetime(6) NOT NULL, `IsDismissed` tinyint(1) NOT NULL, PRIMARY KEY (`Id`), CONSTRAINT `FK_SavedJobSearchResults_Search` FOREIGN KEY (`SavedJobSearchId`) REFERENCES `SavedJobSearches` (`Id`) ON DELETE CASCADE) CHARACTER SET=utf8mb4;
CREATE UNIQUE INDEX IF NOT EXISTS `IX_SavedJobSearchResults_Search_External` ON `SavedJobSearchResults` (`SavedJobSearchId`, `ExternalJobId`);
""";
}
@@ -1005,6 +1005,35 @@ namespace JobTrackerApi.Migrations
b.ToTable("CareerEvidence");
});
modelBuilder.Entity("JobTrackerApi.Models.SavedJobSearch", b =>
{
b.Property<int>("Id").ValueGeneratedOnAdd().HasColumnType("INTEGER");
b.Property<DateTimeOffset>("CreatedAtUtc").HasColumnType("TEXT");
b.Property<bool>("IsActive").HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LastRunAtUtc").HasColumnType("TEXT");
b.Property<string>("Location").IsRequired().HasMaxLength(255).HasColumnType("TEXT");
b.Property<string>("Name").IsRequired().HasMaxLength(255).HasColumnType("TEXT");
b.Property<string>("OwnerUserId").IsRequired().HasMaxLength(255).HasColumnType("TEXT");
b.Property<string>("Query").IsRequired().HasMaxLength(500).HasColumnType("TEXT");
b.Property<DateTimeOffset>("UpdatedAtUtc").HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("OwnerUserId", "UpdatedAtUtc");
b.ToTable("SavedJobSearches");
});
modelBuilder.Entity("JobTrackerApi.Models.SavedJobSearchResult", b =>
{
b.Property<int>("Id").ValueGeneratedOnAdd().HasColumnType("INTEGER");
b.Property<string>("ExternalJobId").IsRequired().HasMaxLength(128).HasColumnType("TEXT");
b.Property<DateTimeOffset>("FirstSeenAtUtc").HasColumnType("TEXT");
b.Property<bool>("IsDismissed").HasColumnType("INTEGER");
b.Property<DateTimeOffset>("LastSeenAtUtc").HasColumnType("TEXT");
b.Property<int>("SavedJobSearchId").HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("SavedJobSearchId", "ExternalJobId").IsUnique();
b.ToTable("SavedJobSearchResults");
});
modelBuilder.Entity("JobTrackerApi.Models.Correspondence", b =>
{
b.Property<int>("Id")
@@ -2721,6 +2750,17 @@ namespace JobTrackerApi.Migrations
b.Navigation("JobApplication");
});
modelBuilder.Entity("JobTrackerApi.Models.SavedJobSearchResult", b =>
{
b.HasOne("JobTrackerApi.Models.SavedJobSearch", "SavedJobSearch")
.WithMany("Results")
.HasForeignKey("SavedJobSearchId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SavedJobSearch");
});
modelBuilder.Entity("JobTrackerApi.Models.SubmittedApplicationPackage", b =>
{
b.HasOne("JobTrackerApi.Models.JobApplication", "JobApplication")
@@ -2976,6 +3016,11 @@ namespace JobTrackerApi.Migrations
{
b.Navigation("Attachments");
});
modelBuilder.Entity("JobTrackerApi.Models.SavedJobSearch", b =>
{
b.Navigation("Results");
});
#pragma warning restore 612, 618
}
}