Split templates table into emailApi, cvMatcher, and myAi schemas #25

Merged
gelu merged 6 commits from feature/split-templates into main 2026-05-28 05:57:16 +00:00
Owner

What

Split the single myAi.Templates table (which mixed three unrelated concerns) into three purpose-specific tables, each owned by the service that uses it.

Why

  • mail.* templates were being read by pi and cv-search-job via MyAiDbContext (wrong ownership)
  • i.* system prompt was being read by cv-matcher-api via MyAiDbContext (wrong ownership)
  • HTML shell in SmtpEmailDispatcher was hardcoded as static string constants instead of DB templates
  • Operator copy email address was stored in config (Contact:ToEmail) instead of the template row

Changes

  • New project mail-api-data: EmailApiDbContext (schema: mailApi) with IEmailTemplateRepository, IEmailTemplateService (singleton, 10-min cache), and initial migration seeding all mail.* templates + html shell fragments with OperatorCopy column
  • mail-api: wired to mail-api-data; runs EmailApiDbContext migrations on startup; SmtpEmailDispatcher loads html shell from DB (removes hardcoded constants)
  • pi: registers EmailApiDbContext + IEmailTemplateService; EmailApiEmailSender uses IEmailTemplateService for all mail.* rendering; SendMatchAsync reads operator copy from GetOperatorCopy() instead of Contact:ToEmail config
  • cv-search-job: registers EmailApiDbContext + IEmailTemplateService; removes MyAiDbContext + ITemplateService; CvSearchEmailSender reads operator copy from GetOperatorCopy() instead of IConfiguration["Contact:ToEmail"]; Contact__ToEmail removed from docker-compose cv-search-job block
  • cv-matcher-data: new AiPrompts table (schema: cvMatcher) with AddAiPrompts migration seeding i.cv-match.system-prompt
  • cv-matcher-api: new IAiPromptsRepository/EfAiPromptsRepository; CvMatcherService reads prompt from DB; removes MyAiDbContext, ITemplateService, and myai-data project reference
  • myai-data: new DeleteMigratedTemplates migration removes all mail.* and i.* rows (keeping only html.*)
  • docker-compose: adds Database__* env vars to mail-api service (needed for new EmailApiDbContext)
  • CLAUDE.md: updated solution layout, database schemas table, and EF CLI migration commands

Test plan

  • dotnet build myAi.sln — 0 errors, 0 warnings
  • cv-matcher-api.csproj has no myai-data reference
  • mail-api has no hardcoded HTML string constants
  • cv-search-job docker-compose has no Contact__ToEmail env var
  • Manual: deploy to staging; CV match email arrives at user + contact@myai.ro
  • Manual: job search results email arrives at both addresses
  • Manual: update OperatorCopy in staging DB → next email uses new address without restart

Closes #24

🤖 Generated with Claude Code

## What Split the single myAi.Templates table (which mixed three unrelated concerns) into three purpose-specific tables, each owned by the service that uses it. ## Why - mail.* templates were being read by pi and cv-search-job via MyAiDbContext (wrong ownership) - i.* system prompt was being read by cv-matcher-api via MyAiDbContext (wrong ownership) - HTML shell in SmtpEmailDispatcher was hardcoded as static string constants instead of DB templates - Operator copy email address was stored in config (Contact:ToEmail) instead of the template row ## Changes - **New project mail-api-data**: EmailApiDbContext (schema: mailApi) with IEmailTemplateRepository, IEmailTemplateService (singleton, 10-min cache), and initial migration seeding all mail.* templates + html shell fragments with OperatorCopy column - **mail-api**: wired to mail-api-data; runs EmailApiDbContext migrations on startup; SmtpEmailDispatcher loads html shell from DB (removes hardcoded constants) - **pi**: registers EmailApiDbContext + IEmailTemplateService; EmailApiEmailSender uses IEmailTemplateService for all mail.* rendering; SendMatchAsync reads operator copy from GetOperatorCopy() instead of Contact:ToEmail config - **cv-search-job**: registers EmailApiDbContext + IEmailTemplateService; removes MyAiDbContext + ITemplateService; CvSearchEmailSender reads operator copy from GetOperatorCopy() instead of IConfiguration["Contact:ToEmail"]; Contact__ToEmail removed from docker-compose cv-search-job block - **cv-matcher-data**: new AiPrompts table (schema: cvMatcher) with AddAiPrompts migration seeding i.cv-match.system-prompt - **cv-matcher-api**: new IAiPromptsRepository/EfAiPromptsRepository; CvMatcherService reads prompt from DB; removes MyAiDbContext, ITemplateService, and myai-data project reference - **myai-data**: new DeleteMigratedTemplates migration removes all mail.* and i.* rows (keeping only html.*) - **docker-compose**: adds Database__* env vars to mail-api service (needed for new EmailApiDbContext) - **CLAUDE.md**: updated solution layout, database schemas table, and EF CLI migration commands ## Test plan - [ ] dotnet build myAi.sln — 0 errors, 0 warnings ✅ - [ ] cv-matcher-api.csproj has no myai-data reference ✅ - [ ] mail-api has no hardcoded HTML string constants ✅ - [ ] cv-search-job docker-compose has no Contact__ToEmail env var ✅ - [ ] Manual: deploy to staging; CV match email arrives at user + contact@myai.ro - [ ] Manual: job search results email arrives at both addresses - [ ] Manual: update OperatorCopy in staging DB → next email uses new address without restart Closes #24 🤖 Generated with [Claude Code](https://claude.com/claude-code)
gelu added 6 commits 2026-05-28 05:50:56 +00:00
New data project owning the emailApi schema:
- EmailTemplateEntity with Key, Language, Value, Description, UpdatedAt, OperatorCopy
- EmailApiDbContext (schema: emailApi, custom migration table _EmailApiMigrations)
- IEmailTemplateRepository / EfEmailTemplateRepository (scoped)
- IEmailTemplateService / EmailTemplateService (singleton, 10-min cache)
  - GetOperatorCopy falls back to first non-empty OperatorCopy across all rows
- Initial migration CreateEmailTemplates: creates table + seeds all email.*
  templates (match + search-results in en/ro) and html-shell fragments
  with OperatorCopy = "contact@myai.ro" for addressable rows

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ProjectReference to email-api-data
- Register EmailApiDbContext + run migrations on startup
- Register IEmailTemplateRepository (scoped) and IEmailTemplateService (singleton)
- SmtpEmailDispatcher: inject IEmailTemplateService; replace hardcoded
  HtmlShellStart/HtmlShellEnd string constants with DB template lookups
  (email.html-shell.start / email.html-shell.end, language "*")

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ProjectReference to email-api-data
- Register EmailApiDbContext (no migrate — email-api owns migrations)
- Register IEmailTemplateRepository (scoped) and IEmailTemplateService (singleton)
- EmailApiEmailSender: replace ITemplateService with IEmailTemplateService for
  all email.* template rendering (match body/subject/footer)
- SendMatchAsync: replace _contact.ToEmail operator copy with
  GetOperatorCopy("email.match.subject", "en") from DB template

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ProjectReference to email-api-data; remove myai-data reference
- Program.cs: register EmailApiDbContext (no migrate), IEmailTemplateRepository
  (scoped), IEmailTemplateService (singleton); remove MyAiDbContext +
  ITemplateService registrations and their migration call
- CvSearchEmailSender: inject IEmailTemplateService; replace
  _config["Contact:ToEmail"] with GetOperatorCopy("email.search-results.subject")
  for operator copy logic; remove IConfiguration injection
- docker-compose: remove Contact__ToEmail from cv-search-job service block;
  add Database__* env vars to email-api service (needed for EmailApiDbContext)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cv-matcher-data:
- Add AiPromptEntity (Key, Language, Value, Description, UpdatedAt)
- Add AiPrompts DbSet to CvMatcherDbContext with composite PK
- Migration AddAiPrompts: create cvMatcher.AiPrompts table and seed
  ai.cv-match.system-prompt (language "*") with the current prompt value

cv-matcher-api:
- Add IAiPromptsRepository / EfAiPromptsRepository under Data/Repositories/
- CvMatcherService: inject IAiPromptsRepository; replace _templates.Render(...)
  with async DB lookup + simple string replacement
- Program.cs: register IAiPromptsRepository (scoped); remove MyAiDbContext,
  ITemplateService/DbTemplateService registrations and MyAiDbContext migration call
- Remove myai-data ProjectReference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add DeleteMigratedTemplates migration: removes all email.* and ai.*
  rows from myAi.Templates (now owned by emailApi.EmailTemplates and
  cvMatcher.AiPrompts respectively)
- CLAUDE.md: add email-api-data to solution layout; add emailApi schema
  to database schemas table; add email-api-data EF CLI migration command;
  note cv-matcher-api no longer runs MyAi migrations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
claude approved these changes 2026-05-28 05:56:59 +00:00
gelu merged commit 7d92f2f8d9 into main 2026-05-28 05:57:16 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: AI/myAi#25