The poll is the fallback, not the mechanism. It is 30s on staging but 300s
on production, so a green run could sit five minutes ahead of the deploy it
claimed to have made -- the smoke job was absorbing that wait.
Copied from easyDent verbatim, including the soft failure: an unset secret
or an unreachable API logs and falls back to the poll rather than failing
the build. That matters right now because production has no Watchtower HTTP
API yet, so the production URL will do nothing until the infra stack there
is updated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpTYCBLH58XzrM7n3xPJ5N
jecreativ.ro's first production deploy went red for behaving exactly as
configured: it runs in UnderConstruction mode, so `/` correctly answers 302
to the placeholder, and the check asserted a bare 200.
Now `-L` follows the redirect and the FINAL code is asserted. A redirect
means the app is up and routing, which is what this step is for; a real
failure (502, 500, refused) still reports.
The version gate itself was right throughout -- it read 1bafc14 from the
production host, including through the under-construction middleware.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpTYCBLH58XzrM7n3xPJ5N
Same guard as the four site repos. myAi was the ONLY stack that kept its
environment variables through the 26 July migration, so it alone deployed
correctly all along -- but it shares the ${IMAGE_TAG:-staging} default that
made the others fail silently, so it gets the same treatment.
1. ${IMAGE_TAG:-staging} -> ${IMAGE_TAG:-IMAGE_TAG-NOT-SET} on all eight
images: an unconfigured stack fails the pull rather than quietly becoming
staging.
2. A smoke job that asks the deploy host what commit it serves, via a
/version.json stamped into the web image at build time.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpTYCBLH58XzrM7n3xPJ5N
Deployed staging/prod envs now use Database__Host=mssql.easysoft.ro (LAN DNS -> the MSSQL
VM 10.0.0.240) instead of the raw IP, matching the infra DNS standardization. The
docker-compose default stays 'sqlserver' for local dev. (.env/.env.staging/.env.production
are gitignored; their deployed values were updated locally and apply on redeploy.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep the cache win (no base-image nuke); back to the legacy builder which the job
context supports. BuildKit needs buildx installed in the runner before it can be used.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DOCKER_BUILDKIT=1 (explicit) so the restore layer is cached across builds; drop the
explicit 'docker builder prune' (it was wiping that cache) and rely on BuildKit's own
GC + 86GB headroom. Cleanup keeps dangling-image removal only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stop wiping the layer cache + base images after every build (the host has 86GB free).
Keep base images (prune dangling only) and ~2 weeks of build cache, so the cache-friendly
Dockerfiles (COPY *.csproj + restore before source) actually benefit -> warm rebuilds skip
restore and base-image pulls.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main = day-to-day work (no deploy). Merge into staging -> :staging, into
production -> :production; IMAGE_TAG = branch name. Also fixes the checkout to
build the PUSHED commit (git checkout $GITHUB_SHA) instead of always cloning
the default branch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CompactJsonFormatter in both ConfigureJsonSerilog overloads; rename Service->Application,
EnvironmentName->Environment (keep AppVersion). Applies to all myAi services.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The AI prompt now instructs the LLM to derive keywords entirely from the
candidate's CV (seniority level, primary role title, core technologies they
emphasize) rather than from the job description being matched. This ensures
the job-board search keywords used by cv-search-job represent who the
candidate actually is, not a mirror of the job they happened to match against.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. Pass session.Language to MatchJobRequest in cv-search-job so the LLM
uses the correct language-specific prompt for job titles and summaries.
2. Replace hardcoded "Manual job description" with a template-driven label
(email.match.manual-job-label) seeded in English and Romanian, so the
match email subject and Job row reflect the user's language.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
[ENV_NAME] prefix is now only prepended in non-production environments
(Development, Staging, etc.). Production emails get a clean subject line.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds nullable JobSearchSessionId to PageFetchEntity and FetchPageRequest.
cv-search-job passes session.Id on every fetch so all Playwright page
loads for a job search session can be traced back to their session.
Includes index on JobSearchSessionId for efficient lookup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Captures the IP when the user submits the CV match form and stores it on
the token, giving a full audit trail: token holds the match-site IP,
session holds the email link-click IP.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously always stored empty string; now stores the full page text
returned by page-fetcher-api, which is already in scope at save time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Captures client IP at job-search link-click time and threads it through to the session.
Both Email and ClientIpAddress are copied from session to each result row during processing.
Closes#47
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Threads the caller's email and client IP through the match pipeline so
every Results row records who triggered the match and from where.
Closes#45
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
page-fetcher-api always uses Playwright (networkidle by default), so the
per-provider flag that chose between headless and plain HTTP is obsolete.
- Removed from JobProviderEntity, CvSearchDbContext, JobProviderConfig, JobTokenService
- HtmlJobSearcher no longer passes WaitFor (uses page-fetcher-api default)
- EF migration drops the column from cvSearch.JobProviders
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Refit 10.1.6 signing certificate was revoked; upgraded to 11.0.1 in Directory.Packages.props
- cv-matcher-api/Dockerfile and cv-search-job/Dockerfile were missing COPY steps
for page-fetcher-api-models (added in this feature branch)
All 8 images now build cleanly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the spurious Api segment to match the pattern used by all other
models projects: CvMatcher.Models.*, Rag.Models.*, PageFetcher.Models.*.
Updated all consumers: email-api, api, cv-search-job.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes inconsistency where email-api used EmailApi.* and page-fetcher-api
used PageFetcherApi.*, while cv-matcher-api and rag-api use the generic
Api.* namespace. All four API projects now follow the same pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Settings classes now live in the -models project alongside DTOs and client
interfaces, eliminating the Settings/ folder from both API projects.
- SmtpSettings: email-api/Settings/ → email-api-models/Settings/ (namespace EmailApi.Models.Settings)
- PageFetcherSettings: page-fetcher-api/Settings/ → page-fetcher-api-models/Settings/ (namespace PageFetcher.Models.Settings)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server-side-only settings (internal config not needed by callers) belong in
the API project itself, not in the models project. PageFetcherSettings
(DefaultWaitFor, TimeoutSeconds, MaxTextChars) mirrors SmtpSettings in
email-api/Settings/ — callers never reference these.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Settings class now lives in Apis/page-fetcher-api-models/Settings/ with
namespace PageFetcher.Models.Settings, matching how EmailApiSettings is
placed in email-api-models/Settings/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Settings classes belong in Settings/ with namespace PageFetcherApi.Settings,
not Services/. Matches the SmtpSettings placement in email-api.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use http://page-fetcher-api:8080 (the Compose service key) for Docker DNS
resolution, consistent with all other internal service URLs (rag-api,
email-api, cv-matcher-api).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces page-fetcher-api, a new internal ASP.NET Core service that
centralises all web-page fetching through a single Playwright (headless
Chromium) browser instance. All fetches are persisted to the pageFetcher
SQL schema for auditing.
New projects:
- Apis/page-fetcher-api-models: FetchPageRequest, FetchPageResponse, IPageFetcherApiClient
- Apis/page-fetcher-data: PageFetchDbContext, PageFetchEntity, InitialSchema migration (schema: pageFetcher)
- Apis/page-fetcher-api: PlaywrightBrowserService (singleton), PageFetcherService, PageController
Changes to existing services:
- cv-matcher-api: JobTextExtractor now calls IPageFetcherApiClient instead of HttpClient
- cv-search-job: HtmlJobSearcher uses IPageFetcherApiClient (removes inline Playwright);
CvSearchJobTask fetches individual job pages and applies keyword pre-filter before
LLM call; passes pre-fetched JobDescription to cv-matcher-api to skip re-fetch
- common: add PageFetcherApiSettings
- docker-compose.yml, build.yml: add new service + env vars for callers
Closes#43
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After resolving relative hrefs against the base search URL, some ejobs.ro
links were producing file:/// URIs (e.g. file:///user/locuri-de-munca/...).
These were sent to cv-matcher-api and rejected with HTTP 400, causing 0 matches.
Added a scheme guard after URI resolution to skip any URL that is not
http:// or https://, preventing malformed URLs from reaching the matcher.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>