From a762a15495a02d047af99f1705aeb606a557b98d Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Fri, 5 May 2023 13:59:27 -0400 Subject: [PATCH] Support archived enterprise versions in /api/pageinfo (#36896) --- lib/is-archived-version.js | 3 ++ src/pageinfo/middleware.js | 51 ++++++++++++++++++++++++---------- src/pageinfo/tests/pageinfo.js | 23 +++++++++++++++ 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/lib/is-archived-version.js b/lib/is-archived-version.js index 7b9e358464ab..1e8589a38881 100644 --- a/lib/is-archived-version.js +++ b/lib/is-archived-version.js @@ -5,7 +5,10 @@ export default function isArchivedVersion(req) { // if this is an assets path, use the referrer // if this is a docs path, use the req.path const pathToCheck = patterns.assetPaths.test(req.path) ? req.get('referrer') : req.path + return isArchivedVersionByPath(pathToCheck) +} +export function isArchivedVersionByPath(pathToCheck) { // ignore paths that don't have an enterprise version number if ( !( diff --git a/src/pageinfo/middleware.js b/src/pageinfo/middleware.js index de71e16ee2d5..27c38363988d 100644 --- a/src/pageinfo/middleware.js +++ b/src/pageinfo/middleware.js @@ -12,18 +12,24 @@ import shortVersions from '../../middleware/contextualizers/short-versions.js' import contextualize from '../../middleware/context.js' import features from '../../middleware/contextualizers/features.js' import getRedirect from '../../lib/get-redirect.js' +import { isArchivedVersionByPath } from '../../lib/is-archived-version.js' const router = express.Router() const validationMiddleware = (req, res, next) => { - let { pathname } = req.query + const { pathname } = req.query if (!pathname) { return res.status(400).json({ error: `No 'pathname' query` }) } if (!pathname.trim()) { return res.status(400).json({ error: `'pathname' query empty` }) } + req.pageinfo = { pathname } + return next() +} +const pageinfoMiddleware = (req, res, next) => { + let { pathname } = req.pageinfo // We can't use the `findPage` middleware utility function because we // need to know when the pathname is a redirect. // This is important so that the final `pathname` value @@ -46,21 +52,24 @@ const validationMiddleware = (req, res, next) => { } if (!(pathname in req.context.pages)) { - const redirect = getRedirect(pathname, redirectsContext) - if (redirect) { - pathname = redirect + // If a pathname is not a known page, it might *either* be a redirect, + // or an archived enterprise version, or both. + // That's why it's import to not bother looking at the redirects + // if the pathname is an archived enterprise version. + // This mimics how our middleware work and their order. + req.pageinfo.archived = isArchivedVersionByPath(pathname) + if (!req.pageinfo.archived.isArchived) { + const redirect = getRedirect(pathname, redirectsContext) + if (redirect) { + pathname = redirect + } } } - const page = req.context.pages[pathname] - if (!page) { - return res.status(400).json({ error: `No page found for '${pathname}'` }) - } - - req.pageinfo = { - pathname, - page, - } + // Remember this might yield undefined if the pathname is not a page + req.pageinfo.page = req.context.pages[pathname] + // The pathname might have changed if it was a redirect + req.pageinfo.pathname = pathname return next() } @@ -68,13 +77,27 @@ const validationMiddleware = (req, res, next) => { router.get( '/v1', validationMiddleware, + pageinfoMiddleware, catchMiddlewareError(async function pageInfo(req, res) { // Remember, the `validationMiddleware` will use redirects if the // `pathname` used is a redirect (e.g. /en/articles/foo or // /articles or '/en/enterprise-server@latest/foo/bar) // So by the time we get here, the pathname should be one of the // page's valid permalinks. - const { page, pathname } = req.pageinfo + const { page, pathname, archived } = req.pageinfo + + if (archived && archived.isArchived) { + const { requestedVersion } = archived + const title = `GitHub Enterprise Server ${requestedVersion} Help Documentation` + const intro = '' + const product = 'GitHub Enterprise Server' + defaultCacheControl(res) + return res.json({ info: { intro, title, product } }) + } + + if (!page) { + return res.status(400).json({ error: `No page found for '${pathname}'` }) + } const pagePermalinks = page.permalinks.map((p) => p.href) if (!pagePermalinks.includes(pathname)) { diff --git a/src/pageinfo/tests/pageinfo.js b/src/pageinfo/tests/pageinfo.js index 4a99f9ced4b1..4ce2eaf3c66e 100644 --- a/src/pageinfo/tests/pageinfo.js +++ b/src/pageinfo/tests/pageinfo.js @@ -151,4 +151,27 @@ describe('pageinfo api', () => { expect(info.title).toMatch('GitHub Enterprise Server Fixture Documentation') } }) + + test('archived enterprise versions', async () => { + // For example /en/enterprise-server@3.8 is a valid Page in the + // site tree, but /en/enterprise-server@2.6 is not. Yet we can + // 200 OK and serve content for that. This needs to be reflected in + // page info too. Even if we have to "fabricate" the title a bit. + + // At the time of writing, the latest archived version + { + const res = await get(makeURL('/en/enterprise-server@3.2')) + expect(res.statusCode).toBe(200) + const { info } = JSON.parse(res.body) + expect(info.title).toMatch('GitHub Enterprise Server 3.2 Help Documentation') + } + + // The oldest known archived version that we proxy + { + const res = await get(makeURL('/en/enterprise/11.10.340')) + expect(res.statusCode).toBe(200) + const { info } = JSON.parse(res.body) + expect(info.title).toMatch('GitHub Enterprise Server 11.10.340 Help Documentation') + } + }) })