From 2807672074d46e247f20c417f50f5333f2828dd0 Mon Sep 17 00:00:00 2001 From: Gelu Mihes Date: Thu, 7 May 2026 14:25:16 +0300 Subject: [PATCH] Changes --- api/appsettings.Development.json | 83 ------------------- api/appsettings.json | 4 + .../Settings/SmtpSettings.cs | 12 --- cv-matcher-api/Program.cs | 15 +++- cv-matcher-api/appsettings.json | 18 ++-- docker-compose/.env.production | 67 +++++++++++++++ docker-compose/.env.staging | 68 +++++++++++++++ docker-compose/.env.template | 73 ++++++++++++++++ docker-compose/docker-compose.dcproj | 4 +- docker-compose/docker-compose.production.yml | 8 +- rag-api/Program.cs | 11 ++- rag-api/appsettings.json | 9 +- shared-models/Settings/BatabaseSettings.cs | 12 +++ startup-helpers/DatabaseExtensions.cs | 23 +++++ startup-helpers/startup-helpers.csproj | 4 + 15 files changed, 292 insertions(+), 119 deletions(-) delete mode 100644 api/appsettings.Development.json delete mode 100644 cv-matcher-api-models/Settings/SmtpSettings.cs create mode 100644 docker-compose/.env.production create mode 100644 docker-compose/.env.staging create mode 100644 docker-compose/.env.template create mode 100644 shared-models/Settings/BatabaseSettings.cs create mode 100644 startup-helpers/DatabaseExtensions.cs diff --git a/api/appsettings.Development.json b/api/appsettings.Development.json deleted file mode 100644 index b44be4c..0000000 --- a/api/appsettings.Development.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "Serilog": { - "MinimumLevel": { - "Default": "Debug", - "Override": { - "Microsoft.AspNetCore": "Information", - "Microsoft.AspNetCore.Hosting": "Information", - "Microsoft.AspNetCore.Routing": "Warning", - "System.Net.Http.HttpClient": "Warning", - "Api": "Debug" - } - }, - "WriteTo": [ - { - "Name": "Console", - "Args": { - "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}" - } - }, - { - "Name": "File", - "Args": { - "path": "logs/dev-.log", - "rollingInterval": "Day", - "retainedFileCountLimit": 7, - "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}" - } - } - ] - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Information", - "Microsoft.AspNetCore.Hosting": "Information", - "Microsoft.AspNetCore.Routing": "Warning", - "System.Net.Http.HttpClient": "Warning", - "Api": "Debug" - } - }, - "LogEnvironmentOnStartup": true, - "KeyVault": { - "VaultUri": "", - "Enabled": false - }, - "Google": { - "TagManagerId": "", - "MapKey": "" - }, - "Contact": { - "ToEmail": "", - "FromEmail": "", - "SubjectPrefix": "" - }, - "Subscribe": { - "ToEmail": "", - "SubjectPrefix": "" - }, - "Smtp": { - "Host": "mail.example.com", - "Port": 587, - "Username": "", - "Password": "", - "UseStartTls": false - }, - "Captcha": { - "Provider": "Recaptcha", - "SecretKey": "", - "PublicKey": "", - "MinimumScore": 0.5 - }, - "FileStorage": { - "Path": "Files", - "DefaultFileName": "", - "ToEmail": "", - "FromEmail": "", - "SubjectPrefix": "[File Download]" - }, - "CvMatcherApi": { - "BaseUrl": "", - "InternalApiKey": "" - } -} diff --git a/api/appsettings.json b/api/appsettings.json index 88de872..23242c7 100644 --- a/api/appsettings.json +++ b/api/appsettings.json @@ -105,5 +105,9 @@ "ToEmail": "", "FromEmail": "", "SubjectPrefix": "[File Download]" + }, + "CvMatcherApi": { + "BaseUrl": "", + "InternalApiKey": "" } } \ No newline at end of file diff --git a/cv-matcher-api-models/Settings/SmtpSettings.cs b/cv-matcher-api-models/Settings/SmtpSettings.cs deleted file mode 100644 index 2d523e7..0000000 --- a/cv-matcher-api-models/Settings/SmtpSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace CvMatcher.Models.Settings; - -public sealed class SmtpSettings -{ - public string Host { get; set; } = string.Empty; - public int Port { get; set; } = 587; - public string Username { get; set; } = string.Empty; - public string Password { get; set; } = string.Empty; - public bool UseStartTls { get; set; } = true; - public string FromEmail { get; set; } = "noreply@myai.ro"; - public string ToEmail { get; set; } = string.Empty; -} diff --git a/cv-matcher-api/Program.cs b/cv-matcher-api/Program.cs index 9bc51e0..89eaa7c 100644 --- a/cv-matcher-api/Program.cs +++ b/cv-matcher-api/Program.cs @@ -1,4 +1,3 @@ -using System.Reflection; using Api.Clients.Ai; using Api.Clients.Ai.Contracts; using Api.Clients.Api; @@ -14,6 +13,8 @@ using Refit; using Serilog; using Shared.Models.Settings; using StartupHelpers; +using System.Reflection; +using System.Xml.Linq; StartupExtensions.LoadDotEnvFile(); @@ -29,11 +30,11 @@ try builder.AddAzureKeyVaultIfConfigured(); + builder.Services.Configure(builder.Configuration.GetSection("Database")); builder.Services.Configure(builder.Configuration.GetSection("RagApi")); builder.Services.Configure(builder.Configuration.GetSection("InternalApi")); builder.Services.Configure(builder.Configuration.GetSection("Ai")); builder.Services.Configure(builder.Configuration.GetSection("Matcher")); - builder.Services.Configure(builder.Configuration.GetSection("Smtp")); builder.Services.AddRefitClient() .ConfigureHttpClient((sp, c) => @@ -49,9 +50,15 @@ try builder.Services.AddScoped(); builder.Services.AddHttpClient(); builder.Services.AddHttpClient(); + builder.Services.AddDbContext(options => - options.UseSqlServer(builder.Configuration.GetConnectionString("CvMatcherDb") - ?? throw new InvalidOperationException("Connection string 'CvMatcherDb' is missing."))); + { + var configuration = builder.Configuration; + var connectionString = builder.Services.GetConfiguredDbConnectionString(configuration); + + options.UseSqlServer(connectionString); + }); + builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/cv-matcher-api/appsettings.json b/cv-matcher-api/appsettings.json index 12bea33..0379e6c 100644 --- a/cv-matcher-api/appsettings.json +++ b/cv-matcher-api/appsettings.json @@ -73,8 +73,13 @@ "VaultUri": "", "Enabled": false }, - "ConnectionStrings": { - "CvMatcherDb": "Server=localhost,1433;Database=MyAiCvMatcher;User Id=sa;Password=Your_strong_password123;TrustServerCertificate=True" + "Database": { + "Host": "localhost", + "Port": 1433, + "Name": "MyAiCvMatcher", + "User": "sa", + "Password": "", + "TrustServerCertificate": true }, "InternalApi": { "ApiKey": "", @@ -101,14 +106,5 @@ "TopK": 10, "DeepScoreTopN": 5, "MaxJobTextChars": 60000 - }, - "Smtp": { - "Host": "", - "Port": 587, - "Username": "", - "Password": "", - "UseStartTls": true, - "FromEmail": "noreply@myai.ro", - "ToEmail": "" } } diff --git a/docker-compose/.env.production b/docker-compose/.env.production new file mode 100644 index 0000000..4d91329 --- /dev/null +++ b/docker-compose/.env.production @@ -0,0 +1,67 @@ +# .env.production - production environment variables for docker-compose +# DO NOT commit real secrets to git + +# Common +ASPNETCORE_ENVIRONMENT=Production + +# API (main) +ASPNETCORE_URLS=http://+:8080 +APP_ENVIRONMENT_NAME=easySoft.ro-Production + +# SMTP +Smtp__Host=mail.easysoft.ro +Smtp__Port=587 +Smtp__Username=no-reply@myai.ro +Smtp__Password=37,_,tunSis +Smtp__UseStartTls=true + +# AI settings +Ai__Provider=OpenAI +Ai__OpenAI__ApiKey=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 +Ai__OpenAI__ChatModel=gpt-4o-mini +Ai__OpenAI__EmbeddingModel=text-embedding-3-small +Ai__OpenAI__TimeoutSeconds=60 +Ai__Ollama__BaseUrl= +Ai__Ollama__ChatModel= +Ai__Ollama__EmbeddingModel= +Ai__Ollama__TimeoutSeconds= + +# Captcha +Captcha__Provider=Recaptcha +Captcha__SecretKey=6LfR3NUsAAAAAP6ZDeJMmksyHZMkApQ29Kb4xZ5v +Captcha__PublicKey=6LfR3NUsAAAAAH1bFYTKlgwp9SBKf5IRB2IOrhBe +Captcha__MinimumScore=0.5 + +# File Storage +FileStorage__Path=/opt/myai/files +FileStorage__DefaultFileName= +FileStorage__ToEmail=webmaster@myai.ro +FileStorage__SubjectPrefix=[File Download] + +# Contact / Subscribe +Contact__ToEmail=contact@myai.ro +Contact__SubjectPrefix=[Contact] +Subscribe__ToEmail=contact@myai.ro +Subscribe__SubjectPrefix=[Subscribe] + +# CORS +Cors__AllowedOrigins__0=https://myai.ro + +# Logging +Logging__LogLevel__Default=Information +Logging__LogLevel__Microsoft=Warning +Logging__LogLevel__Microsoft__AspNetCore=Warning +Logging__LogLevel__Api=Information + +# Serilog +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=37,_,tunSis +Serilog__WriteTo__2__Args__port=587 +Serilog__WriteTo__2__Args__enableSsl=true + +# CvMatcher API internal +CvMatcherApi__BaseUrl=http://cv-matcher-api:8081 +CvMatcherApi__InternalApiKey=614ffeda-6300-4cb8-acad-806679daa7ec diff --git a/docker-compose/.env.staging b/docker-compose/.env.staging new file mode 100644 index 0000000..139d06b --- /dev/null +++ b/docker-compose/.env.staging @@ -0,0 +1,68 @@ +# .env.staging - staging environment variables for docker-compose +# DO NOT commit real secrets to git + +# Common +ASPNETCORE_ENVIRONMENT=Staging + +# API (main) +ASPNETCORE_URLS=http://+:8080 +APP_ENVIRONMENT_NAME=myai.ro-Staging + +# SMTP +Smtp__Host=mail.easysoft.ro +Smtp__Port=587 +Smtp__Username=no-reply-staging@easysoft.ro +Smtp__Password=37,_,tunSis +Smtp__UseStartTls=true + +# AI settings +Ai__Provider=OpenAI +Ai__OpenAI__ApiKey=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 +Ai__OpenAI__ChatModel=gpt-4 +Ai__OpenAI__EmbeddingModel=text-embedding-3-large +Ai__OpenAI__TimeoutSeconds=30 +Ai__Ollama__BaseUrl= +Ai__Ollama__ChatModel= +Ai__Ollama__EmbeddingModel= +Ai__Ollama__TimeoutSeconds= + +# Captcha +Captcha__Provider=Recaptcha +Captcha__SecretKey=6LfR3NUsAAAAAP6ZDeJMmksyHZMkApQ29Kb4xZ5v +Captcha__PublicKey=6LfR3NUsAAAAAH1bFYTKlgwp9SBKf5IRB2IOrhBe +Captcha__MinimumScore=0.5 + +# File Storage +FileStorage__Path=/opt/easysoft/files +FileStorage__DefaultFileName= +FileStorage__ToEmail=webmaster-staging@easysoft.ro +FileStorage__SubjectPrefix=[File Download] + +# Contact / Subscribe +Contact__ToEmail=contact-staging@easysoft.ro +Contact__SubjectPrefix=[Contact] +Subscribe__ToEmail=contact-staging@easysoft.ro +Subscribe__SubjectPrefix=[Subscribe] + +# CORS +Cors__AllowedOrigins__0=http://localhost:3000 +Cors__AllowedOrigins__1=http://localhost:5000 + +# Logging +Logging__LogLevel__Default=Information +Logging__LogLevel__Microsoft=Warning +Logging__LogLevel__Microsoft__AspNetCore=Warning +Logging__LogLevel__Api=Information + +# Serilog +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=37,_,tunSis +Serilog__WriteTo__2__Args__port=587 +Serilog__WriteTo__2__Args__enableSsl=true + +# CvMatcher API internal +CvMatcherApi__BaseUrl=http://cv-matcher-api:8081 +CvMatcherApi__InternalApiKey=614ffeda-6300-4cb8-acad-806679daa7ec diff --git a/docker-compose/.env.template b/docker-compose/.env.template new file mode 100644 index 0000000..c3b80e5 --- /dev/null +++ b/docker-compose/.env.template @@ -0,0 +1,73 @@ +# .env.template - Template of environment variables for docker-compose +# Copy this file to `.env.production` or `.env.staging` and fill the secret values. +# Do NOT commit your `.env.*` files containing real secrets. + +# Common +ASPNETCORE_ENVIRONMENT=Development + +# API (main) +ASPNETCORE_URLS=http://+:8080 +APP_ENVIRONMENT_NAME=myai-Development + +# SMTP Configuration +Smtp__Host=your.smtp.host +Smtp__Port=587 +Smtp__Username=your-smtp-username +Smtp__Password= +Smtp__UseStartTls=true + +# AI Settings (choose provider: OpenAI or Ollama) +Ai__Provider=OpenAI + +# OpenAI settings +Ai__OpenAI__ApiKey= +Ai__OpenAI__ChatModel=gpt-4 +Ai__OpenAI__EmbeddingModel=text-embedding-3-large +Ai__OpenAI__TimeoutSeconds=30 + +# Ollama settings +Ai__Ollama__BaseUrl=http://localhost:11434 +Ai__Ollama__ChatModel=llama2 +Ai__Ollama__EmbeddingModel=embedding-model +Ai__Ollama__TimeoutSeconds=30 + +# Captcha +Captcha__Provider=Recaptcha +Captcha__SecretKey= +Captcha__PublicKey= +Captcha__MinimumScore=0.5 + +# File Storage +FileStorage__Path=/opt/easysoft/files +FileStorage__DefaultFileName= +FileStorage__ToEmail= +FileStorage__SubjectPrefix=[File Download] + +# Contact / Subscribe +Contact__ToEmail= +Contact__SubjectPrefix=[Contact] +Subscribe__ToEmail= +Subscribe__SubjectPrefix=[Subscribe] + +# CORS +Cors__AllowedOrigins__0=http://localhost:3000 +Cors__AllowedOrigins__1=http://localhost:5000 + +# Logging +Logging__LogLevel__Default=Information +Logging__LogLevel__Microsoft=Warning +Logging__LogLevel__Microsoft__AspNetCore=Warning +Logging__LogLevel__Api=Information + +# Serilog +Serilog__WriteTo__2__Args__fromEmail= +Serilog__WriteTo__2__Args__toEmail= +Serilog__WriteTo__2__Args__mailServer= +Serilog__WriteTo__2__Args__networkCredential__userName= +Serilog__WriteTo__2__Args__networkCredential__password= +Serilog__WriteTo__2__Args__port=587 +Serilog__WriteTo__2__Args__enableSsl=true + +# CvMatcher API internal +CvMatcherApi__BaseUrl=http://cv-matcher-api:8081 +CvMatcherApi__InternalApiKey= diff --git a/docker-compose/docker-compose.dcproj b/docker-compose/docker-compose.dcproj index de2e9e4..c6e2d00 100644 --- a/docker-compose/docker-compose.dcproj +++ b/docker-compose/docker-compose.dcproj @@ -9,7 +9,9 @@ 2.1 - + + + docker-compose.yml diff --git a/docker-compose/docker-compose.production.yml b/docker-compose/docker-compose.production.yml index b460bdf..2f57561 100644 --- a/docker-compose/docker-compose.production.yml +++ b/docker-compose/docker-compose.production.yml @@ -37,10 +37,10 @@ services: - 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 + - Ai__OpenAI__ApiKey=${Ai__OpenAI__ApiKey} + - Ai__OpenAI__ChatModel=${Ai__OpenAI__ChatModel} + - Ai__OpenAI__EmbeddingModel=${Ai__OpenAI__EmbeddingModel} + - Ai__OpenAI__TimeoutSeconds=${Ai__OpenAI__TimeoutSeconds} - Rag__MaxPdfSizeMb=5 - Rag__ChunkSize=900 - Rag__ChunkOverlap=150 diff --git a/rag-api/Program.cs b/rag-api/Program.cs index fdcc3a1..f227e93 100644 --- a/rag-api/Program.cs +++ b/rag-api/Program.cs @@ -26,14 +26,21 @@ try builder.AddAzureKeyVaultIfConfigured(); + builder.Services.Configure(builder.Configuration.GetSection("Database")); builder.Services.Configure(builder.Configuration.GetSection("Rag")); builder.Services.Configure(builder.Configuration.GetSection("Ai")); builder.Services.Configure(builder.Configuration.GetSection("InternalApi")); builder.Services.AddDbContext(options => - options.UseSqlServer(builder.Configuration.GetConnectionString("RagDb") - ?? throw new InvalidOperationException("Connection string 'RagDb' is missing."))); + { + var configuration = builder.Configuration; + var connectionString = builder.Services.GetConfiguredDbConnectionString(configuration); + options.UseSqlServer(connectionString); + }); + + builder.Services.AddHttpClient(); + builder.Services.AddScoped(); builder.Services.AddHttpClient(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/rag-api/appsettings.json b/rag-api/appsettings.json index c78258a..6328bfd 100644 --- a/rag-api/appsettings.json +++ b/rag-api/appsettings.json @@ -73,8 +73,13 @@ "VaultUri": "", "Enabled": false }, - "ConnectionStrings": { - "RagDb": "Server=localhost,1433;Database=MyAiRag;User Id=sa;Password=Your_strong_password123;TrustServerCertificate=True" + "Database": { + "Host": "localhost", + "Port": 1433, + "Name": "MyAiCvMatcher", + "User": "sa", + "Password": "", + "TrustServerCertificate": true }, "InternalApi": { "ApiKey": "", diff --git a/shared-models/Settings/BatabaseSettings.cs b/shared-models/Settings/BatabaseSettings.cs new file mode 100644 index 0000000..d5f89e9 --- /dev/null +++ b/shared-models/Settings/BatabaseSettings.cs @@ -0,0 +1,12 @@ +namespace Shared.Models.Settings +{ + public class DatabaseSettings + { + public string Host { get; set; } = ""; + public int Port { get; set; } + public string Name { get; set; } = ""; + public string User { get; set; } = ""; + public string Password { get; set; } = ""; + public bool TrustServerCertificate { get; set; } + } +} diff --git a/startup-helpers/DatabaseExtensions.cs b/startup-helpers/DatabaseExtensions.cs new file mode 100644 index 0000000..abe85e3 --- /dev/null +++ b/startup-helpers/DatabaseExtensions.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Shared.Models.Settings; + +namespace StartupHelpers; + +public static class DatabaseExtensions +{ + public static string GetConfiguredDbConnectionString(this IServiceCollection services, IConfiguration configuration) + { + var dbOptions = configuration + .GetSection("Database") + .Get() + ?? throw new InvalidOperationException("Database config missing"); + + return + $"Server={dbOptions.Host},{dbOptions.Port};" + + $"Database={dbOptions.Name};" + + $"User Id={dbOptions.User};" + + $"Password={dbOptions.Password};" + + $"TrustServerCertificate={dbOptions.TrustServerCertificate};"; + } +} diff --git a/startup-helpers/startup-helpers.csproj b/startup-helpers/startup-helpers.csproj index 0937fa7..61a7234 100644 --- a/startup-helpers/startup-helpers.csproj +++ b/startup-helpers/startup-helpers.csproj @@ -21,4 +21,8 @@ + + + +