aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/feeds.ts155
-rw-r--r--server/helpers/middlewares/accounts.ts8
-rw-r--r--server/middlewares/validators/feeds.ts5
-rw-r--r--server/tests/feeds/feeds.ts8
4 files changed, 107 insertions, 69 deletions
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts
index 6e9f7e60c..5c95069fc 100644
--- a/server/controllers/feeds.ts
+++ b/server/controllers/feeds.ts
@@ -18,7 +18,6 @@ import { cacheRoute } from '../middlewares/cache'
18import { VideoModel } from '../models/video/video' 18import { VideoModel } from '../models/video/video'
19import { VideoCommentModel } from '../models/video/video-comment' 19import { VideoCommentModel } from '../models/video/video-comment'
20import { VideoFilter } from '../../shared/models/videos/video-query.type' 20import { VideoFilter } from '../../shared/models/videos/video-query.type'
21import { logger } from '../helpers/logger'
22 21
23const feedsRouter = express.Router() 22const feedsRouter = express.Router()
24 23
@@ -47,10 +46,24 @@ feedsRouter.get('/feeds/videos.:format',
47 })(ROUTE_CACHE_LIFETIME.FEEDS)), 46 })(ROUTE_CACHE_LIFETIME.FEEDS)),
48 commonVideosFiltersValidator, 47 commonVideosFiltersValidator,
49 asyncMiddleware(videoFeedsValidator), 48 asyncMiddleware(videoFeedsValidator),
50 asyncMiddleware(videoSubscriptonFeedsValidator),
51 asyncMiddleware(generateVideoFeed) 49 asyncMiddleware(generateVideoFeed)
52) 50)
53 51
52feedsRouter.get('/feeds/subscriptions.:format',
53 videosSortValidator,
54 setDefaultVideosSort,
55 feedsFormatValidator,
56 setFeedFormatContentType,
57 asyncMiddleware(cacheRoute({
58 headerBlacklist: [
59 'Content-Type'
60 ]
61 })(ROUTE_CACHE_LIFETIME.FEEDS)),
62 commonVideosFiltersValidator,
63 asyncMiddleware(videoSubscriptonFeedsValidator),
64 asyncMiddleware(generateVideoFeedForSubscriptions)
65)
66
54// --------------------------------------------------------------------------- 67// ---------------------------------------------------------------------------
55 68
56export { 69export {
@@ -61,7 +74,6 @@ export {
61 74
62async function generateVideoCommentsFeed (req: express.Request, res: express.Response) { 75async function generateVideoCommentsFeed (req: express.Request, res: express.Response) {
63 const start = 0 76 const start = 0
64
65 const video = res.locals.videoAll 77 const video = res.locals.videoAll
66 const account = res.locals.account 78 const account = res.locals.account
67 const videoChannel = res.locals.videoChannel 79 const videoChannel = res.locals.videoChannel
@@ -125,10 +137,8 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res
125 137
126async function generateVideoFeed (req: express.Request, res: express.Response) { 138async function generateVideoFeed (req: express.Request, res: express.Response) {
127 const start = 0 139 const start = 0
128
129 const account = res.locals.account 140 const account = res.locals.account
130 const videoChannel = res.locals.videoChannel 141 const videoChannel = res.locals.videoChannel
131 const token = req.query.token
132 const nsfw = buildNSFWFilter(res, req.query.nsfw) 142 const nsfw = buildNSFWFilter(res, req.query.nsfw)
133 143
134 let name: string 144 let name: string
@@ -152,21 +162,10 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
152 queryString: new URL(WEBSERVER.URL + req.url).search 162 queryString: new URL(WEBSERVER.URL + req.url).search
153 }) 163 })
154 164
155 /** 165 const options = {
156 * We have two ways to query video results: 166 accountId: account ? account.id : null,
157 * - one with account and token -> get subscription videos 167 videoChannelId: videoChannel ? videoChannel.id : null
158 * - one with either account, channel, or nothing: just videos with these filters 168 }
159 */
160 const options = token && token !== '' && res.locals.user
161 ? {
162 followerActorId: res.locals.user.Account.Actor.id,
163 user: res.locals.user,
164 includeLocalVideos: false
165 }
166 : {
167 accountId: account ? account.id : null,
168 videoChannelId: videoChannel ? videoChannel.id : null
169 }
170 169
171 const resultList = await VideoModel.listForApi({ 170 const resultList = await VideoModel.listForApi({
172 start, 171 start,
@@ -179,10 +178,86 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
179 ...options 178 ...options
180 }) 179 })
181 180
181 addVideosToFeed(feed, resultList.data)
182
183 // Now the feed generation is done, let's send it!
184 return sendFeed(feed, req, res)
185}
186
187async function generateVideoFeedForSubscriptions (req: express.Request, res: express.Response) {
188 const start = 0
189 const account = res.locals.account
190 const nsfw = buildNSFWFilter(res, req.query.nsfw)
191 const name = account.getDisplayName()
192 const description = account.description
193
194 const feed = initFeed({
195 name,
196 description,
197 resourceType: 'videos',
198 queryString: new URL(WEBSERVER.URL + req.url).search
199 })
200
201 const options = {
202 followerActorId: res.locals.user.Account.Actor.id,
203 user: res.locals.user
204 }
205
206 const resultList = await VideoModel.listForApi({
207 start,
208 count: FEEDS.COUNT,
209 sort: req.query.sort,
210 includeLocalVideos: true,
211 nsfw,
212 filter: req.query.filter as VideoFilter,
213 withFiles: true,
214 ...options
215 })
216
217 addVideosToFeed(feed, resultList.data)
218
219 // Now the feed generation is done, let's send it!
220 return sendFeed(feed, req, res)
221}
222
223function initFeed (parameters: {
224 name: string
225 description: string
226 resourceType?: 'videos' | 'video-comments'
227 queryString?: string
228}) {
229 const webserverUrl = WEBSERVER.URL
230 const { name, description, resourceType, queryString } = parameters
231
232 return new Feed({
233 title: name,
234 description,
235 // updated: TODO: somehowGetLatestUpdate, // optional, default = today
236 id: webserverUrl,
237 link: webserverUrl,
238 image: webserverUrl + '/client/assets/images/icons/icon-96x96.png',
239 favicon: webserverUrl + '/client/assets/images/favicon.png',
240 copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` +
241 ` and potential licenses granted by each content's rightholder.`,
242 generator: `Toraifōsu`, // ^.~
243 feedLinks: {
244 json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`,
245 atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`,
246 rss: `${webserverUrl}/feeds/${resourceType}.xml${queryString}`
247 },
248 author: {
249 name: 'Instance admin of ' + CONFIG.INSTANCE.NAME,
250 email: CONFIG.ADMIN.EMAIL,
251 link: `${webserverUrl}/about`
252 }
253 })
254}
255
256function addVideosToFeed (feed, videos: VideoModel[]) {
182 /** 257 /**
183 * Adding video items to the feed object, one at a time 258 * Adding video items to the feed object, one at a time
184 */ 259 */
185 resultList.data.forEach(video => { 260 for (const video of videos) {
186 const formattedVideoFiles = video.getFormattedVideoFilesJSON() 261 const formattedVideoFiles = video.getFormattedVideoFilesJSON()
187 262
188 const torrents = formattedVideoFiles.map(videoFile => ({ 263 const torrents = formattedVideoFiles.map(videoFile => ({
@@ -252,43 +327,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
252 } 327 }
253 ] 328 ]
254 }) 329 })
255 }) 330 }
256
257 // Now the feed generation is done, let's send it!
258 return sendFeed(feed, req, res)
259}
260
261function initFeed (parameters: {
262 name: string
263 description: string
264 resourceType?: 'videos' | 'video-comments'
265 queryString?: string
266}) {
267 const webserverUrl = WEBSERVER.URL
268 const { name, description, resourceType, queryString } = parameters
269
270 return new Feed({
271 title: name,
272 description,
273 // updated: TODO: somehowGetLatestUpdate, // optional, default = today
274 id: webserverUrl,
275 link: webserverUrl,
276 image: webserverUrl + '/client/assets/images/icons/icon-96x96.png',
277 favicon: webserverUrl + '/client/assets/images/favicon.png',
278 copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` +
279 ` and potential licenses granted by each content's rightholder.`,
280 generator: `Toraifōsu`, // ^.~
281 feedLinks: {
282 json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`,
283 atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`,
284 rss: `${webserverUrl}/feeds/${resourceType}.xml${queryString}`
285 },
286 author: {
287 name: 'Instance admin of ' + CONFIG.INSTANCE.NAME,
288 email: CONFIG.ADMIN.EMAIL,
289 link: `${webserverUrl}/about`
290 }
291 })
292} 331}
293 332
294function sendFeed (feed, req: express.Request, res: express.Response) { 333function sendFeed (feed, req: express.Request, res: express.Response) {
diff --git a/server/helpers/middlewares/accounts.ts b/server/helpers/middlewares/accounts.ts
index 9be80167c..fa4a51e6c 100644
--- a/server/helpers/middlewares/accounts.ts
+++ b/server/helpers/middlewares/accounts.ts
@@ -28,8 +28,7 @@ async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, se
28 if (!account) { 28 if (!account) {
29 if (sendNotFound === true) { 29 if (sendNotFound === true) {
30 res.status(404) 30 res.status(404)
31 .send({ error: 'Account not found' }) 31 .json({ error: 'Account not found' })
32 .end()
33 } 32 }
34 33
35 return false 34 return false
@@ -41,12 +40,11 @@ async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, se
41} 40}
42 41
43async function doesUserFeedTokenCorrespond (id: number | string, token: string, res: Response) { 42async function doesUserFeedTokenCorrespond (id: number | string, token: string, res: Response) {
44 const user = await UserModel.loadById(parseInt(id + '', 10)) 43 const user = await UserModel.loadByIdWithChannels(parseInt(id + '', 10))
45 44
46 if (token !== user.feedToken) { 45 if (token !== user.feedToken) {
47 res.status(401) 46 res.status(401)
48 .send({ error: 'User and token mismatch' }) 47 .json({ error: 'User and token mismatch' })
49 .end()
50 48
51 return false 49 return false
52 } 50 }
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts
index 5c76a679f..35080ffca 100644
--- a/server/middlewares/validators/feeds.ts
+++ b/server/middlewares/validators/feeds.ts
@@ -64,8 +64,8 @@ const videoFeedsValidator = [
64] 64]
65 65
66const videoSubscriptonFeedsValidator = [ 66const videoSubscriptonFeedsValidator = [
67 query('accountId').optional().custom(isIdValid), 67 query('accountId').custom(isIdValid),
68 query('token').optional(), 68 query('token'),
69 69
70 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 70 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
71 logger.debug('Checking feeds parameters', { parameters: req.query }) 71 logger.debug('Checking feeds parameters', { parameters: req.query })
@@ -74,6 +74,7 @@ const videoSubscriptonFeedsValidator = [
74 74
75 // a token alone is erroneous 75 // a token alone is erroneous
76 if (req.query.token && !req.query.accountId) return 76 if (req.query.token && !req.query.accountId) return
77 if (req.query.accountId && !await doesAccountIdExist(req.query.accountId, res)) return
77 if (req.query.token && !await doesUserFeedTokenCorrespond(res.locals.account.userId, req.query.token, res)) return 78 if (req.query.token && !await doesUserFeedTokenCorrespond(res.locals.account.userId, req.query.token, res)) return
78 79
79 return next() 80 return next()
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts
index 2cd9b2d0a..175ea9102 100644
--- a/server/tests/feeds/feeds.ts
+++ b/server/tests/feeds/feeds.ts
@@ -326,7 +326,7 @@ describe('Test syndication feeds', () => {
326 const res = await listUserSubscriptionVideos(servers[0].url, feeduserAccessToken) 326 const res = await listUserSubscriptionVideos(servers[0].url, feeduserAccessToken)
327 expect(res.body.total).to.equal(0) 327 expect(res.body.total).to.equal(0)
328 328
329 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: feeduserAccountId, token: feeduserFeedToken }) 329 const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: feeduserAccountId, token: feeduserFeedToken })
330 const jsonObj = JSON.parse(json.text) 330 const jsonObj = JSON.parse(json.text)
331 expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos 331 expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos
332 } 332 }
@@ -337,7 +337,7 @@ describe('Test syndication feeds', () => {
337 const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) 337 const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken)
338 expect(res.body.total).to.equal(0) 338 expect(res.body.total).to.equal(0)
339 339
340 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken }) 340 const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken })
341 const jsonObj = JSON.parse(json.text) 341 const jsonObj = JSON.parse(json.text)
342 expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos 342 expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos
343 } 343 }
@@ -354,7 +354,7 @@ describe('Test syndication feeds', () => {
354 expect(res.body.total).to.equal(1) 354 expect(res.body.total).to.equal(1)
355 expect(res.body.data[0].name).to.equal('user video') 355 expect(res.body.data[0].name).to.equal('user video')
356 356
357 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken, version: 1 }) 357 const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 1 })
358 const jsonObj = JSON.parse(json.text) 358 const jsonObj = JSON.parse(json.text)
359 expect(jsonObj.items.length).to.be.equal(1) // subscribed to self, it should not list the instance's videos but list john's 359 expect(jsonObj.items.length).to.be.equal(1) // subscribed to self, it should not list the instance's videos but list john's
360 } 360 }
@@ -370,7 +370,7 @@ describe('Test syndication feeds', () => {
370 const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) 370 const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken)
371 expect(res.body.total).to.equal(2, "there should be 2 videos part of the subscription") 371 expect(res.body.total).to.equal(2, "there should be 2 videos part of the subscription")
372 372
373 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken, version: 2 }) 373 const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 2 })
374 const jsonObj = JSON.parse(json.text) 374 const jsonObj = JSON.parse(json.text)
375 expect(jsonObj.items.length).to.be.equal(2) // subscribed to root, it should not list the instance's videos but list root/john's 375 expect(jsonObj.items.length).to.be.equal(2) // subscribed to root, it should not list the instance's videos but list root/john's
376 } 376 }