refactor scoped token service
This commit is contained in:
parent
afff310e50
commit
5beb89f223
|
@ -3,7 +3,7 @@ import { AfterViewChecked, Component, OnInit, ViewChild } from '@angular/core'
|
|||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
|
||||
import { Notifier } from '@app/core'
|
||||
import { copyToClipboard } from '../../../assets/player/utils'
|
||||
import { copyToClipboard } from '../../../root-helpers/utils'
|
||||
import { InstanceService } from '@app/shared/shared-instance'
|
||||
import { ServerConfig } from '@shared/models'
|
||||
import { ResolverData } from './about-instance.resolver'
|
||||
|
|
|
@ -161,7 +161,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
|
|||
getVideoEmbed (entry: VideoBlacklist) {
|
||||
return buildVideoOrPlaylistEmbed(
|
||||
buildVideoLink({
|
||||
baseUrl: `${environment.embedUrl}/videos/embed/${entry.video.uuid}`,
|
||||
baseUrl: `${environment.originServerUrl}/videos/embed/${entry.video.uuid}`,
|
||||
title: false,
|
||||
warningTitle: false
|
||||
})
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
<div class="form-group col-12 col-lg-4 col-xl-3">
|
||||
<h2 i18n class="applications-title">SUBSCRIPTION FEED</h2>
|
||||
<div i18n class="applications-description">
|
||||
Used to retrieve the list of videos of the creators
|
||||
you subscribed to from outside PeerTube
|
||||
Use third-party feed aggregators to retrieve the list of videos from
|
||||
channels you subscribed to. Make sure to keep your token private.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { AuthService, Notifier, ConfirmService } from '@app/core'
|
||||
import { AuthService, Notifier, ConfirmService, ScopedTokensService } from '@app/core'
|
||||
import { VideoService } from '@app/shared/shared-main'
|
||||
import { FeedFormat } from '@shared/models'
|
||||
import { Subject, merge } from 'rxjs'
|
||||
import { debounceTime } from 'rxjs/operators'
|
||||
import { ScopedToken } from '@shared/models/users/user-scoped-token'
|
||||
import { environment } from '../../../environments/environment'
|
||||
|
||||
@Component({
|
||||
selector: 'my-account-applications',
|
||||
|
@ -15,11 +15,11 @@ export class MyAccountApplicationsComponent implements OnInit {
|
|||
feedUrl: string
|
||||
feedToken: string
|
||||
|
||||
private baseURL = window.location.protocol + '//' + window.location.host
|
||||
private tokenStream = new Subject()
|
||||
private baseURL = environment.originServerUrl
|
||||
|
||||
constructor (
|
||||
private authService: AuthService,
|
||||
private scopedTokensService: ScopedTokensService,
|
||||
private videoService: VideoService,
|
||||
private notifier: Notifier,
|
||||
private confirmService: ConfirmService
|
||||
|
@ -27,31 +27,40 @@ export class MyAccountApplicationsComponent implements OnInit {
|
|||
|
||||
ngOnInit () {
|
||||
this.feedUrl = this.baseURL
|
||||
this.scopedTokensService.getScopedTokens()
|
||||
.subscribe(
|
||||
tokens => this.regenApplications(tokens),
|
||||
|
||||
merge(
|
||||
this.tokenStream,
|
||||
this.authService.userInformationLoaded
|
||||
).pipe(debounceTime(400))
|
||||
.subscribe(
|
||||
_ => {
|
||||
const user = this.authService.getUser()
|
||||
this.videoService.getVideoSubscriptionFeedUrls(user.account.id)
|
||||
.then(feeds => this.feedUrl = this.baseURL + feeds.find(f => f.format === FeedFormat.RSS).url)
|
||||
.then(_ => this.authService.getScopedTokens().then(tokens => this.feedToken = tokens.feedToken))
|
||||
},
|
||||
|
||||
err => {
|
||||
this.notifier.error(err.message)
|
||||
}
|
||||
)
|
||||
err => {
|
||||
this.notifier.error(err.message)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async renewToken () {
|
||||
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')
|
||||
const res = await this.confirmService.confirm(
|
||||
$localize`Renewing the token will disallow previously configured clients from retrieving the feed until they use the new token. Proceed?`,
|
||||
$localize`Renew token`
|
||||
)
|
||||
if (res === false) return
|
||||
|
||||
await this.authService.renewScopedTokens()
|
||||
this.notifier.success('Token renewed. Update your client configuration accordingly.')
|
||||
this.tokenStream.next()
|
||||
this.scopedTokensService.renewScopedTokens().subscribe(
|
||||
tokens => {
|
||||
this.regenApplications(tokens)
|
||||
this.notifier.success($localize`Token renewed. Update your client configuration accordingly.`)
|
||||
},
|
||||
|
||||
err => {
|
||||
this.notifier.error(err.message)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
private regenApplications (tokens: ScopedToken) {
|
||||
const user = this.authService.getUser()
|
||||
const feeds = this.videoService.getVideoSubscriptionFeedUrls(user.account.id, tokens.feedToken)
|
||||
this.feedUrl = this.baseURL + feeds.find(f => f.format === FeedFormat.RSS).url
|
||||
this.feedToken = tokens.feedToken
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,11 +41,6 @@ export class MyAccountComponent implements OnInit {
|
|||
label: $localize`Abuse reports`,
|
||||
routerLink: '/my-account/abuses',
|
||||
iconName: 'flag'
|
||||
},
|
||||
{
|
||||
label: $localize`Applications`,
|
||||
routerLink: '/my-account/applications',
|
||||
iconName: 'codesandbox'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -61,6 +56,11 @@ export class MyAccountComponent implements OnInit {
|
|||
routerLink: '/my-account/notifications'
|
||||
},
|
||||
|
||||
{
|
||||
label: $localize`Applications`,
|
||||
routerLink: '/my-account/applications'
|
||||
},
|
||||
|
||||
moderationEntries
|
||||
]
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ import { MyAccountDangerZoneComponent } from './my-account-settings/my-account-d
|
|||
import { MyAccountNotificationPreferencesComponent } from './my-account-settings/my-account-notification-preferences'
|
||||
import { MyAccountProfileComponent } from './my-account-settings/my-account-profile/my-account-profile.component'
|
||||
import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
|
||||
import { MyAccountApplicationsComponent } from './my-account-applications/my-account-applications.component'
|
||||
import { MyAccountComponent } from './my-account.component'
|
||||
import { VideoChangeOwnershipComponent } from './my-account-applications/my-account-applications.component'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -46,13 +46,13 @@ import { VideoChangeOwnershipComponent } from './my-account-applications/my-acco
|
|||
MyAccountChangePasswordComponent,
|
||||
MyAccountProfileComponent,
|
||||
MyAccountChangeEmailComponent,
|
||||
MyAccountApplicationsComponent,
|
||||
|
||||
MyAccountDangerZoneComponent,
|
||||
MyAccountBlocklistComponent,
|
||||
MyAccountAbusesListComponent,
|
||||
MyAccountServerBlocklistComponent,
|
||||
MyAccountNotificationsComponent,
|
||||
MyAccountNotificationPreferencesComponent,
|
||||
MyAccountNotificationPreferencesComponent
|
||||
],
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core'
|
||||
import { AuthService, LocalStorageService, Notifier, ScopedTokensService, ScreenService, ServerService, UserService } from '@app/core'
|
||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||
import { immutableAssign } from '@app/helpers'
|
||||
import { VideoService } from '@app/shared/shared-main'
|
||||
|
@ -9,6 +9,7 @@ import { AbstractVideoList, OwnerDisplayType } from '@app/shared/shared-video-mi
|
|||
import { VideoSortField, FeedFormat } from '@shared/models'
|
||||
import { copyToClipboard } from '../../../root-helpers/utils'
|
||||
import { environment } from '../../../environments/environment'
|
||||
import { forkJoin } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-user-subscriptions',
|
||||
|
@ -32,7 +33,8 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
|
|||
protected storageService: LocalStorageService,
|
||||
private userSubscription: UserSubscriptionService,
|
||||
private hooks: HooksService,
|
||||
private videoService: VideoService
|
||||
private videoService: VideoService,
|
||||
private scopedTokensService: ScopedTokensService
|
||||
) {
|
||||
super()
|
||||
|
||||
|
@ -49,9 +51,19 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
|
|||
super.ngOnInit()
|
||||
|
||||
const user = this.authService.getUser()
|
||||
let feedUrl = environment.embedUrl
|
||||
this.videoService.getVideoSubscriptionFeedUrls(user.account.id)
|
||||
.then((feeds: any) => feedUrl = feedUrl + feeds.find((f: any) => f.format === FeedFormat.RSS).url)
|
||||
let feedUrl = environment.originServerUrl
|
||||
|
||||
this.scopedTokensService.getScopedTokens().subscribe(
|
||||
tokens => {
|
||||
const feeds = this.videoService.getVideoSubscriptionFeedUrls(user.account.id, tokens.feedToken)
|
||||
feedUrl = feedUrl + feeds.find((f: any) => f.format === FeedFormat.RSS).url
|
||||
},
|
||||
|
||||
err => {
|
||||
this.notifier.error(err.message)
|
||||
}
|
||||
)
|
||||
|
||||
this.actions.unshift({
|
||||
label: $localize`Feed`,
|
||||
iconName: 'syndication',
|
||||
|
|
|
@ -11,7 +11,6 @@ import { environment } from '../../../environments/environment'
|
|||
import { RestExtractor } from '../rest/rest-extractor.service'
|
||||
import { AuthStatus } from './auth-status.model'
|
||||
import { AuthUser } from './auth-user.model'
|
||||
import { ScopedTokenType, ScopedToken } from '@shared/models/users/user-scoped-token'
|
||||
|
||||
interface UserLoginWithUsername extends UserLogin {
|
||||
access_token: string
|
||||
|
@ -27,7 +26,6 @@ export class AuthService {
|
|||
private static BASE_CLIENT_URL = environment.apiUrl + '/api/v1/oauth-clients/local'
|
||||
private static BASE_TOKEN_URL = environment.apiUrl + '/api/v1/users/token'
|
||||
private static BASE_REVOKE_TOKEN_URL = environment.apiUrl + '/api/v1/users/revoke-token'
|
||||
private static BASE_SCOPED_TOKENS_URL = environment.apiUrl + '/api/v1/users/scoped-tokens'
|
||||
private static BASE_USER_INFORMATION_URL = environment.apiUrl + '/api/v1/users/me'
|
||||
private static LOCAL_STORAGE_OAUTH_CLIENT_KEYS = {
|
||||
CLIENT_ID: 'client_id',
|
||||
|
@ -43,7 +41,6 @@ export class AuthService {
|
|||
private loginChanged: Subject<AuthStatus>
|
||||
private user: AuthUser = null
|
||||
private refreshingTokenObservable: Observable<any>
|
||||
private scopedTokens: ScopedToken
|
||||
|
||||
constructor (
|
||||
private http: HttpClient,
|
||||
|
@ -247,48 +244,6 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
|
|||
)
|
||||
}
|
||||
|
||||
getScopedTokens (): Promise<ScopedToken> {
|
||||
return new Promise((res, rej) => {
|
||||
if (this.scopedTokens) return res(this.scopedTokens)
|
||||
|
||||
const authHeaderValue = this.getRequestHeaderValue()
|
||||
const headers = new HttpHeaders().set('Authorization', authHeaderValue)
|
||||
|
||||
this.http.get<ScopedToken>(AuthService.BASE_SCOPED_TOKENS_URL, { headers })
|
||||
.subscribe(
|
||||
scopedTokens => {
|
||||
this.scopedTokens = scopedTokens
|
||||
res(this.scopedTokens)
|
||||
},
|
||||
|
||||
err => {
|
||||
console.error(err)
|
||||
rej(err)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
renewScopedTokens (): Promise<ScopedToken> {
|
||||
return new Promise((res, rej) => {
|
||||
const authHeaderValue = this.getRequestHeaderValue()
|
||||
const headers = new HttpHeaders().set('Authorization', authHeaderValue)
|
||||
|
||||
this.http.post<ScopedToken>(AuthService.BASE_SCOPED_TOKENS_URL, {}, { headers })
|
||||
.subscribe(
|
||||
scopedTokens => {
|
||||
this.scopedTokens = scopedTokens
|
||||
res(this.scopedTokens)
|
||||
},
|
||||
|
||||
err => {
|
||||
console.error(err)
|
||||
rej(err)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> {
|
||||
// User is not loaded yet, set manually auth header
|
||||
const headers = new HttpHeaders().set('Authorization', `${obj.token_type} ${obj.access_token}`)
|
||||
|
|
|
@ -12,6 +12,7 @@ import { LoadingBarModule } from '@ngx-loading-bar/core'
|
|||
import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client'
|
||||
import { LoadingBarRouterModule } from '@ngx-loading-bar/router'
|
||||
import { AuthService } from './auth'
|
||||
import { ScopedTokensService } from './scoped-tokens'
|
||||
import { ConfirmService } from './confirm'
|
||||
import { CheatSheetComponent } from './hotkeys'
|
||||
import { MenuService } from './menu'
|
||||
|
@ -57,6 +58,7 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra
|
|||
|
||||
providers: [
|
||||
AuthService,
|
||||
ScopedTokensService,
|
||||
ConfirmService,
|
||||
ServerService,
|
||||
ThemeService,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export * from './auth'
|
||||
export * from './scoped-tokens'
|
||||
export * from './confirm'
|
||||
export * from './hotkeys'
|
||||
export * from './menu'
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './scoped-tokens.service'
|
|
@ -0,0 +1,33 @@
|
|||
import { Injectable } from '@angular/core'
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { environment } from '../../../environments/environment'
|
||||
import { AuthService } from '../auth'
|
||||
import { ScopedToken } from '@shared/models/users/user-scoped-token'
|
||||
import { catchError } from 'rxjs/operators'
|
||||
import { RestExtractor } from '../rest'
|
||||
|
||||
@Injectable()
|
||||
export class ScopedTokensService {
|
||||
private static BASE_SCOPED_TOKENS_URL = environment.apiUrl + '/api/v1/users/scoped-tokens'
|
||||
|
||||
constructor (
|
||||
private authHttp: HttpClient,
|
||||
private restExtractor: RestExtractor
|
||||
) {}
|
||||
|
||||
getScopedTokens () {
|
||||
return this.authHttp
|
||||
.get<ScopedToken>(ScopedTokensService.BASE_SCOPED_TOKENS_URL)
|
||||
.pipe(
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
}
|
||||
|
||||
renewScopedTokens () {
|
||||
return this.authHttp
|
||||
.post<ScopedToken>(ScopedTokensService.BASE_SCOPED_TOKENS_URL, {})
|
||||
.pipe(
|
||||
catchError(res => this.restExtractor.handleError(res))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -58,7 +58,7 @@ function getAbsoluteAPIUrl () {
|
|||
}
|
||||
|
||||
function getAbsoluteEmbedUrl () {
|
||||
let absoluteEmbedUrl = environment.embedUrl
|
||||
let absoluteEmbedUrl = environment.originServerUrl
|
||||
if (!absoluteEmbedUrl) {
|
||||
// The Embed is on the same domain
|
||||
absoluteEmbedUrl = window.location.origin
|
||||
|
|
|
@ -112,7 +112,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
|
|||
getVideoEmbed (abuse: AdminAbuse) {
|
||||
return buildVideoOrPlaylistEmbed(
|
||||
buildVideoLink({
|
||||
baseUrl: `${environment.embedUrl}/videos/embed/${abuse.video.uuid}`,
|
||||
baseUrl: `${environment.originServerUrl}/videos/embed/${abuse.video.uuid}`,
|
||||
title: false,
|
||||
warningTitle: false,
|
||||
startTime: abuse.video.startAt,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Component, Input } from '@angular/core'
|
||||
import { Notifier } from '@app/core'
|
||||
import { FormGroup } from '@angular/forms'
|
||||
|
||||
@Component({
|
||||
selector: 'my-input-readonly-copy',
|
||||
|
|
|
@ -18,8 +18,7 @@ import {
|
|||
VideoFilter,
|
||||
VideoPrivacy,
|
||||
VideoSortField,
|
||||
VideoUpdate,
|
||||
VideoCreate
|
||||
VideoUpdate
|
||||
} from '@shared/models'
|
||||
import { environment } from '../../../../environments/environment'
|
||||
import { Account } from '../account/account.model'
|
||||
|
@ -44,13 +43,13 @@ export interface VideosProvider {
|
|||
export class VideoService implements VideosProvider {
|
||||
static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
|
||||
static BASE_FEEDS_URL = environment.apiUrl + '/feeds/videos.'
|
||||
static BASE_SUBSCRIPTION_FEEDS_URL = environment.apiUrl + '/feeds/subscriptions.'
|
||||
|
||||
constructor (
|
||||
private authHttp: HttpClient,
|
||||
private restExtractor: RestExtractor,
|
||||
private restService: RestService,
|
||||
private serverService: ServerService,
|
||||
private authService: AuthService
|
||||
private serverService: ServerService
|
||||
) {}
|
||||
|
||||
getVideoViewUrl (uuid: string) {
|
||||
|
@ -238,22 +237,22 @@ export class VideoService implements VideosProvider {
|
|||
)
|
||||
}
|
||||
|
||||
buildBaseFeedUrls (params: HttpParams) {
|
||||
buildBaseFeedUrls (params: HttpParams, base = VideoService.BASE_FEEDS_URL) {
|
||||
const feeds = [
|
||||
{
|
||||
format: FeedFormat.RSS,
|
||||
label: 'media rss 2.0',
|
||||
url: VideoService.BASE_FEEDS_URL + FeedFormat.RSS.toLowerCase()
|
||||
url: base + FeedFormat.RSS.toLowerCase()
|
||||
},
|
||||
{
|
||||
format: FeedFormat.ATOM,
|
||||
label: 'atom 1.0',
|
||||
url: VideoService.BASE_FEEDS_URL + FeedFormat.ATOM.toLowerCase()
|
||||
url: base + FeedFormat.ATOM.toLowerCase()
|
||||
},
|
||||
{
|
||||
format: FeedFormat.JSON,
|
||||
label: 'json 1.0',
|
||||
url: VideoService.BASE_FEEDS_URL + FeedFormat.JSON.toLowerCase()
|
||||
url: base + FeedFormat.JSON.toLowerCase()
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -294,14 +293,12 @@ export class VideoService implements VideosProvider {
|
|||
return this.buildBaseFeedUrls(params)
|
||||
}
|
||||
|
||||
async getVideoSubscriptionFeedUrls (accountId: number) {
|
||||
getVideoSubscriptionFeedUrls (accountId: number, feedToken: string) {
|
||||
let params = this.restService.addRestGetParams(new HttpParams())
|
||||
params = params.set('accountId', accountId.toString())
|
||||
|
||||
const { feedToken } = await this.authService.getScopedTokens()
|
||||
params = params.set('token', feedToken)
|
||||
|
||||
return this.buildBaseFeedUrls(params)
|
||||
return this.buildBaseFeedUrls(params, VideoService.BASE_SUBSCRIPTION_FEEDS_URL)
|
||||
}
|
||||
|
||||
getVideoFileMetadata (metadataUrl: string) {
|
||||
|
|
|
@ -2,5 +2,5 @@ export const environment = {
|
|||
production: false,
|
||||
hmr: false,
|
||||
apiUrl: 'http://localhost:9001',
|
||||
embedUrl: 'http://localhost:9001'
|
||||
originServerUrl: 'http://localhost:9001'
|
||||
}
|
||||
|
|
|
@ -2,5 +2,5 @@ export const environment = {
|
|||
production: false,
|
||||
hmr: true,
|
||||
apiUrl: '',
|
||||
embedUrl: 'http://localhost:9000'
|
||||
originServerUrl: 'http://localhost:9000'
|
||||
}
|
||||
|
|
|
@ -2,5 +2,5 @@ export const environment = {
|
|||
production: true,
|
||||
hmr: false,
|
||||
apiUrl: '',
|
||||
embedUrl: ''
|
||||
originServerUrl: ''
|
||||
}
|
||||
|
|
|
@ -12,5 +12,5 @@ export const environment = {
|
|||
production: true,
|
||||
hmr: false,
|
||||
apiUrl: '',
|
||||
embedUrl: ''
|
||||
originServerUrl: ''
|
||||
}
|
||||
|
|
|
@ -225,7 +225,7 @@
|
|||
line-height: $button-height;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
padding: 0 13px 0 13px;
|
||||
padding: 0 17px 0 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ import { cacheRoute } from '../middlewares/cache'
|
|||
import { VideoModel } from '../models/video/video'
|
||||
import { VideoCommentModel } from '../models/video/video-comment'
|
||||
import { VideoFilter } from '../../shared/models/videos/video-query.type'
|
||||
import { logger } from '../helpers/logger'
|
||||
|
||||
const feedsRouter = express.Router()
|
||||
|
||||
|
@ -47,10 +46,24 @@ feedsRouter.get('/feeds/videos.:format',
|
|||
})(ROUTE_CACHE_LIFETIME.FEEDS)),
|
||||
commonVideosFiltersValidator,
|
||||
asyncMiddleware(videoFeedsValidator),
|
||||
asyncMiddleware(videoSubscriptonFeedsValidator),
|
||||
asyncMiddleware(generateVideoFeed)
|
||||
)
|
||||
|
||||
feedsRouter.get('/feeds/subscriptions.:format',
|
||||
videosSortValidator,
|
||||
setDefaultVideosSort,
|
||||
feedsFormatValidator,
|
||||
setFeedFormatContentType,
|
||||
asyncMiddleware(cacheRoute({
|
||||
headerBlacklist: [
|
||||
'Content-Type'
|
||||
]
|
||||
})(ROUTE_CACHE_LIFETIME.FEEDS)),
|
||||
commonVideosFiltersValidator,
|
||||
asyncMiddleware(videoSubscriptonFeedsValidator),
|
||||
asyncMiddleware(generateVideoFeedForSubscriptions)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -61,7 +74,6 @@ export {
|
|||
|
||||
async function generateVideoCommentsFeed (req: express.Request, res: express.Response) {
|
||||
const start = 0
|
||||
|
||||
const video = res.locals.videoAll
|
||||
const account = res.locals.account
|
||||
const videoChannel = res.locals.videoChannel
|
||||
|
@ -125,10 +137,8 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res
|
|||
|
||||
async function generateVideoFeed (req: express.Request, res: express.Response) {
|
||||
const start = 0
|
||||
|
||||
const account = res.locals.account
|
||||
const videoChannel = res.locals.videoChannel
|
||||
const token = req.query.token
|
||||
const nsfw = buildNSFWFilter(res, req.query.nsfw)
|
||||
|
||||
let name: string
|
||||
|
@ -152,21 +162,10 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
|
|||
queryString: new URL(WEBSERVER.URL + req.url).search
|
||||
})
|
||||
|
||||
/**
|
||||
* We have two ways to query video results:
|
||||
* - one with account and token -> get subscription videos
|
||||
* - one with either account, channel, or nothing: just videos with these filters
|
||||
*/
|
||||
const options = token && token !== '' && res.locals.user
|
||||
? {
|
||||
followerActorId: res.locals.user.Account.Actor.id,
|
||||
user: res.locals.user,
|
||||
includeLocalVideos: false
|
||||
}
|
||||
: {
|
||||
accountId: account ? account.id : null,
|
||||
videoChannelId: videoChannel ? videoChannel.id : null
|
||||
}
|
||||
const options = {
|
||||
accountId: account ? account.id : null,
|
||||
videoChannelId: videoChannel ? videoChannel.id : null
|
||||
}
|
||||
|
||||
const resultList = await VideoModel.listForApi({
|
||||
start,
|
||||
|
@ -179,10 +178,86 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
|
|||
...options
|
||||
})
|
||||
|
||||
addVideosToFeed(feed, resultList.data)
|
||||
|
||||
// Now the feed generation is done, let's send it!
|
||||
return sendFeed(feed, req, res)
|
||||
}
|
||||
|
||||
async function generateVideoFeedForSubscriptions (req: express.Request, res: express.Response) {
|
||||
const start = 0
|
||||
const account = res.locals.account
|
||||
const nsfw = buildNSFWFilter(res, req.query.nsfw)
|
||||
const name = account.getDisplayName()
|
||||
const description = account.description
|
||||
|
||||
const feed = initFeed({
|
||||
name,
|
||||
description,
|
||||
resourceType: 'videos',
|
||||
queryString: new URL(WEBSERVER.URL + req.url).search
|
||||
})
|
||||
|
||||
const options = {
|
||||
followerActorId: res.locals.user.Account.Actor.id,
|
||||
user: res.locals.user
|
||||
}
|
||||
|
||||
const resultList = await VideoModel.listForApi({
|
||||
start,
|
||||
count: FEEDS.COUNT,
|
||||
sort: req.query.sort,
|
||||
includeLocalVideos: true,
|
||||
nsfw,
|
||||
filter: req.query.filter as VideoFilter,
|
||||
withFiles: true,
|
||||
...options
|
||||
})
|
||||
|
||||
addVideosToFeed(feed, resultList.data)
|
||||
|
||||
// Now the feed generation is done, let's send it!
|
||||
return sendFeed(feed, req, res)
|
||||
}
|
||||
|
||||
function initFeed (parameters: {
|
||||
name: string
|
||||
description: string
|
||||
resourceType?: 'videos' | 'video-comments'
|
||||
queryString?: string
|
||||
}) {
|
||||
const webserverUrl = WEBSERVER.URL
|
||||
const { name, description, resourceType, queryString } = parameters
|
||||
|
||||
return new Feed({
|
||||
title: name,
|
||||
description,
|
||||
// updated: TODO: somehowGetLatestUpdate, // optional, default = today
|
||||
id: webserverUrl,
|
||||
link: webserverUrl,
|
||||
image: webserverUrl + '/client/assets/images/icons/icon-96x96.png',
|
||||
favicon: webserverUrl + '/client/assets/images/favicon.png',
|
||||
copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` +
|
||||
` and potential licenses granted by each content's rightholder.`,
|
||||
generator: `Toraifōsu`, // ^.~
|
||||
feedLinks: {
|
||||
json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`,
|
||||
atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`,
|
||||
rss: `${webserverUrl}/feeds/${resourceType}.xml${queryString}`
|
||||
},
|
||||
author: {
|
||||
name: 'Instance admin of ' + CONFIG.INSTANCE.NAME,
|
||||
email: CONFIG.ADMIN.EMAIL,
|
||||
link: `${webserverUrl}/about`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addVideosToFeed (feed, videos: VideoModel[]) {
|
||||
/**
|
||||
* Adding video items to the feed object, one at a time
|
||||
*/
|
||||
resultList.data.forEach(video => {
|
||||
for (const video of videos) {
|
||||
const formattedVideoFiles = video.getFormattedVideoFilesJSON()
|
||||
|
||||
const torrents = formattedVideoFiles.map(videoFile => ({
|
||||
|
@ -252,43 +327,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
|
|||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
// Now the feed generation is done, let's send it!
|
||||
return sendFeed(feed, req, res)
|
||||
}
|
||||
|
||||
function initFeed (parameters: {
|
||||
name: string
|
||||
description: string
|
||||
resourceType?: 'videos' | 'video-comments'
|
||||
queryString?: string
|
||||
}) {
|
||||
const webserverUrl = WEBSERVER.URL
|
||||
const { name, description, resourceType, queryString } = parameters
|
||||
|
||||
return new Feed({
|
||||
title: name,
|
||||
description,
|
||||
// updated: TODO: somehowGetLatestUpdate, // optional, default = today
|
||||
id: webserverUrl,
|
||||
link: webserverUrl,
|
||||
image: webserverUrl + '/client/assets/images/icons/icon-96x96.png',
|
||||
favicon: webserverUrl + '/client/assets/images/favicon.png',
|
||||
copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` +
|
||||
` and potential licenses granted by each content's rightholder.`,
|
||||
generator: `Toraifōsu`, // ^.~
|
||||
feedLinks: {
|
||||
json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`,
|
||||
atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`,
|
||||
rss: `${webserverUrl}/feeds/${resourceType}.xml${queryString}`
|
||||
},
|
||||
author: {
|
||||
name: 'Instance admin of ' + CONFIG.INSTANCE.NAME,
|
||||
email: CONFIG.ADMIN.EMAIL,
|
||||
link: `${webserverUrl}/about`
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function sendFeed (feed, req: express.Request, res: express.Response) {
|
||||
|
|
|
@ -28,8 +28,7 @@ async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, se
|
|||
if (!account) {
|
||||
if (sendNotFound === true) {
|
||||
res.status(404)
|
||||
.send({ error: 'Account not found' })
|
||||
.end()
|
||||
.json({ error: 'Account not found' })
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -41,12 +40,11 @@ async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, se
|
|||
}
|
||||
|
||||
async function doesUserFeedTokenCorrespond (id: number | string, token: string, res: Response) {
|
||||
const user = await UserModel.loadById(parseInt(id + '', 10))
|
||||
const user = await UserModel.loadByIdWithChannels(parseInt(id + '', 10))
|
||||
|
||||
if (token !== user.feedToken) {
|
||||
res.status(401)
|
||||
.send({ error: 'User and token mismatch' })
|
||||
.end()
|
||||
.json({ error: 'User and token mismatch' })
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -64,8 +64,8 @@ const videoFeedsValidator = [
|
|||
]
|
||||
|
||||
const videoSubscriptonFeedsValidator = [
|
||||
query('accountId').optional().custom(isIdValid),
|
||||
query('token').optional(),
|
||||
query('accountId').custom(isIdValid),
|
||||
query('token'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking feeds parameters', { parameters: req.query })
|
||||
|
@ -74,6 +74,7 @@ const videoSubscriptonFeedsValidator = [
|
|||
|
||||
// a token alone is erroneous
|
||||
if (req.query.token && !req.query.accountId) return
|
||||
if (req.query.accountId && !await doesAccountIdExist(req.query.accountId, res)) return
|
||||
if (req.query.token && !await doesUserFeedTokenCorrespond(res.locals.account.userId, req.query.token, res)) return
|
||||
|
||||
return next()
|
||||
|
|
|
@ -326,7 +326,7 @@ describe('Test syndication feeds', () => {
|
|||
const res = await listUserSubscriptionVideos(servers[0].url, feeduserAccessToken)
|
||||
expect(res.body.total).to.equal(0)
|
||||
|
||||
const json = await getJSONfeed(servers[0].url, 'videos', { accountId: feeduserAccountId, token: feeduserFeedToken })
|
||||
const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: feeduserAccountId, token: feeduserFeedToken })
|
||||
const jsonObj = JSON.parse(json.text)
|
||||
expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos
|
||||
}
|
||||
|
@ -337,7 +337,7 @@ describe('Test syndication feeds', () => {
|
|||
const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken)
|
||||
expect(res.body.total).to.equal(0)
|
||||
|
||||
const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken })
|
||||
const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken })
|
||||
const jsonObj = JSON.parse(json.text)
|
||||
expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos
|
||||
}
|
||||
|
@ -354,7 +354,7 @@ describe('Test syndication feeds', () => {
|
|||
expect(res.body.total).to.equal(1)
|
||||
expect(res.body.data[0].name).to.equal('user video')
|
||||
|
||||
const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken, version: 1 })
|
||||
const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 1 })
|
||||
const jsonObj = JSON.parse(json.text)
|
||||
expect(jsonObj.items.length).to.be.equal(1) // subscribed to self, it should not list the instance's videos but list john's
|
||||
}
|
||||
|
@ -370,7 +370,7 @@ describe('Test syndication feeds', () => {
|
|||
const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken)
|
||||
expect(res.body.total).to.equal(2, "there should be 2 videos part of the subscription")
|
||||
|
||||
const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken, version: 2 })
|
||||
const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 2 })
|
||||
const jsonObj = JSON.parse(json.text)
|
||||
expect(jsonObj.items.length).to.be.equal(2) // subscribed to root, it should not list the instance's videos but list root/john's
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as request from 'supertest'
|
||||
|
||||
type FeedType = 'videos' | 'video-comments'
|
||||
type FeedType = 'videos' | 'video-comments' | 'subscriptions'
|
||||
|
||||
function getXMLfeed (url: string, feed: FeedType, format?: string) {
|
||||
const path = '/feeds/' + feed + '.xml'
|
||||
|
|
Loading…
Reference in New Issue