Files
myAi/CLAUDE.md
T
claude e95ed36647 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>
2026-05-27 15:26:03 +03:00

9.1 KiB

myAi — Solution Guide

Infrastructure URLs

Purpose URL
Staging app https://myai.easysoft.ro
Production app https://myai.ro
Portainer (container management) https://portainer.easysoft.ro/#!/auth
Grafana (logs) https://grafana.easysoft.ro/login
Gitea (source control) https://git.easysoft.ro

The Gitea instance has two deployment repos:

  • staging repo → auto-deploys to myai.easysoft.ro
  • production repo → auto-deploys to myai.ro

Staging browser testing

To verify a feature against staging use the verify skill pointed at https://myai.easysoft.ro.
Portainer at portainer.easysoft.ro can restart containers or inspect live state.
Grafana at grafana.easysoft.ro shows structured logs from all containers.

Feature workflow (plan → ship)

When a plan is approved and implementation begins:

  1. Add the plan as a Gitea Wiki page in the relevant repository (under a Features/ or Plans/ namespace)
  2. Create Gitea Issues — one per logical work chunk — and link them to the Wiki page
  3. Reference the issue number in commit messages (Closes #N)
  4. Issues are closed automatically (or manually) when the code is merged

This applies to both the staging and production repos as appropriate.

Tech stack

  • .NET 10, ASP.NET Core, Worker Service
  • Entity Framework Core + SQL Server (multi-schema)
  • Refit for typed HTTP clients between services
  • Serilog (JSON structured logging, Console + File + Email sinks)
  • MailKit for SMTP
  • 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 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.
  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.
  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

dotnet restore myAi.sln
dotnet build myAi.sln

Running locally with Docker

docker compose -f docker-compose/docker-compose.yml up --build

Config lives in docker-compose/.env. All env vars use ${VAR:-default} fallback syntax.

Database schemas

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

# cv-matcher-data (schema: cvMatcher)
dotnet ef migrations add <MigrationName> `
  --context CvMatcherDbContext `
  --project Apis/cv-matcher-data `
  --startup-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.

Service dependency chain

web → api → cv-matcher-api → rag-api
               ↑
         cv-search-job

api never talks directly to rag-api — always via cv-matcher-api.

Internal API key auth

All internal service-to-service calls require the X-Internal-Api-Key header.
The key is shared via the CvMatcherApi__InternalApiKey and RagApi__InternalApiKey env vars.
startup-helpers provides UseInternalApiKeyProtection() middleware that enforces it on cv-matcher-api and rag-api.

Shared file storage

CV PDFs are written by api to Apis/api/Files/ and read by cv-cleanup-job and cv-search-job.
All three containers mount the same bind volume:

- ../Apis/api/Files:/app/Files

The path inside containers is controlled by FileStorage__Path (default: Files).

Job task pattern

Every background worker uses the same pattern from job-scheduler:

  1. Implement IJobTask (has TaskType string + ExecuteAsync(CancellationToken))
  2. Register as singleton: services.AddSingleton<IEnumerable<IJobTask>>(sp => new IJobTask[] { ... })
  3. Register JobSchedulerHostedService as hosted service
  4. Configure in appsettings under Jobs:Tasks array: TaskType, Enabled, Interval

Program.cs conventions

Every service follows this structure:

  1. StartupExtensions.LoadDotEnvFile() — must be first, loads docker-compose/.env
  2. StartupExtensions.GetApplicationVersion(Assembly.GetExecutingAssembly())
  3. builder.ConfigureJsonSerilog(ServiceName, appVersion) from startup-helpers
  4. builder.AddAzureKeyVaultIfConfigured() (APIs only)
  5. app.UseDefaultSerilogRequestLogging()
  6. app.UseJsonExceptionHandler(ServiceName)
  7. EF migrations in a scoped block before app.Run()

Coding conventions

  • No XML doc comments on internal code; Swagger annotations on public controller actions
  • No explanatory inline comments — code should be self-describing
  • Use $$"""...""" raw string literals (not $""") when the content contains CSS or other curly-brace-heavy text — avoids CS9006 brace-escaping errors
  • sealed on all concrete service classes
  • Settings classes injected via IOptions<T> — registered with Configure<T>(config.GetSection("..."))
  • Refit clients configured via a shared local function when multiple clients share the same base URL and auth header (see api/Program.csConfigureCvMatcherApiClient)