The job-search status page HTML wrapper was baked into a static helper
method in CvMatcherController. Extracted to a new template key
html.job-search.shell (*) with {{title}} and {{message}} placeholders.
Added to AddTemplates seed and a new AddHtmlJobSearchShell migration
for existing DBs. Controller now calls _templates.Render() for all paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Convert email.match.body, email.match.job-search-footer, email.search-results.body, and email.search-results.empty templates from plain text to proper HTML format in InitialSchema migration
- Update EmailApiEmailSender.BuildMatchEmailBody() to work with HTML templates instead of plain text
- Add WebUtility.HtmlEncode() for security when inserting dynamic content (summary)
- Templates now use semantic HTML tags (table, h2, h3, ul, li, p, div, hr, a) instead of plain text with newlines
- All 32 email template variants (16 keys × 2 languages) and 8 html.job-search.* templates seeded via migration
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Refactoring:
- Rename EmailApiDbContext class to EmailDbContext for consistency with other DbContext naming
- Rename DbSet property from EmailTemplates to Templates
- Rename table from EmailTemplates to Templates
- Update all references in Program.cs files (email-api, api, cv-search-job)
- Update all migration files and model snapshot
- Fix cv-search-job migrations assembly name: email-api-data → email-data
This improves naming consistency across the solution.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Piggybacks keyword extraction onto the existing CV-to-job LLM call —
no extra API calls. The system prompt now instructs the model to return
8-12 English job-search terms (job titles, technologies, skills, domains)
in a new `keywords` field alongside the existing score/summary fields.
Keywords flow: LLM JSON → JobMatchResponse.Keywords → CreateJobSearchTokenRequest →
JobSearchTokenEntity.Keywords (stored comma-separated) → JobSearchSessionEntity.Keywords
(copied at session-creation time, no RAG call needed).
Changes:
- Add Keywords to JobMatchResponse, CreateJobSearchTokenRequest, JobSearchTokenEntity
- IJobTokenService.CreateTokenAsync now accepts IReadOnlyList<string> keywords
- JobTokenService: store keywords on token; TriggerStartAsync reads token.Keywords
instead of fetching CV text from RAG — removes IRagApiClient dependency
- Remove heuristic ExtractKeywords method
- Migration AddKeywordsToJobSearchTokens: adds Keywords column to cvSearch.JobSearchTokens
- Migration UpdateCvMatchSystemPromptKeywords: updates ai.cv-match.system-prompt seed
to include keywords in the JSON shape
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JobTokenService.CreateTokenAsync queries cvSearch.JobProviders for any
enabled row; returns null (no token created) when the table is empty or
all providers are disabled. TriggerStartAsync snapshots enabled providers
from DB at session-start time, preserving the existing snapshot contract.
CvMatcherController guards link-building on a non-null TokenId so the
"Start a job search" CTA is omitted from match emails when no providers
are configured.
JobSearchSettings.Providers list removed — provider config now lives
exclusively in the DB. CvSearchJobTask.GetProviders falls back to an
empty list with a warning (snapshot should always be populated from DB).
Closes#35
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update all Dockerfile COPY commands to reference the renamed email-data project
instead of email-api-data. This resolves Docker build failures introduced by the
email-api-data → email-data rename.
- Apis/api/Dockerfile: Update lines 8 and 20
- Apis/email-api/Dockerfile: Update lines 6 and 17
- Jobs/cv-search-job/Dockerfile: Update lines 10 and 23
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Rename project folder Apis/email-api-data → Apis/email-data
- Rename csproj file: email-api-data.csproj → email-data.csproj
- Update csproj properties: AssemblyName and RootNamespace (email-data, Email.Data)
- Update C# namespaces: EmailApi.Data → Email.Data across all email-data files
- Update project references in api.csproj and email-api.csproj
- Update migration assembly references in api/Program.cs and email-api/Program.cs
- Update cv-search-job references to use email-data project and Email.Data namespace
- Update solution file to reference new email-data project path
- Remove hardcoded schema name from SmtpEmailDispatcher, use template service instead
This maintains consistency with other data project naming convention (no service-type suffix).
All tests passing, build succeeds.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Both api and cv-search-job need to connect to email-api for sending emails.
Add EmailApi section to their appsettings.json with BaseUrl and InternalApiKey
placeholders. Environment variables from docker-compose will populate these at runtime.
Also add EmailApi credentials to docker-compose/.env:
- EmailApi__BaseUrl=http://email-api:8080
- EmailApi__InternalApiKey=<shared key>
- EmailApi__RequireApiKey=true
This ensures both services can authenticate and call the email-api service.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The api.csproj references both email-api-data and email-api-models, but the
Dockerfile was not copying them. This caused compilation warnings and potential
build failures.
Added COPY commands for both projects before restore and publish steps.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Docker builds were failing because the centralized package version
management file (Directory.Packages.props) was not being copied into the
build context. This file is required for NuGet to resolve package versions
in projects that don't specify explicit versions.
Updated all Dockerfiles to copy Directory.Packages.props before running
dotnet restore:
- Apis/api/Dockerfile
- Apis/cv-matcher-api/Dockerfile
- Apis/rag-api/Dockerfile
- Jobs/cv-cleanup-job/Dockerfile
- Jobs/cv-search-job/Dockerfile
- web/Dockerfile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- UseJsonExceptionHandler now maps InvalidOperationException to 400 (was 500),
so upstream business-rule rejections reach the browser as actionable messages.
- CvMatcherController forwards Refit 4xx bodies from cv-matcher-api instead
of swallowing them in a generic 502.
- ErrorResponse.Score removed; CaptchaController puts the score in Detail.
- Frontend extractApiError helper reads the server Error/error/title field for
4xx responses and falls back to a generic i18n string for 5xx / missing body.
- All four failure handlers (CV upload, CV match, contact form, subscribe form)
updated to use extractApiError with the correct rate-limit i18n key.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Update CLAUDE.md: replace incorrect 'no XML doc on internal code' rule
with the correct convention (XML doc on all public methods and
non-trivial private/protected helpers)
- Restore /// <summary> on FileDownloadController private helpers
(HandleRangeRequest, StreamRangeAsync)
- Add full XML doc to all service contracts:
ICaptchaVerifier, IEmailSender, ICvMatcherService, IJobTextExtractor,
IJobTokenService, IDocumentClassifier, IRagService, ITextChunker,
ITextExtractor, IEmailTemplateService, ITemplateService
- Add /// <summary> and /// <inheritdoc /> to all concrete service classes
and their methods: RecaptchaVerifier, EmailApiEmailSender,
SmtpEmailDispatcher, CvMatcherService, JobTextExtractor, JobTokenService,
RagService, DocumentClassifier, TextChunker, TextExtractor,
HtmlJobSearcher, CvSearchEmailSender, CvSearchJobTask,
EmailTemplateService, DbTemplateService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- EmailController: add class summary, full SwaggerResponse/ProducesResponseType
for 400 and 500, and Description on SwaggerOperation
- ContactController: fix terse "Failed." error message to
"Could not process subscription."
- FileDownloadController: remove redundant XML <response code> tags from
the public action doc block; convert private-method /// <summary> to //
(project convention: no XML doc on internal code)
- CvMatcherService: remove two dead commented-out blocks (old email send
and BuildEmailBody helper)
- JobTokenService: comment the phone/contact-line regex filter in
ExtractKeywords
- DocumentClassifier: comment the keyword-frequency scoring approach and
the confidence formula
- TextChunker: comment the sliding-window step (chunkSize - overlap)
- CvSearchJobTask: comment the GdprConsent = true rationale and the
BuildCvFileName sanitisation logic
- HtmlJobSearcher: comment GetLeftPart(UriPartial.Path) query-strip dedup
Closes#26
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- EmailApiEmailSender calls email-api via IEmailApiClient Refit client
- HTML bodies built inline for contact/subscribe/file-download emails
- match and job-search emails use DB templates (rendered in caller)
- SmtpSettings moved from api-models to email-api (kept in Models.Settings namespace)
- MailKit removed from api.csproj
- SmtpEmailSender deleted; IEmailSender interface unchanged
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New Apis/myai-models project: MyAiDbContext (schema myAi), TemplateEntity,
ITemplateService, DbTemplateService with 10-min in-memory cache
- Seeds EN+RO variants for all user-facing templates (match email, job search
results email, HTML status pages, AI system prompt)
- Match result email now sent in user's UI language (en/ro)
- Job search results email now respects session language
- Language propagates: MatchJobRequest -> token -> session -> email
- Add Language column to JobSearchTokens and JobSearchSessions (default 'en')
- All three Dockerfiles updated to include myai-models in build context
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Every public action now has <summary>, <param>, and <returns> XML docs
plus matching SwaggerOperation/SwaggerResponse attributes with typed response
descriptions. Class-level summaries added to CvController, JobSearchController,
and RagController. Explanatory inline comments removed from FileDownloadController
per project conventions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses GetApplicationVersion(Assembly.GetExecutingAssembly()) — the same
timestamp-based version already logged at startup and baked into the
assembly via the csproj <Version> property. Removes the minimal-API
/version endpoint from web/Program.cs and reverts the web Dockerfile
APP_VERSION build-arg (no longer needed).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New cv-search-models shared library: EF entities + CvSearchDbContext for cvSearch schema (JobSearchTokens, JobSearchSessions, JobSearchResults tables)
- New cv-search-job worker service: polls DB for pending sessions, scrapes job boards via configurable HTML scraping, runs LLM scoring via cv-matcher-api, emails ranked results
- cv-matcher-api: JobTokenService creates one-time tokens; JobSearchController handles link clicks and creates sessions
- api: proxies job-search start endpoint, appends job search link to match result email
- CI workflow updated to build and push myai-cv-search-job:staging image
- CLAUDE.md documentation added for all affected services
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>