Adapt feeds content-type to accept header
authorRigel Kent <sendmemail@rigelk.eu>
Thu, 9 Jan 2020 15:51:51 +0000 (16:51 +0100)
committerChocobozzz <chocobozzz@cpy.re>
Fri, 10 Jan 2020 09:14:04 +0000 (10:14 +0100)
server/controllers/activitypub/client.ts
server/controllers/api/overviews.ts
server/controllers/api/server/stats.ts
server/controllers/bots.ts
server/controllers/feeds.ts
server/controllers/static.ts
server/middlewares/cache.ts
server/middlewares/validators/feeds.ts

index 5ed0435ffa89698476d38939ad81c4494b5c4256..62412cd6237676ed6d1d0631242f7fbf4902fd5a 100644 (file)
@@ -74,7 +74,7 @@ activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId',
 
 activityPubClientRouter.get('/videos/watch/:id',
   executeIfActivityPub,
-  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS)),
+  asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS)),
   asyncMiddleware(videosCustomGetValidator('only-video-with-rights')),
   asyncMiddleware(videoController)
 )
index 23706767a66069f02810646f775eacf00377fb30..46e76ac6b87e5b1f55dbe64dffecde87a5623eb9 100644 (file)
@@ -11,7 +11,7 @@ import * as memoizee from 'memoizee'
 const overviewsRouter = express.Router()
 
 overviewsRouter.get('/videos',
-  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS)),
+  asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS)),
   asyncMiddleware(getVideosOverview)
 )
 
index 951b9820987179415cc40e213e17e7e12a07c60c..3616c074d35c378a7b434aef4e0a7eeb0389a421 100644 (file)
@@ -14,7 +14,7 @@ import { CONFIG } from '../../../initializers/config'
 const statsRouter = express.Router()
 
 statsRouter.get('/stats',
-  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.STATS)),
+  asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.STATS)),
   asyncMiddleware(getStats)
 )
 
index f3e778b04b11ce3cf475305a87f39af8ac34fcdb..63280dabbf2e14f2182c4d52ce02f2495e6eb122 100644 (file)
@@ -14,7 +14,7 @@ const botsRouter = express.Router()
 // Special route that add OpenGraph and oEmbed tags
 // Do not use a template engine for a so little thing
 botsRouter.use('/sitemap.xml',
-  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.SITEMAP)),
+  asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.SITEMAP)),
   asyncMiddleware(getSitemap)
 )
 
index fa6c7ac714869a6ecba7795fe791d83025a1c6f9..72628dffb94bc3d26a919a2971e44e3bc3d46965 100644 (file)
@@ -6,7 +6,9 @@ import {
   setDefaultSort,
   videoCommentsFeedsValidator,
   videoFeedsValidator,
-  videosSortValidator
+  videosSortValidator,
+  feedsFormatValidator,
+  setFeedFormatContentType
 } from '../middlewares'
 import { VideoModel } from '../models/video/video'
 import * as Feed from 'pfeed'
@@ -18,7 +20,13 @@ import { CONFIG } from '../initializers/config'
 const feedsRouter = express.Router()
 
 feedsRouter.get('/feeds/video-comments.:format',
-  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
+  feedsFormatValidator,
+  setFeedFormatContentType,
+  asyncMiddleware(cacheRoute({
+    headerBlacklist: [
+      'Content-Type'
+    ]
+  })(ROUTE_CACHE_LIFETIME.FEEDS)),
   asyncMiddleware(videoCommentsFeedsValidator),
   asyncMiddleware(generateVideoCommentsFeed)
 )
@@ -26,7 +34,13 @@ feedsRouter.get('/feeds/video-comments.:format',
 feedsRouter.get('/feeds/videos.:format',
   videosSortValidator,
   setDefaultSort,
-  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
+  feedsFormatValidator,
+  setFeedFormatContentType,
+  asyncMiddleware(cacheRoute({
+    headerBlacklist: [
+      'Content-Type'
+    ]
+  })(ROUTE_CACHE_LIFETIME.FEEDS)),
   commonVideosFiltersValidator,
   asyncMiddleware(videoFeedsValidator),
   asyncMiddleware(generateVideoFeed)
@@ -224,26 +238,21 @@ function sendFeed (feed, req: express.Request, res: express.Response) {
   const format = req.params.format
 
   if (format === 'atom' || format === 'atom1') {
-    res.set('Content-Type', 'application/atom+xml')
     return res.send(feed.atom1()).end()
   }
 
   if (format === 'json' || format === 'json1') {
-    res.set('Content-Type', 'application/json')
     return res.send(feed.json1()).end()
   }
 
   if (format === 'rss' || format === 'rss2') {
-    res.set('Content-Type', 'application/rss+xml')
     return res.send(feed.rss2()).end()
   }
 
   // We're in the ambiguous '.xml' case and we look at the format query parameter
   if (req.query.format === 'atom' || req.query.format === 'atom1') {
-    res.set('Content-Type', 'application/atom+xml')
     return res.send(feed.atom1()).end()
   }
 
-  res.set('Content-Type', 'application/rss+xml')
   return res.send(feed.rss2()).end()
 }
index f86a0cb5b0a12c45f11cc478f30b4809be8a96b7..a4bb3a4d96b38a281dc29ee18f2ec91977e09f5e 100644 (file)
@@ -112,7 +112,7 @@ staticRouter.use(
 
 // robots.txt service
 staticRouter.get('/robots.txt',
-  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS)),
+  asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.ROBOTS)),
   (_, res: express.Response) => {
     res.type('text/plain')
     return res.send(CONFIG.INSTANCE.ROBOTS)
@@ -127,7 +127,7 @@ staticRouter.get('/security.txt',
 )
 
 staticRouter.get('/.well-known/security.txt',
-  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.SECURITYTXT)),
+  asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.SECURITYTXT)),
   (_, res: express.Response) => {
     res.type('text/plain')
     return res.send(CONFIG.INSTANCE.SECURITYTXT + CONFIG.INSTANCE.SECURITYTXT_CONTACT)
@@ -136,7 +136,7 @@ staticRouter.get('/.well-known/security.txt',
 
 // nodeinfo service
 staticRouter.use('/.well-known/nodeinfo',
-  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO)),
+  asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.NODEINFO)),
   (_, res: express.Response) => {
     return res.json({
       links: [
@@ -149,13 +149,13 @@ staticRouter.use('/.well-known/nodeinfo',
   }
 )
 staticRouter.use('/nodeinfo/:version.json',
-  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO)),
+  asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.NODEINFO)),
   asyncMiddleware(generateNodeinfo)
 )
 
 // dnt-policy.txt service (see https://www.eff.org/dnt-policy)
 staticRouter.use('/.well-known/dnt-policy.txt',
-  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.DNT_POLICY)),
+  asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.DNT_POLICY)),
   (_, res: express.Response) => {
     res.type('text/plain')
 
index ef8611875b980c9768ceb137d1aaf6f6af0a6320..cb24d9e0e51698eb317b1c8cdfb9d9c7da38b700 100644 (file)
@@ -4,12 +4,18 @@ import * as apicache from 'apicache'
 // Ensure Redis is initialized
 Redis.Instance.init()
 
-const options = {
+const defaultOptions = {
   redisClient: Redis.Instance.getClient(),
-  appendKey: () => Redis.Instance.getPrefix()
+  appendKey: () => Redis.Instance.getPrefix(),
+  statusCodes: {
+    exclude: [ 404, 403 ]
+  }
 }
 
-const cacheRoute = apicache.options(options).middleware
+const cacheRoute = (extraOptions = {}) => apicache.options({
+  ...defaultOptions,
+  ...extraOptions
+}).middleware
 
 // ---------------------------------------------------------------------------
 
index 1bef9891b358e304613c3d440444d44031e6db4d..29f6c87bebfaa35890b5d31556ccdae86c739742 100644 (file)
@@ -12,9 +12,37 @@ import {
   doesVideoChannelNameWithHostExist
 } from '../../helpers/middlewares'
 
-const videoFeedsValidator = [
+const feedsFormatValidator = [
   param('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'),
-  query('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'),
+  query('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)')
+]
+
+function setFeedFormatContentType (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const format = req.query.format || req.params.format || 'rss'
+
+  let acceptableContentTypes: string[]
+  if (format === 'atom' || format === 'atom1') {
+    acceptableContentTypes = ['application/atom+xml', 'application/xml', 'text/xml']
+  } else if (format === 'json' || format === 'json1') {
+    acceptableContentTypes = ['application/json']
+  } else if (format === 'rss' || format === 'rss2') {
+    acceptableContentTypes = ['application/rss+xml', 'application/xml', 'text/xml']
+  } else {
+    acceptableContentTypes = ['application/xml', 'text/xml']
+  }
+
+  if (req.accepts(acceptableContentTypes)) {
+    res.set('Content-Type', req.accepts(acceptableContentTypes) as string)
+  } else {
+    return res.status(406).send({
+      message: `You should accept at least one of the following content-types: ${acceptableContentTypes.join(', ')}`
+    }).end()
+  }
+
+  return next()
+}
+
+const videoFeedsValidator = [
   query('accountId').optional().custom(isIdValid),
   query('accountName').optional(),
   query('videoChannelId').optional().custom(isIdValid),
@@ -35,8 +63,6 @@ const videoFeedsValidator = [
 ]
 
 const videoCommentsFeedsValidator = [
-  param('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'),
-  query('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'),
   query('videoId').optional().custom(isIdOrUUIDValid),
 
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
@@ -53,6 +79,8 @@ const videoCommentsFeedsValidator = [
 // ---------------------------------------------------------------------------
 
 export {
+  feedsFormatValidator,
+  setFeedFormatContentType,
   videoFeedsValidator,
   videoCommentsFeedsValidator
 }