1
- import express from 'express'
2
- import type { RequestHandler , Response } from 'express'
1
+ import type { Response } from 'express'
3
2
import type { ExtendedRequestWithPageInfo } from '../types'
4
3
5
4
import type { ExtendedRequest , Page , Context , Permalink } from '@/types'
6
- import statsd from '@/observability/lib/statsd.js'
7
- import { defaultCacheControl } from '@/frame/middleware/cache-control.js'
8
- import catchMiddlewareError from '@/observability/middleware/catch-middleware-error.js'
9
- import {
10
- SURROGATE_ENUMS ,
11
- setFastlySurrogateKey ,
12
- makeLanguageSurrogateKey ,
13
- } from '@/frame/middleware/set-fastly-surrogate-key.js'
14
5
import shortVersions from '@/versions/middleware/short-versions.js'
15
6
import contextualize from '@/frame/middleware/context/context'
16
7
import features from '@/versions/middleware/features.js'
17
8
import { readCompressedJsonFile } from '@/frame/lib/read-json-file.js'
18
- import { pathValidationMiddleware , pageValidationMiddleware } from './validation'
19
-
20
- const router = express . Router ( )
21
9
22
10
// If you have pre-computed page info into a JSON file on disk, this is
23
11
// where it would be expected to be found.
@@ -96,7 +84,7 @@ type CachedPageInfo = {
96
84
}
97
85
98
86
let _cache : CachedPageInfo | null = null
99
- async function getPageInfoFromCache ( page : Page , pathname : string ) {
87
+ export async function getPageInfoFromCache ( page : Page , pathname : string ) {
100
88
let cacheInfo = ''
101
89
if ( _cache === null ) {
102
90
try {
@@ -111,12 +99,12 @@ async function getPageInfoFromCache(page: Page, pathname: string) {
111
99
}
112
100
}
113
101
114
- let info = _cache [ pathname ]
102
+ let meta = _cache [ pathname ]
115
103
if ( ! cacheInfo ) {
116
- cacheInfo = info ? 'hit' : 'miss'
104
+ cacheInfo = meta ? 'hit' : 'miss'
117
105
}
118
- if ( ! info ) {
119
- info = await getPageInfo ( page , pathname )
106
+ if ( ! meta ) {
107
+ meta = await getPageInfo ( page , pathname )
120
108
// You might wonder; why do we not store this compute information
121
109
// into the `_cache` from here?
122
110
// The short answer is; it won't be used again.
@@ -128,74 +116,37 @@ async function getPageInfoFromCache(page: Page, pathname: string) {
128
116
// In CI, we use the caching because the CI runs
129
117
// `npm run precompute-pageinfo` right before it runs vitest tests.
130
118
}
131
- info . cacheInfo = cacheInfo
132
- return info
119
+ meta . cacheInfo = cacheInfo
120
+ return meta
133
121
}
134
122
135
- router . get (
136
- '/v1' ,
137
- pathValidationMiddleware as RequestHandler ,
138
- pageValidationMiddleware as RequestHandler ,
139
- catchMiddlewareError ( async function pageInfo ( req : ExtendedRequestWithPageInfo , res : Response ) {
140
- // Remember, the `validationMiddleware` will use redirects if the
141
- // `pathname` used is a redirect (e.g. /en/articles/foo or
142
- // /articles or '/en/enterprise-server@latest/foo/bar)
143
- // So by the time we get here, the pathname should be one of the
144
- // page's valid permalinks.
145
- const { page, pathname, archived } = req . pageinfo
146
-
147
- if ( archived && archived . isArchived ) {
148
- const { requestedVersion } = archived
149
- const title = `GitHub Enterprise Server ${ requestedVersion } Help Documentation`
150
- const intro = ''
151
- const product = 'GitHub Enterprise Server'
152
- defaultCacheControl ( res )
153
- return res . json ( { info : { intro, title, product } } )
154
- }
123
+ export async function getMetadata ( req : ExtendedRequestWithPageInfo ) {
124
+ // Remember, the `validationMiddleware` will use redirects if the
125
+ // `pathname` used is a redirect (e.g. /en/articles/foo or
126
+ // /articles or '/en/enterprise-server@latest/foo/bar)
127
+ // So by the time we get here, the pathname should be one of the
128
+ // page's valid permalinks.
129
+ const { page, pathname, archived } = req . pageinfo
130
+
131
+ if ( archived && archived . isArchived ) {
132
+ const { requestedVersion } = archived
133
+ const title = `GitHub Enterprise Server ${ requestedVersion } Help Documentation`
134
+ const intro = ''
135
+ const product = 'GitHub Enterprise Server'
136
+ return { meta : { intro, title, product } }
137
+ }
155
138
156
- if ( ! page ) {
157
- return res . status ( 400 ) . json ( { error : `No page found for '${ pathname } '` } )
158
- }
139
+ if ( ! page ) {
140
+ throw new Error ( `No page found for '${ pathname } '` )
141
+ }
159
142
160
- const pagePermalinks = page . permalinks . map ( ( p : Permalink ) => p . href )
161
- if ( ! pagePermalinks . includes ( pathname ) ) {
162
- throw new Error ( `pathname '${ pathname } ' not one of the page's permalinks` )
163
- }
143
+ const pagePermalinks = page . permalinks . map ( ( p : Permalink ) => p . href )
144
+ if ( ! pagePermalinks . includes ( pathname ) ) {
145
+ throw new Error ( `pathname '${ pathname } ' not one of the page's permalinks` )
146
+ }
164
147
165
- const fromCache = await getPageInfoFromCache ( page , pathname )
166
- const { cacheInfo, ...info } = fromCache
167
-
168
- const tags = [
169
- // According to https://docs.datadoghq.com/getting_started/tagging/#define-tags
170
- // the max length of a tag is 200 characters. Most of ours are less than
171
- // that but we truncate just to be safe.
172
- `pathname:${ pathname } ` . slice ( 0 , 200 ) ,
173
- `language:${ page . languageCode } ` ,
174
- `cache:${ cacheInfo } ` ,
175
- ]
176
- statsd . increment ( 'pageinfo.lookup' , 1 , tags )
177
-
178
- defaultCacheControl ( res )
179
-
180
- // This is necessary so that the `Surrogate-Key` header is set with
181
- // the correct language surrogate key bit. By default, it's set
182
- // from the pathname but `/api/**` URLs don't have a language
183
- // (other than the default 'en').
184
- // We do this so that all of these URLs are cached in Fastly by language
185
- // which we need for the staggered purge.
186
-
187
- setFastlySurrogateKey (
188
- res ,
189
- `${ SURROGATE_ENUMS . DEFAULT } ${ makeLanguageSurrogateKey ( page . languageCode ) } ` ,
190
- true ,
191
- )
192
- res . status ( 200 ) . json ( { info } )
193
- } ) ,
194
- )
195
-
196
- // Alias for the latest version
197
- router . get ( '/' , ( req , res ) => {
198
- res . redirect ( 307 , req . originalUrl . replace ( '/pageinfo' , '/pageinfo/v1' ) )
199
- } )
200
-
201
- export default router
148
+ const fromCache = await getPageInfoFromCache ( page , pathname )
149
+ const { cacheInfo, ...meta } = fromCache
150
+
151
+ return { meta, cacheInfo }
152
+ }
0 commit comments