From fe3dbc37ad4db0681c49792e0eb5694db2aed658 Mon Sep 17 00:00:00 2001 From: Gelu Mihes Date: Thu, 7 May 2026 17:09:24 +0300 Subject: [PATCH] Changes --- cv-matcher-api/Data/CvMatcherDbContext.cs | 10 +- .../Data/Repositories/EfMatcherRepository.cs | 2 +- ...7140442_InitialCvMatcherSchema.Designer.cs | 95 +++++++++ .../20260507140442_InitialCvMatcherSchema.cs | 70 +++++++ .../CvMatcherDbContextModelSnapshot.cs | 92 +++++++++ cv-matcher-api/Program.cs | 6 +- docker-compose/docker-compose.dcproj | 6 - docker-compose/docker-compose.production.yml | 94 --------- docker-compose/docker-compose.staging.yml | 79 -------- docker-compose/docker-compose.yml | 167 ++++++++++++++-- rag-api/Data/RagDbContext.cs | 13 +- rag-api/Data/Repositories/EfRagRepository.cs | 2 +- ...0260507140305_InitialRagSchema.Designer.cs | 188 ++++++++++++++++++ .../20260507140305_InitialRagSchema.cs | 137 +++++++++++++ .../Migrations/RagDbContextModelSnapshot.cs | 185 +++++++++++++++++ rag-api/Program.cs | 5 +- 16 files changed, 939 insertions(+), 212 deletions(-) create mode 100644 cv-matcher-api/Migrations/20260507140442_InitialCvMatcherSchema.Designer.cs create mode 100644 cv-matcher-api/Migrations/20260507140442_InitialCvMatcherSchema.cs create mode 100644 cv-matcher-api/Migrations/CvMatcherDbContextModelSnapshot.cs delete mode 100644 docker-compose/docker-compose.production.yml delete mode 100644 docker-compose/docker-compose.staging.yml create mode 100644 rag-api/Migrations/20260507140305_InitialRagSchema.Designer.cs create mode 100644 rag-api/Migrations/20260507140305_InitialRagSchema.cs create mode 100644 rag-api/Migrations/RagDbContextModelSnapshot.cs diff --git a/cv-matcher-api/Data/CvMatcherDbContext.cs b/cv-matcher-api/Data/CvMatcherDbContext.cs index 37d2c4d..5889116 100644 --- a/cv-matcher-api/Data/CvMatcherDbContext.cs +++ b/cv-matcher-api/Data/CvMatcherDbContext.cs @@ -3,8 +3,12 @@ using Microsoft.EntityFrameworkCore; namespace Api.Data; + public sealed class CvMatcherDbContext : DbContext { + public const string SchemaName = "cvMatcher"; + public const string MigrationTableName = "_Migrations"; + public CvMatcherDbContext(DbContextOptions options) : base(options) { } @@ -14,9 +18,11 @@ public sealed class CvMatcherDbContext : DbContext protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.HasDefaultSchema(SchemaName); + modelBuilder.Entity(entity => { - entity.ToTable("CvMatchResults"); + entity.ToTable("Results"); entity.HasKey(x => x.Id); entity.Property(x => x.Id).HasMaxLength(64); entity.Property(x => x.CvDocumentId).HasMaxLength(64).IsRequired(); @@ -28,7 +34,7 @@ public sealed class CvMatcherDbContext : DbContext modelBuilder.Entity(entity => { - entity.ToTable("CvMatcherChatCache"); + entity.ToTable("ChatCache"); entity.HasKey(x => x.CacheKey); entity.Property(x => x.CacheKey).HasMaxLength(64); entity.Property(x => x.Model).HasMaxLength(120).IsRequired(); diff --git a/cv-matcher-api/Data/Repositories/EfMatcherRepository.cs b/cv-matcher-api/Data/Repositories/EfMatcherRepository.cs index 7354eda..c81618b 100644 --- a/cv-matcher-api/Data/Repositories/EfMatcherRepository.cs +++ b/cv-matcher-api/Data/Repositories/EfMatcherRepository.cs @@ -21,7 +21,7 @@ public sealed class EfMatcherRepository : IMatcherRepository public async Task InitializeAsync(CancellationToken ct) { _logger.LogInformation("Ensuring CV matcher database schema exists using EF Core"); - await _db.Database.EnsureCreatedAsync(ct); + //await _db.Database.EnsureCreatedAsync(ct); } public async Task GetMatchAsync(string cvDocumentId, string jobDocumentId, CancellationToken ct) diff --git a/cv-matcher-api/Migrations/20260507140442_InitialCvMatcherSchema.Designer.cs b/cv-matcher-api/Migrations/20260507140442_InitialCvMatcherSchema.Designer.cs new file mode 100644 index 0000000..1ef6774 --- /dev/null +++ b/cv-matcher-api/Migrations/20260507140442_InitialCvMatcherSchema.Designer.cs @@ -0,0 +1,95 @@ +// +using System; +using Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Api.Migrations +{ + [DbContext(typeof(CvMatcherDbContext))] + [Migration("20260507140442_InitialCvMatcherSchema")] + partial class InitialCvMatcherSchema + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("cvMatcher") + .HasAnnotation("ProductVersion", "10.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Api.Data.Entities.CvMatchResultEntity", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("CvDocumentId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("JobDocumentId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("ResultJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Score") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CvDocumentId", "JobDocumentId") + .IsUnique(); + + b.ToTable("Results", "cvMatcher"); + }); + + modelBuilder.Entity("Api.Data.Entities.CvMatcherChatCacheEntity", b => + { + b.Property("CacheKey") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("Model") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("ResponseText") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Temperature") + .HasColumnType("decimal(4,2)"); + + b.HasKey("CacheKey"); + + b.ToTable("ChatCache", "cvMatcher"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/cv-matcher-api/Migrations/20260507140442_InitialCvMatcherSchema.cs b/cv-matcher-api/Migrations/20260507140442_InitialCvMatcherSchema.cs new file mode 100644 index 0000000..4c62b0f --- /dev/null +++ b/cv-matcher-api/Migrations/20260507140442_InitialCvMatcherSchema.cs @@ -0,0 +1,70 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Api.Migrations +{ + /// + public partial class InitialCvMatcherSchema : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "cvMatcher"); + + migrationBuilder.CreateTable( + name: "ChatCache", + schema: "cvMatcher", + columns: table => new + { + CacheKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + Model = table.Column(type: "nvarchar(120)", maxLength: 120, nullable: false), + Temperature = table.Column(type: "decimal(4,2)", nullable: false), + ResponseText = table.Column(type: "nvarchar(max)", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()") + }, + constraints: table => + { + table.PrimaryKey("PK_ChatCache", x => x.CacheKey); + }); + + migrationBuilder.CreateTable( + name: "Results", + schema: "cvMatcher", + columns: table => new + { + Id = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + CvDocumentId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + JobDocumentId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + ResultJson = table.Column(type: "nvarchar(max)", nullable: false), + Score = table.Column(type: "int", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()") + }, + constraints: table => + { + table.PrimaryKey("PK_Results", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Results_CvDocumentId_JobDocumentId", + schema: "cvMatcher", + table: "Results", + columns: new[] { "CvDocumentId", "JobDocumentId" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ChatCache", + schema: "cvMatcher"); + + migrationBuilder.DropTable( + name: "Results", + schema: "cvMatcher"); + } + } +} diff --git a/cv-matcher-api/Migrations/CvMatcherDbContextModelSnapshot.cs b/cv-matcher-api/Migrations/CvMatcherDbContextModelSnapshot.cs new file mode 100644 index 0000000..9a435fa --- /dev/null +++ b/cv-matcher-api/Migrations/CvMatcherDbContextModelSnapshot.cs @@ -0,0 +1,92 @@ +// +using System; +using Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Api.Migrations +{ + [DbContext(typeof(CvMatcherDbContext))] + partial class CvMatcherDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("cvMatcher") + .HasAnnotation("ProductVersion", "10.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Api.Data.Entities.CvMatchResultEntity", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("CvDocumentId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("JobDocumentId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("ResultJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Score") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CvDocumentId", "JobDocumentId") + .IsUnique(); + + b.ToTable("Results", "cvMatcher"); + }); + + modelBuilder.Entity("Api.Data.Entities.CvMatcherChatCacheEntity", b => + { + b.Property("CacheKey") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("Model") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("ResponseText") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Temperature") + .HasColumnType("decimal(4,2)"); + + b.HasKey("CacheKey"); + + b.ToTable("ChatCache", "cvMatcher"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/cv-matcher-api/Program.cs b/cv-matcher-api/Program.cs index 89eaa7c..f640e1a 100644 --- a/cv-matcher-api/Program.cs +++ b/cv-matcher-api/Program.cs @@ -14,7 +14,6 @@ using Serilog; using Shared.Models.Settings; using StartupHelpers; using System.Reflection; -using System.Xml.Linq; StartupExtensions.LoadDotEnvFile(); @@ -56,7 +55,10 @@ try var configuration = builder.Configuration; var connectionString = builder.Services.GetConfiguredDbConnectionString(configuration); - options.UseSqlServer(connectionString); + options.UseSqlServer(connectionString, sql => + { + sql.MigrationsHistoryTable(CvMatcherDbContext.MigrationTableName, CvMatcherDbContext.SchemaName); + }); }); builder.Services.AddScoped(); diff --git a/docker-compose/docker-compose.dcproj b/docker-compose/docker-compose.dcproj index 3a9c20d..7a3a07a 100644 --- a/docker-compose/docker-compose.dcproj +++ b/docker-compose/docker-compose.dcproj @@ -20,11 +20,5 @@ .env - - docker-compose.yml - - - docker-compose.yml - \ No newline at end of file diff --git a/docker-compose/docker-compose.production.yml b/docker-compose/docker-compose.production.yml deleted file mode 100644 index ddeed46..0000000 --- a/docker-compose/docker-compose.production.yml +++ /dev/null @@ -1,94 +0,0 @@ -services: - api: - image: registry.easysoft.ro/apps/myai-api:production - container_name: myai-api - environment: - - APP_ENVIRONMENT_NAME=easySoft.ro-Production - - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://+:8080 - - Smtp__Host=mail.easysoft.ro - - Smtp__Port=587 - - Smtp__Username=no-reply@myai.ro - - Smtp__Password=${Smtp__Password} - - Smtp__UseStartTls=true - - FileStorage__Path=Files - - FileStorage__DefaultFileName= - - FileStorage__ToEmail=webmaster@myai.ro - - FileStorage__SubjectPrefix=[File Download] - - Captcha__Provider=Recaptcha - - Captcha__SecretKey=${Captcha__SecretKey} - - Captcha__PublicKey=6LfR3NUsAAAAAH1bFYTKlgwp9SBKf5IRB2IOrhBe - - Captcha__MinimumScore=0.5 - - Google__TagManagerId=GTM-NHWC9N2K - - Google__MapKey= - - Contact__ToEmail=contact@myai.ro - - Contact__SubjectPrefix=[Contact] - - Subscribe__ToEmail=contact@myai.ro - - Subscribe__SubjectPrefix=[Subscribe] - - Cors__AllowedOrigins__0=https://myai.ro - - Logging__LogLevel__Default=Information - - Logging__LogLevel__Microsoft=Warning - - Logging__LogLevel__Microsoft__AspNetCore=Warning - - Logging__LogLevel__Api=Information - - Serilog__WriteTo__2__Args__fromEmail=no-reply@myai.ro - - Serilog__WriteTo__2__Args__toEmail=webmaster@myai.ro - - Serilog__WriteTo__2__Args__mailServer=mail.easysoft.ro - - Serilog__WriteTo__2__Args__networkCredential__userName=no-reply@myai.ro - - Serilog__WriteTo__2__Args__networkCredential__password=${Smtp__Password} - - Serilog__WriteTo__2__Args__port=587 - - Serilog__WriteTo__2__Args__enableSsl=true - - Ai__OpenAI__ApiKey=${Ai__OpenAI__ApiKey} - - Ai__OpenAI__ChatModel=${Ai__OpenAI__ChatModel} - - Ai__OpenAI__EmbeddingModel=${Ai__OpenAI__EmbeddingModel} - - Ai__OpenAI__TimeoutSeconds=${Ai__OpenAI__TimeoutSeconds} - # Database (shared) - - Database__Host=${Database__Host} - - Database__Port=${Database__Port} - - Database__Name=${Database__Name} - - Database__User=${Database__User} - - Database__Password=${Database__Password} - - Database__TrustServerCertificate=${Database__TrustServerCertificate} - # Internal API protection - - InternalApi__ApiKey=${InternalApi__ApiKey} - - InternalApi__RequireApiKey=${InternalApi__RequireApiKey} - # CvMatcher API settings used by this service - - CvMatcherApi__BaseUrl=${CvMatcherApi__BaseUrl} - - CvMatcherApi__InternalApiKey=${CvMatcherApi__InternalApiKey} - # RAG settings - - Rag__MaxFileSizeMb=${Rag__MaxFileSizeMb} - - Rag__ChunkSize=${Rag__ChunkSize} - - Rag__ChunkOverlap=${Rag__ChunkOverlap} - - Rag__MaxTextChars=${Rag__MaxTextChars} - - Rag__DefaultTopK=${Rag__DefaultTopK} - - Rag__MaxTopK=${Rag__MaxTopK} - - Rag__ClassifyWithAi=${Rag__ClassifyWithAi} - volumes: - - myai_api_logs:/app/logs - - /opt/myai/files:/app/Files - networks: - - myai-network - extra_hosts: - - "mail.easysoft.ro:10.0.0.225" - restart: unless-stopped - labels: - - "com.centurylinklabs.watchtower.enable=true" - - web: - image: registry.easysoft.ro/apps/myai-web:production - container_name: myai-web - depends_on: - - api - ports: - - "5140:8080" - networks: - - myai-network - restart: unless-stopped - labels: - - "com.centurylinklabs.watchtower.enable=true" - -networks: - myai-network: - driver: bridge - -volumes: - myai_api_logs: \ No newline at end of file diff --git a/docker-compose/docker-compose.staging.yml b/docker-compose/docker-compose.staging.yml deleted file mode 100644 index 96317a0..0000000 --- a/docker-compose/docker-compose.staging.yml +++ /dev/null @@ -1,79 +0,0 @@ -services: - api: - image: registry.easysoft.ro/apps/myai-api:staging - container_name: myai-api - environment: - - APP_ENVIRONMENT_NAME=myAi.ro-Staging - - ASPNETCORE_ENVIRONMENT=Staging - - ASPNETCORE_URLS=http://+:8080 - - Smtp__Host=mail.easysoft.ro - - Smtp__Port=587 - - Smtp__Username=no-reply-staging@easysoft.ro - - Smtp__Password=${Smtp__Password} - - Smtp__UseStartTls=true - - FileStorage__Path=Files - - FileStorage__DefaultFileName= - - FileStorage__ToEmail=webmaster-staging@easysoft.ro - - FileStorage__SubjectPrefix=[File Download] - - Captcha__Provider=Recaptcha - - Captcha__SecretKey=${Captcha__SecretKey} - - Captcha__PublicKey=6LfR3NUsAAAAAH1bFYTKlgwp9SBKf5IRB2IOrhBe - - Captcha__MinimumScore=0.5 - - Google__TagManagerId=GTM-NHWC9N2K - - Google__MapKey= - - Contact__ToEmail=contact-staging@easysoft.ro - - Contact__SubjectPrefix=[Contact] - - Subscribe__ToEmail=contact-staging@easysoft.ro - - Subscribe__SubjectPrefix=[Subscribe] - - Cors__AllowedOrigins__0=https://myai.easysoft.ro - - Logging__LogLevel__Default=Information - - Logging__LogLevel__Microsoft=Warning - - Logging__LogLevel__Microsoft__AspNetCore=Warning - - Logging__LogLevel__Api=Information - - Serilog__WriteTo__2__Args__fromEmail=no-reply-staging@easysoft.ro - - Serilog__WriteTo__2__Args__toEmail=webmaster-staging@easysoft.ro - - Serilog__WriteTo__2__Args__mailServer=mail.easysoft.ro - - Serilog__WriteTo__2__Args__networkCredential__userName=no-reply-staging@easysoft.ro - - Serilog__WriteTo__2__Args__networkCredential__password=${Smtp__Password} - - Serilog__WriteTo__2__Args__port=587 - - Serilog__WriteTo__2__Args__enableSsl=true - - OpenAI__ApiKey=${OpenAI__ApiKey} - - OpenAI__ChatModel=gpt-4o-mini - - OpenAI__EmbeddingModel=text-embedding-3-small - - OpenAI__TimeoutSeconds=60 - - Rag__MaxPdfSizeMb=5 - - Rag__ChunkSize=900 - - Rag__ChunkOverlap=150 - - Rag__CvTtlMinutes=60 - - Rag__MaxJobTextChars=20000 - - Rag__TopK=6 - volumes: - - myai_api_logs:/app/logs - - /opt/myai/files:/app/Files - networks: - - myai-network - extra_hosts: - - "mail.easysoft.ro:10.0.0.225" - restart: unless-stopped - labels: - - "com.centurylinklabs.watchtower.enable=true" - - web: - image: registry.easysoft.ro/apps/myai-web:staging - container_name: myai-web - depends_on: - - api - ports: - - "5140:8080" - networks: - - myai-network - restart: unless-stopped - labels: - - "com.centurylinklabs.watchtower.enable=true" - -networks: - myai-network: - driver: bridge - -volumes: - myai_api_logs: \ No newline at end of file diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 5a35dfb..0fcb1d7 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -11,20 +11,56 @@ services: env_file: - .env environment: + # ASP.NET - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Development} - - ASPNETCORE_URLS=http://+:8080 - - # Database settings (read by the apps as Database:Host/Port/Name/User/Password) - - Database__Host=${Database__Host:-mssql} + - ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080} + - APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.local} + - LogEnvironmentOnStartup=${LogEnvironmentOnStartup:-true} + + # Database: matches rag-api appsettings Database section + - Database__Host=${Database__Host:-sqlserver} - Database__Port=${Database__Port:-1433} - - Database__Name=${Database__Name:-MyAiCvMatcher} + - Database__Name=${Database__Name:-MyAiDb} - Database__User=${Database__User:-sa} - - Database__Password=${Database__Password:-Your_strong_password123} + - Database__Password=${Database__Password:-} - Database__TrustServerCertificate=${Database__TrustServerCertificate:-true} - - InternalApi__RequireApiKey=true + + # InternalApi: matches rag-api appsettings InternalApi section - InternalApi__ApiKey=${InternalApi__ApiKey:-change-this-internal-key} + - InternalApi__RequireApiKey=${InternalApi__RequireApiKey:-false} + + # Rag: matches rag-api appsettings Rag section + - Rag__MaxFileSizeMb=${Rag__MaxFileSizeMb:-8} + - Rag__ChunkSize=${Rag__ChunkSize:-900} + - Rag__ChunkOverlap=${Rag__ChunkOverlap:-150} + - Rag__MaxTextChars=${Rag__MaxTextChars:-60000} + - Rag__DefaultTopK=${Rag__DefaultTopK:-20} + - Rag__MaxTopK=${Rag__MaxTopK:-50} + - Rag__ClassifyWithAi=${Rag__ClassifyWithAi:-false} + + # Ai: matches rag-api appsettings Ai section - Ai__Provider=${Ai__Provider:-OpenAI} - Ai__OpenAI__ApiKey=${Ai__OpenAI__ApiKey:-} + - Ai__OpenAI__ChatModel=${Ai__OpenAI__ChatModel:-gpt-4o-mini} + - Ai__OpenAI__EmbeddingModel=${Ai__OpenAI__EmbeddingModel:-text-embedding-3-small} + - Ai__OpenAI__TimeoutSeconds=${Ai__OpenAI__TimeoutSeconds:-90} - Ai__Ollama__BaseUrl=${Ai__Ollama__BaseUrl:-http://host.docker.internal:11434} + - Ai__Ollama__ChatModel=${Ai__Ollama__ChatModel:-llama3.1:8b} + - Ai__Ollama__EmbeddingModel=${Ai__Ollama__EmbeddingModel:-nomic-embed-text} + - Ai__Ollama__TimeoutSeconds=${Ai__Ollama__TimeoutSeconds:-180} + + # Logging / Serilog + - Logging__LogLevel__Default=${Logging__LogLevel__Default:-Information} + - Logging__LogLevel__Microsoft=${Logging__LogLevel__Microsoft:-Warning} + - Logging__LogLevel__Microsoft__AspNetCore=${Logging__LogLevel__Microsoft__AspNetCore:-Warning} + - Logging__LogLevel__Api=${Logging__LogLevel__Api:-Information} + - Serilog__WriteTo__2__Args__fromEmail=${Serilog__WriteTo__2__Args__fromEmail:-} + - Serilog__WriteTo__2__Args__toEmail=${Serilog__WriteTo__2__Args__toEmail:-} + - Serilog__WriteTo__2__Args__mailServer=${Serilog__WriteTo__2__Args__mailServer:-} + - Serilog__WriteTo__2__Args__networkCredential__userName=${Serilog__WriteTo__2__Args__networkCredential__userName:-} + - Serilog__WriteTo__2__Args__networkCredential__password=${Serilog__WriteTo__2__Args__networkCredential__password:-} + - Serilog__WriteTo__2__Args__port=${Serilog__WriteTo__2__Args__port:-587} + - Serilog__WriteTo__2__Args__enableSsl=${Serilog__WriteTo__2__Args__enableSsl:-true} networks: - myai-network restart: unless-stopped @@ -41,47 +77,133 @@ services: env_file: - .env environment: + # ASP.NET - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Development} - - ASPNETCORE_URLS=http://+:8080 - - # Database settings (cv-matcher uses same keys) - - Database__Host=${Database__Host:-mssql} + - ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080} + - APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.local} + - LogEnvironmentOnStartup=${LogEnvironmentOnStartup:-true} + + # Database: matches cv-matcher-api appsettings Database section + - Database__Host=${Database__Host:-sqlserver} - Database__Port=${Database__Port:-1433} - - Database__Name=${Database__Name:-MyAiCvMatcher} + - Database__Name=${Database__Name:-MyAiDb} - Database__User=${Database__User:-sa} - - Database__Password=${Database__Password:-Your_strong_password123} + - Database__Password=${Database__Password:-} - Database__TrustServerCertificate=${Database__TrustServerCertificate:-true} - - InternalApi__RequireApiKey=true + + # InternalApi: matches cv-matcher-api appsettings InternalApi section - InternalApi__ApiKey=${InternalApi__ApiKey:-change-this-internal-key} - - RagApi__BaseUrl=http://rag-api:8080 - - RagApi__InternalApiKey=${InternalApi__ApiKey:-change-this-internal-key} + - InternalApi__RequireApiKey=${InternalApi__RequireApiKey:-false} + + # RagApi: matches cv-matcher-api appsettings RagApi section + # IMPORTANT: this must use the container-internal port, not the host-mapped port. + - RagApi__BaseUrl=${RagApi__BaseUrl:-http://rag-api:8080} + - RagApi__InternalApiKey=${RagApi__InternalApiKey:-${InternalApi__ApiKey:-change-this-internal-key}} + + # Ai: matches cv-matcher-api appsettings Ai section - Ai__Provider=${Ai__Provider:-OpenAI} - Ai__OpenAI__ApiKey=${Ai__OpenAI__ApiKey:-} + - Ai__OpenAI__ChatModel=${Ai__OpenAI__ChatModel:-gpt-4o-mini} + - Ai__OpenAI__TimeoutSeconds=${Ai__OpenAI__TimeoutSeconds:-90} - Ai__Ollama__BaseUrl=${Ai__Ollama__BaseUrl:-http://host.docker.internal:11434} + - Ai__Ollama__ChatModel=${Ai__Ollama__ChatModel:-llama3.1:8b} + - Ai__Ollama__TimeoutSeconds=${Ai__Ollama__TimeoutSeconds:-180} + + # Matcher: matches cv-matcher-api appsettings Matcher section + - Matcher__TopK=${Matcher__TopK:-10} + - Matcher__DeepScoreTopN=${Matcher__DeepScoreTopN:-5} + - Matcher__MaxJobTextChars=${Matcher__MaxJobTextChars:-60000} + + # Logging / Serilog + - Logging__LogLevel__Default=${Logging__LogLevel__Default:-Information} + - Logging__LogLevel__Microsoft=${Logging__LogLevel__Microsoft:-Warning} + - Logging__LogLevel__Microsoft__AspNetCore=${Logging__LogLevel__Microsoft__AspNetCore:-Warning} + - Logging__LogLevel__Api=${Logging__LogLevel__Api:-Information} + - Serilog__WriteTo__2__Args__fromEmail=${Serilog__WriteTo__2__Args__fromEmail:-} + - Serilog__WriteTo__2__Args__toEmail=${Serilog__WriteTo__2__Args__toEmail:-} + - Serilog__WriteTo__2__Args__mailServer=${Serilog__WriteTo__2__Args__mailServer:-} + - Serilog__WriteTo__2__Args__networkCredential__userName=${Serilog__WriteTo__2__Args__networkCredential__userName:-} + - Serilog__WriteTo__2__Args__networkCredential__password=${Serilog__WriteTo__2__Args__networkCredential__password:-} + - Serilog__WriteTo__2__Args__port=${Serilog__WriteTo__2__Args__port:-587} + - Serilog__WriteTo__2__Args__enableSsl=${Serilog__WriteTo__2__Args__enableSsl:-true} networks: - myai-network restart: unless-stopped api: - depends_on: - - cv-matcher-api build: context: .. dockerfile: api/Dockerfile container_name: myai-api + depends_on: + - cv-matcher-api ports: - "8080:8080" env_file: - - ../api/.env - .env + # Keep this only if api/.env contains api-specific overrides not present in docker-compose/.env. + # - ../api/.env environment: + # ASP.NET - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Development} - ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080} - - Cors__AllowedOrigins__0=http://localhost:5000 - - Cors__AllowedOrigins__1=http://web:8080 - - CvMatcherApi__BaseUrl=http://cv-matcher-api:8080 - - CvMatcherApi__InternalApiKey=${CvMatcherApi__InternalApiKey:-change-this-internal-key} + - APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.local} + - LogEnvironmentOnStartup=${LogEnvironmentOnStartup:-true} + + # Google: matches api appsettings Google section + - Google__TagManagerId=${Google__TagManagerId:-} + - Google__MapKey=${Google__MapKey:-} + + # Contact / Subscribe: matches api appsettings Contact and Subscribe sections + - Contact__ToEmail=${Contact__ToEmail:-} + - Contact__FromEmail=${Contact__FromEmail:-${Smtp__Username:-}} + - Contact__SubjectPrefix=${Contact__SubjectPrefix:-} + - Subscribe__ToEmail=${Subscribe__ToEmail:-} + - Subscribe__SubjectPrefix=${Subscribe__SubjectPrefix:-} + + # SMTP: matches api appsettings Smtp section + - Smtp__Host=${Smtp__Host:-mail.example.com} + - Smtp__Port=${Smtp__Port:-587} + - Smtp__Username=${Smtp__Username:-} + - Smtp__Password=${Smtp__Password:-} + - Smtp__UseStartTls=${Smtp__UseStartTls:-false} + + # Captcha: matches api appsettings Captcha section + - Captcha__Provider=${Captcha__Provider:-Recaptcha} + - Captcha__SecretKey=${Captcha__SecretKey:-} + - Captcha__PublicKey=${Captcha__PublicKey:-} + - Captcha__MinimumScore=${Captcha__MinimumScore:-0.5} + + # FileStorage: matches api appsettings FileStorage section + - FileStorage__Path=${FileStorage__Path:-Files} + - FileStorage__DefaultFileName=${FileStorage__DefaultFileName:-} + - FileStorage__ToEmail=${FileStorage__ToEmail:-} + - FileStorage__FromEmail=${FileStorage__FromEmail:-${Smtp__Username:-}} + - FileStorage__SubjectPrefix=${FileStorage__SubjectPrefix:-[File Download]} + + # CvMatcherApi: matches api appsettings CvMatcherApi section + - CvMatcherApi__BaseUrl=${CvMatcherApi__BaseUrl:-http://cv-matcher-api:8080} + - CvMatcherApi__InternalApiKey=${CvMatcherApi__InternalApiKey:-${InternalApi__ApiKey:-change-this-internal-key}} + + # CORS: not in the uploaded api appsettings, but used by your API startup config. + - Cors__AllowedOrigins__0=${Cors__AllowedOrigins__0:-http://localhost:5000} + - Cors__AllowedOrigins__1=${Cors__AllowedOrigins__1:-http://web:8080} + + # Logging / Serilog + - Logging__LogLevel__Default=${Logging__LogLevel__Default:-Information} + - Logging__LogLevel__Microsoft=${Logging__LogLevel__Microsoft:-Warning} + - Logging__LogLevel__Microsoft__AspNetCore=${Logging__LogLevel__Microsoft__AspNetCore:-Warning} + - Logging__LogLevel__Api=${Logging__LogLevel__Api:-Information} + - Serilog__WriteTo__2__Args__fromEmail=${Serilog__WriteTo__2__Args__fromEmail:-} + - Serilog__WriteTo__2__Args__toEmail=${Serilog__WriteTo__2__Args__toEmail:-} + - Serilog__WriteTo__2__Args__mailServer=${Serilog__WriteTo__2__Args__mailServer:-} + - Serilog__WriteTo__2__Args__networkCredential__userName=${Serilog__WriteTo__2__Args__networkCredential__userName:-} + - Serilog__WriteTo__2__Args__networkCredential__password=${Serilog__WriteTo__2__Args__networkCredential__password:-} + - Serilog__WriteTo__2__Args__port=${Serilog__WriteTo__2__Args__port:-587} + - Serilog__WriteTo__2__Args__enableSsl=${Serilog__WriteTo__2__Args__enableSsl:-true} volumes: - ../api/logs:/app/logs + - ${FileStorage__Path:-../Files}:/app/Files networks: - myai-network restart: unless-stopped @@ -100,10 +222,11 @@ services: environment: - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Development} - ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080} + - APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.local} networks: - myai-network restart: unless-stopped networks: myai-network: - driver: bridge \ No newline at end of file + driver: bridge diff --git a/rag-api/Data/RagDbContext.cs b/rag-api/Data/RagDbContext.cs index 11a1642..1564ef1 100644 --- a/rag-api/Data/RagDbContext.cs +++ b/rag-api/Data/RagDbContext.cs @@ -5,6 +5,9 @@ namespace Api.Data; public sealed class RagDbContext : DbContext { + public const string SchemaName = "rag"; + public const string MigrationTableName = "_Migrations"; + public RagDbContext(DbContextOptions options) : base(options) { } @@ -16,9 +19,11 @@ public sealed class RagDbContext : DbContext protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.HasDefaultSchema(SchemaName); + modelBuilder.Entity(entity => { - entity.ToTable("RagDocuments"); + entity.ToTable("Documents"); entity.HasKey(x => x.Id); entity.Property(x => x.Id).HasMaxLength(64); entity.Property(x => x.DocumentType).HasMaxLength(80).IsRequired(); @@ -34,7 +39,7 @@ public sealed class RagDbContext : DbContext modelBuilder.Entity(entity => { - entity.ToTable("RagChunks"); + entity.ToTable("Chunks"); entity.HasKey(x => x.Id); entity.Property(x => x.Id).HasMaxLength(64); entity.Property(x => x.DocumentId).HasMaxLength(64).IsRequired(); @@ -48,7 +53,7 @@ public sealed class RagDbContext : DbContext modelBuilder.Entity(entity => { - entity.ToTable("RagEmbeddingCache"); + entity.ToTable("EmbeddingCache"); entity.HasKey(x => x.CacheKey); entity.Property(x => x.CacheKey).HasMaxLength(64); entity.Property(x => x.Model).HasMaxLength(120).IsRequired(); @@ -60,7 +65,7 @@ public sealed class RagDbContext : DbContext modelBuilder.Entity(entity => { - entity.ToTable("RagChatCompletionCache"); + entity.ToTable("ChatCompletionCache"); entity.HasKey(x => x.CacheKey); entity.Property(x => x.CacheKey).HasMaxLength(64); entity.Property(x => x.Model).HasMaxLength(120).IsRequired(); diff --git a/rag-api/Data/Repositories/EfRagRepository.cs b/rag-api/Data/Repositories/EfRagRepository.cs index df2b1e5..e07b4ee 100644 --- a/rag-api/Data/Repositories/EfRagRepository.cs +++ b/rag-api/Data/Repositories/EfRagRepository.cs @@ -20,7 +20,7 @@ public sealed class EfRagRepository : IRagRepository public async Task InitializeAsync(CancellationToken ct) { _logger.LogInformation("Ensuring RAG database schema exists using EF Core"); - await _db.Database.EnsureCreatedAsync(ct); + //await _db.Database.EnsureCreatedAsync(ct); } public async Task GetDocumentByTextHashAsync(string textHash, string? sourceUrl, CancellationToken ct) diff --git a/rag-api/Migrations/20260507140305_InitialRagSchema.Designer.cs b/rag-api/Migrations/20260507140305_InitialRagSchema.Designer.cs new file mode 100644 index 0000000..3fcaaae --- /dev/null +++ b/rag-api/Migrations/20260507140305_InitialRagSchema.Designer.cs @@ -0,0 +1,188 @@ +// +using System; +using Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Api.Migrations +{ + [DbContext(typeof(RagDbContext))] + [Migration("20260507140305_InitialRagSchema")] + partial class InitialRagSchema + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("rag") + .HasAnnotation("ProductVersion", "10.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Api.Data.Entities.RagChatCompletionCacheEntity", b => + { + b.Property("CacheKey") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("Model") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("ResponseText") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Temperature") + .HasColumnType("decimal(4,2)"); + + b.HasKey("CacheKey"); + + b.ToTable("ChatCompletionCache", "rag"); + }); + + modelBuilder.Entity("Api.Data.Entities.RagChunkEntity", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("ChunkIndex") + .HasColumnType("int"); + + b.Property("DocumentId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("Embedding") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.ToTable("Chunks", "rag"); + }); + + modelBuilder.Entity("Api.Data.Entities.RagDocumentEntity", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("nvarchar(80)"); + + b.Property("MetadataJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("{}"); + + b.Property("RawText") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SourceUrl") + .HasMaxLength(1200) + .HasColumnType("nvarchar(1200)"); + + b.Property("TextHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("TypeConfidence") + .HasColumnType("float"); + + b.HasKey("Id"); + + b.HasIndex("DocumentType"); + + b.HasIndex("TextHash"); + + b.ToTable("Documents", "rag"); + }); + + modelBuilder.Entity("Api.Data.Entities.RagEmbeddingCacheEntity", b => + { + b.Property("CacheKey") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("Model") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("TextHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("Vector") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.HasKey("CacheKey"); + + b.HasIndex("TextHash"); + + b.ToTable("EmbeddingCache", "rag"); + }); + + modelBuilder.Entity("Api.Data.Entities.RagChunkEntity", b => + { + b.HasOne("Api.Data.Entities.RagDocumentEntity", "Document") + .WithMany("Chunks") + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Api.Data.Entities.RagDocumentEntity", b => + { + b.Navigation("Chunks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/rag-api/Migrations/20260507140305_InitialRagSchema.cs b/rag-api/Migrations/20260507140305_InitialRagSchema.cs new file mode 100644 index 0000000..fc6216c --- /dev/null +++ b/rag-api/Migrations/20260507140305_InitialRagSchema.cs @@ -0,0 +1,137 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Api.Migrations +{ + /// + public partial class InitialRagSchema : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "rag"); + + migrationBuilder.CreateTable( + name: "ChatCompletionCache", + schema: "rag", + columns: table => new + { + CacheKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + Model = table.Column(type: "nvarchar(120)", maxLength: 120, nullable: false), + Temperature = table.Column(type: "decimal(4,2)", nullable: false), + ResponseText = table.Column(type: "nvarchar(max)", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()") + }, + constraints: table => + { + table.PrimaryKey("PK_ChatCompletionCache", x => x.CacheKey); + }); + + migrationBuilder.CreateTable( + name: "Documents", + schema: "rag", + columns: table => new + { + Id = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + DocumentType = table.Column(type: "nvarchar(80)", maxLength: 80, nullable: false), + Title = table.Column(type: "nvarchar(300)", maxLength: 300, nullable: false), + SourceUrl = table.Column(type: "nvarchar(1200)", maxLength: 1200, nullable: true), + RawText = table.Column(type: "nvarchar(max)", nullable: false), + TextHash = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + TypeConfidence = table.Column(type: "float", nullable: false), + MetadataJson = table.Column(type: "nvarchar(max)", nullable: false, defaultValue: "{}"), + CreatedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()") + }, + constraints: table => + { + table.PrimaryKey("PK_Documents", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "EmbeddingCache", + schema: "rag", + columns: table => new + { + CacheKey = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + Model = table.Column(type: "nvarchar(120)", maxLength: 120, nullable: false), + TextHash = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + Vector = table.Column(type: "varbinary(max)", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()") + }, + constraints: table => + { + table.PrimaryKey("PK_EmbeddingCache", x => x.CacheKey); + }); + + migrationBuilder.CreateTable( + name: "Chunks", + schema: "rag", + columns: table => new + { + Id = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + DocumentId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), + ChunkIndex = table.Column(type: "int", nullable: false), + Text = table.Column(type: "nvarchar(max)", nullable: false), + Embedding = table.Column(type: "varbinary(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Chunks", x => x.Id); + table.ForeignKey( + name: "FK_Chunks_Documents_DocumentId", + column: x => x.DocumentId, + principalSchema: "rag", + principalTable: "Documents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Chunks_DocumentId", + schema: "rag", + table: "Chunks", + column: "DocumentId"); + + migrationBuilder.CreateIndex( + name: "IX_Documents_DocumentType", + schema: "rag", + table: "Documents", + column: "DocumentType"); + + migrationBuilder.CreateIndex( + name: "IX_Documents_TextHash", + schema: "rag", + table: "Documents", + column: "TextHash"); + + migrationBuilder.CreateIndex( + name: "IX_EmbeddingCache_TextHash", + schema: "rag", + table: "EmbeddingCache", + column: "TextHash"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ChatCompletionCache", + schema: "rag"); + + migrationBuilder.DropTable( + name: "Chunks", + schema: "rag"); + + migrationBuilder.DropTable( + name: "EmbeddingCache", + schema: "rag"); + + migrationBuilder.DropTable( + name: "Documents", + schema: "rag"); + } + } +} diff --git a/rag-api/Migrations/RagDbContextModelSnapshot.cs b/rag-api/Migrations/RagDbContextModelSnapshot.cs new file mode 100644 index 0000000..908ae81 --- /dev/null +++ b/rag-api/Migrations/RagDbContextModelSnapshot.cs @@ -0,0 +1,185 @@ +// +using System; +using Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Api.Migrations +{ + [DbContext(typeof(RagDbContext))] + partial class RagDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("rag") + .HasAnnotation("ProductVersion", "10.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Api.Data.Entities.RagChatCompletionCacheEntity", b => + { + b.Property("CacheKey") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("Model") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("ResponseText") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Temperature") + .HasColumnType("decimal(4,2)"); + + b.HasKey("CacheKey"); + + b.ToTable("ChatCompletionCache", "rag"); + }); + + modelBuilder.Entity("Api.Data.Entities.RagChunkEntity", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("ChunkIndex") + .HasColumnType("int"); + + b.Property("DocumentId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("Embedding") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.ToTable("Chunks", "rag"); + }); + + modelBuilder.Entity("Api.Data.Entities.RagDocumentEntity", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("nvarchar(80)"); + + b.Property("MetadataJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("{}"); + + b.Property("RawText") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SourceUrl") + .HasMaxLength(1200) + .HasColumnType("nvarchar(1200)"); + + b.Property("TextHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("TypeConfidence") + .HasColumnType("float"); + + b.HasKey("Id"); + + b.HasIndex("DocumentType"); + + b.HasIndex("TextHash"); + + b.ToTable("Documents", "rag"); + }); + + modelBuilder.Entity("Api.Data.Entities.RagEmbeddingCacheEntity", b => + { + b.Property("CacheKey") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("Model") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("TextHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("Vector") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.HasKey("CacheKey"); + + b.HasIndex("TextHash"); + + b.ToTable("EmbeddingCache", "rag"); + }); + + modelBuilder.Entity("Api.Data.Entities.RagChunkEntity", b => + { + b.HasOne("Api.Data.Entities.RagDocumentEntity", "Document") + .WithMany("Chunks") + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Api.Data.Entities.RagDocumentEntity", b => + { + b.Navigation("Chunks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/rag-api/Program.cs b/rag-api/Program.cs index f227e93..1c2acd7 100644 --- a/rag-api/Program.cs +++ b/rag-api/Program.cs @@ -36,7 +36,10 @@ try var configuration = builder.Configuration; var connectionString = builder.Services.GetConfiguredDbConnectionString(configuration); - options.UseSqlServer(connectionString); + options.UseSqlServer(connectionString, sql => + { + sql.MigrationsHistoryTable(RagDbContext.MigrationTableName, RagDbContext.SchemaName); + }); }); builder.Services.AddHttpClient();