mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
feat(version): expose offlineGeoEnabled on the public version endpoint for monitoring
`/api/version` now returns an additional `offlineGeoEnabled: boolean` field that mirrors `offlineGeoReady()` — the same source the runtime resolver checks before deciding whether to fall back to ipwho.is. A monitor (or the admin status surface in the next commit) can read the flag without crawling logs. The route flips from `force-static` to `force-dynamic` because the readiness check hits the filesystem on each request; the previous static caching would have frozen the answer at build time. Two new test cases cover the true / false branches alongside the existing shape assertions.
This commit is contained in:
1 parent
b5866694c8
commit
4eb22387a2
2 files changed
+41
-4
No files matched your search
@@ -4,11 +4,17 @@ vi.mock("@/lib/api-handler", () => ({
|
||||
apiHandler: <T extends (...args: unknown[]) => unknown>(fn: T) => fn,
|
||||
}));
|
||||
|
||||
const offlineGeoReadyMock = vi.fn(() => false);
|
||||
vi.mock("@/lib/geo", () => ({
|
||||
offlineGeoReady: () => offlineGeoReadyMock(),
|
||||
}));
|
||||
|
||||
import { GET } from "../route";
|
||||
|
||||
beforeEach(() => {
|
||||
delete process.env.NEXT_PUBLIC_APP_BUILD_SHA;
|
||||
delete process.env.NEXT_PUBLIC_APP_BUILT_AT;
|
||||
offlineGeoReadyMock.mockReturnValue(false);
|
||||
});
|
||||
|
||||
interface VersionEnvelope {
|
||||
@@ -20,6 +26,7 @@ interface VersionEnvelope {
|
||||
repository: string;
|
||||
changelog: string;
|
||||
docs: string;
|
||||
offlineGeoEnabled: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -49,4 +56,18 @@ describe("GET /api/version", () => {
|
||||
expect(body.data.buildSha).toBe("abc1234");
|
||||
expect(body.data.builtAt).toBe("2026-05-08T12:00:00.000Z");
|
||||
});
|
||||
|
||||
it("reports offlineGeoEnabled=false when the GeoLite2 databases are absent", async () => {
|
||||
offlineGeoReadyMock.mockReturnValue(false);
|
||||
const response = await (GET as unknown as () => Promise<Response>)();
|
||||
const body = (await response.json()) as VersionEnvelope;
|
||||
expect(body.data.offlineGeoEnabled).toBe(false);
|
||||
});
|
||||
|
||||
it("reports offlineGeoEnabled=true when the GeoLite2 databases are present", async () => {
|
||||
offlineGeoReadyMock.mockReturnValue(true);
|
||||
const response = await (GET as unknown as () => Promise<Response>)();
|
||||
const body = (await response.json()) as VersionEnvelope;
|
||||
expect(body.data.offlineGeoEnabled).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,16 +1,24 @@
|
||||
import { apiHandler } from "@/lib/api-handler";
|
||||
import { apiSuccess } from "@/lib/api-response";
|
||||
import { offlineGeoReady } from "@/lib/geo";
|
||||
import packageJson from "../../../../package.json";
|
||||
|
||||
export const dynamic = "force-static";
|
||||
export const revalidate = false;
|
||||
// v1.4.27 R5 — `offlineGeoEnabled` reads from the same source the geo
|
||||
// resolver uses for the runtime fallback gate, so the public state and
|
||||
// the runtime behaviour cannot disagree. The check hits the filesystem
|
||||
// (`existsSync` on the `.empty` marker + the City MMDB) which forces
|
||||
// the route to be dynamic — the previous `force-static` setting would
|
||||
// have frozen the answer at build time.
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* GET /api/version
|
||||
*
|
||||
* Public endpoint exposing the running build's version. Used by the
|
||||
* Settings → About surface, the footer of every page, and the
|
||||
* "Check for updates" button (which compares against the GHCR API).
|
||||
* Settings → About surface, the footer of every page, the
|
||||
* "Check for updates" button (which compares against the GHCR API),
|
||||
* and the admin status-summary row that surfaces the offline-geo
|
||||
* availability state.
|
||||
*
|
||||
* The build SHA and built-at timestamp come from environment variables
|
||||
* baked at image build time:
|
||||
@@ -21,11 +29,18 @@ export const revalidate = false;
|
||||
*
|
||||
* For local `pnpm dev` neither is set; the route returns `null` and
|
||||
* the UI falls back to "development" wording.
|
||||
*
|
||||
* `offlineGeoEnabled` is `true` when the GeoLite2-City MMDB is present
|
||||
* at the configured `GEOLITE2_DIR` and no `.empty` marker is set.
|
||||
* When `false` the runtime falls back to the online `ipwho.is`
|
||||
* provider and the resolver emits a one-shot admin notification on
|
||||
* first use.
|
||||
*/
|
||||
export const GET = apiHandler(async () => {
|
||||
const version = packageJson.version;
|
||||
const buildSha = process.env.NEXT_PUBLIC_APP_BUILD_SHA?.trim() || null;
|
||||
const builtAt = process.env.NEXT_PUBLIC_APP_BUILT_AT?.trim() || null;
|
||||
const offlineGeoEnabled = offlineGeoReady();
|
||||
|
||||
return apiSuccess({
|
||||
version,
|
||||
@@ -35,5 +50,6 @@ export const GET = apiHandler(async () => {
|
||||
repository: "https://github.com/MBombeck/HealthLog",
|
||||
changelog: "https://github.com/MBombeck/HealthLog/blob/main/CHANGELOG.md",
|
||||
docs: "https://docs.healthlog.dev",
|
||||
offlineGeoEnabled,
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user