diff --git a/services/mam-api/src/index.js b/services/mam-api/src/index.js index fb914fa..18fa901 100644 --- a/services/mam-api/src/index.js +++ b/services/mam-api/src/index.js @@ -11,6 +11,7 @@ import { errorHandler } from './middleware/errors.js'; import { requireAuth } from './middleware/auth.js'; import { loadS3ConfigFromDb } from './s3/client.js'; +import authRouter from './routes/auth.js'; // Routes import assetsRouter from './routes/assets.js'; import projectsRouter from './routes/projects.js'; @@ -104,6 +105,7 @@ app.use('/api/v1', (req, res, next) => { }); // ── API Routes ──────────────────────────────────────────────────────────────── +app.use('/api/v1/auth', authRouter); app.use('/api/v1/assets', assetsRouter); app.use('/api/v1/projects', projectsRouter); app.use('/api/v1/bins', binsRouter); diff --git a/services/mam-api/src/routes/auth.js b/services/mam-api/src/routes/auth.js new file mode 100644 index 0000000..4956045 --- /dev/null +++ b/services/mam-api/src/routes/auth.js @@ -0,0 +1,23 @@ +import express from 'express'; +import pool from '../db/pool.js'; +import { DEV_USER_ID } from '../middleware/auth.js'; + +const router = express.Router(); + +// Real users = anyone except the seeded dev row. +async function realUserCount() { + const { rows } = await pool.query( + `SELECT COUNT(*)::int AS n FROM users WHERE id <> $1`, [DEV_USER_ID]); + return rows[0].n; +} + +// GET /api/v1/auth/setup-required +// Cheap, no auth. Used by AuthGate to decide between Login and Setup screens. +router.get('/setup-required', async (_req, res, next) => { + try { + res.json({ required: (await realUserCount()) === 0 }); + } catch (err) { next(err); } +}); + +export default router; +export { realUserCount }; diff --git a/services/mam-api/test/routes/auth.test.js b/services/mam-api/test/routes/auth.test.js new file mode 100644 index 0000000..d0fbebe --- /dev/null +++ b/services/mam-api/test/routes/auth.test.js @@ -0,0 +1,37 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { isTestDbConfigured, setupTestDb } from '../helpers/setup-db.js'; +import express from 'express'; +import authRouter from '../../src/routes/auth.js'; + +async function appWithAuth(pool) { + process.env.DATABASE_URL = process.env.TEST_DATABASE_URL; + const app = express(); + app.use(express.json()); + app.use('/api/v1/auth', authRouter); + return new Promise(r => { + const srv = app.listen(0, '127.0.0.1', () => { + r({ baseUrl: 'http://127.0.0.1:' + srv.address().port, close: () => new Promise(rs => srv.close(rs)) }); + }); + }); +} + +test('GET /auth/setup-required returns { required: true } on empty users (modulo dev seed)', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => { + const pool = await setupTestDb(); + const { baseUrl, close } = await appWithAuth(pool); + try { + const res = await fetch(baseUrl + '/api/v1/auth/setup-required'); + assert.equal(res.status, 200); + assert.deepEqual(await res.json(), { required: true }); + } finally { await close(); await pool.end(); } +}); + +test('GET /auth/setup-required returns { required: false } once a real user exists', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => { + const pool = await setupTestDb(); + await pool.query(`INSERT INTO users (username, password_hash) VALUES ('admin', 'x')`); + const { baseUrl, close } = await appWithAuth(pool); + try { + const res = await fetch(baseUrl + '/api/v1/auth/setup-required'); + assert.deepEqual(await res.json(), { required: false }); + } finally { await close(); await pool.end(); } +});