refactor: restructure solution into -models/-data/-api project taxonomy
Phases 1-10 of the planned refactoring:
Phase 1: rename shared-models -> common
- namespace Shared.Models -> Common throughout
- remove stale AspNetCore.Http.Features 5.0 reference
Phase 2: create shared-data with abstract BaseEntity
- BaseEntity: required string Id { get; init; } + DateTime CreatedAt { get; init; }
Phase 3: rename myai-models -> myai-data
- namespace MyAi.Models -> MyAi.Data
- MigrationsAssembly("myai-data")
Phase 4: rename cv-search-models -> cv-search-data
- namespace CvSearch.Models -> CvSearch.Data
- move JobSearchSettings to cv-matcher-api-models
- JobSearch*Entity now inherits BaseEntity
Phase 5: extract rag-data from rag-api
- new project: Apis/rag-data with RagDbContext + entities + migrations
- RagDocumentEntity inherits BaseEntity; cache entities use CacheKey PK
- fix duplicate AddHttpClient<RagAiClient>/AddScoped registrations in rag-api
- MigrationsAssembly("rag-data")
Phase 6: extract cv-matcher-data from cv-matcher-api
- new project: Apis/cv-matcher-data with CvMatcherDbContext + entities + migrations
- CvMatchResultEntity inherits BaseEntity; CvMatcherChatCacheEntity uses CacheKey PK
- MigrationsAssembly("cv-matcher-data")
Phase 7: create empty cv-cleanup-job-models and cv-search-job-models
Phase 8: update all 5 Dockerfiles for renamed/new projects
Phase 9: reorganise .sln virtual folders (Apis/Jobs/Models/Data/Helpers)
- update root CLAUDE.md with new project taxonomy and migration commands
- update cv-matcher-api/CLAUDE.md and cv-search-job/CLAUDE.md
Phase 10: add Directory.Packages.props for centralised NuGet versions
- remove Version= from all PackageReference elements in active .csproj files
No database changes. No runtime behaviour changes.
All MigrationId strings in __EFMigrationsHistory are unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using Shared.Models.Requests;
|
||||
using Common.Requests;
|
||||
|
||||
namespace Models.Requests
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
@@ -8,11 +8,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="5.0.17" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\shared-models\shared-models.csproj" />
|
||||
<ProjectReference Include="..\common\common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -4,7 +4,7 @@ using Microsoft.Extensions.Options;
|
||||
using Models.Settings;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using Models.Requests;
|
||||
using Shared.Models.Responses;
|
||||
using Common.Responses;
|
||||
|
||||
namespace Api.Controllers
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ using Microsoft.Extensions.Options;
|
||||
using Models.Settings;
|
||||
using Models.Requests;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using Shared.Models.Responses;
|
||||
using Common.Responses;
|
||||
|
||||
namespace Api.Controllers
|
||||
{
|
||||
|
||||
@@ -9,8 +9,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using Shared.Models.Responses;
|
||||
using MyAi.Models.Services;
|
||||
using Common.Responses;
|
||||
using MyAi.Data.Services;
|
||||
|
||||
namespace Api.Controllers;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using Shared.Models.Responses;
|
||||
using Common.Responses;
|
||||
|
||||
namespace Api.Controllers
|
||||
{
|
||||
|
||||
+7
-5
@@ -3,19 +3,21 @@ ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
|
||||
COPY Apis/api/api.csproj Apis/api/
|
||||
COPY Apis/shared-models/shared-models.csproj Apis/shared-models/
|
||||
COPY Apis/common/common.csproj Apis/common/
|
||||
COPY Apis/api-models/api-models.csproj Apis/api-models/
|
||||
COPY Apis/cv-matcher-api-models/cv-matcher-api-models.csproj Apis/cv-matcher-api-models/
|
||||
COPY Apis/myai-models/myai-models.csproj Apis/myai-models/
|
||||
COPY Apis/myai-data/myai-data.csproj Apis/myai-data/
|
||||
COPY Apis/shared-data/shared-data.csproj Apis/shared-data/
|
||||
COPY Helpers/startup-helpers/startup-helpers.csproj Helpers/startup-helpers/
|
||||
|
||||
RUN dotnet restore Apis/api/api.csproj
|
||||
|
||||
COPY Apis/api/ Apis/api/
|
||||
COPY Apis/shared-models/ Apis/shared-models/
|
||||
COPY Apis/common/ Apis/common/
|
||||
COPY Apis/api-models/ Apis/api-models/
|
||||
COPY Apis/cv-matcher-api-models/ Apis/cv-matcher-api-models/
|
||||
COPY Apis/myai-models/ Apis/myai-models/
|
||||
COPY Apis/myai-data/ Apis/myai-data/
|
||||
COPY Apis/shared-data/ Apis/shared-data/
|
||||
COPY Helpers/startup-helpers/ Helpers/startup-helpers/
|
||||
|
||||
RUN dotnet publish Apis/api/api.csproj -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
@@ -27,4 +29,4 @@ ENV ASPNETCORE_URLS=http://0.0.0.0:8080
|
||||
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
ENTRYPOINT ["dotnet", "api.dll"]
|
||||
ENTRYPOINT ["dotnet", "api.dll"]
|
||||
|
||||
+4
-4
@@ -3,11 +3,11 @@ using Api.Services;
|
||||
using Api.Services.Contracts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Models.Settings;
|
||||
using MyAi.Models.Data;
|
||||
using MyAi.Models.Services;
|
||||
using MyAi.Data;
|
||||
using MyAi.Data.Services;
|
||||
using Refit;
|
||||
using Serilog;
|
||||
using Shared.Models.Settings;
|
||||
using Common.Settings;
|
||||
using StartupHelpers;
|
||||
|
||||
StartupExtensions.LoadDotEnvFile();
|
||||
@@ -39,7 +39,7 @@ try
|
||||
var connectionString = builder.Services.GetConfiguredDbConnectionString(builder.Configuration);
|
||||
options.UseSqlServer(connectionString, sql =>
|
||||
{
|
||||
sql.MigrationsAssembly("myai-models");
|
||||
sql.MigrationsAssembly("myai-data");
|
||||
sql.MigrationsHistoryTable(MyAiDbContext.MigrationTableName, MyAiDbContext.SchemaName);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ using MimeKit;
|
||||
using Models.Settings;
|
||||
using Models.Requests;
|
||||
using CvMatcher.Models.Responses;
|
||||
using MyAi.Models.Services;
|
||||
using MyAi.Data.Services;
|
||||
|
||||
namespace Api.Services
|
||||
{
|
||||
|
||||
+14
-14
@@ -16,18 +16,18 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.5.1" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.21.0" />
|
||||
<PackageReference Include="DotNetEnv" Version="3.2.0" />
|
||||
<PackageReference Include="MailKit" Version="4.16.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Email" Version="4.2.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="10.1.7" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" Version="10.1.6" />
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="DotNetEnv" />
|
||||
<PackageReference Include="MailKit" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" />
|
||||
<PackageReference Include="Serilog.AspNetCore" />
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" />
|
||||
<PackageReference Include="Serilog.Sinks.Email" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -37,9 +37,9 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\api-models\api-models.csproj" />
|
||||
<ProjectReference Include="..\cv-matcher-api-models\cv-matcher-api-models.csproj" />
|
||||
<ProjectReference Include="..\shared-models\shared-models.csproj" />
|
||||
<ProjectReference Include="..\common\common.csproj" />
|
||||
<ProjectReference Include="..\..\Helpers\startup-helpers\startup-helpers.csproj" />
|
||||
<ProjectReference Include="..\myai-models\myai-models.csproj" />
|
||||
<ProjectReference Include="..\myai-data\myai-data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Shared.Models.Requests
|
||||
namespace Common.Requests
|
||||
{
|
||||
public class UploadFileRequest
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Shared.Models.Responses;
|
||||
namespace Common.Responses;
|
||||
|
||||
public sealed class ErrorResponse
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Shared.Models.Settings
|
||||
namespace Common.Settings
|
||||
{
|
||||
public class AiSettings
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Shared.Models.Settings
|
||||
namespace Common.Settings
|
||||
{
|
||||
public class DatabaseSettings
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Shared.Models.Settings
|
||||
namespace Common.Settings
|
||||
{
|
||||
public class InternalApiSettings
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Shared.Models.Settings
|
||||
namespace Common.Settings
|
||||
{
|
||||
public class OllamaSettings
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Shared.Models.Settings
|
||||
namespace Common.Settings
|
||||
{
|
||||
public class OpenAiSettings
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Shared.Models.Settings
|
||||
namespace Common.Settings
|
||||
{
|
||||
public class RateLimitingSettings
|
||||
{
|
||||
@@ -1,14 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>Shared.Models</RootNamespace>
|
||||
<AssemblyName>common</AssemblyName>
|
||||
<RootNamespace>Common</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="5.0.17" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,8 +1,8 @@
|
||||
using Shared.Models.Settings;
|
||||
using Common.Settings;
|
||||
|
||||
namespace CvMatcher.Models.Settings;
|
||||
|
||||
public sealed class AiSettings : Shared.Models.Settings.AiSettings
|
||||
public sealed class AiSettings : Common.Settings.AiSettings
|
||||
{
|
||||
public OpenAiSettings OpenAI { get; set; } = new();
|
||||
public OllamaSettings Ollama { get; set; } = new();
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace CvMatcher.Models.Settings;
|
||||
|
||||
public sealed class JobSearchSettings
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
public string JobSearchLinkBaseUrl { get; set; } = string.Empty;
|
||||
public int TokenExpiryDays { get; set; } = 7;
|
||||
public int MinMatchScore { get; set; } = 15;
|
||||
public int MaxJobsToMatch { get; set; } = 15;
|
||||
public List<JobProviderConfig> Providers { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class JobProviderConfig
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public bool Enabled { get; set; } = true;
|
||||
public string SearchUrlTemplate { get; set; } = string.Empty;
|
||||
public string JobLinkContains { get; set; } = string.Empty;
|
||||
public List<string> InitialKeywords { get; set; } = [];
|
||||
public int MaxResults { get; set; } = 20;
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\shared-models\shared-models.csproj" />
|
||||
<ProjectReference Include="..\common\common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -36,8 +36,8 @@ Default model: `gpt-4o-mini`. Timeout: 90 s.
|
||||
|
||||
Both contexts use the same SQL Server connection string (from `Database:*` settings).
|
||||
|
||||
- `CvMatcherDbContext` — schema `cvMatcher`; migrations in `cv-matcher-api` assembly
|
||||
- `CvSearchDbContext` — schema `cvSearch`; migrations in `cv-search-models` assembly (MigrationsAssembly = "cv-search-models")
|
||||
- `CvMatcherDbContext` — schema `cvMatcher`; migrations in `cv-matcher-data` assembly (`Apis/cv-matcher-data/`)
|
||||
- `CvSearchDbContext` — schema `cvSearch`; migrations in `cv-search-data` assembly (`Apis/cv-search-data/`)
|
||||
|
||||
## Keyword extraction (JobTokenService.ExtractKeywords)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using CvMatcher.Models.Settings;
|
||||
using Api.Data.Repositories.Contracts;
|
||||
using CvMatcher.Data.Repositories.Contracts;
|
||||
using Api.Clients.Ai.Contracts;
|
||||
using CommonHelpers;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Api.Clients.Ai.Contracts;
|
||||
using Api.Data.Repositories.Contracts;
|
||||
using CvMatcher.Data.Repositories.Contracts;
|
||||
using CommonHelpers;
|
||||
using CvMatcher.Models.Settings;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -2,9 +2,9 @@ using CvMatcher.Models.Requests;
|
||||
using Api.Services.Contracts;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using CvMatcher.Models.Responses;
|
||||
using Shared.Models.Requests;
|
||||
using Common.Requests;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using Shared.Models.Responses;
|
||||
using Common.Responses;
|
||||
|
||||
namespace Api.Controllers;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ using Api.Services.Contracts;
|
||||
using CvMatcher.Models.Requests;
|
||||
using CvMatcher.Models.Responses;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Shared.Models.Responses;
|
||||
using Common.Responses;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace Api.Controllers;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using CvMatcher.Models.Responses;
|
||||
|
||||
namespace Api.Data.Repositories.Contracts;
|
||||
namespace CvMatcher.Data.Repositories.Contracts;
|
||||
|
||||
public interface IMatcherRepository
|
||||
{
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System.Text.Json;
|
||||
using Api.Data;
|
||||
using Api.Data.Entities;
|
||||
using Api.Data.Repositories.Contracts;
|
||||
using CvMatcher.Data;
|
||||
using CvMatcher.Data.Entities;
|
||||
using CvMatcher.Data.Repositories.Contracts;
|
||||
using CvMatcher.Models.Responses;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Api.Data.Repositories;
|
||||
namespace CvMatcher.Data.Repositories;
|
||||
|
||||
public sealed class EfMatcherRepository : IMatcherRepository
|
||||
{
|
||||
|
||||
@@ -3,20 +3,24 @@ ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
|
||||
COPY Apis/cv-matcher-api/cv-matcher-api.csproj Apis/cv-matcher-api/
|
||||
COPY Apis/cv-search-models/cv-search-models.csproj Apis/cv-search-models/
|
||||
COPY Apis/shared-models/shared-models.csproj Apis/shared-models/
|
||||
COPY Apis/cv-search-data/cv-search-data.csproj Apis/cv-search-data/
|
||||
COPY Apis/cv-matcher-data/cv-matcher-data.csproj Apis/cv-matcher-data/
|
||||
COPY Apis/common/common.csproj Apis/common/
|
||||
COPY Apis/cv-matcher-api-models/cv-matcher-api-models.csproj Apis/cv-matcher-api-models/
|
||||
COPY Apis/myai-models/myai-models.csproj Apis/myai-models/
|
||||
COPY Apis/myai-data/myai-data.csproj Apis/myai-data/
|
||||
COPY Apis/shared-data/shared-data.csproj Apis/shared-data/
|
||||
COPY Helpers/common-helpers/common-helpers.csproj Helpers/common-helpers/
|
||||
COPY Helpers/startup-helpers/startup-helpers.csproj Helpers/startup-helpers/
|
||||
|
||||
RUN dotnet restore Apis/cv-matcher-api/cv-matcher-api.csproj
|
||||
|
||||
COPY Apis/cv-matcher-api/ Apis/cv-matcher-api/
|
||||
COPY Apis/cv-search-models/ Apis/cv-search-models/
|
||||
COPY Apis/shared-models/ Apis/shared-models/
|
||||
COPY Apis/cv-search-data/ Apis/cv-search-data/
|
||||
COPY Apis/cv-matcher-data/ Apis/cv-matcher-data/
|
||||
COPY Apis/common/ Apis/common/
|
||||
COPY Apis/cv-matcher-api-models/ Apis/cv-matcher-api-models/
|
||||
COPY Apis/myai-models/ Apis/myai-models/
|
||||
COPY Apis/myai-data/ Apis/myai-data/
|
||||
COPY Apis/shared-data/ Apis/shared-data/
|
||||
COPY Helpers/common-helpers/ Helpers/common-helpers/
|
||||
COPY Helpers/startup-helpers/ Helpers/startup-helpers/
|
||||
|
||||
@@ -29,4 +33,4 @@ ENV ASPNETCORE_URLS=http://0.0.0.0:8080
|
||||
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
ENTRYPOINT ["dotnet", "cv-matcher-api.dll"]
|
||||
ENTRYPOINT ["dotnet", "cv-matcher-api.dll"]
|
||||
|
||||
@@ -2,20 +2,19 @@ using Api.Clients.Ai;
|
||||
using Api.Clients.Ai.Contracts;
|
||||
using Api.Clients.Api;
|
||||
using Api.Clients.Api.Contracts;
|
||||
using Api.Data;
|
||||
using Api.Data.Repositories;
|
||||
using Api.Data.Repositories.Contracts;
|
||||
using CvMatcher.Data;
|
||||
using CvMatcher.Data.Repositories;
|
||||
using CvMatcher.Data.Repositories.Contracts;
|
||||
using Api.Services;
|
||||
using Api.Services.Contracts;
|
||||
using CvMatcher.Models.Settings;
|
||||
using CvSearch.Models.Data;
|
||||
using CvSearch.Models.Settings;
|
||||
using CvSearch.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MyAi.Models.Data;
|
||||
using MyAi.Models.Services;
|
||||
using MyAi.Data;
|
||||
using MyAi.Data.Services;
|
||||
using Refit;
|
||||
using Serilog;
|
||||
using Shared.Models.Settings;
|
||||
using Common.Settings;
|
||||
using StartupHelpers;
|
||||
using System.Reflection;
|
||||
|
||||
@@ -63,6 +62,7 @@ try
|
||||
options.UseSqlServer(connectionString, sql =>
|
||||
{
|
||||
sql.MigrationsHistoryTable(CvMatcherDbContext.MigrationTableName, CvMatcherDbContext.SchemaName);
|
||||
sql.MigrationsAssembly("cv-matcher-data");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,7 +71,7 @@ try
|
||||
var connectionString = builder.Services.GetConfiguredDbConnectionString(builder.Configuration);
|
||||
options.UseSqlServer(connectionString, sql =>
|
||||
{
|
||||
sql.MigrationsAssembly("cv-search-models");
|
||||
sql.MigrationsAssembly("cv-search-data");
|
||||
sql.MigrationsHistoryTable(CvSearchDbContext.MigrationTableName, CvSearchDbContext.SchemaName);
|
||||
});
|
||||
});
|
||||
@@ -81,7 +81,7 @@ try
|
||||
var connectionString = builder.Services.GetConfiguredDbConnectionString(builder.Configuration);
|
||||
options.UseSqlServer(connectionString, sql =>
|
||||
{
|
||||
sql.MigrationsAssembly("myai-models");
|
||||
sql.MigrationsAssembly("myai-data");
|
||||
sql.MigrationsHistoryTable(MyAiDbContext.MigrationTableName, MyAiDbContext.SchemaName);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System.Text.Json;
|
||||
using Api.Clients.Ai.Contracts;
|
||||
using Api.Clients.Api.Contracts;
|
||||
using Api.Data.Repositories.Contracts;
|
||||
using CvMatcher.Data.Repositories.Contracts;
|
||||
using CvMatcher.Models.Requests;
|
||||
using CvMatcher.Models.Responses;
|
||||
using CvMatcher.Models.Settings;
|
||||
using Api.Services.Contracts;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MyAi.Models.Services;
|
||||
using MyAi.Data.Services;
|
||||
|
||||
namespace Api.Services;
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ using System.Text.RegularExpressions;
|
||||
using Api.Clients.Api.Contracts;
|
||||
using Api.Services.Contracts;
|
||||
using CvMatcher.Models.Responses;
|
||||
using CvSearch.Models.Data;
|
||||
using CvSearch.Models.Data.Entities;
|
||||
using CvSearch.Models.Settings;
|
||||
using CvSearch.Data;
|
||||
using CvSearch.Data.Entities;
|
||||
using CvMatcher.Models.Settings;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
||||
@@ -58,30 +58,31 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.5.1" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.21.0" />
|
||||
<PackageReference Include="DotNetEnv" Version="3.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="DotNetEnv" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MailKit" Version="4.16.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Email" Version="4.2.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="10.1.7" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" Version="10.1.6" />
|
||||
<PackageReference Include="MailKit" />
|
||||
<PackageReference Include="Serilog.AspNetCore" />
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="Serilog.Sinks.Email" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Helpers\common-helpers\common-helpers.csproj" />
|
||||
<ProjectReference Include="..\cv-matcher-api-models\cv-matcher-api-models.csproj" />
|
||||
<ProjectReference Include="..\cv-search-models\cv-search-models.csproj" />
|
||||
<ProjectReference Include="..\shared-models\shared-models.csproj" />
|
||||
<ProjectReference Include="..\..\Helpers\startup-helpers\startup-helpers.csproj" />
|
||||
<ProjectReference Include="..\myai-models\myai-models.csproj" />
|
||||
</ItemGroup>
|
||||
<ProjectReference Include="..\cv-search-data\cv-search-data.csproj" />
|
||||
<ProjectReference Include="..\cv-matcher-data\cv-matcher-data.csproj" />
|
||||
<ProjectReference Include="..\common\common.csproj" />
|
||||
<ProjectReference Include="..\..\Helpers\startup-helpers\startup-helpers.csproj" />
|
||||
<ProjectReference Include="..\myai-data\myai-data.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
using Api.Data.Entities;
|
||||
using CvMatcher.Data.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Api.Data;
|
||||
|
||||
namespace CvMatcher.Data;
|
||||
|
||||
public sealed class CvMatcherDbContext : DbContext
|
||||
{
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
namespace Api.Data.Entities;
|
||||
using Shared.Data.Entities;
|
||||
|
||||
public sealed class CvMatchResultEntity
|
||||
namespace CvMatcher.Data.Entities;
|
||||
|
||||
public sealed class CvMatchResultEntity : BaseEntity
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string CvDocumentId { get; set; } = string.Empty;
|
||||
public string JobDocumentId { get; set; } = string.Empty;
|
||||
public string Language { get; set; } = "en";
|
||||
public string ResultJson { get; set; } = string.Empty;
|
||||
public int Score { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
namespace Api.Data.Entities;
|
||||
namespace CvMatcher.Data.Entities;
|
||||
|
||||
// CacheKey PK — BaseEntity not applicable
|
||||
public sealed class CvMatcherChatCacheEntity
|
||||
{
|
||||
public string CacheKey { get; set; } = string.Empty;
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Api.Data;
|
||||
using CvMatcher.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
@@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Api.Migrations
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(CvMatcherDbContext))]
|
||||
[Migration("20260507140442_InitialCvMatcherSchema")]
|
||||
@@ -26,7 +26,7 @@ namespace Api.Migrations
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.CvMatchResultEntity", b =>
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatchResultEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
@@ -62,7 +62,7 @@ namespace Api.Migrations
|
||||
b.ToTable("Results", "cvMatcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.CvMatcherChatCacheEntity", b =>
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatcherChatCacheEntity", b =>
|
||||
{
|
||||
b.Property<string>("CacheKey")
|
||||
.HasMaxLength(64)
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Api.Migrations
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCvMatcherSchema : Migration
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Api.Data;
|
||||
using CvMatcher.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
@@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Api.Migrations
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(CvMatcherDbContext))]
|
||||
[Migration("20260524140335_AddLanguageToCvMatchResult")]
|
||||
@@ -26,7 +26,7 @@ namespace Api.Migrations
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.CvMatchResultEntity", b =>
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatchResultEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
@@ -66,7 +66,7 @@ namespace Api.Migrations
|
||||
b.ToTable("Results", "cvMatcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.CvMatcherChatCacheEntity", b =>
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatcherChatCacheEntity", b =>
|
||||
{
|
||||
b.Property<string>("CacheKey")
|
||||
.HasMaxLength(64)
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Api.Migrations
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddLanguageToCvMatchResult : Migration
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Api.Data;
|
||||
using CvMatcher.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
@@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Api.Migrations
|
||||
namespace CvMatcher.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(CvMatcherDbContext))]
|
||||
partial class CvMatcherDbContextModelSnapshot : ModelSnapshot
|
||||
@@ -23,7 +23,7 @@ namespace Api.Migrations
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.CvMatchResultEntity", b =>
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatchResultEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
@@ -63,7 +63,7 @@ namespace Api.Migrations
|
||||
b.ToTable("Results", "cvMatcher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.CvMatcherChatCacheEntity", b =>
|
||||
modelBuilder.Entity("CvMatcher.Data.Entities.CvMatcherChatCacheEntity", b =>
|
||||
{
|
||||
b.Property<string>("CacheKey")
|
||||
.HasMaxLength(64)
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AssemblyName>cv-matcher-data</AssemblyName>
|
||||
<RootNamespace>CvMatcher.Data</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\shared-data\shared-data.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,62 @@
|
||||
using CvSearch.Data.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CvSearch.Data;
|
||||
|
||||
public sealed class CvSearchDbContext : DbContext
|
||||
{
|
||||
public const string SchemaName = "cvSearch";
|
||||
public const string MigrationTableName = "_Migrations";
|
||||
|
||||
public CvSearchDbContext(DbContextOptions<CvSearchDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<JobSearchTokenEntity> JobSearchTokens => Set<JobSearchTokenEntity>();
|
||||
public DbSet<JobSearchSessionEntity> JobSearchSessions => Set<JobSearchSessionEntity>();
|
||||
public DbSet<JobSearchResultEntity> JobSearchResults => Set<JobSearchResultEntity>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasDefaultSchema(SchemaName);
|
||||
|
||||
modelBuilder.Entity<JobSearchTokenEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("JobSearchTokens");
|
||||
entity.HasKey(x => x.Id);
|
||||
entity.Property(x => x.Id).HasMaxLength(64);
|
||||
entity.Property(x => x.CvDocumentId).HasMaxLength(64).IsRequired();
|
||||
entity.Property(x => x.Email).HasMaxLength(256).IsRequired();
|
||||
entity.Property(x => x.Language).HasMaxLength(8).HasDefaultValue("en").IsRequired();
|
||||
entity.Property(x => x.Used).HasDefaultValue(false);
|
||||
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<JobSearchSessionEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("JobSearchSessions");
|
||||
entity.HasKey(x => x.Id);
|
||||
entity.Property(x => x.Id).HasMaxLength(64);
|
||||
entity.Property(x => x.TokenId).HasMaxLength(64).IsRequired();
|
||||
entity.Property(x => x.CvDocumentId).HasMaxLength(64).IsRequired();
|
||||
entity.Property(x => x.Email).HasMaxLength(256).IsRequired();
|
||||
entity.Property(x => x.Status).HasMaxLength(32).IsRequired();
|
||||
entity.Property(x => x.Keywords).HasMaxLength(1000);
|
||||
entity.Property(x => x.ProviderConfigJson).IsRequired(false);
|
||||
entity.Property(x => x.Language).HasMaxLength(8).HasDefaultValue("en").IsRequired();
|
||||
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
entity.HasIndex(x => x.Status);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<JobSearchResultEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("JobSearchResults");
|
||||
entity.HasKey(x => x.Id);
|
||||
entity.Property(x => x.Id).HasMaxLength(64);
|
||||
entity.Property(x => x.SessionId).HasMaxLength(64).IsRequired();
|
||||
entity.Property(x => x.ProviderName).HasMaxLength(128);
|
||||
entity.Property(x => x.JobUrl).HasMaxLength(2048);
|
||||
entity.Property(x => x.JobTitle).HasMaxLength(512);
|
||||
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
entity.HasIndex(x => x.SessionId);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Shared.Data.Entities;
|
||||
|
||||
namespace CvSearch.Data.Entities;
|
||||
|
||||
public sealed class JobSearchResultEntity : BaseEntity
|
||||
{
|
||||
public string SessionId { get; set; } = string.Empty;
|
||||
public string ProviderName { get; set; } = string.Empty;
|
||||
public string JobUrl { get; set; } = string.Empty;
|
||||
public string JobTitle { get; set; } = string.Empty;
|
||||
public string JobText { get; set; } = string.Empty;
|
||||
public int Score { get; set; }
|
||||
public string ResultJson { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Shared.Data.Entities;
|
||||
|
||||
namespace CvSearch.Data.Entities;
|
||||
|
||||
public sealed class JobSearchSessionEntity : BaseEntity
|
||||
{
|
||||
public string TokenId { get; set; } = string.Empty;
|
||||
public string CvDocumentId { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = JobSearchStatus.Pending;
|
||||
public string Keywords { get; set; } = string.Empty;
|
||||
public string? ProviderConfigJson { get; set; }
|
||||
public string Language { get; set; } = "en";
|
||||
}
|
||||
|
||||
public static class JobSearchStatus
|
||||
{
|
||||
public const string Pending = "Pending";
|
||||
public const string Processing = "Processing";
|
||||
public const string Done = "Done";
|
||||
public const string Failed = "Failed";
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Shared.Data.Entities;
|
||||
|
||||
namespace CvSearch.Data.Entities;
|
||||
|
||||
public sealed class JobSearchTokenEntity : BaseEntity
|
||||
{
|
||||
public string CvDocumentId { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string Language { get; set; } = "en";
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
public bool Used { get; set; }
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using CvSearch.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvSearch.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(CvSearchDbContext))]
|
||||
[Migration("20260522093356_AddJobSearchTables")]
|
||||
partial class AddJobSearchTables
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("cvSearch")
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("CvSearch.Data.Entities.JobSearchResultEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("JobText")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("JobTitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("JobUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("nvarchar(2048)");
|
||||
|
||||
b.Property<string>("ProviderName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ResultJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Score")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SessionId");
|
||||
|
||||
b.ToTable("JobSearchResults", "cvSearch");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CvSearch.Data.Entities.JobSearchSessionEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("CvDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("Keywords")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<string>("ProviderConfigJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("JobSearchSessions", "cvSearch");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CvSearch.Data.Entities.JobSearchTokenEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("CvDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("Used")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("JobSearchTokens", "cvSearch");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvSearch.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddJobSearchTables : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "cvSearch");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "JobSearchResults",
|
||||
schema: "cvSearch",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
SessionId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
ProviderName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
JobUrl = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: false),
|
||||
JobTitle = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
|
||||
JobText = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Score = table.Column<int>(type: "int", nullable: false),
|
||||
ResultJson = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_JobSearchResults", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "JobSearchSessions",
|
||||
schema: "cvSearch",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
TokenId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
CvDocumentId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
Status = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
Keywords = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
|
||||
ProviderConfigJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_JobSearchSessions", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "JobSearchTokens",
|
||||
schema: "cvSearch",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
CvDocumentId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
ExpiresAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
Used = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_JobSearchTokens", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_JobSearchResults_SessionId",
|
||||
schema: "cvSearch",
|
||||
table: "JobSearchResults",
|
||||
column: "SessionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_JobSearchSessions_Status",
|
||||
schema: "cvSearch",
|
||||
table: "JobSearchSessions",
|
||||
column: "Status");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "JobSearchResults",
|
||||
schema: "cvSearch");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "JobSearchSessions",
|
||||
schema: "cvSearch");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "JobSearchTokens",
|
||||
schema: "cvSearch");
|
||||
}
|
||||
}
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using CvSearch.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvSearch.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(CvSearchDbContext))]
|
||||
[Migration("20260524145702_AddLanguageToJobSearchEntities")]
|
||||
partial class AddLanguageToJobSearchEntities
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("cvSearch")
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("CvSearch.Data.Entities.JobSearchResultEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("JobText")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("JobTitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("JobUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("nvarchar(2048)");
|
||||
|
||||
b.Property<string>("ProviderName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ResultJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Score")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SessionId");
|
||||
|
||||
b.ToTable("JobSearchResults", "cvSearch");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CvSearch.Data.Entities.JobSearchSessionEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("CvDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("Keywords")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("nvarchar(8)")
|
||||
.HasDefaultValue("en");
|
||||
|
||||
b.Property<string>("ProviderConfigJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("JobSearchSessions", "cvSearch");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CvSearch.Data.Entities.JobSearchTokenEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("CvDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("nvarchar(8)")
|
||||
.HasDefaultValue("en");
|
||||
|
||||
b.Property<bool>("Used")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("JobSearchTokens", "cvSearch");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvSearch.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddLanguageToJobSearchEntities : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Language",
|
||||
schema: "cvSearch",
|
||||
table: "JobSearchTokens",
|
||||
type: "nvarchar(8)",
|
||||
maxLength: 8,
|
||||
nullable: false,
|
||||
defaultValue: "en");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Language",
|
||||
schema: "cvSearch",
|
||||
table: "JobSearchSessions",
|
||||
type: "nvarchar(8)",
|
||||
maxLength: 8,
|
||||
nullable: false,
|
||||
defaultValue: "en");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Language",
|
||||
schema: "cvSearch",
|
||||
table: "JobSearchTokens");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Language",
|
||||
schema: "cvSearch",
|
||||
table: "JobSearchSessions");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using CvSearch.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CvSearch.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(CvSearchDbContext))]
|
||||
partial class CvSearchDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("cvSearch")
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("CvSearch.Data.Entities.JobSearchResultEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("JobText")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("JobTitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("JobUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("nvarchar(2048)");
|
||||
|
||||
b.Property<string>("ProviderName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ResultJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Score")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SessionId");
|
||||
|
||||
b.ToTable("JobSearchResults", "cvSearch");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CvSearch.Data.Entities.JobSearchSessionEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("CvDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("Keywords")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("nvarchar(8)")
|
||||
.HasDefaultValue("en");
|
||||
|
||||
b.Property<string>("ProviderConfigJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("JobSearchSessions", "cvSearch");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CvSearch.Data.Entities.JobSearchTokenEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("CvDocumentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("nvarchar(8)")
|
||||
.HasDefaultValue("en");
|
||||
|
||||
b.Property<bool>("Used")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("JobSearchTokens", "cvSearch");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AssemblyName>cv-search-data</AssemblyName>
|
||||
<RootNamespace>CvSearch.Data</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\shared-data\shared-data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>CvSearch.Models</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace MyAi.Data.Entities;
|
||||
|
||||
// composite PK (Key + Language) — BaseEntity not applicable
|
||||
public sealed class TemplateEntity
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string Language { get; set; } = string.Empty;
|
||||
public string Value { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using MyAi.Data.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MyAi.Data;
|
||||
|
||||
public sealed class MyAiDbContext : DbContext
|
||||
{
|
||||
public const string SchemaName = "myAi";
|
||||
public const string MigrationTableName = "_MyAiMigrations";
|
||||
|
||||
public MyAiDbContext(DbContextOptions<MyAiDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<TemplateEntity> Templates => Set<TemplateEntity>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasDefaultSchema(SchemaName);
|
||||
|
||||
modelBuilder.Entity<TemplateEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("Templates");
|
||||
entity.HasKey(x => new { x.Key, x.Language });
|
||||
entity.Property(x => x.Key).HasMaxLength(128);
|
||||
entity.Property(x => x.Language).HasMaxLength(8);
|
||||
entity.Property(x => x.Value).IsRequired();
|
||||
entity.Property(x => x.Description).HasMaxLength(500).HasDefaultValue(string.Empty);
|
||||
entity.Property(x => x.UpdatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using MyAi.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MyAi.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(MyAiDbContext))]
|
||||
[Migration("20260524145351_AddTemplates")]
|
||||
partial class AddTemplates
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("myAi")
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("MyAi.Data.Entities.TemplateEntity", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("nvarchar(8)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)")
|
||||
.HasDefaultValue("");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Key", "Language");
|
||||
|
||||
b.ToTable("Templates", "myAi");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MyAi.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddTemplates : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "myAi");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Templates",
|
||||
schema: "myAi",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Language = table.Column<string>(type: "nvarchar(8)", maxLength: 8, nullable: false),
|
||||
Value = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false, defaultValue: ""),
|
||||
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "SYSUTCDATETIME()")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Templates", x => new { x.Key, x.Language });
|
||||
});
|
||||
|
||||
Seed(migrationBuilder);
|
||||
}
|
||||
|
||||
private static void Seed(MigrationBuilder m)
|
||||
{
|
||||
void Row(string key, string lang, string value, string description = "")
|
||||
=> m.InsertData("Templates", ["Key", "Language", "Value", "Description"], [key, lang, value, description], "myAi");
|
||||
|
||||
// Match result email — subject
|
||||
Row("email.match.subject", "en", "MyAi.ro CV Match: {{score}}% - {{jobLabel}}", "Subject for the CV match result email");
|
||||
Row("email.match.subject", "ro", "MyAi.ro Potrivire CV: {{score}}% - {{jobLabel}}", "Subiect email rezultat potrivire CV");
|
||||
|
||||
// Match result email — body
|
||||
Row("email.match.body", "en",
|
||||
"CV Matcher result\n\nCV Document ID: {{cvDocumentId}}\nJob: {{jobLabel}}\nJob URL: {{jobUrl}}\nScore: {{score}}%\n\nSummary:\n{{summary}}\n\nStrengths:\n{{strengths}}\n\nGaps:\n{{gaps}}\n\nRecommendations:\n{{recommendations}}",
|
||||
"Body for the CV match result email");
|
||||
Row("email.match.body", "ro",
|
||||
"Rezultat potrivire CV\n\nID document CV: {{cvDocumentId}}\nJob: {{jobLabel}}\nURL job: {{jobUrl}}\nScor: {{score}}%\n\nRezumat:\n{{summary}}\n\nPuncte forte:\n{{strengths}}\n\nLipsuri:\n{{gaps}}\n\nRecomandări:\n{{recommendations}}",
|
||||
"Corpul emailului pentru rezultatul potrivirii CV");
|
||||
|
||||
// Match result email — job search CTA footer
|
||||
Row("email.match.job-search-footer", "en",
|
||||
"\n\n---\nWant to find more jobs matching your CV?\nClick: {{jobSearchLink}}\n(link valid for {{expiryDays}} days)",
|
||||
"Job search CTA appended to match result email");
|
||||
Row("email.match.job-search-footer", "ro",
|
||||
"\n\n---\nVrei sa gasesti mai multe joburi potrivite CV-ului tau?\nClick: {{jobSearchLink}}\n(link valabil {{expiryDays}} zile)",
|
||||
"CTA cautare joburi adaugat la emailul de potrivire CV");
|
||||
|
||||
// Job search results email — subject
|
||||
Row("email.search-results.subject", "en", "MyAi.ro: {{count}} jobs matching your CV", "Subject for job search results email");
|
||||
Row("email.search-results.subject", "ro", "MyAi.ro: {{count}} joburi potrivite CV-ului tau", "Subiect email rezultate cautare joburi");
|
||||
|
||||
// Job search results email — body preamble (items appended in code)
|
||||
Row("email.search-results.body", "en", "MyAi.ro found {{count}} jobs matching your CV:\n\n{{items}}", "Body preamble for job search results email");
|
||||
Row("email.search-results.body", "ro", "MyAi.ro a gasit {{count}} joburi potrivite CV-ului tau:\n\n{{items}}", "Corpul emailului de rezultate cautare joburi");
|
||||
|
||||
// Job search results email — no results found
|
||||
Row("email.search-results.empty", "en", "MyAi.ro found no jobs matching your CV. Try again later or update your CV.", "No results message for job search results email");
|
||||
Row("email.search-results.empty", "ro", "MyAi.ro nu a gasit joburi care sa corespunda CV-ului tau. Incercati mai tarziu sau ajustati CV-ul.", "Mesaj fara rezultate pentru emailul de cautare joburi");
|
||||
|
||||
// HTML job-search start page messages
|
||||
Row("html.job-search.started.title", "en", "Job search started", "Title for job search started page");
|
||||
Row("html.job-search.started.message", "en", "Your job search has started. Results will be sent to your email shortly.", "Message for job search started page");
|
||||
Row("html.job-search.started.title", "ro", "Căutare joburi pornită", "Titlu pagina cautare joburi pornita");
|
||||
Row("html.job-search.started.message", "ro", "Căutarea joburilor a început. Rezultatele vor fi trimise pe email în scurt timp.", "Mesaj pagina cautare joburi pornita");
|
||||
|
||||
Row("html.job-search.already-used.title", "en", "Link already used", "Title for already-used page");
|
||||
Row("html.job-search.already-used.message", "en", "This job search link has already been used.", "Message for already-used page");
|
||||
Row("html.job-search.already-used.title", "ro", "Link deja folosit", "Titlu pagina link deja folosit");
|
||||
Row("html.job-search.already-used.message", "ro", "Acest link de cautare joburi a fost deja folosit.", "Mesaj pagina link deja folosit");
|
||||
|
||||
Row("html.job-search.expired.title", "en", "Link expired", "Title for expired link page");
|
||||
Row("html.job-search.expired.message", "en", "This job search link has expired. Please request a new CV match to get a fresh link.", "Message for expired link page");
|
||||
Row("html.job-search.expired.title", "ro", "Link expirat", "Titlu pagina link expirat");
|
||||
Row("html.job-search.expired.message", "ro", "Acest link de cautare joburi a expirat. Solicita o noua potrivire CV pentru a primi un link nou.", "Mesaj pagina link expirat");
|
||||
|
||||
Row("html.job-search.invalid.title", "en", "Invalid link", "Title for invalid link page");
|
||||
Row("html.job-search.invalid.message", "en", "This job search link is not valid.", "Message for invalid link page");
|
||||
Row("html.job-search.invalid.title", "ro", "Link invalid", "Titlu pagina link invalid");
|
||||
Row("html.job-search.invalid.message", "ro", "Acest link de cautare joburi nu este valid.", "Mesaj pagina link invalid");
|
||||
|
||||
Row("html.job-search.error.title", "en", "Error", "Title for error page");
|
||||
Row("html.job-search.error.message", "en", "An error occurred. Please try again later.", "Message for error page");
|
||||
Row("html.job-search.error.title", "ro", "Eroare", "Titlu pagina eroare");
|
||||
Row("html.job-search.error.message", "ro", "A apărut o eroare. Te rugăm să încerci din nou mai târziu.", "Mesaj pagina eroare");
|
||||
|
||||
// AI system prompt for CV matching (language is a {{languageName}} variable inside it)
|
||||
Row("ai.cv-match.system-prompt", "*",
|
||||
"You are a strict CV-to-job matching engine. Return JSON only. Score realistically from 0 to 100.\nPenalize missing required skills. Do not invent experience. Use concise business language.\nRespond entirely in {{languageName}} — all text fields in the JSON must be in {{languageName}}.\nJSON shape: {\"score\":number,\"summary\":\"...\",\"strengths\":[\"...\"],\"gaps\":[\"...\"],\"recommendations\":[\"...\"],\"evidence\":[\"...\"]}",
|
||||
"System prompt template for the CV-to-job LLM matching call. {{languageName}} is substituted at runtime.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Templates",
|
||||
schema: "myAi");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using MyAi.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MyAi.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(MyAiDbContext))]
|
||||
partial class MyAiDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("myAi")
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("MyAi.Data.Entities.TemplateEntity", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("nvarchar(8)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)")
|
||||
.HasDefaultValue("");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("datetime2")
|
||||
.HasDefaultValueSql("SYSUTCDATETIME()");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Key", "Language");
|
||||
|
||||
b.ToTable("Templates", "myAi");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MyAi.Data;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MyAi.Data.Services;
|
||||
|
||||
public sealed class DbTemplateService : ITemplateService
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<DbTemplateService> _logger;
|
||||
private ConcurrentDictionary<string, string> _cache = new(StringComparer.OrdinalIgnoreCase);
|
||||
private DateTime _loadedAt = DateTime.MinValue;
|
||||
private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(10);
|
||||
|
||||
public DbTemplateService(IServiceScopeFactory scopeFactory, ILogger<DbTemplateService> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string Get(string key, string language = "en")
|
||||
{
|
||||
EnsureCacheLoaded();
|
||||
|
||||
if (_cache.TryGetValue(CacheKey(key, language), out var value))
|
||||
return value;
|
||||
|
||||
if (!string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)
|
||||
&& _cache.TryGetValue(CacheKey(key, "en"), out var fallback))
|
||||
return fallback;
|
||||
|
||||
_logger.LogWarning("Template not found: key={Key}, language={Language}", key, language);
|
||||
return key;
|
||||
}
|
||||
|
||||
public string Render(string key, string language, params (string Key, string Value)[] placeholders)
|
||||
{
|
||||
var template = Get(key, language);
|
||||
foreach (var (k, v) in placeholders)
|
||||
template = template.Replace($"{{{{{k}}}}}", v, StringComparison.OrdinalIgnoreCase);
|
||||
return template;
|
||||
}
|
||||
|
||||
private void EnsureCacheLoaded()
|
||||
{
|
||||
if (DateTime.UtcNow - _loadedAt < CacheTtl) return;
|
||||
|
||||
try
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<MyAiDbContext>();
|
||||
var rows = db.Templates.AsNoTracking().ToList();
|
||||
var fresh = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var row in rows)
|
||||
fresh[CacheKey(row.Key, row.Language)] = row.Value;
|
||||
|
||||
_cache = fresh;
|
||||
_loadedAt = DateTime.UtcNow;
|
||||
_logger.LogDebug("Template cache refreshed. {Count} templates loaded.", rows.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to refresh template cache. Serving stale cache.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string CacheKey(string key, string language) => $"{key}::{language}";
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MyAi.Data.Services;
|
||||
|
||||
public interface ITemplateService
|
||||
{
|
||||
string Get(string key, string language = "en");
|
||||
string Render(string key, string language, params (string Key, string Value)[] placeholders);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AssemblyName>myai-data</AssemblyName>
|
||||
<RootNamespace>MyAi.Data</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\shared-data\shared-data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>MyAi.Models</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Rag.Models.Settings;
|
||||
|
||||
public sealed class OllamaSettings : Shared.Models.Settings.OllamaSettings
|
||||
public sealed class OllamaSettings : Common.Settings.OllamaSettings
|
||||
{
|
||||
public string EmbeddingModel { get; set; } = "nomic-embed-text";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Rag.Models.Settings;
|
||||
|
||||
public sealed class OpenAiSettings: Shared.Models.Settings.OpenAiSettings
|
||||
public sealed class OpenAiSettings : Common.Settings.OpenAiSettings
|
||||
{
|
||||
public string EmbeddingModel { get; set; } = "text-embedding-3-small";
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\shared-models\shared-models.csproj" />
|
||||
<ProjectReference Include="..\common\common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Rag.Models.Settings;
|
||||
using Api.Data.Repositories.Contracts;
|
||||
using Rag.Data.Repositories.Contracts;
|
||||
using Api.Clients.Ai.Contracts;
|
||||
using CommonHelpers;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using Api.Services.Contracts;
|
||||
using Rag.Models.Requests;
|
||||
using Rag.Models.Responses;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using Shared.Models.Responses;
|
||||
using Common.Responses;
|
||||
|
||||
namespace Api.Controllers;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Rag.Models;
|
||||
|
||||
namespace Api.Data.Repositories.Contracts;
|
||||
namespace Rag.Data.Repositories.Contracts;
|
||||
|
||||
public interface IRagRepository
|
||||
{
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using Api.Data;
|
||||
using Api.Data.Entities;
|
||||
using Rag.Data;
|
||||
using Rag.Data.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Api.Data.Repositories.Contracts;
|
||||
using Rag.Data.Repositories.Contracts;
|
||||
using Rag.Models;
|
||||
|
||||
namespace Api.Data.Repositories;
|
||||
namespace Rag.Data.Repositories;
|
||||
|
||||
public sealed class EfRagRepository : IRagRepository
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Api.Data.Repositories;
|
||||
namespace Rag.Data.Repositories;
|
||||
|
||||
public static class VectorSerializer
|
||||
{
|
||||
|
||||
@@ -3,16 +3,20 @@ ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
|
||||
COPY Apis/rag-api/rag-api.csproj Apis/rag-api/
|
||||
COPY Apis/shared-models/shared-models.csproj Apis/shared-models/
|
||||
COPY Apis/rag-data/rag-data.csproj Apis/rag-data/
|
||||
COPY Apis/common/common.csproj Apis/common/
|
||||
COPY Apis/rag-api-models/rag-api-models.csproj Apis/rag-api-models/
|
||||
COPY Apis/shared-data/shared-data.csproj Apis/shared-data/
|
||||
COPY Helpers/common-helpers/common-helpers.csproj Helpers/common-helpers/
|
||||
COPY Helpers/startup-helpers/startup-helpers.csproj Helpers/startup-helpers/
|
||||
|
||||
RUN dotnet restore Apis/rag-api/rag-api.csproj
|
||||
|
||||
COPY Apis/rag-api/ Apis/rag-api/
|
||||
COPY Apis/shared-models/ Apis/shared-models/
|
||||
COPY Apis/rag-data/ Apis/rag-data/
|
||||
COPY Apis/common/ Apis/common/
|
||||
COPY Apis/rag-api-models/ Apis/rag-api-models/
|
||||
COPY Apis/shared-data/ Apis/shared-data/
|
||||
COPY Helpers/common-helpers/ Helpers/common-helpers/
|
||||
COPY Helpers/startup-helpers/ Helpers/startup-helpers/
|
||||
|
||||
@@ -25,4 +29,4 @@ ENV ASPNETCORE_URLS=http://0.0.0.0:8080
|
||||
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
ENTRYPOINT ["dotnet", "rag-api.dll"]
|
||||
ENTRYPOINT ["dotnet", "rag-api.dll"]
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using System.Reflection;
|
||||
using Api.Clients.Ai;
|
||||
using Api.Clients.Ai.Contracts;
|
||||
using Api.Data;
|
||||
using Api.Data.Repositories;
|
||||
using Api.Data.Repositories.Contracts;
|
||||
using Rag.Data;
|
||||
using Rag.Data.Repositories;
|
||||
using Rag.Data.Repositories.Contracts;
|
||||
using Api.Services;
|
||||
using Api.Services.Contracts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Rag.Models.Settings;
|
||||
using Serilog;
|
||||
using Shared.Models.Settings;
|
||||
using Common.Settings;
|
||||
using StartupHelpers;
|
||||
|
||||
StartupExtensions.LoadDotEnvFile();
|
||||
@@ -39,11 +39,10 @@ try
|
||||
options.UseSqlServer(connectionString, sql =>
|
||||
{
|
||||
sql.MigrationsHistoryTable(RagDbContext.MigrationTableName, RagDbContext.SchemaName);
|
||||
sql.MigrationsAssembly("rag-data");
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddHttpClient<RagAiClient>();
|
||||
builder.Services.AddScoped<IRagRepository, EfRagRepository>();
|
||||
builder.Services.AddHttpClient<RagAiClient>();
|
||||
builder.Services.AddScoped<IRagRepository, EfRagRepository>();
|
||||
builder.Services.AddScoped<IAiClient, CachedRagAiClient>();
|
||||
|
||||
@@ -4,7 +4,7 @@ using Api.Services.Contracts;
|
||||
using Rag.Models.Requests;
|
||||
using Rag.Models.Responses;
|
||||
using Rag.Models.Settings;
|
||||
using Api.Data.Repositories.Contracts;
|
||||
using Rag.Data.Repositories.Contracts;
|
||||
using Api.Clients.Ai.Contracts;
|
||||
using Rag.Models;
|
||||
using CommonHelpers;
|
||||
|
||||
+17
-16
@@ -58,28 +58,29 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.5.1" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.21.0" />
|
||||
<PackageReference Include="DotNetEnv" Version="3.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="DotNetEnv" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="PdfPig" Version="0.1.14" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Email" Version="4.2.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="10.1.7" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" Version="10.1.6" />
|
||||
<PackageReference Include="PdfPig" />
|
||||
<PackageReference Include="Serilog.AspNetCore" />
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="Serilog.Sinks.Email" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Helpers\common-helpers\common-helpers.csproj" />
|
||||
<ProjectReference Include="..\rag-api-models\rag-api-models.csproj" />
|
||||
<ProjectReference Include="..\shared-models\shared-models.csproj" />
|
||||
<ProjectReference Include="..\..\Helpers\startup-helpers\startup-helpers.csproj" />
|
||||
</ItemGroup>
|
||||
<ProjectReference Include="..\common\common.csproj" />
|
||||
<ProjectReference Include="..\rag-data\rag-data.csproj" />
|
||||
<ProjectReference Include="..\..\Helpers\startup-helpers\startup-helpers.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
namespace Api.Data.Entities;
|
||||
namespace Rag.Data.Entities;
|
||||
|
||||
// CacheKey PK — BaseEntity not applicable
|
||||
public sealed class RagChatCompletionCacheEntity
|
||||
{
|
||||
public string CacheKey { get; set; } = string.Empty;
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
namespace Api.Data.Entities;
|
||||
namespace Rag.Data.Entities;
|
||||
|
||||
// no CreatedAt column in schema — BaseEntity not applicable
|
||||
public sealed class RagChunkEntity
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
+4
-4
@@ -1,8 +1,9 @@
|
||||
namespace Api.Data.Entities;
|
||||
using Shared.Data.Entities;
|
||||
|
||||
public sealed class RagDocumentEntity
|
||||
namespace Rag.Data.Entities;
|
||||
|
||||
public sealed class RagDocumentEntity : BaseEntity
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string DocumentType { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string? SourceUrl { get; set; }
|
||||
@@ -10,7 +11,6 @@ public sealed class RagDocumentEntity
|
||||
public string TextHash { get; set; } = string.Empty;
|
||||
public double TypeConfidence { get; set; }
|
||||
public string MetadataJson { get; set; } = "{}";
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public ICollection<RagChunkEntity> Chunks { get; set; } = [];
|
||||
}
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
namespace Api.Data.Entities;
|
||||
namespace Rag.Data.Entities;
|
||||
|
||||
// CacheKey PK — BaseEntity not applicable
|
||||
public sealed class RagEmbeddingCacheEntity
|
||||
{
|
||||
public string CacheKey { get; set; } = string.Empty;
|
||||
+10
-10
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Api.Data;
|
||||
using Rag.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
@@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Api.Migrations
|
||||
namespace Rag.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(RagDbContext))]
|
||||
[Migration("20260507140305_InitialRagSchema")]
|
||||
@@ -26,7 +26,7 @@ namespace Api.Migrations
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.RagChatCompletionCacheEntity", b =>
|
||||
modelBuilder.Entity("Rag.Data.Entities.RagChatCompletionCacheEntity", b =>
|
||||
{
|
||||
b.Property<string>("CacheKey")
|
||||
.HasMaxLength(64)
|
||||
@@ -54,7 +54,7 @@ namespace Api.Migrations
|
||||
b.ToTable("ChatCompletionCache", "rag");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.RagChunkEntity", b =>
|
||||
modelBuilder.Entity("Rag.Data.Entities.RagChunkEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
@@ -83,7 +83,7 @@ namespace Api.Migrations
|
||||
b.ToTable("Chunks", "rag");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.RagDocumentEntity", b =>
|
||||
modelBuilder.Entity("Rag.Data.Entities.RagDocumentEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
@@ -135,7 +135,7 @@ namespace Api.Migrations
|
||||
b.ToTable("Documents", "rag");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.RagEmbeddingCacheEntity", b =>
|
||||
modelBuilder.Entity("Rag.Data.Entities.RagEmbeddingCacheEntity", b =>
|
||||
{
|
||||
b.Property<string>("CacheKey")
|
||||
.HasMaxLength(64)
|
||||
@@ -167,9 +167,9 @@ namespace Api.Migrations
|
||||
b.ToTable("EmbeddingCache", "rag");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.RagChunkEntity", b =>
|
||||
modelBuilder.Entity("Rag.Data.Entities.RagChunkEntity", b =>
|
||||
{
|
||||
b.HasOne("Api.Data.Entities.RagDocumentEntity", "Document")
|
||||
b.HasOne("Rag.Data.Entities.RagDocumentEntity", "Document")
|
||||
.WithMany("Chunks")
|
||||
.HasForeignKey("DocumentId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -178,7 +178,7 @@ namespace Api.Migrations
|
||||
b.Navigation("Document");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.RagDocumentEntity", b =>
|
||||
modelBuilder.Entity("Rag.Data.Entities.RagDocumentEntity", b =>
|
||||
{
|
||||
b.Navigation("Chunks");
|
||||
});
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Api.Migrations
|
||||
namespace Rag.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialRagSchema : Migration
|
||||
+10
-10
@@ -1,6 +1,6 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Api.Data;
|
||||
using Rag.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
@@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Api.Migrations
|
||||
namespace Rag.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(RagDbContext))]
|
||||
partial class RagDbContextModelSnapshot : ModelSnapshot
|
||||
@@ -23,7 +23,7 @@ namespace Api.Migrations
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.RagChatCompletionCacheEntity", b =>
|
||||
modelBuilder.Entity("Rag.Data.Entities.RagChatCompletionCacheEntity", b =>
|
||||
{
|
||||
b.Property<string>("CacheKey")
|
||||
.HasMaxLength(64)
|
||||
@@ -51,7 +51,7 @@ namespace Api.Migrations
|
||||
b.ToTable("ChatCompletionCache", "rag");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.RagChunkEntity", b =>
|
||||
modelBuilder.Entity("Rag.Data.Entities.RagChunkEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
@@ -80,7 +80,7 @@ namespace Api.Migrations
|
||||
b.ToTable("Chunks", "rag");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.RagDocumentEntity", b =>
|
||||
modelBuilder.Entity("Rag.Data.Entities.RagDocumentEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
@@ -132,7 +132,7 @@ namespace Api.Migrations
|
||||
b.ToTable("Documents", "rag");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.RagEmbeddingCacheEntity", b =>
|
||||
modelBuilder.Entity("Rag.Data.Entities.RagEmbeddingCacheEntity", b =>
|
||||
{
|
||||
b.Property<string>("CacheKey")
|
||||
.HasMaxLength(64)
|
||||
@@ -164,9 +164,9 @@ namespace Api.Migrations
|
||||
b.ToTable("EmbeddingCache", "rag");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.RagChunkEntity", b =>
|
||||
modelBuilder.Entity("Rag.Data.Entities.RagChunkEntity", b =>
|
||||
{
|
||||
b.HasOne("Api.Data.Entities.RagDocumentEntity", "Document")
|
||||
b.HasOne("Rag.Data.Entities.RagDocumentEntity", "Document")
|
||||
.WithMany("Chunks")
|
||||
.HasForeignKey("DocumentId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -175,7 +175,7 @@ namespace Api.Migrations
|
||||
b.Navigation("Document");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Api.Data.Entities.RagDocumentEntity", b =>
|
||||
modelBuilder.Entity("Rag.Data.Entities.RagDocumentEntity", b =>
|
||||
{
|
||||
b.Navigation("Chunks");
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
using Api.Data.Entities;
|
||||
using Rag.Data.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Api.Data;
|
||||
namespace Rag.Data;
|
||||
|
||||
public sealed class RagDbContext : DbContext
|
||||
{
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AssemblyName>rag-data</AssemblyName>
|
||||
<RootNamespace>Rag.Data</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\shared-data\shared-data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Shared.Data.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base for all EF entities that carry a surrogate string PK and an audit timestamp.
|
||||
/// Entities with a composite PK or a non-<c>Id</c> primary key should NOT inherit this class;
|
||||
/// document the exception with a brief comment on the entity.
|
||||
/// </summary>
|
||||
public abstract class BaseEntity
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public DateTime CreatedAt { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AssemblyName>shared-data</AssemblyName>
|
||||
<RootNamespace>Shared.Data</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -39,29 +39,55 @@ This applies to both the staging and production repos as appropriate.
|
||||
- Docker Compose for local and production deployment
|
||||
- Watchtower for automatic container updates in production
|
||||
|
||||
## Project taxonomy
|
||||
|
||||
| Category | Naming | Contains | EF dependency |
|
||||
|----------|--------|----------|---------------|
|
||||
| Executable | `{name}-api`, `{name}-job` | Controllers, Services, Program.cs | Via `ProjectReference` to a `-data` project |
|
||||
| Domain contracts | `{name}-models`, `{name}-api-models`, `{name}-job-models` | DTOs, Refit interfaces, domain-specific Settings | No |
|
||||
| Data layer | `{name}-data` | DbContext, EF entities, Migrations | Yes |
|
||||
| Common contracts | `common` (no suffix) | Infrastructure/technical primitives — no domain ownership | No |
|
||||
| Common base entities | `shared-data` | Abstract `BaseEntity` class (Id + CreatedAt). No DbContext. | No |
|
||||
|
||||
### The `common` project rule
|
||||
|
||||
`common` holds **only infrastructure/technical primitives** with no specific service domain ownership: `DatabaseSettings`, `InternalApiSettings`, `ErrorResponse`, `RateLimitingSettings`, `UploadFileRequest`, AI provider settings, etc. **Never put a business-domain type in `common`** — domain types belong in the owning service's `-models` project.
|
||||
|
||||
### Where migrations live
|
||||
|
||||
**Migrations always live in the `-data` project**, never in an API or Job project. EF CLI split: `--project` = `-data` project (owns the schema); `--startup-project` = whichever API supplies the DB connection string.
|
||||
|
||||
## Solution layout
|
||||
|
||||
```
|
||||
Apis/
|
||||
api/ Public-facing proxy API (port 8080). Handles CORS, rate limiting, captcha, email.
|
||||
api-models/ DTOs and settings shared by api only.
|
||||
cv-matcher-api/ Internal CV match engine (port 8082). Owns cvMatcher + cvSearch DB schemas.
|
||||
cv-matcher-api-models/ DTOs shared between api and cv-matcher-api.
|
||||
cv-search-models/ EF entities + DbContext for cvSearch schema. Shared by cv-matcher-api and cv-search-job.
|
||||
rag-api/ Internal RAG/vector-search service (port 8081). Owns rag DB schema.
|
||||
api-models/ DTOs and settings for api only.
|
||||
cv-matcher-api/ Internal CV match engine (port 8082). Runs CvMatcher + CvSearch + MyAi DB migrations.
|
||||
cv-matcher-api-models/ DTOs shared between api and cv-matcher-api (incl. JobSearchSettings).
|
||||
rag-api/ Internal RAG/vector-search service (port 8081).
|
||||
rag-api-models/ DTOs shared with rag-api.
|
||||
shared-models/ Cross-service shared models (DatabaseSettings, etc.).
|
||||
common/ Cross-service infrastructure primitives (DatabaseSettings, InternalApiSettings, etc.).
|
||||
shared-data/ Abstract BaseEntity base class. No DbContext.
|
||||
cv-matcher-data/ CvMatcherDbContext + entities + migrations (schema: cvMatcher).
|
||||
cv-search-data/ CvSearchDbContext + entities + migrations (schema: cvSearch).
|
||||
rag-data/ RagDbContext + entities + migrations (schema: rag).
|
||||
myai-data/ MyAiDbContext + entities + migrations (schema: myAi).
|
||||
Helpers/
|
||||
startup-helpers/ Shared Program.cs bootstrap: Serilog, Swagger, .env loading, Azure Key Vault, middleware.
|
||||
common-helpers/ Utility helpers.
|
||||
Jobs/
|
||||
job-scheduler/ IJobTask + JobSchedulerHostedService — the reusable scheduled-job engine.
|
||||
cv-cleanup-job/ Worker: deletes old CVs from file storage. Runs hourly.
|
||||
cv-cleanup-job-models/ Job-specific models for cv-cleanup-job (proactive; currently empty).
|
||||
cv-search-job/ Worker: picks up pending job search sessions, scrapes providers, emails results.
|
||||
web/ Razor Pages / Blazor front-end (port 5000).
|
||||
cv-search-job-models/ Job-specific models for cv-search-job (proactive; currently empty).
|
||||
web/ Razor Pages / Blazor front-end (port 5140).
|
||||
docker-compose/ docker-compose.yml + .env file.
|
||||
```
|
||||
|
||||
Virtual solution folders in `.sln`: `Apis` (executables + web), `Models` (DTOs/contracts), `Data` (data layers), `Jobs`, `Helpers`.
|
||||
|
||||
## Build & restore
|
||||
|
||||
```powershell
|
||||
@@ -79,27 +105,41 @@ Config lives in `docker-compose/.env`. All env vars use `${VAR:-default}` fallba
|
||||
|
||||
## Database schemas
|
||||
|
||||
| Schema | Owner DbContext | Migrations assembly |
|
||||
|-------------|----------------------|-----------------------|
|
||||
| `cvMatcher` | `CvMatcherDbContext` | `cv-matcher-api` |
|
||||
| `rag` | `RagDbContext` | `rag-api` |
|
||||
| `cvSearch` | `CvSearchDbContext` | `cv-search-models` |
|
||||
| Schema | Owner DbContext | Migrations project | Startup project |
|
||||
|-------------|----------------------|-----------------------|-----------------------|
|
||||
| `cvMatcher` | `CvMatcherDbContext` | `cv-matcher-data` | `cv-matcher-api` |
|
||||
| `rag` | `RagDbContext` | `rag-data` | `rag-api` |
|
||||
| `cvSearch` | `CvSearchDbContext` | `cv-search-data` | `cv-matcher-api` |
|
||||
| `myAi` | `MyAiDbContext` | `myai-data` | `api` |
|
||||
|
||||
Both `cv-matcher-api` and `cv-search-job` register `CvSearchDbContext` and call `db.Database.Migrate()` on startup (idempotent — safe for both to run).
|
||||
|
||||
## EF Core migrations
|
||||
|
||||
```powershell
|
||||
# Add a migration to cv-search-models
|
||||
dotnet ef migrations add <MigrationName> \
|
||||
--context CvSearchDbContext \
|
||||
--project Apis/cv-search-models \
|
||||
# cv-matcher-data (schema: cvMatcher)
|
||||
dotnet ef migrations add <MigrationName> `
|
||||
--context CvMatcherDbContext `
|
||||
--project Apis/cv-matcher-data `
|
||||
--startup-project Apis/cv-matcher-api
|
||||
|
||||
# Add a migration to cv-matcher-api
|
||||
dotnet ef migrations add <MigrationName> \
|
||||
--context CvMatcherDbContext \
|
||||
--project Apis/cv-matcher-api
|
||||
# rag-data (schema: rag)
|
||||
dotnet ef migrations add <MigrationName> `
|
||||
--context RagDbContext `
|
||||
--project Apis/rag-data `
|
||||
--startup-project Apis/rag-api
|
||||
|
||||
# cv-search-data (schema: cvSearch)
|
||||
dotnet ef migrations add <MigrationName> `
|
||||
--context CvSearchDbContext `
|
||||
--project Apis/cv-search-data `
|
||||
--startup-project Apis/cv-matcher-api
|
||||
|
||||
# myai-data (schema: myAi)
|
||||
dotnet ef migrations add <MigrationName> `
|
||||
--context MyAiDbContext `
|
||||
--project Apis/myai-data `
|
||||
--startup-project Apis/api
|
||||
```
|
||||
|
||||
EF tools version warning ("older than runtime") is expected and harmless. The `HostAbortedException` output during migration scaffolding is normal — EF starts the host to discover DbContext then aborts it.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Azure -->
|
||||
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.5.1" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.21.0" />
|
||||
<!-- EF Core -->
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.7" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7" />
|
||||
<!-- Extensions -->
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<!-- Config -->
|
||||
<PackageVersion Include="DotNetEnv" Version="3.2.0" />
|
||||
<!-- HTTP / Refit -->
|
||||
<PackageVersion Include="Refit.HttpClientFactory" Version="10.1.6" />
|
||||
<!-- Serilog -->
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Environment" Version="3.0.1" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||
<PackageVersion Include="Serilog.Sinks.Email" Version="4.2.1" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<!-- Swagger -->
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="10.1.7" />
|
||||
<!-- Web -->
|
||||
<PackageVersion Include="Yarp.ReverseProxy" Version="2.3.0" />
|
||||
<PackageVersion Include="MailKit" Version="4.16.0" />
|
||||
<PackageVersion Include="PdfPig" Version="0.1.14" />
|
||||
<!-- Tooling -->
|
||||
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Shared.Models.Settings;
|
||||
using Common.Settings;
|
||||
|
||||
namespace StartupHelpers;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Shared.Models.Settings;
|
||||
using Common.Settings;
|
||||
|
||||
namespace StartupHelpers;
|
||||
|
||||
|
||||
@@ -12,19 +12,19 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.5.1" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.21.0" />
|
||||
<PackageReference Include="DotNetEnv" Version="3.2.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Email" Version="4.2.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="10.1.7" />
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="DotNetEnv" />
|
||||
<PackageReference Include="Serilog.AspNetCore" />
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" />
|
||||
<PackageReference Include="Serilog.Sinks.Email" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Apis\shared-models\shared-models.csproj" />
|
||||
<ProjectReference Include="..\..\Apis\common\common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AssemblyName>cv-cleanup-job-models</AssemblyName>
|
||||
<RootNamespace>CvCleanup.Job.Models</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -6,7 +6,7 @@ COPY Jobs/cv-cleanup-job/cv-cleanup-job.csproj Jobs/cv-cleanup-job/
|
||||
COPY Jobs/job-scheduler/job-scheduler.csproj Jobs/job-scheduler/
|
||||
COPY Helpers/startup-helpers/startup-helpers.csproj Helpers/startup-helpers/
|
||||
COPY Apis/api-models/api-models.csproj Apis/api-models/
|
||||
COPY Apis/shared-models/shared-models.csproj Apis/shared-models/
|
||||
COPY Apis/common/common.csproj Apis/common/
|
||||
|
||||
RUN dotnet restore Jobs/cv-cleanup-job/cv-cleanup-job.csproj
|
||||
|
||||
@@ -14,7 +14,7 @@ COPY Jobs/cv-cleanup-job/ Jobs/cv-cleanup-job/
|
||||
COPY Jobs/job-scheduler/ Jobs/job-scheduler/
|
||||
COPY Helpers/startup-helpers/ Helpers/startup-helpers/
|
||||
COPY Apis/api-models/ Apis/api-models/
|
||||
COPY Apis/shared-models/ Apis/shared-models/
|
||||
COPY Apis/common/ Apis/common/
|
||||
|
||||
RUN dotnet publish Jobs/cv-cleanup-job/cv-cleanup-job.csproj -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AssemblyName>cv-search-job-models</AssemblyName>
|
||||
<RootNamespace>CvSearch.Job.Models</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -86,5 +86,5 @@ Follows the same scheme as `cv-cleanup-job`:
|
||||
## EF migrations
|
||||
|
||||
This project runs `CvSearchDbContext.Database.Migrate()` on startup.
|
||||
Migrations live in `Apis/cv-search-models/Migrations/`.
|
||||
Migrations live in `Apis/cv-search-data/Migrations/`.
|
||||
To add a migration: see root CLAUDE.md.
|
||||
|
||||
@@ -4,20 +4,22 @@ WORKDIR /src
|
||||
|
||||
COPY Jobs/cv-search-job/cv-search-job.csproj Jobs/cv-search-job/
|
||||
COPY Jobs/job-scheduler/job-scheduler.csproj Jobs/job-scheduler/
|
||||
COPY Apis/cv-search-models/cv-search-models.csproj Apis/cv-search-models/
|
||||
COPY Apis/cv-search-data/cv-search-data.csproj Apis/cv-search-data/
|
||||
COPY Apis/cv-matcher-api-models/cv-matcher-api-models.csproj Apis/cv-matcher-api-models/
|
||||
COPY Apis/shared-models/shared-models.csproj Apis/shared-models/
|
||||
COPY Apis/myai-models/myai-models.csproj Apis/myai-models/
|
||||
COPY Apis/common/common.csproj Apis/common/
|
||||
COPY Apis/myai-data/myai-data.csproj Apis/myai-data/
|
||||
COPY Apis/shared-data/shared-data.csproj Apis/shared-data/
|
||||
COPY Helpers/startup-helpers/startup-helpers.csproj Helpers/startup-helpers/
|
||||
|
||||
RUN dotnet restore Jobs/cv-search-job/cv-search-job.csproj
|
||||
|
||||
COPY Jobs/cv-search-job/ Jobs/cv-search-job/
|
||||
COPY Jobs/job-scheduler/ Jobs/job-scheduler/
|
||||
COPY Apis/cv-search-models/ Apis/cv-search-models/
|
||||
COPY Apis/cv-search-data/ Apis/cv-search-data/
|
||||
COPY Apis/cv-matcher-api-models/ Apis/cv-matcher-api-models/
|
||||
COPY Apis/shared-models/ Apis/shared-models/
|
||||
COPY Apis/myai-models/ Apis/myai-models/
|
||||
COPY Apis/common/ Apis/common/
|
||||
COPY Apis/myai-data/ Apis/myai-data/
|
||||
COPY Apis/shared-data/ Apis/shared-data/
|
||||
COPY Helpers/startup-helpers/ Helpers/startup-helpers/
|
||||
|
||||
RUN dotnet publish Jobs/cv-search-job/cv-search-job.csproj -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Reflection;
|
||||
using CvSearch.Models.Data;
|
||||
using CvSearch.Models.Settings;
|
||||
using CvMatcher.Models.Settings;
|
||||
using CvSearch.Data;
|
||||
using CvSearchJob.Clients;
|
||||
using CvSearchJob.Services;
|
||||
using CvSearchJob.Tasks;
|
||||
@@ -9,11 +9,11 @@ using JobScheduler.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MyAi.Models.Data;
|
||||
using MyAi.Models.Services;
|
||||
using MyAi.Data;
|
||||
using MyAi.Data.Services;
|
||||
using Refit;
|
||||
using Serilog;
|
||||
using Shared.Models.Settings;
|
||||
using Common.Settings;
|
||||
using StartupHelpers;
|
||||
|
||||
const string ServiceName = "cv-search-job";
|
||||
@@ -36,7 +36,7 @@ try
|
||||
var connectionString = builder.Services.GetConfiguredDbConnectionString(builder.Configuration);
|
||||
options.UseSqlServer(connectionString, sql =>
|
||||
{
|
||||
sql.MigrationsAssembly("cv-search-models");
|
||||
sql.MigrationsAssembly("cv-search-data");
|
||||
sql.MigrationsHistoryTable(CvSearchDbContext.MigrationTableName, CvSearchDbContext.SchemaName);
|
||||
});
|
||||
});
|
||||
@@ -58,7 +58,7 @@ try
|
||||
var connectionString = builder.Services.GetConfiguredDbConnectionString(builder.Configuration);
|
||||
options.UseSqlServer(connectionString, sql =>
|
||||
{
|
||||
sql.MigrationsAssembly("myai-models");
|
||||
sql.MigrationsAssembly("myai-data");
|
||||
sql.MigrationsHistoryTable(MyAiDbContext.MigrationTableName, MyAiDbContext.SchemaName);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using CvMatcher.Models.Responses;
|
||||
using CvSearch.Models.Data.Entities;
|
||||
using CvSearch.Data.Entities;
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Security;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MimeKit;
|
||||
using MyAi.Models.Services;
|
||||
using MyAi.Data.Services;
|
||||
|
||||
namespace CvSearchJob.Services;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using CvSearch.Models.Settings;
|
||||
using CvMatcher.Models.Settings;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CvSearchJob.Services;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user