diff --git a/Rayson.CV/.env.production b/Rayson.CV/.env.production
index 41c0374..47988cd 100644
--- a/Rayson.CV/.env.production
+++ b/Rayson.CV/.env.production
@@ -1,3 +1,2 @@
-POSTGRES_PASSWORD=change_me_to_a_secure_password
JWT_SIGNING_KEY=change_me_to_a_secure_jwt_signing_key
GRAPH_CLIENT_SECRET=take_your_best_guess_lol
diff --git a/Rayson.CV/Api/Database/Database.csproj b/Rayson.CV/Api/Database/Database.csproj
deleted file mode 100644
index 465613d..0000000
--- a/Rayson.CV/Api/Database/Database.csproj
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- net8.0
- enable
- enable
-
-
-
diff --git a/Rayson.CV/Api/Database/Exceptions/DatabaseConfigurationException.cs b/Rayson.CV/Api/Database/Exceptions/DatabaseConfigurationException.cs
deleted file mode 100644
index 3e12f9e..0000000
--- a/Rayson.CV/Api/Database/Exceptions/DatabaseConfigurationException.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Database.Exceptions;
-
-public class DatabaseConfigurationException(string message)
- : InvalidOperationException(message)
-{
-}
diff --git a/Rayson.CV/Api/Database/Extensions/ServiceCollectionExtensions.cs b/Rayson.CV/Api/Database/Extensions/ServiceCollectionExtensions.cs
deleted file mode 100644
index bacf4d5..0000000
--- a/Rayson.CV/Api/Database/Extensions/ServiceCollectionExtensions.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using Database.Exceptions;
-using Database.HealthChecks;
-using Database.SeedData;
-using Domain.Repositories;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Database.Extensions;
-
-public static class ServiceCollectionExtensions
-{
- public static void AddDatabaseServices(this IServiceCollection services, IConfiguration configuration)
- {
- var connectionString = BuildConnectionString(configuration);
-
- services.AddDbContext((options) =>
- {
- options.UseNpgsql(connectionString);
- });
-
- services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
- services.AddScoped();
-
- services.AddHealthChecks()
- .AddCheck("postgresql", tags: new[] { "ready" });
- }
-
- private static string BuildConnectionString(IConfiguration configuration)
- {
- var host = configuration["POSTGRES_HOST"];
- var port = configuration["POSTGRES_PORT"];
- var username = configuration["POSTGRES_USERNAME"];
- var password = configuration["POSTGRES_PASSWORD"];
- var database = configuration["POSTGRES_DATABASE"];
-
- if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(port) ||
- string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) ||
- string.IsNullOrEmpty(database))
- {
- throw new DatabaseConfigurationException("Database connection string is missing. Please provide all PostgreSQL environment variables (POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USERNAME, POSTGRES_PASSWORD, POSTGRES_DATABASE).");
- }
-
- return $"Server={host};Port={port};Database={database};User Id={username};Password={password};TrustServerCertificate=True";
- }
-}
diff --git a/Rayson.CV/Api/Database/HealthChecks/NpgsqlHealthCheck.cs b/Rayson.CV/Api/Database/HealthChecks/NpgsqlHealthCheck.cs
deleted file mode 100644
index 223586e..0000000
--- a/Rayson.CV/Api/Database/HealthChecks/NpgsqlHealthCheck.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Diagnostics.HealthChecks;
-
-namespace Database.HealthChecks;
-
-public class NpgsqlHealthCheck : IHealthCheck
-{
- private readonly RaysonCVDbContext _context;
-
- public NpgsqlHealthCheck(RaysonCVDbContext context)
- {
- _context = context;
- }
-
- public async Task CheckHealthAsync(
- HealthCheckContext context,
- CancellationToken cancellationToken = default)
- {
- try
- {
- await _context.Database.ExecuteSqlRawAsync("SELECT 1", cancellationToken);
- return HealthCheckResult.Healthy("PostgreSQL connection is healthy");
- }
- catch (Exception ex)
- {
- return HealthCheckResult.Unhealthy("PostgreSQL connection failed", ex);
- }
- }
-}
diff --git a/Rayson.CV/Api/Database/Migrations/20260225083042_Init.Designer.cs b/Rayson.CV/Api/Database/Migrations/20260225083042_Init.Designer.cs
deleted file mode 100644
index 2be7ccc..0000000
--- a/Rayson.CV/Api/Database/Migrations/20260225083042_Init.Designer.cs
+++ /dev/null
@@ -1,303 +0,0 @@
-//
-using System;
-using Database;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace Database.Migrations
-{
- [DbContext(typeof(RaysonCVDbContext))]
- [Migration("20260225083042_Init")]
- partial class Init
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "8.0.10")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("Database.Auth.ApplicationRole", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- 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("Database.Auth.ApplicationUser", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- b.Property("AccessFailedCount")
- .HasColumnType("integer");
-
- b.Property("ConcurrencyStamp")
- .IsConcurrencyToken()
- .HasColumnType("text");
-
- b.Property("DisplayName")
- .IsRequired()
- .HasColumnType("text");
-
- b.Property("Email")
- .HasMaxLength(256)
- .HasColumnType("character varying(256)");
-
- b.Property("EmailConfirmed")
- .HasColumnType("boolean");
-
- 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("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- b.Property("FriendlyName")
- .HasColumnType("text");
-
- b.Property("Xml")
- .HasColumnType("text");
-
- b.HasKey("Id");
-
- b.ToTable("DataProtectionKeys");
- });
-
- 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")
- .HasColumnType("integer");
-
- 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")
- .HasColumnType("integer");
-
- 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")
- .HasColumnType("integer");
-
- b.HasKey("LoginProvider", "ProviderKey");
-
- b.HasIndex("UserId");
-
- b.ToTable("AspNetUserLogins", (string)null);
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
- {
- b.Property("UserId")
- .HasColumnType("integer");
-
- b.Property("RoleId")
- .HasColumnType("integer");
-
- b.HasKey("UserId", "RoleId");
-
- b.HasIndex("RoleId");
-
- b.ToTable("AspNetUserRoles", (string)null);
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
- {
- b.Property("UserId")
- .HasColumnType("integer");
-
- 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("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
- {
- b.HasOne("Database.Auth.ApplicationRole", null)
- .WithMany()
- .HasForeignKey("RoleId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
- {
- b.HasOne("Database.Auth.ApplicationUser", null)
- .WithMany()
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
- {
- b.HasOne("Database.Auth.ApplicationUser", null)
- .WithMany()
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
- {
- b.HasOne("Database.Auth.ApplicationRole", null)
- .WithMany()
- .HasForeignKey("RoleId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.HasOne("Database.Auth.ApplicationUser", null)
- .WithMany()
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
- {
- b.HasOne("Database.Auth.ApplicationUser", null)
- .WithMany()
- .HasForeignKey("UserId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/Rayson.CV/Api/Database/Migrations/20260225083042_Init.cs b/Rayson.CV/Api/Database/Migrations/20260225083042_Init.cs
deleted file mode 100644
index 25f8c51..0000000
--- a/Rayson.CV/Api/Database/Migrations/20260225083042_Init.cs
+++ /dev/null
@@ -1,243 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace Database.Migrations
-{
- ///
- public partial class Init : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "AspNetRoles",
- columns: table => new
- {
- Id = table.Column(type: "integer", nullable: false)
- .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
- 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: "integer", nullable: false)
- .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
- DisplayName = table.Column(type: "text", 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: "DataProtectionKeys",
- columns: table => new
- {
- Id = table.Column(type: "integer", nullable: false)
- .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
- FriendlyName = table.Column(type: "text", nullable: true),
- Xml = table.Column(type: "text", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_DataProtectionKeys", 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: "integer", 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: "integer", 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: "integer", 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: "integer", nullable: false),
- RoleId = table.Column(type: "integer", 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: "integer", 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.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);
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- name: "AspNetRoleClaims");
-
- migrationBuilder.DropTable(
- name: "AspNetUserClaims");
-
- migrationBuilder.DropTable(
- name: "AspNetUserLogins");
-
- migrationBuilder.DropTable(
- name: "AspNetUserRoles");
-
- migrationBuilder.DropTable(
- name: "AspNetUserTokens");
-
- migrationBuilder.DropTable(
- name: "DataProtectionKeys");
-
- migrationBuilder.DropTable(
- name: "AspNetRoles");
-
- migrationBuilder.DropTable(
- name: "AspNetUsers");
- }
- }
-}
diff --git a/Rayson.CV/Api/Database/Migrations/20260225093106_RemoveIdentity.Designer.cs b/Rayson.CV/Api/Database/Migrations/20260225093106_RemoveIdentity.Designer.cs
deleted file mode 100644
index 069a460..0000000
--- a/Rayson.CV/Api/Database/Migrations/20260225093106_RemoveIdentity.Designer.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-using Database;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace Database.Migrations
-{
- [DbContext(typeof(RaysonCVDbContext))]
- [Migration("20260225093106_RemoveIdentity")]
- partial class RemoveIdentity
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "8.0.10")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/Rayson.CV/Api/Database/Migrations/20260225093106_RemoveIdentity.cs b/Rayson.CV/Api/Database/Migrations/20260225093106_RemoveIdentity.cs
deleted file mode 100644
index f4f2d81..0000000
--- a/Rayson.CV/Api/Database/Migrations/20260225093106_RemoveIdentity.cs
+++ /dev/null
@@ -1,243 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace Database.Migrations
-{
- ///
- public partial class RemoveIdentity : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- name: "AspNetRoleClaims");
-
- migrationBuilder.DropTable(
- name: "AspNetUserClaims");
-
- migrationBuilder.DropTable(
- name: "AspNetUserLogins");
-
- migrationBuilder.DropTable(
- name: "AspNetUserRoles");
-
- migrationBuilder.DropTable(
- name: "AspNetUserTokens");
-
- migrationBuilder.DropTable(
- name: "DataProtectionKeys");
-
- migrationBuilder.DropTable(
- name: "AspNetRoles");
-
- migrationBuilder.DropTable(
- name: "AspNetUsers");
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "AspNetRoles",
- columns: table => new
- {
- Id = table.Column(type: "integer", nullable: false)
- .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
- ConcurrencyStamp = table.Column(type: "text", nullable: true),
- Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true),
- NormalizedName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetRoles", x => x.Id);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetUsers",
- columns: table => new
- {
- Id = table.Column(type: "integer", nullable: false)
- .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
- AccessFailedCount = table.Column(type: "integer", nullable: false),
- ConcurrencyStamp = table.Column(type: "text", nullable: true),
- DisplayName = table.Column(type: "text", nullable: false),
- Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true),
- EmailConfirmed = table.Column(type: "boolean", nullable: false),
- LockoutEnabled = table.Column(type: "boolean", nullable: false),
- LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true),
- NormalizedEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true),
- NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true),
- PasswordHash = table.Column(type: "text", nullable: true),
- PhoneNumber = table.Column(type: "text", nullable: true),
- PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false),
- SecurityStamp = table.Column(type: "text", nullable: true),
- TwoFactorEnabled = table.Column(type: "boolean", nullable: false),
- UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AspNetUsers", x => x.Id);
- });
-
- migrationBuilder.CreateTable(
- name: "DataProtectionKeys",
- columns: table => new
- {
- Id = table.Column(type: "integer", nullable: false)
- .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
- FriendlyName = table.Column(type: "text", nullable: true),
- Xml = table.Column(type: "text", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_DataProtectionKeys", x => x.Id);
- });
-
- migrationBuilder.CreateTable(
- name: "AspNetRoleClaims",
- columns: table => new
- {
- Id = table.Column(type: "integer", nullable: false)
- .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
- ClaimType = table.Column(type: "text", nullable: true),
- ClaimValue = table.Column(type: "text", nullable: true),
- RoleId = table.Column(type: "integer", nullable: false)
- },
- 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),
- ClaimType = table.Column(type: "text", nullable: true),
- ClaimValue = table.Column(type: "text", nullable: true),
- UserId = table.Column(type: "integer", nullable: false)
- },
- 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: "integer", 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: "integer", nullable: false),
- RoleId = table.Column(type: "integer", 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: "integer", 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.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);
- }
- }
-}
diff --git a/Rayson.CV/Api/Database/Migrations/20260227205757_AddCvChunks.Designer.cs b/Rayson.CV/Api/Database/Migrations/20260227205757_AddCvChunks.Designer.cs
deleted file mode 100644
index f630e7d..0000000
--- a/Rayson.CV/Api/Database/Migrations/20260227205757_AddCvChunks.Designer.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-using System;
-using Database;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace Database.Migrations
-{
- [DbContext(typeof(RaysonCVDbContext))]
- [Migration("20260227205757_AddCvChunks")]
- partial class AddCvChunks
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "8.0.10")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("Domain.CvChunk", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- b.Property("ChunkIndex")
- .HasColumnType("integer");
-
- b.Property("Content")
- .IsRequired()
- .HasColumnType("text");
-
- b.Property("CreatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("DeletedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Embedding")
- .IsRequired()
- .HasColumnType("double precision[]");
-
- b.Property("Section")
- .IsRequired()
- .HasColumnType("text");
-
- b.HasKey("Id");
-
- b.ToTable("CvChunks");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/Rayson.CV/Api/Database/Migrations/20260227205757_AddCvChunks.cs b/Rayson.CV/Api/Database/Migrations/20260227205757_AddCvChunks.cs
deleted file mode 100644
index 941a8e3..0000000
--- a/Rayson.CV/Api/Database/Migrations/20260227205757_AddCvChunks.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace Database.Migrations
-{
- ///
- public partial class AddCvChunks : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.Sql("CREATE EXTENSION IF NOT EXISTS vector");
-
- migrationBuilder.CreateTable(
- name: "CvChunks",
- columns: table => new
- {
- Id = table.Column(type: "integer", nullable: false)
- .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
- Content = table.Column(type: "text", nullable: false),
- Section = table.Column(type: "text", nullable: false),
- Embedding = table.Column(type: "double precision[]", nullable: false),
- ChunkIndex = table.Column(type: "integer", nullable: false),
- CreatedAt = table.Column(type: "timestamp with time zone", nullable: false),
- DeletedAt = table.Column(type: "timestamp with time zone", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_CvChunks", x => x.Id);
- });
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- name: "CvChunks");
- }
- }
-}
diff --git a/Rayson.CV/Api/Database/Migrations/20260228000000_FixCvChunksVectorType.Designer.cs b/Rayson.CV/Api/Database/Migrations/20260228000000_FixCvChunksVectorType.Designer.cs
deleted file mode 100644
index be67098..0000000
--- a/Rayson.CV/Api/Database/Migrations/20260228000000_FixCvChunksVectorType.Designer.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-using System;
-using Database;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace Database.Migrations
-{
- [DbContext(typeof(RaysonCVDbContext))]
- [Migration("20260228000000_FixCvChunksVectorType")]
- partial class FixCvChunksVectorType
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "8.0.10")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("Domain.CvChunk", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- b.Property("ChunkIndex")
- .HasColumnType("integer");
-
- b.Property("Content")
- .IsRequired()
- .HasColumnType("text");
-
- b.Property("CreatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("DeletedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Embedding")
- .IsRequired()
- .HasColumnType("vector(768)");
-
- b.Property("Section")
- .IsRequired()
- .HasColumnType("text");
-
- b.HasKey("Id");
-
- b.ToTable("CvChunks");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/Rayson.CV/Api/Database/Migrations/20260228000000_FixCvChunksVectorType.cs b/Rayson.CV/Api/Database/Migrations/20260228000000_FixCvChunksVectorType.cs
deleted file mode 100644
index b3a619f..0000000
--- a/Rayson.CV/Api/Database/Migrations/20260228000000_FixCvChunksVectorType.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using Microsoft.EntityFrameworkCore.Migrations;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace Database.Migrations
-{
- ///
- public partial class FixCvChunksVectorType : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.Sql("CREATE EXTENSION IF NOT EXISTS vector");
-
- migrationBuilder.DropTable(name: "CvChunks");
-
- migrationBuilder.CreateTable(
- name: "CvChunks",
- columns: table => new
- {
- Id = table.Column(type: "integer", nullable: false)
- .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
- Content = table.Column(type: "text", nullable: false),
- Section = table.Column(type: "text", nullable: false),
- Embedding = table.Column(type: "vector(768)", nullable: false),
- ChunkIndex = table.Column(type: "integer", nullable: false),
- CreatedAt = table.Column(type: "timestamp with time zone", nullable: false),
- DeletedAt = table.Column(type: "timestamp with time zone", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_CvChunks", x => x.Id);
- });
-
- migrationBuilder.Sql(@"
- CREATE INDEX IF NOT EXISTS cvchunks_embedding_idx
- ON ""CvChunks""
- USING ivfflat (""Embedding"" vector_cosine_ops)
- WITH (lists = 100)");
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(name: "CvChunks");
- }
- }
-}
diff --git a/Rayson.CV/Api/Database/Migrations/20260317000000_ClearCvChunks.Designer.cs b/Rayson.CV/Api/Database/Migrations/20260317000000_ClearCvChunks.Designer.cs
deleted file mode 100644
index a6e7e5e..0000000
--- a/Rayson.CV/Api/Database/Migrations/20260317000000_ClearCvChunks.Designer.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-using Database;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace Database.Migrations
-{
- [DbContext(typeof(RaysonCVDbContext))]
- [Migration("20260317000000_ClearCvChunks")]
- partial class ClearCvChunks
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
- }
- }
-}
diff --git a/Rayson.CV/Api/Database/Migrations/20260317000000_ClearCvChunks.cs b/Rayson.CV/Api/Database/Migrations/20260317000000_ClearCvChunks.cs
deleted file mode 100644
index 26b505f..0000000
--- a/Rayson.CV/Api/Database/Migrations/20260317000000_ClearCvChunks.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace Database.Migrations
-{
- ///
- public partial class ClearCvChunks : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.Sql("TRUNCATE TABLE \"CvChunks\" CASCADE");
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- }
- }
-}
diff --git a/Rayson.CV/Api/Database/Migrations/RaysonCVDbContextModelSnapshot.cs b/Rayson.CV/Api/Database/Migrations/RaysonCVDbContextModelSnapshot.cs
deleted file mode 100644
index 7c92b30..0000000
--- a/Rayson.CV/Api/Database/Migrations/RaysonCVDbContextModelSnapshot.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-//
-using System;
-using Database;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace Database.Migrations
-{
- [DbContext(typeof(RaysonCVDbContext))]
- partial class RaysonCVDbContextModelSnapshot : ModelSnapshot
- {
- protected override void BuildModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "8.0.10")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("Domain.CvChunk", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- b.Property("ChunkIndex")
- .HasColumnType("integer");
-
- b.Property("Content")
- .IsRequired()
- .HasColumnType("text");
-
- b.Property("CreatedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("DeletedAt")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Embedding")
- .IsRequired()
- .HasColumnType("vector(768)");
-
- b.Property("Section")
- .IsRequired()
- .HasColumnType("text");
-
- b.HasKey("Id");
-
- b.ToTable("CvChunks");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/Rayson.CV/Api/Database/RaysonDevDbContext.cs b/Rayson.CV/Api/Database/RaysonDevDbContext.cs
deleted file mode 100644
index 4cbe25b..0000000
--- a/Rayson.CV/Api/Database/RaysonDevDbContext.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Globalization;
-using Microsoft.EntityFrameworkCore;
-using Domain;
-
-namespace Database;
-
-public class RaysonCVDbContext : DbContext
-{
- public RaysonCVDbContext()
- : base() { }
-
- public RaysonCVDbContext(DbContextOptions options)
- : base(options) { }
-
- public DbSet CvChunks { get; set; } = null!;
-
- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- {
- optionsBuilder.UseNpgsql();
- }
-
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- modelBuilder.Entity(entity =>
- {
- entity.Property(e => e.Embedding)
- .HasColumnType("vector(768)")
- .HasConversion(
- v => "[" + string.Join(",", v.Select(e => e.ToString("G10", CultureInfo.InvariantCulture))) + "]",
- v => v.Trim('[', ']').Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
- .Select(double.Parse)
- .ToArray()
- );
- });
- }
-}
\ No newline at end of file
diff --git a/Rayson.CV/Api/Database/Repository.cs b/Rayson.CV/Api/Database/Repository.cs
deleted file mode 100644
index 1f01465..0000000
--- a/Rayson.CV/Api/Database/Repository.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-using Domain;
-using Domain.Repositories;
-using Microsoft.EntityFrameworkCore;
-
-namespace Database;
-
-public class Repository(RaysonCVDbContext context) : IRepository where T : Entity
-{
- protected readonly DbContext _context = context;
- protected readonly DbSet _dbSet = context.Set();
-
- public IQueryable Query()
- {
- return _context.Set().AsQueryable();
- }
-
- public async Task AddAsync(T entity)
- {
- await _context.Set().AddAsync(entity);
- await _context.SaveChangesAsync();
- }
-
- public async Task UpdateAsync(T entity)
- {
- _context.Entry(entity).State = EntityState.Modified;
- await _context.SaveChangesAsync();
- }
-
- public async Task SoftDeleteAsync(T entity)
- {
- entity.DeletedAt = DateTime.UtcNow;
- _context.Entry(entity).State = EntityState.Modified;
- await _context.SaveChangesAsync();
- }
-
- public async Task HardDeleteAsync(T entity)
- {
- _context.Set().Remove(entity);
- await _context.SaveChangesAsync();
- }
-
- public async Task> GetListAsync(IQueryable? predicate = null, bool includeDeleted = false)
- {
- IQueryable query = (predicate ?? Query()).Where(e => includeDeleted || e.DeletedAt == null);
- return await query.ToListAsync();
- }
-
- public async Task GetByIdAsync(int id)
- {
- return await _context.Set().FindAsync(id);
- }
-
- public async Task AddManyAsync(T[] entities)
- {
- foreach (var entity in entities)
- {
- await _context.Set().AddAsync(entity);
- }
- await _context.SaveChangesAsync();
- }
-
- public async Task UpdateManyAsync(T[] entities)
- {
- foreach (var entity in entities)
- {
- _context.Entry(entity).State = EntityState.Modified;
- }
- await _context.SaveChangesAsync();
- }
-
- public async Task SoftDeleteManyAsync(T[] entities)
- {
- foreach (var entity in entities)
- {
- entity.DeletedAt = DateTime.UtcNow;
- _context.Entry(entity).State = EntityState.Modified;
- }
- await _context.SaveChangesAsync();
- }
-
- public async Task HardDeleteManyAsync(T[] entities)
- {
- foreach (var entity in entities)
- {
- _context.Set().Remove(entity);
- }
- await _context.SaveChangesAsync();
- }
- public async Task> GetByIdManyAsync(int[] ids)
- {
- return await _context.Set().Where(e => ids.Contains(e.Id)).ToListAsync();
- }
-}
diff --git a/Rayson.CV/Api/Database/SeedData/DataSeeder.cs b/Rayson.CV/Api/Database/SeedData/DataSeeder.cs
deleted file mode 100644
index fe14120..0000000
--- a/Rayson.CV/Api/Database/SeedData/DataSeeder.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Database.SeedData;
-
-public class DataSeeder : IDataSeeder
-{
- public Task SeedDatabaseAsync()
- {
- return Task.CompletedTask;
- }
-}
diff --git a/Rayson.CV/Api/Database/SeedData/IDataSeeder.cs b/Rayson.CV/Api/Database/SeedData/IDataSeeder.cs
deleted file mode 100644
index 2ae1700..0000000
--- a/Rayson.CV/Api/Database/SeedData/IDataSeeder.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Database.SeedData;
-
-public interface IDataSeeder
-{
- public Task SeedDatabaseAsync();
-}
diff --git a/Rayson.CV/Api/Dockerfile b/Rayson.CV/Api/Dockerfile
index aec5763..9663e31 100644
--- a/Rayson.CV/Api/Dockerfile
+++ b/Rayson.CV/Api/Dockerfile
@@ -13,7 +13,6 @@ COPY ["Presentation/Presentation.csproj", "Presentation/"]
COPY ["Application/Application.csproj", "Application/"]
COPY ["Domain/Domain.csproj", "Domain/"]
COPY ["Infrastructure/Infrastructure.csproj", "Infrastructure/"]
-COPY ["Database/Database.csproj", "Database/"]
# Restore NuGet packages based on project references
RUN dotnet restore "Presentation/Presentation.csproj"
@@ -60,11 +59,6 @@ ENV ASPNETCORE_URLS=http://+:8080
ENV AuthOptions__Issuer=danrayson
ENV AuthOptions__Audience=RaysonCVUsers
ENV AuthOptions__IssuerSigningKey=development_signing_key_for_testing_only_not_for_production
-ENV POSTGRES_HOST=host.docker.internal
-ENV POSTGRES_PORT=5433
-ENV POSTGRES_DATABASE=raysoncv
-ENV POSTGRES_USERNAME=postgres
-ENV POSTGRES_PASSWORD=postgres
ENV Cors__AllowedOrigins=http://localhost:3000
ENV OLLAMA__BASEURL=http://ollama:11434
diff --git a/Rayson.CV/Api/Domain/CvChunk.cs b/Rayson.CV/Api/Domain/CvChunk.cs
deleted file mode 100644
index 4ad5b9e..0000000
--- a/Rayson.CV/Api/Domain/CvChunk.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Domain;
-
-public class CvChunk : Entity
-{
- public required string Content { get; set; }
- public required string Section { get; set; }
- public required double[] Embedding { get; set; }
- public int ChunkIndex { get; set; }
- public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
-}
diff --git a/Rayson.CV/Api/Domain/IRepository.cs b/Rayson.CV/Api/Domain/IRepository.cs
deleted file mode 100644
index b9ce800..0000000
--- a/Rayson.CV/Api/Domain/IRepository.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Domain.Repositories;
-
-public interface IRepository where T : Entity
-{
- IQueryable Query();
- Task AddAsync(T entity);
- Task AddManyAsync(T[] entities);
- Task UpdateAsync(T entity);
- Task UpdateManyAsync(T[] entities);
- Task SoftDeleteAsync(T entity);
- Task SoftDeleteManyAsync(T[] entities);
- Task HardDeleteAsync(T entity);
- Task HardDeleteManyAsync(T[] entities);
- Task> GetListAsync(IQueryable? predicate = null, bool includeDeleted = false);
- Task GetByIdAsync(int id);
- Task> GetByIdManyAsync(int[] ids);
-}
\ No newline at end of file
diff --git a/Rayson.CV/Api/Domain/Resources/cv.md b/Rayson.CV/Api/Domain/Resources/cv.md
index cec993c..0f8581d 100644
--- a/Rayson.CV/Api/Domain/Resources/cv.md
+++ b/Rayson.CV/Api/Domain/Resources/cv.md
@@ -4,7 +4,7 @@ Senior DotNet Developer
PERSONAL DETAILS
-ADDRESS | 85 Bagots Oak, ST17 9SB
+ADDRESS | Stafford, ST17 9SB
TELEPHONE | 07703 574867
EMAIL | daniel@rayson.dev
WEBSITE | www.rayson.dev
@@ -35,27 +35,27 @@ Has worked with continuous integration frameworks such as Octopus, Jenkins and T
WORK EXPERIENCE
-Freelance (Personal and Brochure Websites) 2007 – 2012
+Freelance (Personal and Brochure Websites) 2007 - 2012
Title: Website Development and Support
• Design and implementation of custom websites
• General site maintenance and updates
• Site specification and general IT consultation
-DBLogic (Software House) Spring 2012 - Spring 2014
+DBLogic (Software House) 2012 - 2014
Title: Software Development Engineer
• Full development life-cycle ownership
• Customer support and software bug fixing
• Experience with the SCRUM method
• Multiple completed business applications
-ARVS ltd (Car Accident Claims Management) Summer 2014 - Summer 2016
+ARVS ltd (Car Accident Claims Management) 2014 - 2016
Title: Web Developer
• Supporting and maintaining a bespoke motor accident management website
• Creating add-ons and new services in an agile environment
• Advisor to junior members of staff and introducer of new technologies to the team
• Consulted with end users to achieve the best results
-Inspired Gaming UK Ltd (Video Streaming and Back-end Services) Winter 2017 – Winter 2021
+Inspired Gaming UK Ltd (Video Streaming and Back-end Services) 2017 – 2021
Title: Senior Software Developer
• Development ownership of solo-projects
• Supporting and advising the development team
@@ -63,18 +63,21 @@ Title: Senior Software Developer
• Unit, Behaviour, and Integration testing using various suites
• Completed multiple projects through to production
-The Site Doctor (Small and Highly Skilled Software House) Winter 2021 – Winter 2023
+The Site Doctor (Small and Highly Skilled Software House) 2021 – 2024
Title: Software Developer
• Full project ownership, requirement gathering, work specification, deployment and support
• Supporting and advising the development team
• Developed B2B APIs deployed to the Cloud using CI
• Responsible for communicating with clients and work team
-Self Employed (Material Product Arbitrage) Winter 2023 – Current
-Title: N/A
- • Self-directed research and systems design
- • Responsibility for following regulatory requirements
- • Automation, data validation, service interfacing
+Freelance 2024 - Current
+Title: Senior Full-Stack Developer
+ • Followed ISO 29110 processes to ensure quality delivery
+ • Cloud and On-site hosting solutions with full CI/CD pipelines
+ • Full project lifecycle ownership from requirements through deployment
+ • Completed full-stack applications: arbitrage platform and custom e-commerce shop
+ • Contract and legal compliance considerations in project delivery
+ • Self-hosted portfolio website (www.rayson.dev) and additional personal projects
HOME EXPERIENCE
diff --git a/Rayson.CV/Api/Infrastructure/Chatbot/OllamaChatbotService.cs b/Rayson.CV/Api/Infrastructure/Chatbot/OllamaChatbotService.cs
index a51ecd1..4a92e94 100644
--- a/Rayson.CV/Api/Infrastructure/Chatbot/OllamaChatbotService.cs
+++ b/Rayson.CV/Api/Infrastructure/Chatbot/OllamaChatbotService.cs
@@ -7,8 +7,7 @@ namespace Infrastructure.Chatbot;
public class OllamaChatbotService(
IOllamaService ollamaService,
- ICvProvider cvProvider,
- ILogger logger) : IChatbotService
+ ICvProvider cvProvider) : IChatbotService
{
private const string Model = "llama3.2:latest";
private const int MaxTokens = 35; // ~100 words
diff --git a/Rayson.CV/Api/Infrastructure/Extensions/ServiceCollectionExtensions.cs b/Rayson.CV/Api/Infrastructure/Extensions/ServiceCollectionExtensions.cs
index fb03899..16f82f2 100644
--- a/Rayson.CV/Api/Infrastructure/Extensions/ServiceCollectionExtensions.cs
+++ b/Rayson.CV/Api/Infrastructure/Extensions/ServiceCollectionExtensions.cs
@@ -6,9 +6,9 @@
using Infrastructure.Contact;
using Infrastructure.Health;
using Infrastructure.Logging;
-using Infrastructure.RAG;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
using Rayson.Ollama.Extensions;
namespace Infrastructure.Extensions;
@@ -29,8 +29,7 @@ public static void AddInfrastructureServices(this IServiceCollection services, C
services.AddOllama();
services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
+ services.AddHealthChecks()
+ .AddCheck("ollama", tags: new[] { "ready" });
}
}
diff --git a/Rayson.CV/Api/Infrastructure/Health/OllamaHealthCheck.cs b/Rayson.CV/Api/Infrastructure/Health/OllamaHealthCheck.cs
new file mode 100644
index 0000000..b3b24c1
--- /dev/null
+++ b/Rayson.CV/Api/Infrastructure/Health/OllamaHealthCheck.cs
@@ -0,0 +1,22 @@
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Rayson.Ollama;
+
+namespace Infrastructure.Health;
+
+public class OllamaHealthCheck(IOllamaService ollamaService) : IHealthCheck
+{
+ public async Task CheckHealthAsync(
+ HealthCheckContext context,
+ CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ var models = await ollamaService.ListModelsAsync();
+ return HealthCheckResult.Healthy($"Ollama is running with {models.Count()} models available");
+ }
+ catch (Exception ex)
+ {
+ return HealthCheckResult.Unhealthy("Ollama connection failed", ex);
+ }
+ }
+}
diff --git a/Rayson.CV/Api/Infrastructure/Infrastructure.csproj b/Rayson.CV/Api/Infrastructure/Infrastructure.csproj
index 6773c8f..7e17a83 100644
--- a/Rayson.CV/Api/Infrastructure/Infrastructure.csproj
+++ b/Rayson.CV/Api/Infrastructure/Infrastructure.csproj
@@ -3,7 +3,6 @@
-
diff --git a/Rayson.CV/Api/Infrastructure/Ollama/ollama.README.md b/Rayson.CV/Api/Infrastructure/Ollama/ollama.README.md
index 52d6ca1..9398e83 100755
--- a/Rayson.CV/Api/Infrastructure/Ollama/ollama.README.md
+++ b/Rayson.CV/Api/Infrastructure/Ollama/ollama.README.md
@@ -57,12 +57,7 @@ await foreach (var chunk in ollama.ChatStreamAsync(
Console.Write(chunk.Message?.Content);
}
-// Generate embeddings
-var embedding = await ollama.EmbedAsync("Hello", "nomic-embed-text");
-Console.WriteLine(embedding.Embedding.Count);
-```
-
-## Prerequisites
+// ## Prerequisites
### NVIDIA GPU Support
@@ -107,11 +102,11 @@ docker compose ps
### Using Multiple Models
-By default, the container starts with `smollm:135m` and `nomic-embed-text`. To configure models, edit the `CUSTOM_MODELS` environment variable in `ollama.docker-compose.yml`:
+By default, the container starts with `smollm:135m`. To configure models, edit the `CUSTOM_MODELS` environment variable in `ollama.docker-compose.yml`:
```yaml
environment:
- - CUSTOM_MODELS=["smollm:135m","nomic-embed-text"]
+ - CUSTOM_MODELS=["smollm:135m"]
```
The `CUSTOM_MODELS` environment variable accepts a JSON array of model names. The entrypoint script will check if each model exists and pull it only if missing.
@@ -124,7 +119,7 @@ The `CUSTOM_MODELS` environment variable accepts a JSON array of model names. Th
| Environment Variable | Default | Description |
|---------------------|---------|-------------|
-| `CUSTOM_MODELS` | `["smollm:135m","nomic-embed-text"]` | Models to check/pull on startup |
+| `CUSTOM_MODELS` | `["smollm:135m"]` | Models to check/pull on startup |
> **Note:** The `OLLAMA_HOST` in the Ollama container is hardcoded to `0.0.0.0` so connections from other containers are accepted. This could raise security issues for your environment.
diff --git a/Rayson.CV/Api/Infrastructure/Ollama/ollama.docker-compose.yml b/Rayson.CV/Api/Infrastructure/Ollama/ollama.docker-compose.yml
index cb0690c..94f5256 100755
--- a/Rayson.CV/Api/Infrastructure/Ollama/ollama.docker-compose.yml
+++ b/Rayson.CV/Api/Infrastructure/Ollama/ollama.docker-compose.yml
@@ -4,10 +4,10 @@ services:
context: .
dockerfile: ollama.Dockerfile
environment:
- - CUSTOM_MODELS=["llama3.2:latest","nomic-embed-text"]
+ - CUSTOM_MODELS=["llama3.2:latest"]
volumes:
- ollama_data:/root/.ollama
- container_name: ollama
+ container_name: raysoncv-ollama
healthcheck:
test: ["CMD", "/ollama.healthcheck.sh"]
interval: 10s
diff --git a/Rayson.CV/Api/Infrastructure/RAG/CvChunkRepository.cs b/Rayson.CV/Api/Infrastructure/RAG/CvChunkRepository.cs
deleted file mode 100644
index 95ac2f9..0000000
--- a/Rayson.CV/Api/Infrastructure/RAG/CvChunkRepository.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-using Database;
-using Microsoft.EntityFrameworkCore;
-using Npgsql;
-
-namespace Infrastructure.RAG;
-
-public class CvChunkRepository(RaysonCVDbContext context) : ICvChunkRepository
-{
- private readonly RaysonCVDbContext _context = context;
- private const double SectionBoostAmount = 0.15;
-
- private static readonly Dictionary SectionKeywords = new(StringComparer.OrdinalIgnoreCase)
- {
- ["PERSONAL DETAILS"] = ["personal", "details", "contact", "email", "phone", "address", "name"],
- ["EDUCATION"] = ["education", "school", "university", "degree", "qualification", "college", "gcse", "a-level", "mathematics"],
- ["PROFESSIONAL SKILLS SUMMARY"] = ["skill", "skills", "tech", "technology", "stack", "experience", "expertise", "language", "framework", "tool"],
- ["WORK EXPERIENCE"] = ["work", "job", "employment", "career", "position", "role", "company", "employer", "professional"],
- ["HOME EXPERIENCE"] = ["home", "personal", "project", "projects", "hobby", "interest", "side"],
- ["PERSONAL DESCRIPTION"] = ["about", "description", "profile", "background", "summary", "character"],
- ["References"] = ["reference", "references", "referee", "contact"]
- };
-
- public async Task AddRangeAsync(IEnumerable chunks)
- {
- foreach (var chunk in chunks)
- {
- var embeddingStr = string.Join(",", chunk.Embedding.Select(e => e.ToString("G10", System.Globalization.CultureInfo.InvariantCulture)));
- var deletedAtStr = chunk.DeletedAt.HasValue
- ? $"'{chunk.DeletedAt.Value:u}'"
- : "NULL";
-
- await _context.Database.ExecuteSqlRawAsync($@"
- INSERT INTO ""CvChunks"" (""Content"", ""Section"", ""Embedding"", ""ChunkIndex"", ""CreatedAt"", ""DeletedAt"")
- VALUES ('{chunk.Content.Replace("'", "''")}', '{chunk.Section.Replace("'", "''")}', '[{embeddingStr}]'::vector, {chunk.ChunkIndex}, '{chunk.CreatedAt:u}', {deletedAtStr})");
- }
- }
-
- public async Task ExistsAsync()
- {
- return await _context.CvChunks.AnyAsync();
- }
-
- public async Task> GetMostSimilarAsync(float[] embedding, int topK)
- {
- return await GetMostSimilarAsync(embedding, topK, null);
- }
-
- public async Task> GetMostSimilarAsync(float[] embedding, int topK, string? query)
- {
- var embeddingString = string.Join(",", embedding.Select(e => e.ToString("G10", System.Globalization.CultureInfo.InvariantCulture)));
-
- var (matchedSection, boost) = query != null
- ? FindMatchingSection(query)
- : (null, 0.0);
-
- string sql;
- if (matchedSection != null && boost > 0)
- {
- sql = $@"
- SELECT ""Content""
- FROM ""CvChunks""
- ORDER BY ""Embedding"" <-> '[{embeddingString}]'::vector +
- (CASE WHEN ""Section"" = '{matchedSection.Replace("'", "''")}' THEN {boost.ToString("G10", System.Globalization.CultureInfo.InvariantCulture)} ELSE 0 END)
- LIMIT {topK}";
- }
- else
- {
- sql = $@"
- SELECT ""Content""
- FROM ""CvChunks""
- ORDER BY ""Embedding"" <-> '[{embeddingString}]'::vector
- LIMIT {topK}";
- }
-
- var result = await _context.Database
- .SqlQueryRaw(sql)
- .ToListAsync();
-
- return result;
- }
-
- private static (string? Section, double Boost) FindMatchingSection(string query)
- {
- var queryLower = query.ToLowerInvariant();
- var queryWords = queryLower.Split([' ', ',', '.', '!', '?', ';', ':', '\t', '\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
-
- if (queryWords.Length == 0)
- return (null, 0);
-
- var sectionScores = new Dictionary(StringComparer.OrdinalIgnoreCase);
-
- foreach (var (section, keywords) in SectionKeywords)
- {
- var score = 0.0;
-
- foreach (var queryWord in queryWords)
- {
- if (string.IsNullOrWhiteSpace(queryWord) || queryWord.Length < 3)
- continue;
-
- foreach (var keyword in keywords)
- {
- var similarity = CalculateSimilarity(queryWord, keyword);
- if (similarity > 0.6)
- {
- score += similarity;
- }
- else if (keyword.Contains(queryWord, StringComparison.OrdinalIgnoreCase) ||
- queryWord.Contains(keyword, StringComparison.OrdinalIgnoreCase))
- {
- score += 0.5;
- }
- }
- }
-
- if (score > 0)
- {
- sectionScores[section] = score;
- }
- }
-
- if (sectionScores.Count == 0)
- return (null, 0);
-
- var bestSection = sectionScores.OrderByDescending(x => x.Value).First();
-
- var maxPossibleScore = queryWords.Length * 1.0;
- var normalizedBoost = Math.Min(bestSection.Value / maxPossibleScore, 1.0) * SectionBoostAmount;
-
- return (bestSection.Key, normalizedBoost);
- }
-
- private static double CalculateSimilarity(string s1, string s2)
- {
- if (string.IsNullOrEmpty(s1) || string.IsNullOrEmpty(s2))
- return 0;
-
- var distance = LevenshteinDistance(s1, s2);
- var maxLength = Math.Max(s1.Length, s2.Length);
-
- return 1.0 - (double)distance / maxLength;
- }
-
- private static int LevenshteinDistance(string s1, string s2)
- {
- var m = s1.Length;
- var n = s2.Length;
- var d = new int[m + 1, n + 1];
-
- for (var i = 0; i <= m; i++)
- d[i, 0] = i;
- for (var j = 0; j <= n; j++)
- d[0, j] = j;
-
- for (var i = 1; i <= m; i++)
- {
- for (var j = 1; j <= n; j++)
- {
- var cost = s1[i - 1] == s2[j - 1] ? 0 : 1;
- d[i, j] = Math.Min(
- Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
- d[i - 1, j - 1] + cost);
- }
- }
-
- return d[m, n];
- }
-}
diff --git a/Rayson.CV/Api/Infrastructure/RAG/ICvChunkRepository.cs b/Rayson.CV/Api/Infrastructure/RAG/ICvChunkRepository.cs
deleted file mode 100644
index 77ef9bd..0000000
--- a/Rayson.CV/Api/Infrastructure/RAG/ICvChunkRepository.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Infrastructure.RAG;
-
-public interface ICvChunkRepository
-{
- Task AddRangeAsync(IEnumerable chunks);
- Task ExistsAsync();
- Task> GetMostSimilarAsync(float[] embedding, int topK);
- Task> GetMostSimilarAsync(float[] embedding, int topK, string? query);
-}
diff --git a/Rayson.CV/Api/Infrastructure/RAG/IEmbeddingService.cs b/Rayson.CV/Api/Infrastructure/RAG/IEmbeddingService.cs
deleted file mode 100644
index 9aed865..0000000
--- a/Rayson.CV/Api/Infrastructure/RAG/IEmbeddingService.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Infrastructure.RAG;
-
-public interface IEmbeddingService
-{
- Task GenerateEmbeddingAsync(string text);
- Task EnsureModelAvailableAsync();
-}
diff --git a/Rayson.CV/Api/Infrastructure/RAG/IRagService.cs b/Rayson.CV/Api/Infrastructure/RAG/IRagService.cs
deleted file mode 100644
index 7a5c97d..0000000
--- a/Rayson.CV/Api/Infrastructure/RAG/IRagService.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Infrastructure.RAG;
-
-public interface IRagService
-{
- Task InitializeAsync();
- Task> SearchAsync(string query, int topK = 3);
-}
diff --git a/Rayson.CV/Api/Infrastructure/RAG/OllamaEmbeddingService.cs b/Rayson.CV/Api/Infrastructure/RAG/OllamaEmbeddingService.cs
deleted file mode 100644
index f394681..0000000
--- a/Rayson.CV/Api/Infrastructure/RAG/OllamaEmbeddingService.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using Microsoft.Extensions.Logging;
-using Rayson.Ollama;
-
-namespace Infrastructure.RAG;
-
-public class OllamaEmbeddingService(
- IOllamaService ollamaService,
- ILogger logger) : IEmbeddingService
-{
- private const string EmbeddingModel = "nomic-embed-text:latest";
-
- public async Task GenerateEmbeddingAsync(string text)
- {
- var response = await ollamaService.EmbedAsync(text, EmbeddingModel);
-
- if (response?.Embedding == null)
- {
- throw new Exception("Invalid response from Ollama embedding endpoint");
- }
-
- return response.Embedding.Select(x => (float)x).ToArray();
- }
-
- public async Task EnsureModelAvailableAsync()
- {
- logger.LogInformation("Checking if embedding model {Model} is available", EmbeddingModel);
-
- var models = await ollamaService.ListModelsAsync();
- var modelExists = models.Any(m => m.Name == EmbeddingModel);
-
- if (!modelExists)
- {
- logger.LogInformation("Pulling embedding model {Model}...", EmbeddingModel);
- await ollamaService.PullModelAsync(EmbeddingModel);
- }
- else
- {
- logger.LogInformation("Embedding model {Model} is already available", EmbeddingModel);
- }
- }
-}
diff --git a/Rayson.CV/Api/Infrastructure/RAG/RagService.cs b/Rayson.CV/Api/Infrastructure/RAG/RagService.cs
deleted file mode 100644
index 22497bc..0000000
--- a/Rayson.CV/Api/Infrastructure/RAG/RagService.cs
+++ /dev/null
@@ -1,260 +0,0 @@
-using Application.Chatbot;
-using Domain;
-using Microsoft.Extensions.Logging;
-
-namespace Infrastructure.RAG;
-
-public class RagService(
- ICvChunkRepository cvChunkRepository,
- IEmbeddingService embeddingService,
- ICvProvider cvProvider,
- ILogger logger) : IRagService
-{
- private const int MaxChunkSize = 350;
- private const int MinChunkSize = 100;
-
- public async Task InitializeAsync()
- {
- if (await cvChunkRepository.ExistsAsync())
- {
- logger.LogInformation("CV chunks already exist in database, skipping RAG initialization");
- return;
- }
-
- logger.LogInformation("Starting RAG initialization...");
-
- await embeddingService.EnsureModelAvailableAsync();
-
- var cvContent = cvProvider.GetCvContent();
- var chunks = ChunkCvContent(cvContent);
-
- logger.LogInformation("Generated {Count} chunks from CV", chunks.Count);
-
- var cvChunks = new List();
- foreach (var (content, section, index) in chunks)
- {
- logger.LogDebug("Generating embedding for chunk {Index} ({Section})", index, section);
- var embedding = await embeddingService.GenerateEmbeddingAsync(content);
-
- cvChunks.Add(new CvChunk
- {
- Content = content,
- Section = section,
- Embedding = [.. embedding.Select(e => (double)e)],
- ChunkIndex = index,
- CreatedAt = DateTime.UtcNow
- });
- }
-
- await cvChunkRepository.AddRangeAsync(cvChunks);
-
- logger.LogInformation("RAG initialization complete. Saved {Count} chunks to database", cvChunks.Count);
- }
-
- public async Task> SearchAsync(string query, int topK = 3)
- {
- var queryEmbedding = await embeddingService.GenerateEmbeddingAsync(query);
- var results = await cvChunkRepository.GetMostSimilarAsync(queryEmbedding, topK, query);
-
- return results;
- }
-
- private static List<(string Content, string Section, int Index)> ChunkCvContent(string cvContent)
- {
- var chunks = new List<(string Content, string Section, int Index)>();
- var lines = cvContent.Split('\n');
- var currentSection = "General";
- var sectionContent = new List();
- var chunkIndex = 0;
-
- var sectionHeaders = new HashSet(StringComparer.OrdinalIgnoreCase)
- {
- "PERSONAL DETAILS",
- "EDUCATION",
- "PROFESSIONAL SKILLS SUMMARY",
- "WORK EXPERIENCE",
- "HOME EXPERIENCE",
- "PERSONAL DESCRIPTION",
- "References"
- };
-
- foreach (var line in lines)
- {
- var trimmedLine = line.Trim();
-
- if (sectionHeaders.Contains(trimmedLine))
- {
- if (sectionContent.Count > 0)
- {
- var sectionText = string.Join("\n", sectionContent);
- var sectionChunks = SplitIntoChunks(sectionText, currentSection);
- chunks.AddRange(sectionChunks.Select((content, i) => (content, currentSection, chunkIndex++)));
- sectionContent.Clear();
- }
-
- currentSection = trimmedLine;
- }
- else
- {
- if(!string.IsNullOrWhiteSpace(line))
- sectionContent.Add(line);
- }
- }
-
- if (sectionContent.Count > 0)
- {
- var sectionText = string.Join("\n", sectionContent);
- var sectionChunks = SplitIntoChunks(sectionText, currentSection);
- chunks.AddRange(sectionChunks.Select((content, i) => (content, currentSection, chunkIndex++)));
- }
-
- return chunks;
- }
-
- private static List SplitIntoChunks(string text, string section)
- {
- if (string.IsNullOrWhiteSpace(text))
- return [];
-
- var chunks = new List();
- var paragraphs = text.Split("\n\n", StringSplitOptions.RemoveEmptyEntries);
-
- foreach (var paragraph in paragraphs)
- {
- var trimmedParagraph = paragraph.Trim();
- if (string.IsNullOrWhiteSpace(trimmedParagraph))
- continue;
-
- if (trimmedParagraph.Length <= MaxChunkSize)
- {
- chunks.Add(trimmedParagraph);
- continue;
- }
-
- var lines = trimmedParagraph.Split('\n', StringSplitOptions.RemoveEmptyEntries);
- var currentChunk = new List();
-
- foreach (var line in lines)
- {
- var trimmedLine = line.Trim();
- if (string.IsNullOrWhiteSpace(trimmedLine))
- continue;
-
- var potentialChunk = currentChunk.Count > 0
- ? string.Join("\n", currentChunk) + "\n" + trimmedLine
- : trimmedLine;
-
- if (potentialChunk.Length <= MaxChunkSize)
- {
- currentChunk.Add(trimmedLine);
- }
- else
- {
- if (currentChunk.Count > 0)
- {
- chunks.Add(string.Join("\n", currentChunk));
- }
-
- if (trimmedLine.Length > MaxChunkSize)
- {
- chunks.AddRange(SplitBySentences(trimmedLine));
- currentChunk.Clear();
- }
- else
- {
- currentChunk = [trimmedLine];
- }
- }
- }
-
- if (currentChunk.Count > 0)
- {
- chunks.Add(string.Join("\n", currentChunk));
- }
- }
-
- return chunks;
- }
-
- private static List SplitBySentences(string text)
- {
- var chunks = new List();
- if (string.IsNullOrWhiteSpace(text))
- return chunks;
-
- var sentences = SplitIntoSentences(text);
- var currentChunk = new List();
-
- foreach (var sentence in sentences)
- {
- var trimmed = sentence.Trim();
- if (string.IsNullOrWhiteSpace(trimmed))
- continue;
-
- var potentialChunk = currentChunk.Count > 0
- ? string.Join(" ", currentChunk) + " " + trimmed
- : trimmed;
-
- if (potentialChunk.Length <= MaxChunkSize)
- {
- currentChunk.Add(trimmed);
- }
- else
- {
- if (currentChunk.Count > 0)
- {
- chunks.Add(string.Join(" ", currentChunk));
- }
-
- if (trimmed.Length > MaxChunkSize)
- {
- for (var i = 0; i < trimmed.Length; i += MaxChunkSize - MinChunkSize)
- {
- var chunk = trimmed.Substring(i, Math.Min(MaxChunkSize - MinChunkSize, trimmed.Length - i));
- if (!string.IsNullOrWhiteSpace(chunk))
- chunks.Add(chunk);
- }
- currentChunk.Clear();
- }
- else
- {
- currentChunk = [trimmed];
- }
- }
- }
-
- if (currentChunk.Count > 0)
- {
- chunks.Add(string.Join(" ", currentChunk));
- }
-
- return chunks;
- }
-
- private static string[] SplitIntoSentences(string text)
- {
- var sentences = new List();
- var current = new System.Text.StringBuilder();
-
- foreach (var c in text)
- {
- current.Append(c);
-
- if (c == '.' || c == '!' || c == '?')
- {
- if (current.Length > 0)
- {
- sentences.Add(current.ToString());
- current.Clear();
- }
- }
- }
-
- if (current.Length > 0)
- {
- sentences.Add(current.ToString());
- }
-
- return sentences.ToArray();
- }
-}
diff --git a/Rayson.CV/Api/Infrastructure/ollama.Dockerfile b/Rayson.CV/Api/Infrastructure/ollama.Dockerfile
deleted file mode 100755
index 1414c6a..0000000
--- a/Rayson.CV/Api/Infrastructure/ollama.Dockerfile
+++ /dev/null
@@ -1,14 +0,0 @@
-FROM ollama/ollama:latest
-
-RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
-
-ENV OLLAMA_HOST=0.0.0.0
-
-COPY ollama.entrypoint.sh /
-COPY ollama.healthcheck.sh /
-
-RUN chmod +x /ollama.entrypoint.sh /ollama.healthcheck.sh
-
-EXPOSE 11434
-
-ENTRYPOINT ["/ollama.entrypoint.sh"]
\ No newline at end of file
diff --git a/Rayson.CV/Api/Infrastructure/ollama.README.md b/Rayson.CV/Api/Infrastructure/ollama.README.md
deleted file mode 100755
index 52d6ca1..0000000
--- a/Rayson.CV/Api/Infrastructure/ollama.README.md
+++ /dev/null
@@ -1,265 +0,0 @@
-# Rayson.Ollama
-
-.NET client library for Ollama with Docker support.
-
-## Installation
-
-```bash
-dotnet add package Rayson.Ollama
-```
-
-> **Note:** This package creates 5 files in your project when you build, these are your ollama configuration files.
-
-## QuickStart
-
-1. Install the NuGet package and Build your project
-2. Move `ollama.Dockerfile` and `*.sh` scripts to suitable location
-3. Merge `ollama.docker-compose.yml` into your compose
-4. Update `ollama>build>context` to files location
-5. Configure `CUSTOM_MODELS` with your desired models
-6. Configure `Ollama__Url` for your deployment scenario (see Configuration>Docker section below)
-7. Optionally add GPU support, see comment in compose file
-
-### Usage:
-
-Using .NET's ServiceCollection for dependency injection:
-
-```csharp
-using Rayson.Ollama.Extensions
-services.AddOllama();
-```
-
-```csharp
-using Rayson.Ollama
-var ollama = sp.GetRequiredService();
-
-// Generate text (non-streaming)
-var result = await ollama.GenerateAsync("Hello", "smollm:135m");
-Console.WriteLine(result.Response);
-
-// Generate with streaming
-await foreach (var chunk in ollama.GenerateStreamAsync("Hello", "smollm:135m"))
-{
- Console.Write(chunk.Response);
-}
-
-// Chat (non-streaming)
-var chatResult = await ollama.ChatAsync(
- [new() { Role = "user", Content = "Hello" }],
- "smollm:135m");
-Console.WriteLine(chatResult.Message?.Content);
-
-// Chat with streaming
-await foreach (var chunk in ollama.ChatStreamAsync(
- [new() { Role = "user", Content = "Hello" }],
- "smollm:135m"))
-{
- Console.Write(chunk.Message?.Content);
-}
-
-// Generate embeddings
-var embedding = await ollama.EmbedAsync("Hello", "nomic-embed-text");
-Console.WriteLine(embedding.Embedding.Count);
-```
-
-## Prerequisites
-
-### NVIDIA GPU Support
-
-If using an NVIDIA GPU, install the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html):
-
-```bash
-# Configure the repository
-curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
- | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
-curl -fsSL https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
- | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
- | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
-sudo apt-get update
-
-# Install the toolkit
-sudo apt-get install -y nvidia-container-toolkit
-
-# Configure Docker
-sudo nvidia-ctk runtime configure --runtime=docker
-sudo systemctl restart docker
-```
-
-After installation, enable NVIDIA GPU support in `ollama.docker-compose.yml` by uncommenting the `deploy` section.
-
-## Docker Setup
-
-### Running Ollama
-
-```bash
-docker compose up --build
-```
-
-> **Note:** By default, Ollama models are persisted in a Docker volume (`ollama_data`). This preserves your downloaded models across container restarts.
-
-### Healthcheck
-
-The docker-compose includes a healthcheck that polls the Ollama API every 10 seconds. You can check the health status with:
-
-```bash
-docker compose ps
-```
-
-### Using Multiple Models
-
-By default, the container starts with `smollm:135m` and `nomic-embed-text`. To configure models, edit the `CUSTOM_MODELS` environment variable in `ollama.docker-compose.yml`:
-
-```yaml
-environment:
- - CUSTOM_MODELS=["smollm:135m","nomic-embed-text"]
-```
-
-The `CUSTOM_MODELS` environment variable accepts a JSON array of model names. The entrypoint script will check if each model exists and pull it only if missing.
-
-## Configuration
-
-### Docker
-
-#### Ollama container
-
-| Environment Variable | Default | Description |
-|---------------------|---------|-------------|
-| `CUSTOM_MODELS` | `["smollm:135m","nomic-embed-text"]` | Models to check/pull on startup |
-
-> **Note:** The `OLLAMA_HOST` in the Ollama container is hardcoded to `0.0.0.0` so connections from other containers are accepted. This could raise security issues for your environment.
-
-#### Consumer container
-
-| Environment Variable | Default | Description |
-|---------------------|---------|-------------|
-| `Ollama__Url` | `http://ollama:11434` | URL for calling containers configuration to reach Ollama API (Docker internal network) |
-
-### Connecting to Ollama
-
-The `Ollama__Url` environment variable specifies the address other containers use to reach Ollama. Set this based on your deployment scenario:
-
-- **Container-to-container** (recommended): Use `http://ollama:11434` (Docker internal network)
-- **Host machine access**: Use `http://localhost:11434` and add `network_mode: host` to your compose:
-
-```yaml
-services:
- your-app:
- network_mode: host
-```
-
-### .NET Client
-
-Configure the Ollama URL in your .NET application to match the `Ollama__Url` value from your docker-compose:
-
-**Via docker-compose.yml:**
-```yaml
-services:
- webapp:
- build: .
- image: my-example-app:latest
- environment:
- - Ollama__Url=http://ollama:11434
-```
-
-The URL should match the `Ollama__Url` value in your docker-compose.
-
-## API Reference
-
-### IOllamaService
-
-```csharp
-// Generate text (non-streaming)
-Task GenerateAsync(string prompt, string model, GenerateOptions? options = null, CancellationToken ct = default)
-
-// Generate text with streaming
-IAsyncEnumerable GenerateStreamAsync(string prompt, string model, GenerateOptions? options = null, CancellationToken ct = default)
-
-// Chat (non-streaming)
-Task ChatAsync(IList messages, string model, ChatOptions? options = null, CancellationToken ct = default)
-
-// Chat with streaming
-IAsyncEnumerable ChatStreamAsync(IList messages, string model, ChatOptions? options = null, CancellationToken ct = default)
-
-// Generate embeddings
-Task EmbedAsync(string input, string model, CancellationToken ct = default)
-
-// List available models
-Task> ListModelsAsync(CancellationToken ct = default)
-
-// Pull a model
-Task PullModelAsync(string model, IProgress? progress = null, CancellationToken ct = default)
-
-// Delete a model
-Task DeleteModelAsync(string model, CancellationToken ct = default)
-```
-
-### Options
-
-```csharp
-// Options for Generate/GenerateStream
-public class GenerateOptions
-{
- public string? Model { get; set; }
- public string? Prompt { get; set; }
- public string? System { get; set; }
- public string? Template { get; set; }
- public string? Context { get; set; }
- public bool Raw { get; set; }
- public double? Temperature { get; set; }
- public double? TopP { get; set; }
- public int? TopK { get; set; }
- public double? RepeatPenalty { get; set; }
- public int? NumPredict { get; set; }
- public IList? Stop { get; set; }
-}
-
-// Options for Chat/ChatStream
-public class ChatOptions
-{
- public string? Model { get; set; }
- public string? System { get; set; }
- public IList? Messages { get; set; }
- public double? Temperature { get; set; }
- public double? TopP { get; set; }
- public int? TopK { get; set; }
- public double? RepeatPenalty { get; set; }
- public int? NumPredict { get; set; }
- public IList? Stop { get; set; }
-}
-```
-
-### Response Types
-
-```csharp
-public class OllamaMessage
-{
- public string? Role { get; set; }
- public string? Content { get; set; }
-}
-
-public class OllamaResponse
-{
- public string? Model { get; set; }
- public bool Done { get; set; }
- public string? DoneReason { get; set; }
- public long? TotalDuration { get; set; }
- public long? LoadDuration { get; set; }
- public int? PromptEvalCount { get; set; }
- public int? EvalCount { get; set; }
- public long? EvalDuration { get; set; }
-}
-
-public class GenerateResponse : OllamaResponse
-{
- public string? Response { get; set; }
-}
-
-public class ChatResponse : OllamaResponse
-{
- public OllamaMessage? Message { get; set; }
-}
-
-public record ModelInfo(string Name, string ModifiedAt, long Size);
-
-public record EmbeddingsResponse(IReadOnlyList