allow limiting video-comments rss feeds to an account or video channel develop
authorRigel Kent <sendmemail@rigelk.eu>
Sat, 27 Jun 2020 11:12:30 +0000 (13:12 +0200)
committerRigel Kent <sendmemail@rigelk.eu>
Sat, 27 Jun 2020 11:20:59 +0000 (13:20 +0200)
server/controllers/feeds.ts
server/middlewares/validators/feeds.ts
server/models/video/video-comment.ts
shared/extra-utils/users/users.ts
support/doc/api/openapi.yaml

index cb82bfc6d2185f8bee5e076c698f9df325640349..bfcd3fe36f8b0d43621fb760314cb3290e730d05 100644 (file)
@@ -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,
index f34c2b174527085750e48ac5bc11c4e3e549bdae..c3de0f5fec93fe7fd376e8780a035614ff65c208 100644 (file)
@@ -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()
index 091cc2a88b77eca3e881925944ecaf32026a51a3..c465eb3e706585533c4bf5742f1b0610e4b52311 100644 (file)
@@ -427,8 +427,31 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
     return VideoCommentModel.findAndCountAll<MComment>(query)
   }
 
-  static async listForFeed (start: number, count: number, videoId?: number): Promise<MCommentOwnerVideoFeed[]> {
+  static async listForFeed (parameters: {
+    start: number
+    count: number
+    videoId?: number
+    accountId?: number
+    videoChannelId?: number
+  }): Promise<MCommentOwnerVideoFeed[]> {
     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<VideoCommentModel> {
       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<VideoCommentModel> {
             {
               attributes: [ 'accountId' ],
               model: VideoChannelModel.unscoped(),
-              required: true
+              required: true,
+              where: videoChannelWhere
             }
           ]
         }
index 08b7743a6b48f0fd0cbe518b49563a17f7e49ba8..766189dfe07cf4ccc745b8140bf3d4958a73d629 100644 (file)
@@ -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'
index 3b06a25684b410776a483a8baebb1dbbb885da7a..186d7d37da81c382b83e7b6796f5b2c39352c101 100644 (file)
@@ -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 <insert crowdfunding plateform>! <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 <insert crowdfunding plateform>! <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 <insert crowdfunding plateform>! <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 <insert crowdfunding plateform>! <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 <insert crowdfunding plateform>! <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 <insert crowdfunding plateform>! <3
         bulkVideosSupportUpdate:
           type: boolean
           description: 'Update the support field for all videos of this channel'