feat(ops): add database readiness probe
This commit is contained in:
@@ -645,6 +645,23 @@ app.MapGet("/health", (IConfiguration cfg) => Results.Ok(new
|
||||
version = BuildMetadata.ResolveVersion(cfg),
|
||||
})).AllowAnonymous();
|
||||
|
||||
// Readiness is separate from liveness: dependency outages must block a release without causing
|
||||
// Docker to restart an otherwise healthy API process. Keep the public response deliberately terse;
|
||||
// authorised administrators get detailed database diagnostics from /api/admin/system.
|
||||
app.MapGet("/ready", async (JobTrackerContext db, CancellationToken cancellationToken) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return await db.Database.CanConnectAsync(cancellationToken)
|
||||
? Results.Ok(new { status = "ready" })
|
||||
: Results.Json(new { status = "unavailable" }, statusCode: StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Results.Json(new { status = "unavailable" }, statusCode: StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
}).AllowAnonymous();
|
||||
|
||||
// API schema for tooling/docs. Dev-only: not exposed in production deployments.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
|
||||
+6
-2
@@ -305,9 +305,13 @@ than merely "running".
|
||||
- **Backend:** `curl -fsS http://127.0.0.1:8080/health`. Anonymous, and deliberately **does not touch
|
||||
the database** — a health check that queried MariaDB would restart a healthy backend whenever the
|
||||
database blipped. `start_period` is 90s to cover first-boot schema reconciliation.
|
||||
- **API/database readiness:** `curl -fsS https://<host>/ready`. This checks that the API can reach its
|
||||
configured database and returns only `ready` or `unavailable`; detailed diagnostics remain admin-only.
|
||||
Deployment validation requires this probe, while Docker restart policy continues to use liveness.
|
||||
- **Frontend:** `wget` against nginx on port 80.
|
||||
- `frontend` waits for `backend` to be *healthy*, not merely started, because nginx proxies `/api` to
|
||||
it and refuses to start if the upstream cannot be resolved.
|
||||
|
||||
A backend that cannot reach its database exits and is reported `unhealthy`, so a broken deploy does not
|
||||
present as a running stack.
|
||||
A backend that cannot reach its database during startup exits and is reported `unhealthy`. A dependency
|
||||
failure after startup leaves the process alive but makes `/ready` return 503, so deployment validation
|
||||
cannot mistake a live-but-unready stack for a successful release.
|
||||
|
||||
@@ -550,6 +550,16 @@ if ! curl -fsS "${public_base}/" >/dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! curl -fsS "${public_base}/health" >/dev/null; then
|
||||
echo "Public API liveness check failed for ${public_base}/health"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! curl -fsS "${public_base}/ready" >/dev/null; then
|
||||
echo "Public API/database readiness check failed for ${public_base}/ready"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! curl -fsS -D "$auth_config_headers_file" -o "$auth_config_body_file" "${public_base}/api/auth/config"; then
|
||||
echo "Public API smoke check failed for ${public_base}/api/auth/config"
|
||||
exit 1
|
||||
|
||||
@@ -49,6 +49,7 @@ Updated: 2026-08-30
|
||||
- Rebuilt the active developer/operator documentation around the actual Next.js 16/.NET 9 application, replaced CRA and `npm start` guidance, separated normal and Playwright ports, corrected React Router 7 and the SQLite/MariaDB provider matrix, removed the obsolete npm peer override, and verified the documented clean install, lint, test, build and locked-restore commands.
|
||||
- Began the JT-019 schema-ownership retirement with an executable 49-table ownership partition and transferred the leaf `SystemEmailSettings` table from MariaDB-only startup DDL to an additive provider-aware migration. Fresh SQLite now receives the table; legacy rows are preserved and startup no longer creates it.
|
||||
- Continued the job-application controller split by moving canonical pipeline metadata, soft delete/restore, follow-up scheduling, and event history into `JobApplicationLifecycleController` without changing routes, authorization, tenant filtering, response shapes, or event behavior. Status mutation remains with core updates until its shared applied-date invariant has a single service owner.
|
||||
- Added a minimal public `/ready` dependency probe alongside the existing `/health` liveness probe. Nginx exposes both, deployment validation now checks the frontend, API liveness, database readiness, and public auth configuration separately, while detailed dependency metadata remains restricted to Admin/System.
|
||||
- Transferred the independent `UserRuleSettings` table from both provider startup paths to its own provider-aware migration; owner-keyed settings survive adoption, downgrade and retry.
|
||||
- Moved `GmailReviewDecisions` into a provider-aware migration, preserving existing SQLite decisions and closing the previously missing MariaDB table path.
|
||||
- Moved recovery codes, trusted devices, and revocable user sessions into one provider-aware authentication-support migration; populated legacy rows and indexes survive adoption, downgrade, and retry.
|
||||
@@ -94,6 +95,7 @@ Updated: 2026-08-30
|
||||
- Post-cleanup frontend: ESLint passed with zero warnings, all 60 suites and 260/260 tests passed, optimized Next production build and integrated TypeScript passed, and Playwright passed 10/10 including public and long searchable multi-page CV PDFs.
|
||||
- Analytics-controller extraction: Release build passed with 0 warnings/errors and the complete backend suite passed 736/736.
|
||||
- Lifecycle-controller extraction: locked restore passed, Release build passed with 0 warnings/errors, and the complete backend suite passed 736/736.
|
||||
- Health/readiness split: Release build passed with 0 warnings/errors, the complete backend suite passed 736/736, and the focused Playwright probe passed 1/1 against a real disposable SQLite-backed API process.
|
||||
- Focused frontend: 2 suites, 6 tests passed.
|
||||
- Full frontend: 64 suites, 272 tests passed.
|
||||
- Next production build and TypeScript: passed.
|
||||
|
||||
@@ -57,6 +57,17 @@ test.beforeEach(async ({ page }) => {
|
||||
await page.addInitScript(() => window.localStorage.setItem("uiLanguage", "en"));
|
||||
});
|
||||
|
||||
test("API liveness and database readiness are separate public probes", async ({ request }) => {
|
||||
const origin = apiUrl.replace(/\/api$/, "");
|
||||
const liveness = await request.get(`${origin}/health`);
|
||||
const readiness = await request.get(`${origin}/ready`);
|
||||
|
||||
expect(liveness.status()).toBe(200);
|
||||
expect(await liveness.json()).toMatchObject({ status: "ok" });
|
||||
expect(readiness.status()).toBe(200);
|
||||
expect(await readiness.json()).toEqual({ status: "ready" });
|
||||
});
|
||||
|
||||
test("public plans stay honest, responsive and keyboard accessible", async ({ page }) => {
|
||||
for (const scheme of ["light", "dark"] as const) {
|
||||
await page.addInitScript((value) => window.localStorage.setItem("jobtracker.themeMode", value), scheme);
|
||||
|
||||
@@ -41,6 +41,16 @@ server {
|
||||
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
|
||||
}
|
||||
|
||||
location = /ready {
|
||||
proxy_pass http://backend-web:8080/ready;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $http_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://backend-web:8080;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
Reference in New Issue
Block a user