feat(12-06): expose non-secret DB name via GET /api/setup/status (gap 3)

- status returns { setupComplete, dbName } from process.env.DB_NAME (null fallback)
- only the DB name; never DB_HOST/DB_USER/DB_PASSWORD
- SetupStatusResponse carries dbName?: string | null for the PWA read-only field
This commit is contained in:
Lucas Berger
2026-06-15 21:13:47 -04:00
parent e46e80a15c
commit fbd3b77bde
3 changed files with 27 additions and 1 deletions
+4 -1
View File
@@ -88,7 +88,10 @@ setupRouter.get('/status', async (c) => {
// completion (D-10). Using it here keeps the status response in sync with the guard
// without a second DB read pattern (covers setup_complete AND effective-config).
const locked = await isSetupLocked();
return c.json({ setupComplete: locked });
// Gap 3 (backend): surface the NON-SECRET DB name so the PWA can render a read-only
// field giving the "database connection verified" row an on-screen referent. Only the
// database NAME is exposed — never DB_HOST/DB_USER/DB_PASSWORD (connection secrets/topology).
return c.json({ setupComplete: locked, dbName: process.env.DB_NAME ?? null });
});
// ---------------------------------------------------------------------------
+17
View File
@@ -251,6 +251,23 @@ describe('GET /api/setup/status', () => {
expect(body.setupComplete).toBe(false);
});
// Gap 3 (backend): status exposes the NON-SECRET DB name as an on-screen referent
// for the "database connection verified" row. No DB_HOST/DB_USER/DB_PASSWORD.
it('returns the non-secret dbName from process.env.DB_NAME (gap 3)', async () => {
process.env.DB_NAME = 'familysync_test';
const app = await getApp();
const res = await app.fetch(jsonRequest('GET', '/api/setup/status'));
expect(res.status).toBe(200);
const bodyText = await res.text();
const body = JSON.parse(bodyText) as { setupComplete: boolean; dbName?: string | null };
expect(body.dbName).toBe('familysync_test');
// No connection secrets/topology may leak into the status response.
const password = process.env.DB_PASSWORD;
if (password) {
expect(bodyText).not.toContain(password);
}
});
it('returns { setupComplete: true } after app_config.setup_complete is set', async () => {
// Directly set setup_complete in DB (simulates completed setup)
await db
+6
View File
@@ -537,6 +537,12 @@ export async function saveMyCredential(payload: SaveMyCredentialPayload): Promis
*/
export interface SetupStatusResponse {
setupComplete: boolean;
/**
* Non-secret database name (process.env.DB_NAME) surfaced for the wizard's
* read-only "database connection verified" referent (gap 3). Never includes
* DB_HOST/DB_USER/DB_PASSWORD.
*/
dbName?: string | null;
}
/**