Refractor retry transaction function
[oweals/peertube.git] / server / controllers / feeds.ts
1 import * as express from 'express'
2 import { CONFIG, FEEDS, ROUTE_CACHE_LIFETIME } from '../initializers/constants'
3 import { asyncMiddleware, videoFeedsValidator, setDefaultSort, videosSortValidator, videoCommentsFeedsValidator } from '../middlewares'
4 import { VideoModel } from '../models/video/video'
5 import * as Feed from 'pfeed'
6 import { AccountModel } from '../models/account/account'
7 import { cacheRoute } from '../middlewares/cache'
8 import { VideoChannelModel } from '../models/video/video-channel'
9 import { VideoCommentModel } from '../models/video/video-comment'
10
11 const feedsRouter = express.Router()
12
13 feedsRouter.get('/feeds/video-comments.:format',
14   asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
15   asyncMiddleware(videoCommentsFeedsValidator),
16   asyncMiddleware(generateVideoCommentsFeed)
17 )
18
19 feedsRouter.get('/feeds/videos.:format',
20   videosSortValidator,
21   setDefaultSort,
22   asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)),
23   asyncMiddleware(videoFeedsValidator),
24   asyncMiddleware(generateVideoFeed)
25 )
26
27 // ---------------------------------------------------------------------------
28
29 export {
30   feedsRouter
31 }
32
33 // ---------------------------------------------------------------------------
34
35 async function generateVideoCommentsFeed (req: express.Request, res: express.Response, next: express.NextFunction) {
36   let feed = initFeed()
37   const start = 0
38
39   const videoId: number = res.locals.video ? res.locals.video.id : undefined
40
41   const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId)
42
43   // Adding video items to the feed, one at a time
44   comments.forEach(comment => {
45     feed.addItem({
46       title: `${comment.Video.name} - ${comment.Account.getDisplayName()}`,
47       id: comment.url,
48       link: comment.url,
49       content: comment.text,
50       author: [
51         {
52           name: comment.Account.getDisplayName(),
53           link: comment.Account.Actor.url
54         }
55       ],
56       date: comment.createdAt
57     })
58   })
59
60   // Now the feed generation is done, let's send it!
61   return sendFeed(feed, req, res)
62 }
63
64 async function generateVideoFeed (req: express.Request, res: express.Response, next: express.NextFunction) {
65   let feed = initFeed()
66   const start = 0
67
68   const account: AccountModel = res.locals.account
69   const videoChannel: VideoChannelModel = res.locals.videoChannel
70   const hideNSFW = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list'
71
72   const resultList = await VideoModel.listForApi({
73     start,
74     count: FEEDS.COUNT,
75     sort: req.query.sort,
76     hideNSFW,
77     filter: req.query.filter,
78     withFiles: true,
79     accountId: account ? account.id : null,
80     videoChannelId: videoChannel ? videoChannel.id : null
81   })
82
83   // Adding video items to the feed, one at a time
84   resultList.data.forEach(video => {
85     const formattedVideoFiles = video.getFormattedVideoFilesJSON()
86     const torrents = formattedVideoFiles.map(videoFile => ({
87       title: video.name,
88       url: videoFile.torrentUrl,
89       size_in_bytes: videoFile.size
90     }))
91
92     feed.addItem({
93       title: video.name,
94       id: video.url,
95       link: video.url,
96       description: video.getTruncatedDescription(),
97       content: video.description,
98       author: [
99         {
100           name: video.VideoChannel.Account.getDisplayName(),
101           link: video.VideoChannel.Account.Actor.url
102         }
103       ],
104       date: video.publishedAt,
105       language: video.language,
106       nsfw: video.nsfw,
107       torrent: torrents
108     })
109   })
110
111   // Now the feed generation is done, let's send it!
112   return sendFeed(feed, req, res)
113 }
114
115 function initFeed () {
116   const webserverUrl = CONFIG.WEBSERVER.URL
117
118   return new Feed({
119     title: CONFIG.INSTANCE.NAME,
120     description: CONFIG.INSTANCE.DESCRIPTION,
121     // updated: TODO: somehowGetLatestUpdate, // optional, default = today
122     id: webserverUrl,
123     link: webserverUrl,
124     image: webserverUrl + '/client/assets/images/icons/icon-96x96.png',
125     favicon: webserverUrl + '/client/assets/images/favicon.png',
126     copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` +
127     ` and potential licenses granted by each content's rightholder.`,
128     generator: `Toraifōsu`, // ^.~
129     feedLinks: {
130       json: `${webserverUrl}/feeds/videos.json`,
131       atom: `${webserverUrl}/feeds/videos.atom`,
132       rss: `${webserverUrl}/feeds/videos.xml`
133     },
134     author: {
135       name: 'Instance admin of ' + CONFIG.INSTANCE.NAME,
136       email: CONFIG.ADMIN.EMAIL,
137       link: `${webserverUrl}/about`
138     }
139   })
140 }
141
142 function sendFeed (feed, req: express.Request, res: express.Response) {
143   const format = req.params.format
144
145   if (format === 'atom' || format === 'atom1') {
146     res.set('Content-Type', 'application/atom+xml')
147     return res.send(feed.atom1()).end()
148   }
149
150   if (format === 'json' || format === 'json1') {
151     res.set('Content-Type', 'application/json')
152     return res.send(feed.json1()).end()
153   }
154
155   if (format === 'rss' || format === 'rss2') {
156     res.set('Content-Type', 'application/rss+xml')
157     return res.send(feed.rss2()).end()
158   }
159
160   // We're in the ambiguous '.xml' case and we look at the format query parameter
161   if (req.query.format === 'atom' || req.query.format === 'atom1') {
162     res.set('Content-Type', 'application/atom+xml')
163     return res.send(feed.atom1()).end()
164   }
165
166   res.set('Content-Type', 'application/rss+xml')
167   return res.send(feed.rss2()).end()
168 }