aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+about/about-instance/about-instance.component.ts2
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts2
-rw-r--r--client/src/app/+my-account/my-account-applications/my-account-applications.component.html4
-rw-r--r--client/src/app/+my-account/my-account-applications/my-account-applications.component.ts59
-rw-r--r--client/src/app/+my-account/my-account.component.ts10
-rw-r--r--client/src/app/+my-account/my-account.module.ts4
-rw-r--r--client/src/app/+videos/video-list/video-user-subscriptions.component.ts22
-rw-r--r--client/src/app/core/auth/auth.service.ts45
-rw-r--r--client/src/app/core/core.module.ts2
-rw-r--r--client/src/app/core/index.ts1
-rw-r--r--client/src/app/core/scoped-tokens/index.ts1
-rw-r--r--client/src/app/core/scoped-tokens/scoped-tokens.service.ts33
-rw-r--r--client/src/app/helpers/utils.ts2
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts2
-rw-r--r--client/src/app/shared/shared-forms/input-readonly-copy.component.ts1
-rw-r--r--client/src/app/shared/shared-main/video/video.service.ts21
-rw-r--r--client/src/environments/environment.e2e.ts2
-rw-r--r--client/src/environments/environment.hmr.ts2
-rw-r--r--client/src/environments/environment.prod.ts2
-rw-r--r--client/src/environments/environment.ts2
-rw-r--r--client/src/sass/include/_mixins.scss2
-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
-rw-r--r--shared/extra-utils/feeds/feeds.ts2
26 files changed, 223 insertions, 176 deletions
diff --git a/client/src/app/+about/about-instance/about-instance.component.ts b/client/src/app/+about/about-instance/about-instance.component.ts
index e74b5daeb..92ecd5263 100644
--- a/client/src/app/+about/about-instance/about-instance.component.ts
+++ b/client/src/app/+about/about-instance/about-instance.component.ts
@@ -3,7 +3,7 @@ import { AfterViewChecked, Component, OnInit, ViewChild } from '@angular/core'
3import { ActivatedRoute } from '@angular/router' 3import { ActivatedRoute } from '@angular/router'
4import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' 4import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
5import { Notifier } from '@app/core' 5import { Notifier } from '@app/core'
6import { copyToClipboard } from '../../../assets/player/utils' 6import { copyToClipboard } from '../../../root-helpers/utils'
7import { InstanceService } from '@app/shared/shared-instance' 7import { InstanceService } from '@app/shared/shared-instance'
8import { ServerConfig } from '@shared/models' 8import { ServerConfig } from '@shared/models'
9import { ResolverData } from './about-instance.resolver' 9import { ResolverData } from './about-instance.resolver'
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
index 1d0e56bfd..aa6b5d0a9 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
@@ -161,7 +161,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
161 getVideoEmbed (entry: VideoBlacklist) { 161 getVideoEmbed (entry: VideoBlacklist) {
162 return buildVideoOrPlaylistEmbed( 162 return buildVideoOrPlaylistEmbed(
163 buildVideoLink({ 163 buildVideoLink({
164 baseUrl: `${environment.embedUrl}/videos/embed/${entry.video.uuid}`, 164 baseUrl: `${environment.originServerUrl}/videos/embed/${entry.video.uuid}`,
165 title: false, 165 title: false,
166 warningTitle: false 166 warningTitle: false
167 }) 167 })
diff --git a/client/src/app/+my-account/my-account-applications/my-account-applications.component.html b/client/src/app/+my-account/my-account-applications/my-account-applications.component.html
index 62e2cb59b..53a9c91ac 100644
--- a/client/src/app/+my-account/my-account-applications/my-account-applications.component.html
+++ b/client/src/app/+my-account/my-account-applications/my-account-applications.component.html
@@ -7,8 +7,8 @@
7 <div class="form-group col-12 col-lg-4 col-xl-3"> 7 <div class="form-group col-12 col-lg-4 col-xl-3">
8 <h2 i18n class="applications-title">SUBSCRIPTION FEED</h2> 8 <h2 i18n class="applications-title">SUBSCRIPTION FEED</h2>
9 <div i18n class="applications-description"> 9 <div i18n class="applications-description">
10 Used to retrieve the list of videos of the creators 10 Use third-party feed aggregators to retrieve the list of videos from
11 you subscribed to from outside PeerTube 11 channels you subscribed to. Make sure to keep your token private.
12 </div> 12 </div>
13 </div> 13 </div>
14 14
diff --git a/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts b/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts
index c3f09dfe3..233e42c83 100644
--- a/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts
+++ b/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts
@@ -1,10 +1,10 @@
1 1
2import { Component, OnInit } from '@angular/core' 2import { Component, OnInit } from '@angular/core'
3import { AuthService, Notifier, ConfirmService } from '@app/core' 3import { AuthService, Notifier, ConfirmService, ScopedTokensService } from '@app/core'
4import { VideoService } from '@app/shared/shared-main' 4import { VideoService } from '@app/shared/shared-main'
5import { FeedFormat } from '@shared/models' 5import { FeedFormat } from '@shared/models'
6import { Subject, merge } from 'rxjs' 6import { ScopedToken } from '@shared/models/users/user-scoped-token'
7import { debounceTime } from 'rxjs/operators' 7import { environment } from '../../../environments/environment'
8 8
9@Component({ 9@Component({
10 selector: 'my-account-applications', 10 selector: 'my-account-applications',
@@ -15,11 +15,11 @@ export class MyAccountApplicationsComponent implements OnInit {
15 feedUrl: string 15 feedUrl: string
16 feedToken: string 16 feedToken: string
17 17
18 private baseURL = window.location.protocol + '//' + window.location.host 18 private baseURL = environment.originServerUrl
19 private tokenStream = new Subject()
20 19
21 constructor ( 20 constructor (
22 private authService: AuthService, 21 private authService: AuthService,
22 private scopedTokensService: ScopedTokensService,
23 private videoService: VideoService, 23 private videoService: VideoService,
24 private notifier: Notifier, 24 private notifier: Notifier,
25 private confirmService: ConfirmService 25 private confirmService: ConfirmService
@@ -27,31 +27,40 @@ export class MyAccountApplicationsComponent implements OnInit {
27 27
28 ngOnInit () { 28 ngOnInit () {
29 this.feedUrl = this.baseURL 29 this.feedUrl = this.baseURL
30 this.scopedTokensService.getScopedTokens()
31 .subscribe(
32 tokens => this.regenApplications(tokens),
30 33
31 merge( 34 err => {
32 this.tokenStream, 35 this.notifier.error(err.message)
33 this.authService.userInformationLoaded 36 }
34 ).pipe(debounceTime(400)) 37 )
35 .subscribe(
36 _ => {
37 const user = this.authService.getUser()
38 this.videoService.getVideoSubscriptionFeedUrls(user.account.id)
39 .then(feeds => this.feedUrl = this.baseURL + feeds.find(f => f.format === FeedFormat.RSS).url)
40 .then(_ => this.authService.getScopedTokens().then(tokens => this.feedToken = tokens.feedToken))
41 },
42
43 err => {
44 this.notifier.error(err.message)
45 }
46 )
47 } 38 }
48 39
49 async renewToken () { 40 async renewToken () {
50 const res = await this.confirmService.confirm('Renewing the token will disallow previously configured clients from retrieving the feed until they use the new token. Proceed?', 'Renew token') 41 const res = await this.confirmService.confirm(
42 $localize`Renewing the token will disallow previously configured clients from retrieving the feed until they use the new token. Proceed?`,
43 $localize`Renew token`
44 )
51 if (res === false) return 45 if (res === false) return
52 46
53 await this.authService.renewScopedTokens() 47 this.scopedTokensService.renewScopedTokens().subscribe(
54 this.notifier.success('Token renewed. Update your client configuration accordingly.') 48 tokens => {
55 this.tokenStream.next() 49 this.regenApplications(tokens)
50 this.notifier.success($localize`Token renewed. Update your client configuration accordingly.`)
51 },
52
53 err => {
54 this.notifier.error(err.message)
55 }
56 )
57
58 }
59
60 private regenApplications (tokens: ScopedToken) {
61 const user = this.authService.getUser()
62 const feeds = this.videoService.getVideoSubscriptionFeedUrls(user.account.id, tokens.feedToken)
63 this.feedUrl = this.baseURL + feeds.find(f => f.format === FeedFormat.RSS).url
64 this.feedToken = tokens.feedToken
56 } 65 }
57} 66}
diff --git a/client/src/app/+my-account/my-account.component.ts b/client/src/app/+my-account/my-account.component.ts
index 12966aebb..eaf8a72e9 100644
--- a/client/src/app/+my-account/my-account.component.ts
+++ b/client/src/app/+my-account/my-account.component.ts
@@ -41,11 +41,6 @@ export class MyAccountComponent implements OnInit {
41 label: $localize`Abuse reports`, 41 label: $localize`Abuse reports`,
42 routerLink: '/my-account/abuses', 42 routerLink: '/my-account/abuses',
43 iconName: 'flag' 43 iconName: 'flag'
44 },
45 {
46 label: $localize`Applications`,
47 routerLink: '/my-account/applications',
48 iconName: 'codesandbox'
49 } 44 }
50 ] 45 ]
51 } 46 }
@@ -61,6 +56,11 @@ export class MyAccountComponent implements OnInit {
61 routerLink: '/my-account/notifications' 56 routerLink: '/my-account/notifications'
62 }, 57 },
63 58
59 {
60 label: $localize`Applications`,
61 routerLink: '/my-account/applications'
62 },
63
64 moderationEntries 64 moderationEntries
65 ] 65 ]
66 } 66 }
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts
index 70bf58aae..076864563 100644
--- a/client/src/app/+my-account/my-account.module.ts
+++ b/client/src/app/+my-account/my-account.module.ts
@@ -20,8 +20,8 @@ import { MyAccountDangerZoneComponent } from './my-account-settings/my-account-d
20import { MyAccountNotificationPreferencesComponent } from './my-account-settings/my-account-notification-preferences' 20import { MyAccountNotificationPreferencesComponent } from './my-account-settings/my-account-notification-preferences'
21import { MyAccountProfileComponent } from './my-account-settings/my-account-profile/my-account-profile.component' 21import { MyAccountProfileComponent } from './my-account-settings/my-account-profile/my-account-profile.component'
22import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component' 22import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
23import { MyAccountApplicationsComponent } from './my-account-applications/my-account-applications.component'
23import { MyAccountComponent } from './my-account.component' 24import { MyAccountComponent } from './my-account.component'
24import { VideoChangeOwnershipComponent } from './my-account-applications/my-account-applications.component'
25 25
26@NgModule({ 26@NgModule({
27 imports: [ 27 imports: [
@@ -46,13 +46,13 @@ import { VideoChangeOwnershipComponent } from './my-account-applications/my-acco
46 MyAccountChangePasswordComponent, 46 MyAccountChangePasswordComponent,
47 MyAccountProfileComponent, 47 MyAccountProfileComponent,
48 MyAccountChangeEmailComponent, 48 MyAccountChangeEmailComponent,
49 MyAccountApplicationsComponent,
49 50
50 MyAccountDangerZoneComponent, 51 MyAccountDangerZoneComponent,
51 MyAccountBlocklistComponent, 52 MyAccountBlocklistComponent,
52 MyAccountAbusesListComponent, 53 MyAccountAbusesListComponent,
53 MyAccountServerBlocklistComponent, 54 MyAccountServerBlocklistComponent,
54 MyAccountNotificationsComponent, 55 MyAccountNotificationsComponent,
55 MyAccountNotificationPreferencesComponent,
56 MyAccountNotificationPreferencesComponent 56 MyAccountNotificationPreferencesComponent
57 ], 57 ],
58 58
diff --git a/client/src/app/+videos/video-list/video-user-subscriptions.component.ts b/client/src/app/+videos/video-list/video-user-subscriptions.component.ts
index 10031d6cc..03881c295 100644
--- a/client/src/app/+videos/video-list/video-user-subscriptions.component.ts
+++ b/client/src/app/+videos/video-list/video-user-subscriptions.component.ts
@@ -1,6 +1,6 @@
1import { Component, OnDestroy, OnInit } from '@angular/core' 1import { Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' 3import { AuthService, LocalStorageService, Notifier, ScopedTokensService, ScreenService, ServerService, UserService } from '@app/core'
4import { HooksService } from '@app/core/plugins/hooks.service' 4import { HooksService } from '@app/core/plugins/hooks.service'
5import { immutableAssign } from '@app/helpers' 5import { immutableAssign } from '@app/helpers'
6import { VideoService } from '@app/shared/shared-main' 6import { VideoService } from '@app/shared/shared-main'
@@ -9,6 +9,7 @@ import { AbstractVideoList, OwnerDisplayType } from '@app/shared/shared-video-mi
9import { VideoSortField, FeedFormat } from '@shared/models' 9import { VideoSortField, FeedFormat } from '@shared/models'
10import { copyToClipboard } from '../../../root-helpers/utils' 10import { copyToClipboard } from '../../../root-helpers/utils'
11import { environment } from '../../../environments/environment' 11import { environment } from '../../../environments/environment'
12import { forkJoin } from 'rxjs'
12 13
13@Component({ 14@Component({
14 selector: 'my-videos-user-subscriptions', 15 selector: 'my-videos-user-subscriptions',
@@ -32,7 +33,8 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
32 protected storageService: LocalStorageService, 33 protected storageService: LocalStorageService,
33 private userSubscription: UserSubscriptionService, 34 private userSubscription: UserSubscriptionService,
34 private hooks: HooksService, 35 private hooks: HooksService,
35 private videoService: VideoService 36 private videoService: VideoService,
37 private scopedTokensService: ScopedTokensService
36 ) { 38 ) {
37 super() 39 super()
38 40
@@ -49,9 +51,19 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
49 super.ngOnInit() 51 super.ngOnInit()
50 52
51 const user = this.authService.getUser() 53 const user = this.authService.getUser()
52 let feedUrl = environment.embedUrl 54 let feedUrl = environment.originServerUrl
53 this.videoService.getVideoSubscriptionFeedUrls(user.account.id) 55
54 .then((feeds: any) => feedUrl = feedUrl + feeds.find((f: any) => f.format === FeedFormat.RSS).url) 56 this.scopedTokensService.getScopedTokens().subscribe(
57 tokens => {
58 const feeds = this.videoService.getVideoSubscriptionFeedUrls(user.account.id, tokens.feedToken)
59 feedUrl = feedUrl + feeds.find((f: any) => f.format === FeedFormat.RSS).url
60 },
61
62 err => {
63 this.notifier.error(err.message)
64 }
65 )
66
55 this.actions.unshift({ 67 this.actions.unshift({
56 label: $localize`Feed`, 68 label: $localize`Feed`,
57 iconName: 'syndication', 69 iconName: 'syndication',
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts
index 224f35f82..fd6062d3f 100644
--- a/client/src/app/core/auth/auth.service.ts
+++ b/client/src/app/core/auth/auth.service.ts
@@ -11,7 +11,6 @@ import { environment } from '../../../environments/environment'
11import { RestExtractor } from '../rest/rest-extractor.service' 11import { RestExtractor } from '../rest/rest-extractor.service'
12import { AuthStatus } from './auth-status.model' 12import { AuthStatus } from './auth-status.model'
13import { AuthUser } from './auth-user.model' 13import { AuthUser } from './auth-user.model'
14import { ScopedTokenType, ScopedToken } from '@shared/models/users/user-scoped-token'
15 14
16interface UserLoginWithUsername extends UserLogin { 15interface UserLoginWithUsername extends UserLogin {
17 access_token: string 16 access_token: string
@@ -27,7 +26,6 @@ export class AuthService {
27 private static BASE_CLIENT_URL = environment.apiUrl + '/api/v1/oauth-clients/local' 26 private static BASE_CLIENT_URL = environment.apiUrl + '/api/v1/oauth-clients/local'
28 private static BASE_TOKEN_URL = environment.apiUrl + '/api/v1/users/token' 27 private static BASE_TOKEN_URL = environment.apiUrl + '/api/v1/users/token'
29 private static BASE_REVOKE_TOKEN_URL = environment.apiUrl + '/api/v1/users/revoke-token' 28 private static BASE_REVOKE_TOKEN_URL = environment.apiUrl + '/api/v1/users/revoke-token'
30 private static BASE_SCOPED_TOKENS_URL = environment.apiUrl + '/api/v1/users/scoped-tokens'
31 private static BASE_USER_INFORMATION_URL = environment.apiUrl + '/api/v1/users/me' 29 private static BASE_USER_INFORMATION_URL = environment.apiUrl + '/api/v1/users/me'
32 private static LOCAL_STORAGE_OAUTH_CLIENT_KEYS = { 30 private static LOCAL_STORAGE_OAUTH_CLIENT_KEYS = {
33 CLIENT_ID: 'client_id', 31 CLIENT_ID: 'client_id',
@@ -43,7 +41,6 @@ export class AuthService {
43 private loginChanged: Subject<AuthStatus> 41 private loginChanged: Subject<AuthStatus>
44 private user: AuthUser = null 42 private user: AuthUser = null
45 private refreshingTokenObservable: Observable<any> 43 private refreshingTokenObservable: Observable<any>
46 private scopedTokens: ScopedToken
47 44
48 constructor ( 45 constructor (
49 private http: HttpClient, 46 private http: HttpClient,
@@ -247,48 +244,6 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
247 ) 244 )
248 } 245 }
249 246
250 getScopedTokens (): Promise<ScopedToken> {
251 return new Promise((res, rej) => {
252 if (this.scopedTokens) return res(this.scopedTokens)
253
254 const authHeaderValue = this.getRequestHeaderValue()
255 const headers = new HttpHeaders().set('Authorization', authHeaderValue)
256
257 this.http.get<ScopedToken>(AuthService.BASE_SCOPED_TOKENS_URL, { headers })
258 .subscribe(
259 scopedTokens => {
260 this.scopedTokens = scopedTokens
261 res(this.scopedTokens)
262 },
263
264 err => {
265 console.error(err)
266 rej(err)
267 }
268 )
269 })
270 }
271
272 renewScopedTokens (): Promise<ScopedToken> {
273 return new Promise((res, rej) => {
274 const authHeaderValue = this.getRequestHeaderValue()
275 const headers = new HttpHeaders().set('Authorization', authHeaderValue)
276
277 this.http.post<ScopedToken>(AuthService.BASE_SCOPED_TOKENS_URL, {}, { headers })
278 .subscribe(
279 scopedTokens => {
280 this.scopedTokens = scopedTokens
281 res(this.scopedTokens)
282 },
283
284 err => {
285 console.error(err)
286 rej(err)
287 }
288 )
289 })
290 }
291
292 private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> { 247 private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> {
293 // User is not loaded yet, set manually auth header 248 // User is not loaded yet, set manually auth header
294 const headers = new HttpHeaders().set('Authorization', `${obj.token_type} ${obj.access_token}`) 249 const headers = new HttpHeaders().set('Authorization', `${obj.token_type} ${obj.access_token}`)
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts
index 6c0a2245d..f51f1920d 100644
--- a/client/src/app/core/core.module.ts
+++ b/client/src/app/core/core.module.ts
@@ -12,6 +12,7 @@ import { LoadingBarModule } from '@ngx-loading-bar/core'
12import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' 12import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client'
13import { LoadingBarRouterModule } from '@ngx-loading-bar/router' 13import { LoadingBarRouterModule } from '@ngx-loading-bar/router'
14import { AuthService } from './auth' 14import { AuthService } from './auth'
15import { ScopedTokensService } from './scoped-tokens'
15import { ConfirmService } from './confirm' 16import { ConfirmService } from './confirm'
16import { CheatSheetComponent } from './hotkeys' 17import { CheatSheetComponent } from './hotkeys'
17import { MenuService } from './menu' 18import { MenuService } from './menu'
@@ -57,6 +58,7 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra
57 58
58 providers: [ 59 providers: [
59 AuthService, 60 AuthService,
61 ScopedTokensService,
60 ConfirmService, 62 ConfirmService,
61 ServerService, 63 ServerService,
62 ThemeService, 64 ThemeService,
diff --git a/client/src/app/core/index.ts b/client/src/app/core/index.ts
index a0c34543d..9245ff6fc 100644
--- a/client/src/app/core/index.ts
+++ b/client/src/app/core/index.ts
@@ -1,4 +1,5 @@
1export * from './auth' 1export * from './auth'
2export * from './scoped-tokens'
2export * from './confirm' 3export * from './confirm'
3export * from './hotkeys' 4export * from './hotkeys'
4export * from './menu' 5export * from './menu'
diff --git a/client/src/app/core/scoped-tokens/index.ts b/client/src/app/core/scoped-tokens/index.ts
new file mode 100644
index 000000000..c9a48ffcd
--- /dev/null
+++ b/client/src/app/core/scoped-tokens/index.ts
@@ -0,0 +1 @@
export * from './scoped-tokens.service'
diff --git a/client/src/app/core/scoped-tokens/scoped-tokens.service.ts b/client/src/app/core/scoped-tokens/scoped-tokens.service.ts
new file mode 100644
index 000000000..8e3697c31
--- /dev/null
+++ b/client/src/app/core/scoped-tokens/scoped-tokens.service.ts
@@ -0,0 +1,33 @@
1import { Injectable } from '@angular/core'
2import { HttpClient } from '@angular/common/http'
3import { environment } from '../../../environments/environment'
4import { AuthService } from '../auth'
5import { ScopedToken } from '@shared/models/users/user-scoped-token'
6import { catchError } from 'rxjs/operators'
7import { RestExtractor } from '../rest'
8
9@Injectable()
10export class ScopedTokensService {
11 private static BASE_SCOPED_TOKENS_URL = environment.apiUrl + '/api/v1/users/scoped-tokens'
12
13 constructor (
14 private authHttp: HttpClient,
15 private restExtractor: RestExtractor
16 ) {}
17
18 getScopedTokens () {
19 return this.authHttp
20 .get<ScopedToken>(ScopedTokensService.BASE_SCOPED_TOKENS_URL)
21 .pipe(
22 catchError(res => this.restExtractor.handleError(res))
23 )
24 }
25
26 renewScopedTokens () {
27 return this.authHttp
28 .post<ScopedToken>(ScopedTokensService.BASE_SCOPED_TOKENS_URL, {})
29 .pipe(
30 catchError(res => this.restExtractor.handleError(res))
31 )
32 }
33}
diff --git a/client/src/app/helpers/utils.ts b/client/src/app/helpers/utils.ts
index a22507f46..9c805b4ca 100644
--- a/client/src/app/helpers/utils.ts
+++ b/client/src/app/helpers/utils.ts
@@ -58,7 +58,7 @@ function getAbsoluteAPIUrl () {
58} 58}
59 59
60function getAbsoluteEmbedUrl () { 60function getAbsoluteEmbedUrl () {
61 let absoluteEmbedUrl = environment.embedUrl 61 let absoluteEmbedUrl = environment.originServerUrl
62 if (!absoluteEmbedUrl) { 62 if (!absoluteEmbedUrl) {
63 // The Embed is on the same domain 63 // The Embed is on the same domain
64 absoluteEmbedUrl = window.location.origin 64 absoluteEmbedUrl = window.location.origin
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
index ca0d23699..807665b9c 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
@@ -112,7 +112,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
112 getVideoEmbed (abuse: AdminAbuse) { 112 getVideoEmbed (abuse: AdminAbuse) {
113 return buildVideoOrPlaylistEmbed( 113 return buildVideoOrPlaylistEmbed(
114 buildVideoLink({ 114 buildVideoLink({
115 baseUrl: `${environment.embedUrl}/videos/embed/${abuse.video.uuid}`, 115 baseUrl: `${environment.originServerUrl}/videos/embed/${abuse.video.uuid}`,
116 title: false, 116 title: false,
117 warningTitle: false, 117 warningTitle: false,
118 startTime: abuse.video.startAt, 118 startTime: abuse.video.startAt,
diff --git a/client/src/app/shared/shared-forms/input-readonly-copy.component.ts b/client/src/app/shared/shared-forms/input-readonly-copy.component.ts
index 520827a53..b04d69d05 100644
--- a/client/src/app/shared/shared-forms/input-readonly-copy.component.ts
+++ b/client/src/app/shared/shared-forms/input-readonly-copy.component.ts
@@ -1,6 +1,5 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input } from '@angular/core'
2import { Notifier } from '@app/core' 2import { Notifier } from '@app/core'
3import { FormGroup } from '@angular/forms'
4 3
5@Component({ 4@Component({
6 selector: 'my-input-readonly-copy', 5 selector: 'my-input-readonly-copy',
diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts
index b81540e8d..70be5d7d2 100644
--- a/client/src/app/shared/shared-main/video/video.service.ts
+++ b/client/src/app/shared/shared-main/video/video.service.ts
@@ -18,8 +18,7 @@ import {
18 VideoFilter, 18 VideoFilter,
19 VideoPrivacy, 19 VideoPrivacy,
20 VideoSortField, 20 VideoSortField,
21 VideoUpdate, 21 VideoUpdate
22 VideoCreate
23} from '@shared/models' 22} from '@shared/models'
24import { environment } from '../../../../environments/environment' 23import { environment } from '../../../../environments/environment'
25import { Account } from '../account/account.model' 24import { Account } from '../account/account.model'
@@ -44,13 +43,13 @@ export interface VideosProvider {
44export class VideoService implements VideosProvider { 43export class VideoService implements VideosProvider {
45 static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' 44 static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
46 static BASE_FEEDS_URL = environment.apiUrl + '/feeds/videos.' 45 static BASE_FEEDS_URL = environment.apiUrl + '/feeds/videos.'
46 static BASE_SUBSCRIPTION_FEEDS_URL = environment.apiUrl + '/feeds/subscriptions.'
47 47
48 constructor ( 48 constructor (
49 private authHttp: HttpClient, 49 private authHttp: HttpClient,
50 private restExtractor: RestExtractor, 50 private restExtractor: RestExtractor,
51 private restService: RestService, 51 private restService: RestService,
52 private serverService: ServerService, 52 private serverService: ServerService
53 private authService: AuthService
54 ) {} 53 ) {}
55 54
56 getVideoViewUrl (uuid: string) { 55 getVideoViewUrl (uuid: string) {
@@ -238,22 +237,22 @@ export class VideoService implements VideosProvider {
238 ) 237 )
239 } 238 }
240 239
241 buildBaseFeedUrls (params: HttpParams) { 240 buildBaseFeedUrls (params: HttpParams, base = VideoService.BASE_FEEDS_URL) {
242 const feeds = [ 241 const feeds = [
243 { 242 {
244 format: FeedFormat.RSS, 243 format: FeedFormat.RSS,
245 label: 'media rss 2.0', 244 label: 'media rss 2.0',
246 url: VideoService.BASE_FEEDS_URL + FeedFormat.RSS.toLowerCase() 245 url: base + FeedFormat.RSS.toLowerCase()
247 }, 246 },
248 { 247 {
249 format: FeedFormat.ATOM, 248 format: FeedFormat.ATOM,
250 label: 'atom 1.0', 249 label: 'atom 1.0',
251 url: VideoService.BASE_FEEDS_URL + FeedFormat.ATOM.toLowerCase() 250 url: base + FeedFormat.ATOM.toLowerCase()
252 }, 251 },
253 { 252 {
254 format: FeedFormat.JSON, 253 format: FeedFormat.JSON,
255 label: 'json 1.0', 254 label: 'json 1.0',
256 url: VideoService.BASE_FEEDS_URL + FeedFormat.JSON.toLowerCase() 255 url: base + FeedFormat.JSON.toLowerCase()
257 } 256 }
258 ] 257 ]
259 258
@@ -294,14 +293,12 @@ export class VideoService implements VideosProvider {
294 return this.buildBaseFeedUrls(params) 293 return this.buildBaseFeedUrls(params)
295 } 294 }
296 295
297 async getVideoSubscriptionFeedUrls (accountId: number) { 296 getVideoSubscriptionFeedUrls (accountId: number, feedToken: string) {
298 let params = this.restService.addRestGetParams(new HttpParams()) 297 let params = this.restService.addRestGetParams(new HttpParams())
299 params = params.set('accountId', accountId.toString()) 298 params = params.set('accountId', accountId.toString())
300
301 const { feedToken } = await this.authService.getScopedTokens()
302 params = params.set('token', feedToken) 299 params = params.set('token', feedToken)
303 300
304 return this.buildBaseFeedUrls(params) 301 return this.buildBaseFeedUrls(params, VideoService.BASE_SUBSCRIPTION_FEEDS_URL)
305 } 302 }
306 303
307 getVideoFileMetadata (metadataUrl: string) { 304 getVideoFileMetadata (metadataUrl: string) {
diff --git a/client/src/environments/environment.e2e.ts b/client/src/environments/environment.e2e.ts
index b33ff9f86..a1a58e36f 100644
--- a/client/src/environments/environment.e2e.ts
+++ b/client/src/environments/environment.e2e.ts
@@ -2,5 +2,5 @@ export const environment = {
2 production: false, 2 production: false,
3 hmr: false, 3 hmr: false,
4 apiUrl: 'http://localhost:9001', 4 apiUrl: 'http://localhost:9001',
5 embedUrl: 'http://localhost:9001' 5 originServerUrl: 'http://localhost:9001'
6} 6}
diff --git a/client/src/environments/environment.hmr.ts b/client/src/environments/environment.hmr.ts
index 3b6eff302..ab7631920 100644
--- a/client/src/environments/environment.hmr.ts
+++ b/client/src/environments/environment.hmr.ts
@@ -2,5 +2,5 @@ export const environment = {
2 production: false, 2 production: false,
3 hmr: true, 3 hmr: true,
4 apiUrl: '', 4 apiUrl: '',
5 embedUrl: 'http://localhost:9000' 5 originServerUrl: 'http://localhost:9000'
6} 6}
diff --git a/client/src/environments/environment.prod.ts b/client/src/environments/environment.prod.ts
index 2e9b9fefe..e1b736c61 100644
--- a/client/src/environments/environment.prod.ts
+++ b/client/src/environments/environment.prod.ts
@@ -2,5 +2,5 @@ export const environment = {
2 production: true, 2 production: true,
3 hmr: false, 3 hmr: false,
4 apiUrl: '', 4 apiUrl: '',
5 embedUrl: '' 5 originServerUrl: ''
6} 6}
diff --git a/client/src/environments/environment.ts b/client/src/environments/environment.ts
index e00523976..5d7011265 100644
--- a/client/src/environments/environment.ts
+++ b/client/src/environments/environment.ts
@@ -12,5 +12,5 @@ export const environment = {
12 production: true, 12 production: true,
13 hmr: false, 13 hmr: false,
14 apiUrl: '', 14 apiUrl: '',
15 embedUrl: '' 15 originServerUrl: ''
16} 16}
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index 4d70110fe..e6491b492 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -225,7 +225,7 @@
225 line-height: $button-height; 225 line-height: $button-height;
226 border-radius: 3px; 226 border-radius: 3px;
227 text-align: center; 227 text-align: center;
228 padding: 0 13px 0 13px; 228 padding: 0 17px 0 13px;
229 cursor: pointer; 229 cursor: pointer;
230} 230}
231 231
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 }
diff --git a/shared/extra-utils/feeds/feeds.ts b/shared/extra-utils/feeds/feeds.ts
index af6df2b20..bafbb9f94 100644
--- a/shared/extra-utils/feeds/feeds.ts
+++ b/shared/extra-utils/feeds/feeds.ts
@@ -1,6 +1,6 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2 2
3type FeedType = 'videos' | 'video-comments' 3type FeedType = 'videos' | 'video-comments' | 'subscriptions'
4 4
5function getXMLfeed (url: string, feed: FeedType, format?: string) { 5function getXMLfeed (url: string, feed: FeedType, format?: string) {
6 const path = '/feeds/' + feed + '.xml' 6 const path = '/feeds/' + feed + '.xml'