From 00494d6e2ae915741f47869dcd359d9728a0af91 Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Sat, 27 Jun 2020 13:12:30 +0200 Subject: [PATCH] allow limiting video-comments rss feeds to an account or video channel --- server/controllers/feeds.ts | 55 +++++++++++++++++++++----- server/middlewares/validators/feeds.ts | 6 +++ server/models/video/video-comment.ts | 34 ++++++++++++---- shared/extra-utils/users/users.ts | 2 +- support/doc/api/openapi.yaml | 45 ++++++++++++++++++--- 5 files changed, 119 insertions(+), 23 deletions(-) diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index cb82bfc6d..bfcd3fe36 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts @@ -27,6 +27,7 @@ feedsRouter.get('/feeds/video-comments.:format', 'Content-Type' ] })(ROUTE_CACHE_LIFETIME.FEEDS)), + asyncMiddleware(videoFeedsValidator), asyncMiddleware(videoCommentsFeedsValidator), asyncMiddleware(generateVideoCommentsFeed) ) @@ -58,13 +59,36 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res const start = 0 const video = res.locals.videoAll - const videoId: number = video ? video.id : undefined + const account = res.locals.account + const videoChannel = res.locals.videoChannel - const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId) + const comments = await VideoCommentModel.listForFeed({ + start, + count: FEEDS.COUNT, + videoId: video ? video.id : undefined, + accountId: account ? account.id : undefined, + videoChannelId: videoChannel ? videoChannel.id : undefined + }) - const name = video ? video.name : CONFIG.INSTANCE.NAME - const description = video ? video.description : CONFIG.INSTANCE.DESCRIPTION - const feed = initFeed(name, description) + let name: string + let description: string + + if (videoChannel) { + name = videoChannel.getDisplayName() + description = videoChannel.description + } else if (account) { + name = account.getDisplayName() + description = account.description + } else { + name = video ? video.name : CONFIG.INSTANCE.NAME + description = video ? video.description : CONFIG.INSTANCE.DESCRIPTION + } + const feed = initFeed({ + name, + description, + resourceType: 'video-comments', + queryString: new URL(WEBSERVER.URL + req.originalUrl).search + }) // Adding video items to the feed, one at a time for (const comment of comments) { @@ -116,7 +140,12 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { description = CONFIG.INSTANCE.DESCRIPTION } - const feed = initFeed(name, description) + const feed = initFeed({ + name, + description, + resourceType: 'videos', + queryString: new URL(WEBSERVER.URL + req.url).search + }) const resultList = await VideoModel.listForApi({ start, @@ -207,8 +236,14 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { return sendFeed(feed, req, res) } -function initFeed (name: string, description: string) { +function initFeed (parameters: { + name: string + description: string + resourceType?: 'videos' | 'video-comments' + queryString?: string +}) { const webserverUrl = WEBSERVER.URL + const { name, description, resourceType, queryString } = parameters return new Feed({ title: name, @@ -222,9 +257,9 @@ function initFeed (name: string, description: string) { ` and potential licenses granted by each content's rightholder.`, generator: `Toraifōsu`, // ^.~ feedLinks: { - json: `${webserverUrl}/feeds/videos.json`, - atom: `${webserverUrl}/feeds/videos.atom`, - rss: `${webserverUrl}/feeds/videos.xml` + json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`, + atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`, + rss: `${webserverUrl}/feeds/${resourceType}.xml${queryString}` }, author: { name: 'Instance admin of ' + CONFIG.INSTANCE.NAME, diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts index f34c2b174..c3de0f5fe 100644 --- a/server/middlewares/validators/feeds.ts +++ b/server/middlewares/validators/feeds.ts @@ -70,6 +70,12 @@ const videoCommentsFeedsValidator = [ if (areValidationErrors(req, res)) return + if (req.query.videoId && (req.query.videoChannelId || req.query.videoChannelName)) { + return res.status(400).send({ + message: 'videoId cannot be mixed with a channel filter' + }).end() + } + if (req.query.videoId && !await doesVideoExist(req.query.videoId, res)) return return next() diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 091cc2a88..c465eb3e7 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts @@ -427,8 +427,31 @@ export class VideoCommentModel extends Model { return VideoCommentModel.findAndCountAll(query) } - static async listForFeed (start: number, count: number, videoId?: number): Promise { + static async listForFeed (parameters: { + start: number + count: number + videoId?: number + accountId?: number + videoChannelId?: number + }): Promise { const serverActor = await getServerActor() + const { start, count, videoId, accountId, videoChannelId } = parameters + + const accountExclusion = { + [Op.notIn]: Sequelize.literal( + '(' + buildBlockedAccountSQL([ serverActor.Account.id, '"Video->VideoChannel"."accountId"' ]) + ')' + ) + } + const accountWhere = accountId + ? { + [Op.and]: { + ...accountExclusion, + [Op.eq]: accountId + } + } + : accountExclusion + + const videoChannelWhere = videoChannelId ? { id: videoChannelId } : undefined const query = { order: [ [ 'createdAt', 'DESC' ] ] as Order, @@ -436,11 +459,7 @@ export class VideoCommentModel extends Model { limit: count, where: { deletedAt: null, - accountId: { - [Op.notIn]: Sequelize.literal( - '(' + buildBlockedAccountSQL([ serverActor.Account.id, '"Video->VideoChannel"."accountId"' ]) + ')' - ) - } + accountId: accountWhere }, include: [ { @@ -454,7 +473,8 @@ export class VideoCommentModel extends Model { { attributes: [ 'accountId' ], model: VideoChannelModel.unscoped(), - required: true + required: true, + where: videoChannelWhere } ] } diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts index 08b7743a6..766189dfe 100644 --- a/shared/extra-utils/users/users.ts +++ b/shared/extra-utils/users/users.ts @@ -29,7 +29,7 @@ function createUser (parameters: CreateUserArgs) { videoQuota = 1000000, videoQuotaDaily = -1, role = UserRole.USER, - specialStatus = 200 + specialStatus = 201 } = parameters const path = '/api/v1/users' diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index 3b06a2568..186d7d37d 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml @@ -1147,7 +1147,8 @@ paths: description: Whether or not we wait transcoding before publish the video type: string support: - description: Text describing how to support the video uploader + description: A text tell the audience how to support the video creator + example: Please support my work on ! <3 type: string nsfw: description: Whether or not this video contains sensitive content @@ -1305,7 +1306,8 @@ paths: description: Whether or not we wait transcoding before publish the video type: string support: - description: Text describing how to support the video uploader + description: A text tell the audience how to support the video creator + example: Please support my work on ! <3 type: string nsfw: description: Whether or not this video contains sensitive content @@ -1422,7 +1424,8 @@ paths: description: Whether or not we wait transcoding before publish the video type: string support: - description: Text describing how to support the video uploader + description: A text tell the audience how to support the video creator + example: Please support my work on ! <3 type: string nsfw: description: Whether or not this video contains sensitive content @@ -2723,7 +2726,7 @@ paths: - name: format in: path required: true - description: 'format expected (we focus on making `rss` the most featureful ; it serves Media RSS)' + description: 'format expected (we focus on making `rss` the most featureful ; it serves [Media RSS](https://www.rssboard.org/media-rss))' schema: type: string enum: @@ -2739,6 +2742,26 @@ paths: description: 'limit listing to a specific video' schema: type: string + - name: accountId + in: query + description: 'limit listing to a specific account' + schema: + type: string + - name: accountName + in: query + description: 'limit listing to a specific account' + schema: + type: string + - name: videoChannelId + in: query + description: 'limit listing to a specific video channel' + schema: + type: string + - name: videoChannelName + in: query + description: 'limit listing to a specific video channel' + schema: + type: string responses: '204': description: successful operation @@ -2763,6 +2786,13 @@ paths: application/json: schema: type: object + '400': + x-summary: field inconsistencies + description: > + Arises when: + - videoId filter is mixed with a channel filter + '404': + description: video, video channel or account not found '406': description: accept header unsupported '/feeds/videos.{format}': @@ -2781,7 +2811,7 @@ paths: - name: format in: path required: true - description: 'format expected (we focus on making `rss` the most featureful ; it serves Media RSS)' + description: 'format expected (we focus on making `rss` the most featureful ; it serves [Media RSS](https://www.rssboard.org/media-rss))' schema: type: string enum: @@ -2842,6 +2872,8 @@ paths: application/json: schema: type: object + '404': + description: video channel or account not found '406': description: accept header unsupported /plugins: @@ -3775,6 +3807,7 @@ components: type: string support: type: string + description: A text tell the audience how to support the video creator example: Please support my work on ! <3 channel: $ref: '#/components/schemas/VideoChannel' @@ -4806,6 +4839,7 @@ components: support: type: string description: 'A text shown by default on all videos of this channel, to tell the audience how to support it' + example: Please support my work on ! <3 required: - name - displayName @@ -4818,6 +4852,7 @@ components: support: type: string description: 'A text shown by default on all videos of this channel, to tell the audience how to support it' + example: Please support my work on ! <3 bulkVideosSupportUpdate: type: boolean description: 'Update the support field for all videos of this channel'