From 388efc978b355a2549e3d98f0a833ba252d4dc2c Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Thu, 18 Apr 2024 14:59:29 +0200 Subject: [PATCH] feat: serve /config.json --- src/json-pipe.js | 42 +++++++++++++++++++++++++++-------------- test/json-pipe.test.js | 43 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/json-pipe.js b/src/json-pipe.js index 711022e5..f414422e 100644 --- a/src/json-pipe.js +++ b/src/json-pipe.js @@ -46,10 +46,12 @@ async function fetchJsonContent(state, req, res) { owner, repo, ref, contentBusId, partition, s3Loader, log, info, } = state; const { path } = state.info; + state.content.sourceBus = 'content'; let ret = await s3Loader.getObject('helix-content-bus', `${contentBusId}/${partition}${path}`); // if not found, fall back to code bus if (ret.status === 404) { + state.content.sourceBus = 'code'; ret = await s3Loader.getObject('helix-code-bus', `${owner}/${repo}/${ref}${path}`); } @@ -76,15 +78,23 @@ async function fetchJsonContent(state, req, res) { updateLastModified(state, res, extractLastModified(ret.headers)); } else { + state.content.sourceBus = 'content'; res.status = ret.status === 404 ? 404 : 502; res.error = `failed to load ${state.info.resourcePath}: ${ret.status}`; } } -async function computeSurrogateKeys(path, contentBusId) { +async function computeSurrogateKeys(state) { const keys = []; - keys.push(`${contentBusId}${path}`.replace(/\//g, '_')); // TODO: remove - keys.push(await computeSurrogateKey(`${contentBusId}${path}`)); + const pathKey = state.content?.sourceBus === 'code' + ? `${state.ref}--${state.repo}--${state.owner}${state.info.path}` + : `${state.contentBusId}${state.info.path}`; + + if (state.info.path === '/config.json') { + keys.push(await computeSurrogateKey(`${state.site}--${state.org}_config.json`)); + } + keys.push(pathKey.replace(/\//g, '_')); // TODO: remove + keys.push(await computeSurrogateKey(pathKey)); return keys; } @@ -142,23 +152,27 @@ export async function jsonPipe(state, req) { await authenticate(state, req, res); - if (res.error) { + if (res.status === 404 && state.info.path === '/config.json' && state.config.public) { + // special handling for public config + res.status = 200; + res.body = JSON.stringify(state.config.public, null, 2); + } else if (res.error) { if (res.status < 400) { return res; } throw new PipelineStatusError(res.status, res.error); + } else { + // filter data + jsonFilter(state, res, { + limit: limit ? Number.parseInt(limit, 10) : undefined, + offset: offset ? Number.parseInt(offset, 10) : undefined, + sheet, + raw: limit === undefined && offset === undefined && sheet === undefined, + }); } - // filter data - jsonFilter(state, res, { - limit: limit ? Number.parseInt(limit, 10) : undefined, - offset: offset ? Number.parseInt(offset, 10) : undefined, - sheet, - raw: limit === undefined && offset === undefined && sheet === undefined, - }); - // set surrogate keys - const keys = await computeSurrogateKeys(state.info.path, state.contentBusId); + const keys = await computeSurrogateKeys(state); res.headers.set('x-surrogate-key', keys.join(' ')); await setCustomResponseHeaders(state, req, res); @@ -175,7 +189,7 @@ export async function jsonPipe(state, req) { await setCustomResponseHeaders(state, req, res); } if (res.status === 404) { - const keys = await computeSurrogateKeys(state.info.path, state.contentBusId); + const keys = await computeSurrogateKeys(state); res.headers.set('x-surrogate-key', keys.join(' ')); } return res; diff --git a/test/json-pipe.test.js b/test/json-pipe.test.js index 87060447..358a1617 100644 --- a/test/json-pipe.test.js +++ b/test/json-pipe.test.js @@ -110,9 +110,9 @@ describe('JSON Pipe Test', () => { ); }); - function createDefaultState(config = DEFAULT_CONFIG) { + function createDefaultState(config = DEFAULT_CONFIG, path = '/en/index.json') { return new PipelineState({ - path: '/en/index.json', + path, org: 'org', site: 'site', ref: 'ref', @@ -163,6 +163,41 @@ describe('JSON Pipe Test', () => { }); }); + it('sends 404 for missing /config.json', async () => { + const state = createDefaultState(DEFAULT_CONFIG, '/config.json'); + const resp = await jsonPipe(state, new PipelineRequest('https://json-filter.com/?limit=10&offset=5')); + assert.strictEqual(resp.status, 404); + assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), { + 'x-error': 'failed to load /config.json: 404', + 'x-surrogate-key': 'U_NW4adJU7Qazf-I foobar_config.json kz8SoCaNqfp4ohQo', + }); + }); + + it('sends public config for /config.json', async () => { + const config = { + ...DEFAULT_CONFIG, + public: { + test: 'property', + colors: ['a', 'b', 'c'], + }, + }; + const state = createDefaultState(config, '/config.json'); + const resp = await jsonPipe(state, new PipelineRequest('https://json-filter.com/?limit=10&offset=5')); + assert.strictEqual(resp.status, 200); + assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), { + 'content-type': 'application/json', + 'x-surrogate-key': 'U_NW4adJU7Qazf-I foobar_config.json kz8SoCaNqfp4ohQo', + }); + assert.deepStrictEqual(await resp.json(), { + colors: [ + 'a', + 'b', + 'c', + ], + test: 'property', + }); + }); + it('fetches correct content with folder mapping', async () => { const state = createDefaultState(CONFIG_WITH_FOLDER); state.info = getPathInfo('/super/mapped/index.json'); @@ -343,7 +378,7 @@ describe('JSON Pipe Test', () => { assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), { 'content-type': 'application/json', 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT', - 'x-surrogate-key': 'foobar_en_index.json Atrz_qDg26DmSe9a', + 'x-surrogate-key': 'ref--repo--owner_en_index.json SIMSxecp2CJXqGYs', }); }); @@ -378,7 +413,7 @@ describe('JSON Pipe Test', () => { assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), { 'content-type': 'application/json', 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT', - 'x-surrogate-key': 'foobar_en_index.json Atrz_qDg26DmSe9a', + 'x-surrogate-key': 'ref--repo--owner_en_index.json SIMSxecp2CJXqGYs', }); });