diff options
-rw-r--r-- | .editorconfig | 11 | ||||
-rw-r--r-- | client/src/app/shared/shared-share-modal/video-share.component.html | 29 | ||||
-rw-r--r-- | client/src/app/shared/shared-share-modal/video-share.component.ts | 20 | ||||
-rw-r--r-- | client/src/assets/player/shared/hotkeys/peertube-hotkeys-plugin.ts | 27 | ||||
-rw-r--r-- | client/src/root-helpers/video.ts | 21 | ||||
-rw-r--r-- | server/controllers/api/metrics.ts | 5 | ||||
-rw-r--r-- | server/controllers/api/server/contact.ts | 7 | ||||
-rw-r--r-- | server/tests/api/check-params/videos-common-filters.ts | 3 | ||||
-rw-r--r-- | server/tests/api/users/user-subscriptions.ts | 37 | ||||
-rw-r--r-- | server/tests/api/videos/videos-common-filters.ts | 63 | ||||
-rw-r--r-- | server/tests/feeds/feeds.ts | 8 | ||||
-rw-r--r-- | server/tests/fixtures/peertube-plugin-test/main.js | 2 | ||||
-rw-r--r-- | server/tests/plugins/filter-hooks.ts | 8 | ||||
-rw-r--r-- | shared/server-commands/users/subscriptions-command.ts | 18 | ||||
-rw-r--r-- | shared/server-commands/videos/videos-command.ts | 14 |
15 files changed, 184 insertions, 89 deletions
diff --git a/.editorconfig b/.editorconfig index 843d5d926..211eb8550 100644 --- a/.editorconfig +++ b/.editorconfig | |||
@@ -6,16 +6,7 @@ root = true | |||
6 | [*] | 6 | [*] |
7 | end_of_line = lf | 7 | end_of_line = lf |
8 | charset = utf-8 | 8 | charset = utf-8 |
9 | |||
10 | [*.{yml,html}] | ||
11 | indent_style = space | ||
12 | indent_size = 2 | ||
13 | |||
14 | [{client,server,shared,scripts}/**.{ts,json,js}] | ||
15 | trim_trailing_whitespace = true | 9 | trim_trailing_whitespace = true |
16 | insert_final_newline = true | 10 | insert_final_newline = true |
17 | indent_style = space | ||
18 | indent_size = 2 | 11 | indent_size = 2 |
19 | 12 | indent_style = space | |
20 | [*.md] | ||
21 | trim_trailing_whitespace = false | ||
diff --git a/client/src/app/shared/shared-share-modal/video-share.component.html b/client/src/app/shared/shared-share-modal/video-share.component.html index f4d249b41..01d351783 100644 --- a/client/src/app/shared/shared-share-modal/video-share.component.html +++ b/client/src/app/shared/shared-share-modal/video-share.component.html | |||
@@ -72,13 +72,21 @@ | |||
72 | ></my-peertube-checkbox> | 72 | ></my-peertube-checkbox> |
73 | </div> | 73 | </div> |
74 | 74 | ||
75 | <div class="form-group"> | 75 | <ng-container *ngIf="isInPlaylistEmbedTab()"> |
76 | <my-peertube-checkbox | 76 | <div class="form-group"> |
77 | *ngIf="isInPlaylistEmbedTab()" | 77 | <my-peertube-checkbox |
78 | inputName="onlyEmbedUrl" [(ngModel)]="customizations.onlyEmbedUrl" | 78 | inputName="onlyEmbedUrl" [(ngModel)]="customizations.onlyEmbedUrl" |
79 | i18n-labelText labelText="Only display embed URL" | 79 | i18n-labelText labelText="Only display embed URL" |
80 | ></my-peertube-checkbox> | 80 | ></my-peertube-checkbox> |
81 | </div> | 81 | </div> |
82 | |||
83 | <div class="form-group"> | ||
84 | <my-peertube-checkbox | ||
85 | inputName="responsive" [(ngModel)]="customizations.responsive" | ||
86 | i18n-labelText labelText="Responsive embed" | ||
87 | ></my-peertube-checkbox> | ||
88 | </div> | ||
89 | </ng-container> | ||
82 | 90 | ||
83 | <my-plugin-placeholder pluginId="share-modal-playlist-settings"></my-plugin-placeholder> | 91 | <my-plugin-placeholder pluginId="share-modal-playlist-settings"></my-plugin-placeholder> |
84 | </div> | 92 | </div> |
@@ -230,6 +238,13 @@ | |||
230 | <ng-container *ngIf="isInVideoEmbedTab()"> | 238 | <ng-container *ngIf="isInVideoEmbedTab()"> |
231 | <div class="form-group"> | 239 | <div class="form-group"> |
232 | <my-peertube-checkbox | 240 | <my-peertube-checkbox |
241 | inputName="responsive" [(ngModel)]="customizations.responsive" | ||
242 | i18n-labelText labelText="Responsive embed" | ||
243 | ></my-peertube-checkbox> | ||
244 | </div> | ||
245 | |||
246 | <div class="form-group"> | ||
247 | <my-peertube-checkbox | ||
233 | inputName="title" [(ngModel)]="customizations.title" | 248 | inputName="title" [(ngModel)]="customizations.title" |
234 | i18n-labelText labelText="Display video title" | 249 | i18n-labelText labelText="Display video title" |
235 | ></my-peertube-checkbox> | 250 | ></my-peertube-checkbox> |
diff --git a/client/src/app/shared/shared-share-modal/video-share.component.ts b/client/src/app/shared/shared-share-modal/video-share.component.ts index e1db4a3b8..43229c330 100644 --- a/client/src/app/shared/shared-share-modal/video-share.component.ts +++ b/client/src/app/shared/shared-share-modal/video-share.component.ts | |||
@@ -29,6 +29,7 @@ type Customizations = { | |||
29 | warningTitle: boolean | 29 | warningTitle: boolean |
30 | controlBar: boolean | 30 | controlBar: boolean |
31 | peertubeLink: boolean | 31 | peertubeLink: boolean |
32 | responsive: boolean | ||
32 | 33 | ||
33 | includeVideoInPlaylist: boolean | 34 | includeVideoInPlaylist: boolean |
34 | } | 35 | } |
@@ -100,6 +101,7 @@ export class VideoShareComponent { | |||
100 | warningTitle: true, | 101 | warningTitle: true, |
101 | controlBar: true, | 102 | controlBar: true, |
102 | peertubeLink: true, | 103 | peertubeLink: true, |
104 | responsive: false, | ||
103 | 105 | ||
104 | includeVideoInPlaylist: false | 106 | includeVideoInPlaylist: false |
105 | }, { | 107 | }, { |
@@ -152,10 +154,11 @@ export class VideoShareComponent { | |||
152 | ) | 154 | ) |
153 | } | 155 | } |
154 | 156 | ||
155 | async getVideoIframeCode () { | 157 | async getVideoEmbedCode (options: { responsive: boolean }) { |
158 | const { responsive } = options | ||
156 | return this.hooks.wrapFun( | 159 | return this.hooks.wrapFun( |
157 | buildVideoOrPlaylistEmbed, | 160 | buildVideoOrPlaylistEmbed, |
158 | { embedUrl: await this.getVideoEmbedUrl(), embedTitle: this.video.name }, | 161 | { embedUrl: await this.getVideoEmbedUrl(), embedTitle: this.video.name, responsive }, |
159 | 'video-watch', | 162 | 'video-watch', |
160 | 'filter:share.video-embed-code.build.params', | 163 | 'filter:share.video-embed-code.build.params', |
161 | 'filter:share.video-embed-code.build.result' | 164 | 'filter:share.video-embed-code.build.result' |
@@ -186,10 +189,11 @@ export class VideoShareComponent { | |||
186 | ) | 189 | ) |
187 | } | 190 | } |
188 | 191 | ||
189 | async getPlaylistEmbedCode () { | 192 | async getPlaylistEmbedCode (options: { responsive: boolean }) { |
193 | const { responsive } = options | ||
190 | return this.hooks.wrapFun( | 194 | return this.hooks.wrapFun( |
191 | buildVideoOrPlaylistEmbed, | 195 | buildVideoOrPlaylistEmbed, |
192 | { embedUrl: await this.getPlaylistEmbedUrl(), embedTitle: this.playlist.displayName }, | 196 | { embedUrl: await this.getPlaylistEmbedUrl(), embedTitle: this.playlist.displayName, responsive }, |
193 | 'video-watch', | 197 | 'video-watch', |
194 | 'filter:share.video-playlist-embed-code.build.params', | 198 | 'filter:share.video-playlist-embed-code.build.params', |
195 | 'filter:share.video-playlist-embed-code.build.result' | 199 | 'filter:share.video-playlist-embed-code.build.result' |
@@ -204,15 +208,15 @@ export class VideoShareComponent { | |||
204 | if (this.playlist) { | 208 | if (this.playlist) { |
205 | this.playlistUrl = await this.getPlaylistUrl() | 209 | this.playlistUrl = await this.getPlaylistUrl() |
206 | this.playlistEmbedUrl = await this.getPlaylistEmbedUrl() | 210 | this.playlistEmbedUrl = await this.getPlaylistEmbedUrl() |
207 | this.playlistEmbedHTML = await this.getPlaylistEmbedCode() | 211 | this.playlistEmbedHTML = await this.getPlaylistEmbedCode({ responsive: this.customizations.responsive }) |
208 | this.playlistEmbedSafeHTML = this.sanitizer.bypassSecurityTrustHtml(this.playlistEmbedHTML) | 212 | this.playlistEmbedSafeHTML = this.sanitizer.bypassSecurityTrustHtml(await this.getPlaylistEmbedCode({ responsive: false })) |
209 | } | 213 | } |
210 | 214 | ||
211 | if (this.video) { | 215 | if (this.video) { |
212 | this.videoUrl = await this.getVideoUrl() | 216 | this.videoUrl = await this.getVideoUrl() |
213 | this.videoEmbedUrl = await this.getVideoEmbedUrl() | 217 | this.videoEmbedUrl = await this.getVideoEmbedUrl() |
214 | this.videoEmbedHTML = await this.getVideoIframeCode() | 218 | this.videoEmbedHTML = await this.getVideoEmbedCode({ responsive: this.customizations.responsive }) |
215 | this.videoEmbedSafeHTML = this.sanitizer.bypassSecurityTrustHtml(this.videoEmbedHTML) | 219 | this.videoEmbedSafeHTML = this.sanitizer.bypassSecurityTrustHtml(await this.getVideoEmbedCode({ responsive: false })) |
216 | } | 220 | } |
217 | } | 221 | } |
218 | 222 | ||
diff --git a/client/src/assets/player/shared/hotkeys/peertube-hotkeys-plugin.ts b/client/src/assets/player/shared/hotkeys/peertube-hotkeys-plugin.ts index f5b4b3919..2742b21a1 100644 --- a/client/src/assets/player/shared/hotkeys/peertube-hotkeys-plugin.ts +++ b/client/src/assets/player/shared/hotkeys/peertube-hotkeys-plugin.ts | |||
@@ -218,12 +218,37 @@ class PeerTubeHotkeysPlugin extends Plugin { | |||
218 | } | 218 | } |
219 | 219 | ||
220 | private isNaked (event: KeyboardEvent, key: string) { | 220 | private isNaked (event: KeyboardEvent, key: string) { |
221 | return (!event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && event.key === key) | 221 | if (key.length === 1) key = key.toUpperCase() |
222 | |||
223 | return (!event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && this.getLatinKey(event.key, event.code) === key) | ||
222 | } | 224 | } |
223 | 225 | ||
224 | private isNakedOrShift (event: KeyboardEvent, key: string) { | 226 | private isNakedOrShift (event: KeyboardEvent, key: string) { |
225 | return (!event.ctrlKey && !event.altKey && !event.metaKey && event.key === key) | 227 | return (!event.ctrlKey && !event.altKey && !event.metaKey && event.key === key) |
226 | } | 228 | } |
229 | |||
230 | // Thanks Maciej Krawczyk | ||
231 | // https://stackoverflow.com/questions/70211837/keyboard-shortcuts-commands-on-non-latin-alphabet-keyboards-javascript?rq=1 | ||
232 | private getLatinKey (key: string, code: string) { | ||
233 | if (key.length !== 1) { | ||
234 | return key | ||
235 | } | ||
236 | |||
237 | const capitalHetaCode = 880 | ||
238 | const isNonLatin = key.charCodeAt(0) >= capitalHetaCode | ||
239 | |||
240 | if (isNonLatin) { | ||
241 | if (code.indexOf('Key') === 0 && code.length === 4) { // i.e. 'KeyW' | ||
242 | return code.charAt(3) | ||
243 | } | ||
244 | |||
245 | if (code.indexOf('Digit') === 0 && code.length === 6) { // i.e. 'Digit7' | ||
246 | return code.charAt(5) | ||
247 | } | ||
248 | } | ||
249 | |||
250 | return key.toUpperCase() | ||
251 | } | ||
227 | } | 252 | } |
228 | 253 | ||
229 | videojs.registerPlugin('peerTubeHotkeysPlugin', PeerTubeHotkeysPlugin) | 254 | videojs.registerPlugin('peerTubeHotkeysPlugin', PeerTubeHotkeysPlugin) |
diff --git a/client/src/root-helpers/video.ts b/client/src/root-helpers/video.ts index 107ba1eba..01feddbdc 100644 --- a/client/src/root-helpers/video.ts +++ b/client/src/root-helpers/video.ts | |||
@@ -3,19 +3,34 @@ import { HTMLServerConfig, Video, VideoPrivacy } from '@shared/models' | |||
3 | function buildVideoOrPlaylistEmbed (options: { | 3 | function buildVideoOrPlaylistEmbed (options: { |
4 | embedUrl: string | 4 | embedUrl: string |
5 | embedTitle: string | 5 | embedTitle: string |
6 | responsive?: boolean | ||
6 | }) { | 7 | }) { |
7 | const { embedUrl, embedTitle } = options | 8 | const { embedUrl, embedTitle, responsive = false } = options |
8 | 9 | ||
9 | const iframe = document.createElement('iframe') | 10 | const iframe = document.createElement('iframe') |
10 | 11 | ||
11 | iframe.title = embedTitle | 12 | iframe.title = embedTitle |
12 | iframe.width = '560' | 13 | iframe.width = responsive ? '100%' : '560' |
13 | iframe.height = '315' | 14 | iframe.height = responsive ? '100%' : '315' |
14 | iframe.src = embedUrl | 15 | iframe.src = embedUrl |
15 | iframe.frameBorder = '0' | 16 | iframe.frameBorder = '0' |
16 | iframe.allowFullscreen = true | 17 | iframe.allowFullscreen = true |
17 | iframe.sandbox.add('allow-same-origin', 'allow-scripts', 'allow-popups') | 18 | iframe.sandbox.add('allow-same-origin', 'allow-scripts', 'allow-popups') |
18 | 19 | ||
20 | if (responsive) { | ||
21 | const wrapper = document.createElement('div') | ||
22 | |||
23 | wrapper.style.position = 'relative' | ||
24 | wrapper.style['padding-top'] = '56.25%' | ||
25 | |||
26 | iframe.style.position = 'absolute' | ||
27 | iframe.style.inset = '0' | ||
28 | |||
29 | wrapper.appendChild(iframe) | ||
30 | |||
31 | return wrapper.outerHTML | ||
32 | } | ||
33 | |||
19 | return iframe.outerHTML | 34 | return iframe.outerHTML |
20 | } | 35 | } |
21 | 36 | ||
diff --git a/server/controllers/api/metrics.ts b/server/controllers/api/metrics.ts index 578b023a1..f66173875 100644 --- a/server/controllers/api/metrics.ts +++ b/server/controllers/api/metrics.ts | |||
@@ -2,6 +2,7 @@ import express from 'express' | |||
2 | import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics' | 2 | import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics' |
3 | import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models' | 3 | import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models' |
4 | import { addPlaybackMetricValidator, asyncMiddleware } from '../../middlewares' | 4 | import { addPlaybackMetricValidator, asyncMiddleware } from '../../middlewares' |
5 | import { CONFIG } from '@server/initializers/config' | ||
5 | 6 | ||
6 | const metricsRouter = express.Router() | 7 | const metricsRouter = express.Router() |
7 | 8 | ||
@@ -19,6 +20,10 @@ export { | |||
19 | // --------------------------------------------------------------------------- | 20 | // --------------------------------------------------------------------------- |
20 | 21 | ||
21 | function addPlaybackMetric (req: express.Request, res: express.Response) { | 22 | function addPlaybackMetric (req: express.Request, res: express.Response) { |
23 | if (!CONFIG.OPEN_TELEMETRY.METRICS.ENABLED) { | ||
24 | return res.sendStatus(HttpStatusCode.FORBIDDEN_403) | ||
25 | } | ||
26 | |||
22 | const body: PlaybackMetricCreate = req.body | 27 | const body: PlaybackMetricCreate = req.body |
23 | 28 | ||
24 | OpenTelemetryMetrics.Instance.observePlaybackMetric(res.locals.onlyImmutableVideo, body) | 29 | OpenTelemetryMetrics.Instance.observePlaybackMetric(res.locals.onlyImmutableVideo, body) |
diff --git a/server/controllers/api/server/contact.ts b/server/controllers/api/server/contact.ts index 09ff50f69..56596bea5 100644 --- a/server/controllers/api/server/contact.ts +++ b/server/controllers/api/server/contact.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import { logger } from '@server/helpers/logger' | ||
1 | import express from 'express' | 2 | import express from 'express' |
2 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | 3 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' |
3 | import { ContactForm } from '../../../../shared/models/server' | 4 | import { ContactForm } from '../../../../shared/models/server' |
@@ -17,7 +18,11 @@ async function contactAdministrator (req: express.Request, res: express.Response | |||
17 | 18 | ||
18 | Emailer.Instance.addContactFormJob(data.fromEmail, data.fromName, data.subject, data.body) | 19 | Emailer.Instance.addContactFormJob(data.fromEmail, data.fromName, data.subject, data.body) |
19 | 20 | ||
20 | await Redis.Instance.setContactFormIp(req.ip) | 21 | try { |
22 | await Redis.Instance.setContactFormIp(req.ip) | ||
23 | } catch (err) { | ||
24 | logger.error(err) | ||
25 | } | ||
21 | 26 | ||
22 | return res.status(HttpStatusCode.NO_CONTENT_204).end() | 27 | return res.status(HttpStatusCode.NO_CONTENT_204).end() |
23 | } | 28 | } |
diff --git a/server/tests/api/check-params/videos-common-filters.ts b/server/tests/api/check-params/videos-common-filters.ts index 95523ce3d..11d9fd95b 100644 --- a/server/tests/api/check-params/videos-common-filters.ts +++ b/server/tests/api/check-params/videos-common-filters.ts | |||
@@ -42,7 +42,8 @@ describe('Test video filters validators', function () { | |||
42 | '/api/v1/video-channels/root_channel/videos', | 42 | '/api/v1/video-channels/root_channel/videos', |
43 | '/api/v1/accounts/root/videos', | 43 | '/api/v1/accounts/root/videos', |
44 | '/api/v1/videos', | 44 | '/api/v1/videos', |
45 | '/api/v1/search/videos' | 45 | '/api/v1/search/videos', |
46 | '/api/v1/users/me/subscriptions/videos' | ||
46 | ] | 47 | ] |
47 | 48 | ||
48 | for (const path of paths) { | 49 | for (const path of paths) { |
diff --git a/server/tests/api/users/user-subscriptions.ts b/server/tests/api/users/user-subscriptions.ts index b45cfe67e..ad2b82a4a 100644 --- a/server/tests/api/users/user-subscriptions.ts +++ b/server/tests/api/users/user-subscriptions.ts | |||
@@ -167,14 +167,14 @@ describe('Test users subscriptions', function () { | |||
167 | 167 | ||
168 | it('Should list subscription videos', async function () { | 168 | it('Should list subscription videos', async function () { |
169 | { | 169 | { |
170 | const body = await command.listVideos() | 170 | const body = await servers[0].videos.listMySubscriptionVideos() |
171 | expect(body.total).to.equal(0) | 171 | expect(body.total).to.equal(0) |
172 | expect(body.data).to.be.an('array') | 172 | expect(body.data).to.be.an('array') |
173 | expect(body.data).to.have.lengthOf(0) | 173 | expect(body.data).to.have.lengthOf(0) |
174 | } | 174 | } |
175 | 175 | ||
176 | { | 176 | { |
177 | const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) | 177 | const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, sort: 'createdAt' }) |
178 | expect(body.total).to.equal(3) | 178 | expect(body.total).to.equal(3) |
179 | 179 | ||
180 | const videos = body.data | 180 | const videos = body.data |
@@ -185,6 +185,17 @@ describe('Test users subscriptions', function () { | |||
185 | expect(videos[1].name).to.equal('video 2-3') | 185 | expect(videos[1].name).to.equal('video 2-3') |
186 | expect(videos[2].name).to.equal('video server 3 added after follow') | 186 | expect(videos[2].name).to.equal('video server 3 added after follow') |
187 | } | 187 | } |
188 | |||
189 | { | ||
190 | const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, count: 1, start: 1 }) | ||
191 | expect(body.total).to.equal(3) | ||
192 | |||
193 | const videos = body.data | ||
194 | expect(videos).to.be.an('array') | ||
195 | expect(videos).to.have.lengthOf(1) | ||
196 | |||
197 | expect(videos[0].name).to.equal('video 2-3') | ||
198 | } | ||
188 | }) | 199 | }) |
189 | 200 | ||
190 | it('Should upload a video by root on server 1 and see it in the subscription videos', async function () { | 201 | it('Should upload a video by root on server 1 and see it in the subscription videos', async function () { |
@@ -196,14 +207,14 @@ describe('Test users subscriptions', function () { | |||
196 | await waitJobs(servers) | 207 | await waitJobs(servers) |
197 | 208 | ||
198 | { | 209 | { |
199 | const body = await command.listVideos() | 210 | const body = await servers[0].videos.listMySubscriptionVideos() |
200 | expect(body.total).to.equal(0) | 211 | expect(body.total).to.equal(0) |
201 | expect(body.data).to.be.an('array') | 212 | expect(body.data).to.be.an('array') |
202 | expect(body.data).to.have.lengthOf(0) | 213 | expect(body.data).to.have.lengthOf(0) |
203 | } | 214 | } |
204 | 215 | ||
205 | { | 216 | { |
206 | const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) | 217 | const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, sort: 'createdAt' }) |
207 | expect(body.total).to.equal(4) | 218 | expect(body.total).to.equal(4) |
208 | 219 | ||
209 | const videos = body.data | 220 | const videos = body.data |
@@ -264,14 +275,14 @@ describe('Test users subscriptions', function () { | |||
264 | 275 | ||
265 | it('Should still list subscription videos', async function () { | 276 | it('Should still list subscription videos', async function () { |
266 | { | 277 | { |
267 | const body = await command.listVideos() | 278 | const body = await servers[0].videos.listMySubscriptionVideos() |
268 | expect(body.total).to.equal(0) | 279 | expect(body.total).to.equal(0) |
269 | expect(body.data).to.be.an('array') | 280 | expect(body.data).to.be.an('array') |
270 | expect(body.data).to.have.lengthOf(0) | 281 | expect(body.data).to.have.lengthOf(0) |
271 | } | 282 | } |
272 | 283 | ||
273 | { | 284 | { |
274 | const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) | 285 | const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, sort: 'createdAt' }) |
275 | expect(body.total).to.equal(4) | 286 | expect(body.total).to.equal(4) |
276 | 287 | ||
277 | const videos = body.data | 288 | const videos = body.data |
@@ -295,7 +306,7 @@ describe('Test users subscriptions', function () { | |||
295 | 306 | ||
296 | await waitJobs(servers) | 307 | await waitJobs(servers) |
297 | 308 | ||
298 | const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) | 309 | const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, sort: 'createdAt' }) |
299 | expect(body.data[2].name).to.equal('video server 3 added after follow updated') | 310 | expect(body.data[2].name).to.equal('video server 3 added after follow updated') |
300 | }) | 311 | }) |
301 | }) | 312 | }) |
@@ -311,7 +322,7 @@ describe('Test users subscriptions', function () { | |||
311 | }) | 322 | }) |
312 | 323 | ||
313 | it('Should not display its videos anymore', async function () { | 324 | it('Should not display its videos anymore', async function () { |
314 | const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) | 325 | const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, sort: 'createdAt' }) |
315 | expect(body.total).to.equal(1) | 326 | expect(body.total).to.equal(1) |
316 | 327 | ||
317 | const videos = body.data | 328 | const videos = body.data |
@@ -360,7 +371,7 @@ describe('Test users subscriptions', function () { | |||
360 | await waitJobs(servers) | 371 | await waitJobs(servers) |
361 | 372 | ||
362 | { | 373 | { |
363 | const body = await command.listVideos({ token: users[0].accessToken, sort: 'createdAt' }) | 374 | const body = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken, sort: 'createdAt' }) |
364 | expect(body.total).to.equal(3) | 375 | expect(body.total).to.equal(3) |
365 | 376 | ||
366 | const videos = body.data | 377 | const videos = body.data |
@@ -569,13 +580,13 @@ describe('Test users subscriptions', function () { | |||
569 | await waitJobs(servers) | 580 | await waitJobs(servers) |
570 | 581 | ||
571 | { | 582 | { |
572 | const { data } = await command.listVideos({ token: users[0].accessToken }) | 583 | const { data } = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken }) |
573 | expect(data.find(v => v.name === 'internal')).to.not.exist | 584 | expect(data.find(v => v.name === 'internal')).to.not.exist |
574 | } | 585 | } |
575 | }) | 586 | }) |
576 | 587 | ||
577 | it('Should see internal from local user', async function () { | 588 | it('Should see internal from local user', async function () { |
578 | const { data } = await servers[2].subscriptions.listVideos({ token: servers[2].accessToken }) | 589 | const { data } = await servers[2].videos.listMySubscriptionVideos({ token: servers[2].accessToken }) |
579 | expect(data.find(v => v.name === 'internal')).to.exist | 590 | expect(data.find(v => v.name === 'internal')).to.exist |
580 | }) | 591 | }) |
581 | 592 | ||
@@ -586,12 +597,12 @@ describe('Test users subscriptions', function () { | |||
586 | await waitJobs(servers) | 597 | await waitJobs(servers) |
587 | 598 | ||
588 | { | 599 | { |
589 | const { data } = await command.listVideos({ token: users[0].accessToken }) | 600 | const { data } = await servers[0].videos.listMySubscriptionVideos({ token: users[0].accessToken }) |
590 | expect(data.find(v => v.name === 'private')).to.not.exist | 601 | expect(data.find(v => v.name === 'private')).to.not.exist |
591 | } | 602 | } |
592 | 603 | ||
593 | { | 604 | { |
594 | const { data } = await servers[2].subscriptions.listVideos({ token: servers[2].accessToken }) | 605 | const { data } = await servers[2].videos.listMySubscriptionVideos({ token: servers[2].accessToken }) |
595 | expect(data.find(v => v.name === 'private')).to.not.exist | 606 | expect(data.find(v => v.name === 'private')).to.not.exist |
596 | } | 607 | } |
597 | }) | 608 | }) |
diff --git a/server/tests/api/videos/videos-common-filters.ts b/server/tests/api/videos/videos-common-filters.ts index 160091861..1ab78ac49 100644 --- a/server/tests/api/videos/videos-common-filters.ts +++ b/server/tests/api/videos/videos-common-filters.ts | |||
@@ -20,6 +20,8 @@ describe('Test videos filter', function () { | |||
20 | let paths: string[] | 20 | let paths: string[] |
21 | let remotePaths: string[] | 21 | let remotePaths: string[] |
22 | 22 | ||
23 | const subscriptionVideosPath = '/api/v1/users/me/subscriptions/videos' | ||
24 | |||
23 | // --------------------------------------------------------------- | 25 | // --------------------------------------------------------------- |
24 | 26 | ||
25 | before(async function () { | 27 | before(async function () { |
@@ -49,6 +51,9 @@ describe('Test videos filter', function () { | |||
49 | const attributes = { name: 'private ' + server.serverNumber, privacy: VideoPrivacy.PRIVATE } | 51 | const attributes = { name: 'private ' + server.serverNumber, privacy: VideoPrivacy.PRIVATE } |
50 | await server.videos.upload({ attributes }) | 52 | await server.videos.upload({ attributes }) |
51 | } | 53 | } |
54 | |||
55 | // Subscribing to itself | ||
56 | await server.subscriptions.add({ targetUri: 'root_channel@' + server.host }) | ||
52 | } | 57 | } |
53 | 58 | ||
54 | await doubleFollow(servers[0], servers[1]) | 59 | await doubleFollow(servers[0], servers[1]) |
@@ -57,7 +62,8 @@ describe('Test videos filter', function () { | |||
57 | `/api/v1/video-channels/root_channel/videos`, | 62 | `/api/v1/video-channels/root_channel/videos`, |
58 | `/api/v1/accounts/root/videos`, | 63 | `/api/v1/accounts/root/videos`, |
59 | '/api/v1/videos', | 64 | '/api/v1/videos', |
60 | '/api/v1/search/videos' | 65 | '/api/v1/search/videos', |
66 | subscriptionVideosPath | ||
61 | ] | 67 | ] |
62 | 68 | ||
63 | remotePaths = [ | 69 | remotePaths = [ |
@@ -70,10 +76,20 @@ describe('Test videos filter', function () { | |||
70 | 76 | ||
71 | describe('Check deprecated videos filter', function () { | 77 | describe('Check deprecated videos filter', function () { |
72 | 78 | ||
73 | async function getVideosNames (server: PeerTubeServer, token: string, filter: string, expectedStatus = HttpStatusCode.OK_200) { | 79 | async function getVideosNames (options: { |
80 | server: PeerTubeServer | ||
81 | token: string | ||
82 | filter: string | ||
83 | skipSubscription?: boolean | ||
84 | expectedStatus?: HttpStatusCode | ||
85 | }) { | ||
86 | const { server, token, filter, skipSubscription = false, expectedStatus = HttpStatusCode.OK_200 } = options | ||
87 | |||
74 | const videosResults: Video[][] = [] | 88 | const videosResults: Video[][] = [] |
75 | 89 | ||
76 | for (const path of paths) { | 90 | for (const path of paths) { |
91 | if (skipSubscription && path === subscriptionVideosPath) continue | ||
92 | |||
77 | const res = await makeGetRequest({ | 93 | const res = await makeGetRequest({ |
78 | url: server.url, | 94 | url: server.url, |
79 | path, | 95 | path, |
@@ -93,7 +109,7 @@ describe('Test videos filter', function () { | |||
93 | 109 | ||
94 | it('Should display local videos', async function () { | 110 | it('Should display local videos', async function () { |
95 | for (const server of servers) { | 111 | for (const server of servers) { |
96 | const namesResults = await getVideosNames(server, server.accessToken, 'local') | 112 | const namesResults = await getVideosNames({ server, token: server.accessToken, filter: 'local' }) |
97 | for (const names of namesResults) { | 113 | for (const names of namesResults) { |
98 | expect(names).to.have.lengthOf(1) | 114 | expect(names).to.have.lengthOf(1) |
99 | expect(names[0]).to.equal('public ' + server.serverNumber) | 115 | expect(names[0]).to.equal('public ' + server.serverNumber) |
@@ -105,7 +121,7 @@ describe('Test videos filter', function () { | |||
105 | for (const server of servers) { | 121 | for (const server of servers) { |
106 | for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) { | 122 | for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) { |
107 | 123 | ||
108 | const namesResults = await getVideosNames(server, token, 'all-local') | 124 | const namesResults = await getVideosNames({ server, token, filter: 'all-local', skipSubscription: true }) |
109 | for (const names of namesResults) { | 125 | for (const names of namesResults) { |
110 | expect(names).to.have.lengthOf(3) | 126 | expect(names).to.have.lengthOf(3) |
111 | 127 | ||
@@ -121,7 +137,7 @@ describe('Test videos filter', function () { | |||
121 | for (const server of servers) { | 137 | for (const server of servers) { |
122 | for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) { | 138 | for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) { |
123 | 139 | ||
124 | const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames(server, token, 'all') | 140 | const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames({ server, token, filter: 'all' }) |
125 | expect(channelVideos).to.have.lengthOf(3) | 141 | expect(channelVideos).to.have.lengthOf(3) |
126 | expect(accountVideos).to.have.lengthOf(3) | 142 | expect(accountVideos).to.have.lengthOf(3) |
127 | 143 | ||
@@ -162,17 +178,23 @@ describe('Test videos filter', function () { | |||
162 | return res.body.data as Video[] | 178 | return res.body.data as Video[] |
163 | } | 179 | } |
164 | 180 | ||
165 | async function getVideosNames (options: { | 181 | async function getVideosNames ( |
166 | server: PeerTubeServer | 182 | options: { |
167 | isLocal?: boolean | 183 | server: PeerTubeServer |
168 | include?: VideoInclude | 184 | isLocal?: boolean |
169 | privacyOneOf?: VideoPrivacy[] | 185 | include?: VideoInclude |
170 | token?: string | 186 | privacyOneOf?: VideoPrivacy[] |
171 | expectedStatus?: HttpStatusCode | 187 | token?: string |
172 | }) { | 188 | expectedStatus?: HttpStatusCode |
189 | skipSubscription?: boolean | ||
190 | } | ||
191 | ) { | ||
192 | const { skipSubscription = false } = options | ||
173 | const videosResults: string[][] = [] | 193 | const videosResults: string[][] = [] |
174 | 194 | ||
175 | for (const path of paths) { | 195 | for (const path of paths) { |
196 | if (skipSubscription && path === subscriptionVideosPath) continue | ||
197 | |||
176 | const videos = await listVideos({ ...options, path }) | 198 | const videos = await listVideos({ ...options, path }) |
177 | 199 | ||
178 | videosResults.push(videos.map(v => v.name)) | 200 | videosResults.push(videos.map(v => v.name)) |
@@ -196,12 +218,15 @@ describe('Test videos filter', function () { | |||
196 | for (const server of servers) { | 218 | for (const server of servers) { |
197 | for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) { | 219 | for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) { |
198 | 220 | ||
199 | const namesResults = await getVideosNames({ | 221 | const namesResults = await getVideosNames( |
200 | server, | 222 | { |
201 | token, | 223 | server, |
202 | isLocal: true, | 224 | token, |
203 | privacyOneOf: [ VideoPrivacy.UNLISTED, VideoPrivacy.PUBLIC, VideoPrivacy.PRIVATE ] | 225 | isLocal: true, |
204 | }) | 226 | privacyOneOf: [ VideoPrivacy.UNLISTED, VideoPrivacy.PUBLIC, VideoPrivacy.PRIVATE ], |
227 | skipSubscription: true | ||
228 | } | ||
229 | ) | ||
205 | 230 | ||
206 | for (const names of namesResults) { | 231 | for (const names of namesResults) { |
207 | expect(names).to.have.lengthOf(3) | 232 | expect(names).to.have.lengthOf(3) |
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts index 7345f728a..ecd1badc1 100644 --- a/server/tests/feeds/feeds.ts +++ b/server/tests/feeds/feeds.ts | |||
@@ -387,7 +387,7 @@ describe('Test syndication feeds', () => { | |||
387 | } | 387 | } |
388 | 388 | ||
389 | { | 389 | { |
390 | const body = await servers[0].subscriptions.listVideos({ token: feeduserAccessToken }) | 390 | const body = await servers[0].videos.listMySubscriptionVideos({ token: feeduserAccessToken }) |
391 | expect(body.total).to.equal(0) | 391 | expect(body.total).to.equal(0) |
392 | 392 | ||
393 | const query = { accountId: feeduserAccountId, token: feeduserFeedToken } | 393 | const query = { accountId: feeduserAccountId, token: feeduserFeedToken } |
@@ -408,7 +408,7 @@ describe('Test syndication feeds', () => { | |||
408 | }) | 408 | }) |
409 | 409 | ||
410 | it('Should list no videos for a user with videos but no subscriptions', async function () { | 410 | it('Should list no videos for a user with videos but no subscriptions', async function () { |
411 | const body = await servers[0].subscriptions.listVideos({ token: userAccessToken }) | 411 | const body = await servers[0].videos.listMySubscriptionVideos({ token: userAccessToken }) |
412 | expect(body.total).to.equal(0) | 412 | expect(body.total).to.equal(0) |
413 | 413 | ||
414 | const query = { accountId: userAccountId, token: userFeedToken } | 414 | const query = { accountId: userAccountId, token: userFeedToken } |
@@ -424,7 +424,7 @@ describe('Test syndication feeds', () => { | |||
424 | await waitJobs(servers) | 424 | await waitJobs(servers) |
425 | 425 | ||
426 | { | 426 | { |
427 | const body = await servers[0].subscriptions.listVideos({ token: userAccessToken }) | 427 | const body = await servers[0].videos.listMySubscriptionVideos({ token: userAccessToken }) |
428 | expect(body.total).to.equal(1) | 428 | expect(body.total).to.equal(1) |
429 | expect(body.data[0].name).to.equal('user video') | 429 | expect(body.data[0].name).to.equal('user video') |
430 | 430 | ||
@@ -442,7 +442,7 @@ describe('Test syndication feeds', () => { | |||
442 | await waitJobs(servers) | 442 | await waitJobs(servers) |
443 | 443 | ||
444 | { | 444 | { |
445 | const body = await servers[0].subscriptions.listVideos({ token: userAccessToken }) | 445 | const body = await servers[0].videos.listMySubscriptionVideos({ token: userAccessToken }) |
446 | expect(body.total).to.equal(2, 'there should be 2 videos part of the subscription') | 446 | expect(body.total).to.equal(2, 'there should be 2 videos part of the subscription') |
447 | 447 | ||
448 | const query = { accountId: userAccountId, token: userFeedToken } | 448 | const query = { accountId: userAccountId, token: userFeedToken } |
diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js index 60b8b3ccd..5325e14c4 100644 --- a/server/tests/fixtures/peertube-plugin-test/main.js +++ b/server/tests/fixtures/peertube-plugin-test/main.js | |||
@@ -91,7 +91,7 @@ async function register ({ registerHook, registerSetting, settingsManager, stora | |||
91 | 91 | ||
92 | registerHook({ | 92 | registerHook({ |
93 | target: 'filter:api.user.me.subscription-videos.list.params', | 93 | target: 'filter:api.user.me.subscription-videos.list.params', |
94 | handler: obj => Object.assign({}, obj, { count: 1 }) | 94 | handler: obj => addToCount(obj) |
95 | }) | 95 | }) |
96 | 96 | ||
97 | registerHook({ | 97 | registerHook({ |
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts index 7e0be4d4e..4d26ff8b7 100644 --- a/server/tests/plugins/filter-hooks.ts +++ b/server/tests/plugins/filter-hooks.ts | |||
@@ -156,14 +156,14 @@ describe('Test plugin filter hooks', function () { | |||
156 | }) | 156 | }) |
157 | 157 | ||
158 | it('Should run filter:api.user.me.subscription-videos.list.params', async function () { | 158 | it('Should run filter:api.user.me.subscription-videos.list.params', async function () { |
159 | const { data } = await servers[0].subscriptions.listVideos() | 159 | const { data } = await servers[0].videos.listMySubscriptionVideos({ start: 0, count: 2 }) |
160 | 160 | ||
161 | // 1 plugin set the count parameter to 1 | 161 | // 1 plugin do +1 to the count parameter |
162 | expect(data).to.have.lengthOf(1) | 162 | expect(data).to.have.lengthOf(3) |
163 | }) | 163 | }) |
164 | 164 | ||
165 | it('Should run filter:api.user.me.subscription-videos.list.result', async function () { | 165 | it('Should run filter:api.user.me.subscription-videos.list.result', async function () { |
166 | const { total } = await servers[0].subscriptions.listVideos() | 166 | const { total } = await servers[0].videos.listMySubscriptionVideos({ start: 0, count: 2 }) |
167 | 167 | ||
168 | // Plugin do +4 to the total result | 168 | // Plugin do +4 to the total result |
169 | expect(total).to.equal(14) | 169 | expect(total).to.equal(14) |
diff --git a/shared/server-commands/users/subscriptions-command.ts b/shared/server-commands/users/subscriptions-command.ts index edc60e612..b92f037f8 100644 --- a/shared/server-commands/users/subscriptions-command.ts +++ b/shared/server-commands/users/subscriptions-command.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { HttpStatusCode, ResultList, Video, VideoChannel } from '@shared/models' | 1 | import { HttpStatusCode, ResultList, VideoChannel } from '@shared/models' |
2 | import { AbstractCommand, OverrideCommandOptions } from '../shared' | 2 | import { AbstractCommand, OverrideCommandOptions } from '../shared' |
3 | 3 | ||
4 | export class SubscriptionsCommand extends AbstractCommand { | 4 | export class SubscriptionsCommand extends AbstractCommand { |
@@ -38,22 +38,6 @@ export class SubscriptionsCommand extends AbstractCommand { | |||
38 | }) | 38 | }) |
39 | } | 39 | } |
40 | 40 | ||
41 | listVideos (options: OverrideCommandOptions & { | ||
42 | sort?: string // default -createdAt | ||
43 | } = {}) { | ||
44 | const { sort = '-createdAt' } = options | ||
45 | const path = '/api/v1/users/me/subscriptions/videos' | ||
46 | |||
47 | return this.getRequestBody<ResultList<Video>>({ | ||
48 | ...options, | ||
49 | |||
50 | path, | ||
51 | query: { sort }, | ||
52 | implicitToken: true, | ||
53 | defaultExpectedStatus: HttpStatusCode.OK_200 | ||
54 | }) | ||
55 | } | ||
56 | |||
57 | get (options: OverrideCommandOptions & { | 41 | get (options: OverrideCommandOptions & { |
58 | uri: string | 42 | uri: string |
59 | }) { | 43 | }) { |
diff --git a/shared/server-commands/videos/videos-command.ts b/shared/server-commands/videos/videos-command.ts index 590244484..b5df9c325 100644 --- a/shared/server-commands/videos/videos-command.ts +++ b/shared/server-commands/videos/videos-command.ts | |||
@@ -210,6 +210,20 @@ export class VideosCommand extends AbstractCommand { | |||
210 | }) | 210 | }) |
211 | } | 211 | } |
212 | 212 | ||
213 | listMySubscriptionVideos (options: OverrideCommandOptions & VideosCommonQuery = {}) { | ||
214 | const { sort = '-createdAt' } = options | ||
215 | const path = '/api/v1/users/me/subscriptions/videos' | ||
216 | |||
217 | return this.getRequestBody<ResultList<Video>>({ | ||
218 | ...options, | ||
219 | |||
220 | path, | ||
221 | query: { sort, ...this.buildListQuery(options) }, | ||
222 | implicitToken: true, | ||
223 | defaultExpectedStatus: HttpStatusCode.OK_200 | ||
224 | }) | ||
225 | } | ||
226 | |||
213 | // --------------------------------------------------------------------------- | 227 | // --------------------------------------------------------------------------- |
214 | 228 | ||
215 | list (options: OverrideCommandOptions & VideosCommonQuery = {}) { | 229 | list (options: OverrideCommandOptions & VideosCommonQuery = {}) { |