diff --git a/apps/api/src/routes/setup.ts b/apps/api/src/routes/setup.ts index e6c7b01..a347e85 100644 --- a/apps/api/src/routes/setup.ts +++ b/apps/api/src/routes/setup.ts @@ -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 }); }); // --------------------------------------------------------------------------- diff --git a/apps/api/tests/routes/setup.test.ts b/apps/api/tests/routes/setup.test.ts index 630f058..e8ea60f 100644 --- a/apps/api/tests/routes/setup.test.ts +++ b/apps/api/tests/routes/setup.test.ts @@ -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 diff --git a/apps/pwa/src/api/client.ts b/apps/pwa/src/api/client.ts index e758b0d..6b02998 100644 --- a/apps/pwa/src/api/client.ts +++ b/apps/pwa/src/api/client.ts @@ -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; } /**