diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2020-06-27 13:12:30 +0200 |
---|---|---|
committer | Rigel Kent <sendmemail@rigelk.eu> | 2020-06-27 13:20:59 +0200 |
commit | 00494d6e2ae915741f47869dcd359d9728a0af91 (patch) | |
tree | 9642f3d5bf1565d3b8d60d3ad06495fefce80c23 | |
parent | 2c318664305fd2723586ad939e64f958d9d447ff (diff) | |
download | PeerTube-00494d6e2ae915741f47869dcd359d9728a0af91.tar.gz PeerTube-00494d6e2ae915741f47869dcd359d9728a0af91.tar.zst PeerTube-00494d6e2ae915741f47869dcd359d9728a0af91.zip |
allow limiting video-comments rss feeds to an account or video channel
-rw-r--r-- | server/controllers/feeds.ts | 55 | ||||
-rw-r--r-- | server/middlewares/validators/feeds.ts | 6 | ||||
-rw-r--r-- | server/models/video/video-comment.ts | 34 | ||||
-rw-r--r-- | shared/extra-utils/users/users.ts | 2 | ||||
-rw-r--r-- | 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', | |||
27 | 'Content-Type' | 27 | 'Content-Type' |
28 | ] | 28 | ] |
29 | })(ROUTE_CACHE_LIFETIME.FEEDS)), | 29 | })(ROUTE_CACHE_LIFETIME.FEEDS)), |
30 | asyncMiddleware(videoFeedsValidator), | ||
30 | asyncMiddleware(videoCommentsFeedsValidator), | 31 | asyncMiddleware(videoCommentsFeedsValidator), |
31 | asyncMiddleware(generateVideoCommentsFeed) | 32 | asyncMiddleware(generateVideoCommentsFeed) |
32 | ) | 33 | ) |
@@ -58,13 +59,36 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res | |||
58 | const start = 0 | 59 | const start = 0 |
59 | 60 | ||
60 | const video = res.locals.videoAll | 61 | const video = res.locals.videoAll |
61 | const videoId: number = video ? video.id : undefined | 62 | const account = res.locals.account |
63 | const videoChannel = res.locals.videoChannel | ||
62 | 64 | ||
63 | const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId) | 65 | const comments = await VideoCommentModel.listForFeed({ |
66 | start, | ||
67 | count: FEEDS.COUNT, | ||
68 | videoId: video ? video.id : undefined, | ||
69 | accountId: account ? account.id : undefined, | ||
70 | videoChannelId: videoChannel ? videoChannel.id : undefined | ||
71 | }) | ||
64 | 72 | ||
65 | const name = video ? video.name : CONFIG.INSTANCE.NAME | 73 | let name: string |
66 | const description = video ? video.description : CONFIG.INSTANCE.DESCRIPTION | 74 | let description: string |
67 | const feed = initFeed(name, description) | 75 | |
76 | if (videoChannel) { | ||
77 | name = videoChannel.getDisplayName() | ||
78 | description = videoChannel.description | ||
79 | } else if (account) { | ||
80 | name = account.getDisplayName() | ||
81 | description = account.description | ||
82 | } else { | ||
83 | name = video ? video.name : CONFIG.INSTANCE.NAME | ||
84 | description = video ? video.description : CONFIG.INSTANCE.DESCRIPTION | ||
85 | } | ||
86 | const feed = initFeed({ | ||
87 | name, | ||
88 | description, | ||
89 | resourceType: 'video-comments', | ||
90 | queryString: new URL(WEBSERVER.URL + req.originalUrl).search | ||
91 | }) | ||
68 | 92 | ||
69 | // Adding video items to the feed, one at a time | 93 | // Adding video items to the feed, one at a time |
70 | for (const comment of comments) { | 94 | for (const comment of comments) { |
@@ -116,7 +140,12 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
116 | description = CONFIG.INSTANCE.DESCRIPTION | 140 | description = CONFIG.INSTANCE.DESCRIPTION |
117 | } | 141 | } |
118 | 142 | ||
119 | const feed = initFeed(name, description) | 143 | const feed = initFeed({ |
144 | name, | ||
145 | description, | ||
146 | resourceType: 'videos', | ||
147 | queryString: new URL(WEBSERVER.URL + req.url).search | ||
148 | }) | ||
120 | 149 | ||
121 | const resultList = await VideoModel.listForApi({ | 150 | const resultList = await VideoModel.listForApi({ |
122 | start, | 151 | start, |
@@ -207,8 +236,14 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
207 | return sendFeed(feed, req, res) | 236 | return sendFeed(feed, req, res) |
208 | } | 237 | } |
209 | 238 | ||
210 | function initFeed (name: string, description: string) { | 239 | function initFeed (parameters: { |
240 | name: string | ||
241 | description: string | ||
242 | resourceType?: 'videos' | 'video-comments' | ||
243 | queryString?: string | ||
244 | }) { | ||
211 | const webserverUrl = WEBSERVER.URL | 245 | const webserverUrl = WEBSERVER.URL |
246 | const { name, description, resourceType, queryString } = parameters | ||
212 | 247 | ||
213 | return new Feed({ | 248 | return new Feed({ |
214 | title: name, | 249 | title: name, |
@@ -222,9 +257,9 @@ function initFeed (name: string, description: string) { | |||
222 | ` and potential licenses granted by each content's rightholder.`, | 257 | ` and potential licenses granted by each content's rightholder.`, |
223 | generator: `Toraifōsu`, // ^.~ | 258 | generator: `Toraifōsu`, // ^.~ |
224 | feedLinks: { | 259 | feedLinks: { |
225 | json: `${webserverUrl}/feeds/videos.json`, | 260 | json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`, |
226 | atom: `${webserverUrl}/feeds/videos.atom`, | 261 | atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`, |
227 | rss: `${webserverUrl}/feeds/videos.xml` | 262 | rss: `${webserverUrl}/feeds/${resourceType}.xml${queryString}` |
228 | }, | 263 | }, |
229 | author: { | 264 | author: { |
230 | name: 'Instance admin of ' + CONFIG.INSTANCE.NAME, | 265 | 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 = [ | |||
70 | 70 | ||
71 | if (areValidationErrors(req, res)) return | 71 | if (areValidationErrors(req, res)) return |
72 | 72 | ||
73 | if (req.query.videoId && (req.query.videoChannelId || req.query.videoChannelName)) { | ||
74 | return res.status(400).send({ | ||
75 | message: 'videoId cannot be mixed with a channel filter' | ||
76 | }).end() | ||
77 | } | ||
78 | |||
73 | if (req.query.videoId && !await doesVideoExist(req.query.videoId, res)) return | 79 | if (req.query.videoId && !await doesVideoExist(req.query.videoId, res)) return |
74 | 80 | ||
75 | return next() | 81 | 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<VideoCommentModel> { | |||
427 | return VideoCommentModel.findAndCountAll<MComment>(query) | 427 | return VideoCommentModel.findAndCountAll<MComment>(query) |
428 | } | 428 | } |
429 | 429 | ||
430 | static async listForFeed (start: number, count: number, videoId?: number): Promise<MCommentOwnerVideoFeed[]> { | 430 | static async listForFeed (parameters: { |
431 | start: number | ||
432 | count: number | ||
433 | videoId?: number | ||
434 | accountId?: number | ||
435 | videoChannelId?: number | ||
436 | }): Promise<MCommentOwnerVideoFeed[]> { | ||
431 | const serverActor = await getServerActor() | 437 | const serverActor = await getServerActor() |
438 | const { start, count, videoId, accountId, videoChannelId } = parameters | ||
439 | |||
440 | const accountExclusion = { | ||
441 | [Op.notIn]: Sequelize.literal( | ||
442 | '(' + buildBlockedAccountSQL([ serverActor.Account.id, '"Video->VideoChannel"."accountId"' ]) + ')' | ||
443 | ) | ||
444 | } | ||
445 | const accountWhere = accountId | ||
446 | ? { | ||
447 | [Op.and]: { | ||
448 | ...accountExclusion, | ||
449 | [Op.eq]: accountId | ||
450 | } | ||
451 | } | ||
452 | : accountExclusion | ||
453 | |||
454 | const videoChannelWhere = videoChannelId ? { id: videoChannelId } : undefined | ||
432 | 455 | ||
433 | const query = { | 456 | const query = { |
434 | order: [ [ 'createdAt', 'DESC' ] ] as Order, | 457 | order: [ [ 'createdAt', 'DESC' ] ] as Order, |
@@ -436,11 +459,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
436 | limit: count, | 459 | limit: count, |
437 | where: { | 460 | where: { |
438 | deletedAt: null, | 461 | deletedAt: null, |
439 | accountId: { | 462 | accountId: accountWhere |
440 | [Op.notIn]: Sequelize.literal( | ||
441 | '(' + buildBlockedAccountSQL([ serverActor.Account.id, '"Video->VideoChannel"."accountId"' ]) + ')' | ||
442 | ) | ||
443 | } | ||
444 | }, | 463 | }, |
445 | include: [ | 464 | include: [ |
446 | { | 465 | { |
@@ -454,7 +473,8 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
454 | { | 473 | { |
455 | attributes: [ 'accountId' ], | 474 | attributes: [ 'accountId' ], |
456 | model: VideoChannelModel.unscoped(), | 475 | model: VideoChannelModel.unscoped(), |
457 | required: true | 476 | required: true, |
477 | where: videoChannelWhere | ||
458 | } | 478 | } |
459 | ] | 479 | ] |
460 | } | 480 | } |
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) { | |||
29 | videoQuota = 1000000, | 29 | videoQuota = 1000000, |
30 | videoQuotaDaily = -1, | 30 | videoQuotaDaily = -1, |
31 | role = UserRole.USER, | 31 | role = UserRole.USER, |
32 | specialStatus = 200 | 32 | specialStatus = 201 |
33 | } = parameters | 33 | } = parameters |
34 | 34 | ||
35 | const path = '/api/v1/users' | 35 | 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: | |||
1147 | description: Whether or not we wait transcoding before publish the video | 1147 | description: Whether or not we wait transcoding before publish the video |
1148 | type: string | 1148 | type: string |
1149 | support: | 1149 | support: |
1150 | description: Text describing how to support the video uploader | 1150 | description: A text tell the audience how to support the video creator |
1151 | example: Please support my work on <insert crowdfunding plateform>! <3 | ||
1151 | type: string | 1152 | type: string |
1152 | nsfw: | 1153 | nsfw: |
1153 | description: Whether or not this video contains sensitive content | 1154 | description: Whether or not this video contains sensitive content |
@@ -1305,7 +1306,8 @@ paths: | |||
1305 | description: Whether or not we wait transcoding before publish the video | 1306 | description: Whether or not we wait transcoding before publish the video |
1306 | type: string | 1307 | type: string |
1307 | support: | 1308 | support: |
1308 | description: Text describing how to support the video uploader | 1309 | description: A text tell the audience how to support the video creator |
1310 | example: Please support my work on <insert crowdfunding plateform>! <3 | ||
1309 | type: string | 1311 | type: string |
1310 | nsfw: | 1312 | nsfw: |
1311 | description: Whether or not this video contains sensitive content | 1313 | description: Whether or not this video contains sensitive content |
@@ -1422,7 +1424,8 @@ paths: | |||
1422 | description: Whether or not we wait transcoding before publish the video | 1424 | description: Whether or not we wait transcoding before publish the video |
1423 | type: string | 1425 | type: string |
1424 | support: | 1426 | support: |
1425 | description: Text describing how to support the video uploader | 1427 | description: A text tell the audience how to support the video creator |
1428 | example: Please support my work on <insert crowdfunding plateform>! <3 | ||
1426 | type: string | 1429 | type: string |
1427 | nsfw: | 1430 | nsfw: |
1428 | description: Whether or not this video contains sensitive content | 1431 | description: Whether or not this video contains sensitive content |
@@ -2723,7 +2726,7 @@ paths: | |||
2723 | - name: format | 2726 | - name: format |
2724 | in: path | 2727 | in: path |
2725 | required: true | 2728 | required: true |
2726 | description: 'format expected (we focus on making `rss` the most featureful ; it serves Media RSS)' | 2729 | description: 'format expected (we focus on making `rss` the most featureful ; it serves [Media RSS](https://www.rssboard.org/media-rss))' |
2727 | schema: | 2730 | schema: |
2728 | type: string | 2731 | type: string |
2729 | enum: | 2732 | enum: |
@@ -2739,6 +2742,26 @@ paths: | |||
2739 | description: 'limit listing to a specific video' | 2742 | description: 'limit listing to a specific video' |
2740 | schema: | 2743 | schema: |
2741 | type: string | 2744 | type: string |
2745 | - name: accountId | ||
2746 | in: query | ||
2747 | description: 'limit listing to a specific account' | ||
2748 | schema: | ||
2749 | type: string | ||
2750 | - name: accountName | ||
2751 | in: query | ||
2752 | description: 'limit listing to a specific account' | ||
2753 | schema: | ||
2754 | type: string | ||
2755 | - name: videoChannelId | ||
2756 | in: query | ||
2757 | description: 'limit listing to a specific video channel' | ||
2758 | schema: | ||
2759 | type: string | ||
2760 | - name: videoChannelName | ||
2761 | in: query | ||
2762 | description: 'limit listing to a specific video channel' | ||
2763 | schema: | ||
2764 | type: string | ||
2742 | responses: | 2765 | responses: |
2743 | '204': | 2766 | '204': |
2744 | description: successful operation | 2767 | description: successful operation |
@@ -2763,6 +2786,13 @@ paths: | |||
2763 | application/json: | 2786 | application/json: |
2764 | schema: | 2787 | schema: |
2765 | type: object | 2788 | type: object |
2789 | '400': | ||
2790 | x-summary: field inconsistencies | ||
2791 | description: > | ||
2792 | Arises when: | ||
2793 | - videoId filter is mixed with a channel filter | ||
2794 | '404': | ||
2795 | description: video, video channel or account not found | ||
2766 | '406': | 2796 | '406': |
2767 | description: accept header unsupported | 2797 | description: accept header unsupported |
2768 | '/feeds/videos.{format}': | 2798 | '/feeds/videos.{format}': |
@@ -2781,7 +2811,7 @@ paths: | |||
2781 | - name: format | 2811 | - name: format |
2782 | in: path | 2812 | in: path |
2783 | required: true | 2813 | required: true |
2784 | description: 'format expected (we focus on making `rss` the most featureful ; it serves Media RSS)' | 2814 | description: 'format expected (we focus on making `rss` the most featureful ; it serves [Media RSS](https://www.rssboard.org/media-rss))' |
2785 | schema: | 2815 | schema: |
2786 | type: string | 2816 | type: string |
2787 | enum: | 2817 | enum: |
@@ -2842,6 +2872,8 @@ paths: | |||
2842 | application/json: | 2872 | application/json: |
2843 | schema: | 2873 | schema: |
2844 | type: object | 2874 | type: object |
2875 | '404': | ||
2876 | description: video channel or account not found | ||
2845 | '406': | 2877 | '406': |
2846 | description: accept header unsupported | 2878 | description: accept header unsupported |
2847 | /plugins: | 2879 | /plugins: |
@@ -3775,6 +3807,7 @@ components: | |||
3775 | type: string | 3807 | type: string |
3776 | support: | 3808 | support: |
3777 | type: string | 3809 | type: string |
3810 | description: A text tell the audience how to support the video creator | ||
3778 | example: Please support my work on <insert crowdfunding plateform>! <3 | 3811 | example: Please support my work on <insert crowdfunding plateform>! <3 |
3779 | channel: | 3812 | channel: |
3780 | $ref: '#/components/schemas/VideoChannel' | 3813 | $ref: '#/components/schemas/VideoChannel' |
@@ -4806,6 +4839,7 @@ components: | |||
4806 | support: | 4839 | support: |
4807 | type: string | 4840 | type: string |
4808 | description: 'A text shown by default on all videos of this channel, to tell the audience how to support it' | 4841 | description: 'A text shown by default on all videos of this channel, to tell the audience how to support it' |
4842 | example: Please support my work on <insert crowdfunding plateform>! <3 | ||
4809 | required: | 4843 | required: |
4810 | - name | 4844 | - name |
4811 | - displayName | 4845 | - displayName |
@@ -4818,6 +4852,7 @@ components: | |||
4818 | support: | 4852 | support: |
4819 | type: string | 4853 | type: string |
4820 | description: 'A text shown by default on all videos of this channel, to tell the audience how to support it' | 4854 | description: 'A text shown by default on all videos of this channel, to tell the audience how to support it' |
4855 | example: Please support my work on <insert crowdfunding plateform>! <3 | ||
4821 | bulkVideosSupportUpdate: | 4856 | bulkVideosSupportUpdate: |
4822 | type: boolean | 4857 | type: boolean |
4823 | description: 'Update the support field for all videos of this channel' | 4858 | description: 'Update the support field for all videos of this channel' |