diff options
18 files changed, 303 insertions, 65 deletions
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html index 114a9e517..c3ef1d894 100644 --- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html +++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div *ngIf="account" class="row"> | 1 | <div *ngIf="account" class="row"> |
2 | <a | 2 | <a |
3 | *ngFor="let videoChannel of videoChannels" [routerLink]="[ '/video-channels', videoChannel.name ]" | 3 | *ngFor="let videoChannel of videoChannels" [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" |
4 | class="video-channel" i18n-title title="See this video channel" | 4 | class="video-channel" i18n-title title="See this video channel" |
5 | > | 5 | > |
6 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> | 6 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> |
diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html index 3752de49f..2d76990f7 100644 --- a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html +++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html | |||
@@ -1,13 +1,13 @@ | |||
1 | <div class="video-channels" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()"> | 1 | <div class="video-channels" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()"> |
2 | <div *ngFor="let videoChannel of videoChannels" class="video-channel"> | 2 | <div *ngFor="let videoChannel of videoChannels" class="video-channel"> |
3 | <a [routerLink]="[ '/video-channels', videoChannel.name ]"> | 3 | <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]"> |
4 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> | 4 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> |
5 | </a> | 5 | </a> |
6 | 6 | ||
7 | <div class="video-channel-info"> | 7 | <div class="video-channel-info"> |
8 | <a [routerLink]="[ '/video-channels', videoChannel.name ]" class="video-channel-names" i18n-title title="Go to the channel"> | 8 | <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" class="video-channel-names" i18n-title title="Go to the channel"> |
9 | <div class="video-channel-display-name">{{ videoChannel.displayName }}</div> | 9 | <div class="video-channel-display-name">{{ videoChannel.displayName }}</div> |
10 | <div class="video-channel-name">{{ videoChannel.name }}</div> | 10 | <div class="video-channel-name">{{ videoChannel.nameWithHost }}</div> |
11 | </a> | 11 | </a> |
12 | 12 | ||
13 | <div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div> | 13 | <div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div> |
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html index 548645a76..df74b19b6 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html | |||
@@ -7,14 +7,14 @@ | |||
7 | 7 | ||
8 | <div class="video-channels"> | 8 | <div class="video-channels"> |
9 | <div *ngFor="let videoChannel of videoChannels" class="video-channel"> | 9 | <div *ngFor="let videoChannel of videoChannels" class="video-channel"> |
10 | <a [routerLink]="[ '/video-channels', videoChannel.name ]"> | 10 | <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]"> |
11 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> | 11 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> |
12 | </a> | 12 | </a> |
13 | 13 | ||
14 | <div class="video-channel-info"> | 14 | <div class="video-channel-info"> |
15 | <a [routerLink]="[ '/video-channels', videoChannel.name ]" class="video-channel-names" i18n-title title="Go to the channel"> | 15 | <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" class="video-channel-names" i18n-title title="Go to the channel"> |
16 | <div class="video-channel-display-name">{{ videoChannel.displayName }}</div> | 16 | <div class="video-channel-display-name">{{ videoChannel.displayName }}</div> |
17 | <div class="video-channel-name">{{ videoChannel.name }}</div> | 17 | <div class="video-channel-name">{{ videoChannel.nameWithHost }}</div> |
18 | </a> | 18 | </a> |
19 | 19 | ||
20 | <div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div> | 20 | <div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div> |
@@ -23,7 +23,7 @@ | |||
23 | <div class="video-channel-buttons"> | 23 | <div class="video-channel-buttons"> |
24 | <my-delete-button (click)="deleteVideoChannel(videoChannel)"></my-delete-button> | 24 | <my-delete-button (click)="deleteVideoChannel(videoChannel)"></my-delete-button> |
25 | 25 | ||
26 | <my-edit-button [routerLink]="[ 'update', videoChannel.name ]"></my-edit-button> | 26 | <my-edit-button [routerLink]="[ 'update', videoChannel.nameWithHost ]"></my-edit-button> |
27 | </div> | 27 | </div> |
28 | </div> | 28 | </div> |
29 | </div> | 29 | </div> |
diff --git a/client/src/app/+video-channels/video-channels.component.scss b/client/src/app/+video-channels/video-channels.component.scss index a63b1ec06..711b1839d 100644 --- a/client/src/app/+video-channels/video-channels.component.scss +++ b/client/src/app/+video-channels/video-channels.component.scss | |||
@@ -11,11 +11,4 @@ | |||
11 | .actor-name { | 11 | .actor-name { |
12 | flex-grow: 1; | 12 | flex-grow: 1; |
13 | } | 13 | } |
14 | |||
15 | my-subscribe-button { | ||
16 | /deep/ span[role=button] { | ||
17 | padding: 7px 12px; | ||
18 | font-size: 16px; | ||
19 | } | ||
20 | } | ||
21 | } \ No newline at end of file | 14 | } \ No newline at end of file |
diff --git a/client/src/app/search/search.component.html b/client/src/app/search/search.component.html index 83d014987..0d09ebbe6 100644 --- a/client/src/app/search/search.component.html +++ b/client/src/app/search/search.component.html | |||
@@ -27,14 +27,14 @@ | |||
27 | </div> | 27 | </div> |
28 | 28 | ||
29 | <div *ngFor="let videoChannel of videoChannels" class="entry video-channel"> | 29 | <div *ngFor="let videoChannel of videoChannels" class="entry video-channel"> |
30 | <a [routerLink]="[ '/video-channels', videoChannel.name ]"> | 30 | <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]"> |
31 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> | 31 | <img [src]="videoChannel.avatarUrl" alt="Avatar" /> |
32 | </a> | 32 | </a> |
33 | 33 | ||
34 | <div class="video-channel-info"> | 34 | <div class="video-channel-info"> |
35 | <a [routerLink]="[ '/video-channels', videoChannel.name ]" class="video-channel-names"> | 35 | <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" class="video-channel-names"> |
36 | <div class="video-channel-display-name">{{ videoChannel.displayName }}</div> | 36 | <div class="video-channel-display-name">{{ videoChannel.displayName }}</div> |
37 | <div class="video-channel-name">{{ videoChannel.name }}</div> | 37 | <div class="video-channel-name">{{ videoChannel.nameWithHost }}</div> |
38 | </a> | 38 | </a> |
39 | 39 | ||
40 | <div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div> | 40 | <div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div> |
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts index 1a934c8e2..558db9543 100644 --- a/client/src/app/shared/video/video.service.ts +++ b/client/src/app/shared/video/video.service.ts | |||
@@ -4,14 +4,7 @@ import { Injectable } from '@angular/core' | |||
4 | import { Observable } from 'rxjs' | 4 | import { Observable } from 'rxjs' |
5 | import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } from '../../../../../shared' | 5 | import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } from '../../../../../shared' |
6 | import { ResultList } from '../../../../../shared/models/result-list.model' | 6 | import { ResultList } from '../../../../../shared/models/result-list.model' |
7 | import { | 7 | import { UserVideoRate, UserVideoRateUpdate, VideoFilter, VideoRateType, VideoUpdate } from '../../../../../shared/models/videos' |
8 | UserVideoRate, | ||
9 | UserVideoRateUpdate, | ||
10 | VideoChannel, | ||
11 | VideoFilter, | ||
12 | VideoRateType, | ||
13 | VideoUpdate | ||
14 | } from '../../../../../shared/models/videos' | ||
15 | import { FeedFormat } from '../../../../../shared/models/feeds/feed-format.enum' | 8 | import { FeedFormat } from '../../../../../shared/models/feeds/feed-format.enum' |
16 | import { environment } from '../../../environments/environment' | 9 | import { environment } from '../../../environments/environment' |
17 | import { ComponentPagination } from '../rest/component-pagination.model' | 10 | import { ComponentPagination } from '../rest/component-pagination.model' |
@@ -28,6 +21,7 @@ import { AccountService } from '@app/shared/account/account.service' | |||
28 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 21 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
29 | import { ServerService } from '@app/core' | 22 | import { ServerService } from '@app/core' |
30 | import { UserSubscriptionService } from '@app/shared/user-subscription' | 23 | import { UserSubscriptionService } from '@app/shared/user-subscription' |
24 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
31 | 25 | ||
32 | @Injectable() | 26 | @Injectable() |
33 | export class VideoService { | 27 | export class VideoService { |
@@ -151,7 +145,7 @@ export class VideoService { | |||
151 | params = this.restService.addRestGetParams(params, pagination, sort) | 145 | params = this.restService.addRestGetParams(params, pagination, sort) |
152 | 146 | ||
153 | return this.authHttp | 147 | return this.authHttp |
154 | .get<ResultList<Video>>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.name + '/videos', { params }) | 148 | .get<ResultList<Video>>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/videos', { params }) |
155 | .pipe( | 149 | .pipe( |
156 | switchMap(res => this.extractVideos(res)), | 150 | switchMap(res => this.extractVideos(res)), |
157 | catchError(err => this.restExtractor.handleError(err)) | 151 | catchError(err => this.restExtractor.handleError(err)) |
diff --git a/server/controllers/api/search.ts b/server/controllers/api/search.ts index 87aa5d76f..959d79855 100644 --- a/server/controllers/api/search.ts +++ b/server/controllers/api/search.ts | |||
@@ -41,7 +41,6 @@ searchRouter.get('/video-channels', | |||
41 | videoChannelsSearchSortValidator, | 41 | videoChannelsSearchSortValidator, |
42 | setDefaultSearchSort, | 42 | setDefaultSearchSort, |
43 | optionalAuthenticate, | 43 | optionalAuthenticate, |
44 | commonVideosFiltersValidator, | ||
45 | videoChannelsSearchValidator, | 44 | videoChannelsSearchValidator, |
46 | asyncMiddleware(searchVideoChannels) | 45 | asyncMiddleware(searchVideoChannels) |
47 | ) | 46 | ) |
@@ -59,9 +58,9 @@ function searchVideoChannels (req: express.Request, res: express.Response) { | |||
59 | const isURISearch = search.startsWith('http://') || search.startsWith('https://') | 58 | const isURISearch = search.startsWith('http://') || search.startsWith('https://') |
60 | 59 | ||
61 | const parts = search.split('@') | 60 | const parts = search.split('@') |
62 | const isHandleSearch = parts.length === 2 && parts.every(p => p.indexOf(' ') === -1) | 61 | const isWebfingerSearch = parts.length === 2 && parts.every(p => p.indexOf(' ') === -1) |
63 | 62 | ||
64 | if (isURISearch || isHandleSearch) return searchVideoChannelURI(search, isHandleSearch, res) | 63 | if (isURISearch || isWebfingerSearch) return searchVideoChannelURI(search, isWebfingerSearch, res) |
65 | 64 | ||
66 | return searchVideoChannelsDB(query, res) | 65 | return searchVideoChannelsDB(query, res) |
67 | } | 66 | } |
@@ -81,17 +80,21 @@ async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: expr | |||
81 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 80 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
82 | } | 81 | } |
83 | 82 | ||
84 | async function searchVideoChannelURI (search: string, isHandleSearch: boolean, res: express.Response) { | 83 | async function searchVideoChannelURI (search: string, isWebfingerSearch: boolean, res: express.Response) { |
85 | let videoChannel: VideoChannelModel | 84 | let videoChannel: VideoChannelModel |
85 | let uri = search | ||
86 | 86 | ||
87 | if (isUserAbleToSearchRemoteURI(res)) { | 87 | if (isWebfingerSearch) uri = await loadActorUrlOrGetFromWebfinger(search) |
88 | let uri = search | ||
89 | if (isHandleSearch) uri = await loadActorUrlOrGetFromWebfinger(search) | ||
90 | 88 | ||
91 | const actor = await getOrCreateActorAndServerAndModel(uri) | 89 | if (isUserAbleToSearchRemoteURI(res)) { |
92 | videoChannel = actor.VideoChannel | 90 | try { |
91 | const actor = await getOrCreateActorAndServerAndModel(uri) | ||
92 | videoChannel = actor.VideoChannel | ||
93 | } catch (err) { | ||
94 | logger.info('Cannot search remote video channel %s.', uri, { err }) | ||
95 | } | ||
93 | } else { | 96 | } else { |
94 | videoChannel = await VideoChannelModel.loadByUrlAndPopulateAccount(search) | 97 | videoChannel = await VideoChannelModel.loadByUrlAndPopulateAccount(uri) |
95 | } | 98 | } |
96 | 99 | ||
97 | return res.json({ | 100 | return res.json({ |
@@ -138,7 +141,7 @@ async function searchVideoURI (url: string, res: express.Response) { | |||
138 | const result = await getOrCreateVideoAndAccountAndChannel(url, syncParam) | 141 | const result = await getOrCreateVideoAndAccountAndChannel(url, syncParam) |
139 | video = result ? result.video : undefined | 142 | video = result ? result.video : undefined |
140 | } catch (err) { | 143 | } catch (err) { |
141 | logger.info('Cannot search remote video %s.', url) | 144 | logger.info('Cannot search remote video %s.', url, { err }) |
142 | } | 145 | } |
143 | } else { | 146 | } else { |
144 | video = await VideoModel.loadByUrlAndPopulateAccount(url) | 147 | video = await VideoModel.loadByUrlAndPopulateAccount(url) |
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts index 5c60de10c..10fcec462 100644 --- a/server/helpers/webfinger.ts +++ b/server/helpers/webfinger.ts | |||
@@ -3,6 +3,7 @@ import { WebFingerData } from '../../shared' | |||
3 | import { ActorModel } from '../models/activitypub/actor' | 3 | import { ActorModel } from '../models/activitypub/actor' |
4 | import { isTestInstance } from './core-utils' | 4 | import { isTestInstance } from './core-utils' |
5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' | 5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' |
6 | import { CONFIG } from '../initializers' | ||
6 | 7 | ||
7 | const webfinger = new WebFinger({ | 8 | const webfinger = new WebFinger({ |
8 | webfist_fallback: false, | 9 | webfist_fallback: false, |
@@ -13,8 +14,14 @@ const webfinger = new WebFinger({ | |||
13 | 14 | ||
14 | async function loadActorUrlOrGetFromWebfinger (uri: string) { | 15 | async function loadActorUrlOrGetFromWebfinger (uri: string) { |
15 | const [ name, host ] = uri.split('@') | 16 | const [ name, host ] = uri.split('@') |
17 | let actor: ActorModel | ||
18 | |||
19 | if (host === CONFIG.WEBSERVER.HOST) { | ||
20 | actor = await ActorModel.loadLocalByName(name) | ||
21 | } else { | ||
22 | actor = await ActorModel.loadByNameAndHost(name, host) | ||
23 | } | ||
16 | 24 | ||
17 | const actor = await ActorModel.loadByNameAndHost(name, host) | ||
18 | if (actor) return actor.url | 25 | if (actor) return actor.url |
19 | 26 | ||
20 | return getUrlFromWebfinger(uri) | 27 | return getUrlFromWebfinger(uri) |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 9beb9b7c2..a0dd78f42 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -44,7 +44,7 @@ const SORTABLE_COLUMNS = { | |||
44 | FOLLOWING: [ 'createdAt' ], | 44 | FOLLOWING: [ 'createdAt' ], |
45 | 45 | ||
46 | VIDEOS_SEARCH: [ 'match', 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes' ], | 46 | VIDEOS_SEARCH: [ 'match', 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes' ], |
47 | VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName' ] | 47 | VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName', 'createdAt' ] |
48 | } | 48 | } |
49 | 49 | ||
50 | const OAUTH_LIFETIME = { | 50 | const OAUTH_LIFETIME = { |
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index 9922229d2..22e1c9f19 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -48,7 +48,7 @@ async function getOrCreateActorAndServerAndModel (activityActor: string | Activi | |||
48 | 48 | ||
49 | // We don't have this actor in our database, fetch it on remote | 49 | // We don't have this actor in our database, fetch it on remote |
50 | if (!actor) { | 50 | if (!actor) { |
51 | const result = await fetchRemoteActor(actorUrl) | 51 | const { result } = await fetchRemoteActor(actorUrl) |
52 | if (result === undefined) throw new Error('Cannot fetch remote actor.') | 52 | if (result === undefined) throw new Error('Cannot fetch remote actor.') |
53 | 53 | ||
54 | // Create the attributed to actor | 54 | // Create the attributed to actor |
@@ -70,7 +70,13 @@ async function getOrCreateActorAndServerAndModel (activityActor: string | Activi | |||
70 | actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, result, ownerActor) | 70 | actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, result, ownerActor) |
71 | } | 71 | } |
72 | 72 | ||
73 | return retryTransactionWrapper(refreshActorIfNeeded, actor) | 73 | if (actor.Account) actor.Account.Actor = actor |
74 | if (actor.VideoChannel) actor.VideoChannel.Actor = actor | ||
75 | |||
76 | actor = await retryTransactionWrapper(refreshActorIfNeeded, actor) | ||
77 | if (!actor) throw new Error('Actor ' + actor.url + ' does not exist anymore.') | ||
78 | |||
79 | return actor | ||
74 | } | 80 | } |
75 | 81 | ||
76 | function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) { | 82 | function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) { |
@@ -264,7 +270,7 @@ type FetchRemoteActorResult = { | |||
264 | avatarName?: string | 270 | avatarName?: string |
265 | attributedTo: ActivityPubAttributedTo[] | 271 | attributedTo: ActivityPubAttributedTo[] |
266 | } | 272 | } |
267 | async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResult> { | 273 | async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: number, result: FetchRemoteActorResult }> { |
268 | const options = { | 274 | const options = { |
269 | uri: actorUrl, | 275 | uri: actorUrl, |
270 | method: 'GET', | 276 | method: 'GET', |
@@ -281,7 +287,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu | |||
281 | 287 | ||
282 | if (isActorObjectValid(actorJSON) === false) { | 288 | if (isActorObjectValid(actorJSON) === false) { |
283 | logger.debug('Remote actor JSON is not valid.', { actorJSON: actorJSON }) | 289 | logger.debug('Remote actor JSON is not valid.', { actorJSON: actorJSON }) |
284 | return undefined | 290 | return { result: undefined, statusCode: requestResult.response.statusCode } |
285 | } | 291 | } |
286 | 292 | ||
287 | const followersCount = await fetchActorTotalItems(actorJSON.followers) | 293 | const followersCount = await fetchActorTotalItems(actorJSON.followers) |
@@ -307,12 +313,15 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu | |||
307 | 313 | ||
308 | const name = actorJSON.name || actorJSON.preferredUsername | 314 | const name = actorJSON.name || actorJSON.preferredUsername |
309 | return { | 315 | return { |
310 | actor, | 316 | statusCode: requestResult.response.statusCode, |
311 | name, | 317 | result: { |
312 | avatarName, | 318 | actor, |
313 | summary: actorJSON.summary, | 319 | name, |
314 | support: actorJSON.support, | 320 | avatarName, |
315 | attributedTo: actorJSON.attributedTo | 321 | summary: actorJSON.summary, |
322 | support: actorJSON.support, | ||
323 | attributedTo: actorJSON.attributedTo | ||
324 | } | ||
316 | } | 325 | } |
317 | } | 326 | } |
318 | 327 | ||
@@ -355,7 +364,14 @@ async function refreshActorIfNeeded (actor: ActorModel): Promise<ActorModel> { | |||
355 | 364 | ||
356 | try { | 365 | try { |
357 | const actorUrl = await getUrlFromWebfinger(actor.preferredUsername + '@' + actor.getHost()) | 366 | const actorUrl = await getUrlFromWebfinger(actor.preferredUsername + '@' + actor.getHost()) |
358 | const result = await fetchRemoteActor(actorUrl) | 367 | const { result, statusCode } = await fetchRemoteActor(actorUrl) |
368 | |||
369 | if (statusCode === 404) { | ||
370 | logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url) | ||
371 | actor.Account ? actor.Account.destroy() : actor.VideoChannel.destroy() | ||
372 | return undefined | ||
373 | } | ||
374 | |||
359 | if (result === undefined) { | 375 | if (result === undefined) { |
360 | logger.warn('Cannot fetch remote actor in refresh actor.') | 376 | logger.warn('Cannot fetch remote actor in refresh actor.') |
361 | return actor | 377 | return actor |
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index ebb2d47c2..8bc095997 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts | |||
@@ -325,15 +325,13 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
325 | }, | 325 | }, |
326 | include: [ | 326 | include: [ |
327 | { | 327 | { |
328 | attributes: { | 328 | attributes: [ 'id' ], |
329 | exclude: unusedActorAttributesForAPI | 329 | model: ActorModel.unscoped(), |
330 | }, | ||
331 | model: ActorModel, | ||
332 | as: 'ActorFollowing', | 330 | as: 'ActorFollowing', |
333 | required: true, | 331 | required: true, |
334 | include: [ | 332 | include: [ |
335 | { | 333 | { |
336 | model: VideoChannelModel, | 334 | model: VideoChannelModel.unscoped(), |
337 | required: true, | 335 | required: true, |
338 | include: [ | 336 | include: [ |
339 | { | 337 | { |
@@ -344,7 +342,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
344 | required: true | 342 | required: true |
345 | }, | 343 | }, |
346 | { | 344 | { |
347 | model: AccountModel, | 345 | model: AccountModel.unscoped(), |
348 | required: true, | 346 | required: true, |
349 | include: [ | 347 | include: [ |
350 | { | 348 | { |
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index e16bd5d79..119d0c1da 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts | |||
@@ -50,7 +50,9 @@ export const unusedActorAttributesForAPI = [ | |||
50 | 'sharedInboxUrl', | 50 | 'sharedInboxUrl', |
51 | 'followersUrl', | 51 | 'followersUrl', |
52 | 'followingUrl', | 52 | 'followingUrl', |
53 | 'url' | 53 | 'url', |
54 | 'createdAt', | ||
55 | 'updatedAt' | ||
54 | ] | 56 | ] |
55 | 57 | ||
56 | @DefaultScope({ | 58 | @DefaultScope({ |
diff --git a/server/tests/api/check-params/search.ts b/server/tests/api/check-params/search.ts index d35eac7fe..eabf602ac 100644 --- a/server/tests/api/check-params/search.ts +++ b/server/tests/api/check-params/search.ts | |||
@@ -6,7 +6,6 @@ import { flushTests, immutableAssign, killallServers, makeGetRequest, runServer, | |||
6 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' | 6 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' |
7 | 7 | ||
8 | describe('Test videos API validator', function () { | 8 | describe('Test videos API validator', function () { |
9 | const path = '/api/v1/search/videos/' | ||
10 | let server: ServerInfo | 9 | let server: ServerInfo |
11 | 10 | ||
12 | // --------------------------------------------------------------- | 11 | // --------------------------------------------------------------- |
@@ -20,6 +19,8 @@ describe('Test videos API validator', function () { | |||
20 | }) | 19 | }) |
21 | 20 | ||
22 | describe('When searching videos', function () { | 21 | describe('When searching videos', function () { |
22 | const path = '/api/v1/search/videos/' | ||
23 | |||
23 | const query = { | 24 | const query = { |
24 | search: 'coucou' | 25 | search: 'coucou' |
25 | } | 26 | } |
@@ -111,6 +112,30 @@ describe('Test videos API validator', function () { | |||
111 | }) | 112 | }) |
112 | }) | 113 | }) |
113 | 114 | ||
115 | describe('When searching video channels', function () { | ||
116 | const path = '/api/v1/search/video-channels/' | ||
117 | |||
118 | const query = { | ||
119 | search: 'coucou' | ||
120 | } | ||
121 | |||
122 | it('Should fail with a bad start pagination', async function () { | ||
123 | await checkBadStartPagination(server.url, path, null, query) | ||
124 | }) | ||
125 | |||
126 | it('Should fail with a bad count pagination', async function () { | ||
127 | await checkBadCountPagination(server.url, path, null, query) | ||
128 | }) | ||
129 | |||
130 | it('Should fail with an incorrect sort', async function () { | ||
131 | await checkBadSortPagination(server.url, path, null, query) | ||
132 | }) | ||
133 | |||
134 | it('Should success with the correct parameters', async function () { | ||
135 | await makeGetRequest({ url: server.url, path, query, statusCodeExpected: 200 }) | ||
136 | }) | ||
137 | }) | ||
138 | |||
114 | after(async function () { | 139 | after(async function () { |
115 | killallServers([ server ]) | 140 | killallServers([ server ]) |
116 | 141 | ||
diff --git a/server/tests/api/search/index.ts b/server/tests/api/search/index.ts index 64b3d0910..d573c8a9e 100644 --- a/server/tests/api/search/index.ts +++ b/server/tests/api/search/index.ts | |||
@@ -1,2 +1,3 @@ | |||
1 | import './search-activitypub-video-channels' | ||
1 | import './search-activitypub-videos' | 2 | import './search-activitypub-videos' |
2 | import './search-videos' | 3 | import './search-videos' |
diff --git a/server/tests/api/search/search-activitypub-video-channels.ts b/server/tests/api/search/search-activitypub-video-channels.ts new file mode 100644 index 000000000..512cb32fd --- /dev/null +++ b/server/tests/api/search/search-activitypub-video-channels.ts | |||
@@ -0,0 +1,176 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | addVideoChannel, | ||
7 | createUser, | ||
8 | deleteVideoChannel, | ||
9 | flushAndRunMultipleServers, | ||
10 | flushTests, | ||
11 | getVideoChannelsList, | ||
12 | killallServers, | ||
13 | ServerInfo, | ||
14 | setAccessTokensToServers, | ||
15 | updateMyUser, | ||
16 | updateVideoChannel, | ||
17 | uploadVideo, | ||
18 | userLogin, | ||
19 | wait | ||
20 | } from '../../utils' | ||
21 | import { waitJobs } from '../../utils/server/jobs' | ||
22 | import { VideoChannel } from '../../../../shared/models/videos' | ||
23 | import { searchVideoChannel } from '../../utils/search/video-channels' | ||
24 | |||
25 | const expect = chai.expect | ||
26 | |||
27 | describe('Test a ActivityPub video channels search', function () { | ||
28 | let servers: ServerInfo[] | ||
29 | let userServer2Token: string | ||
30 | |||
31 | before(async function () { | ||
32 | this.timeout(120000) | ||
33 | |||
34 | await flushTests() | ||
35 | |||
36 | servers = await flushAndRunMultipleServers(2) | ||
37 | |||
38 | await setAccessTokensToServers(servers) | ||
39 | |||
40 | { | ||
41 | await createUser(servers[0].url, servers[0].accessToken, 'user1_server1', 'password') | ||
42 | const channel = { | ||
43 | name: 'channel1_server1', | ||
44 | displayName: 'Channel 1 server 1' | ||
45 | } | ||
46 | await addVideoChannel(servers[0].url, servers[0].accessToken, channel) | ||
47 | } | ||
48 | |||
49 | { | ||
50 | const user = { username: 'user1_server2', password: 'password' } | ||
51 | await createUser(servers[1].url, servers[1].accessToken, user.username, user.password) | ||
52 | userServer2Token = await userLogin(servers[1], user) | ||
53 | |||
54 | const channel = { | ||
55 | name: 'channel1_server2', | ||
56 | displayName: 'Channel 1 server 2' | ||
57 | } | ||
58 | const resChannel = await addVideoChannel(servers[1].url, userServer2Token, channel) | ||
59 | const channelId = resChannel.body.videoChannel.id | ||
60 | |||
61 | await uploadVideo(servers[1].url, userServer2Token, { name: 'video 1 server 2', channelId }) | ||
62 | await uploadVideo(servers[1].url, userServer2Token, { name: 'video 2 server 2', channelId }) | ||
63 | } | ||
64 | |||
65 | await waitJobs(servers) | ||
66 | }) | ||
67 | |||
68 | it('Should not find a remote video channel', async function () { | ||
69 | { | ||
70 | const search = 'http://localhost:9002/video-channels/channel1_server3' | ||
71 | const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) | ||
72 | |||
73 | expect(res.body.total).to.equal(0) | ||
74 | expect(res.body.data).to.be.an('array') | ||
75 | expect(res.body.data).to.have.lengthOf(0) | ||
76 | } | ||
77 | |||
78 | { | ||
79 | // Without token | ||
80 | const search = 'http://localhost:9002/video-channels/channel1_server2' | ||
81 | const res = await searchVideoChannel(servers[0].url, search) | ||
82 | |||
83 | expect(res.body.total).to.equal(0) | ||
84 | expect(res.body.data).to.be.an('array') | ||
85 | expect(res.body.data).to.have.lengthOf(0) | ||
86 | } | ||
87 | }) | ||
88 | |||
89 | it('Should search a local video channel', async function () { | ||
90 | const searches = [ | ||
91 | 'http://localhost:9001/video-channels/channel1_server1', | ||
92 | 'channel1_server1@localhost:9001' | ||
93 | ] | ||
94 | |||
95 | for (const search of searches) { | ||
96 | const res = await searchVideoChannel(servers[ 0 ].url, search) | ||
97 | |||
98 | expect(res.body.total).to.equal(1) | ||
99 | expect(res.body.data).to.be.an('array') | ||
100 | expect(res.body.data).to.have.lengthOf(1) | ||
101 | expect(res.body.data[ 0 ].name).to.equal('channel1_server1') | ||
102 | expect(res.body.data[ 0 ].displayName).to.equal('Channel 1 server 1') | ||
103 | } | ||
104 | }) | ||
105 | |||
106 | it('Should search a remote video channel with URL or handle', async function () { | ||
107 | const searches = [ | ||
108 | 'http://localhost:9002/video-channels/channel1_server2', | ||
109 | 'channel1_server2@localhost:9002' | ||
110 | ] | ||
111 | |||
112 | for (const search of searches) { | ||
113 | const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) | ||
114 | |||
115 | expect(res.body.total).to.equal(1) | ||
116 | expect(res.body.data).to.be.an('array') | ||
117 | expect(res.body.data).to.have.lengthOf(1) | ||
118 | expect(res.body.data[ 0 ].name).to.equal('channel1_server2') | ||
119 | expect(res.body.data[ 0 ].displayName).to.equal('Channel 1 server 2') | ||
120 | } | ||
121 | }) | ||
122 | |||
123 | it('Should not list this remote video channel', async function () { | ||
124 | const res = await getVideoChannelsList(servers[0].url, 0, 5) | ||
125 | expect(res.body.total).to.equal(3) | ||
126 | expect(res.body.data).to.have.lengthOf(3) | ||
127 | expect(res.body.data[0].name).to.equal('channel1_server1') | ||
128 | expect(res.body.data[1].name).to.equal('user1_server1_channel') | ||
129 | expect(res.body.data[2].name).to.equal('root_channel') | ||
130 | }) | ||
131 | |||
132 | it('Should update video channel of server 2, and refresh it on server 1', async function () { | ||
133 | this.timeout(60000) | ||
134 | |||
135 | await updateVideoChannel(servers[1].url, userServer2Token, 'channel1_server2', { displayName: 'channel updated' }) | ||
136 | await updateMyUser({ url: servers[1].url, accessToken: userServer2Token, displayName: 'user updated' }) | ||
137 | |||
138 | await waitJobs(servers) | ||
139 | // Expire video channel | ||
140 | await wait(10000) | ||
141 | |||
142 | const search = 'http://localhost:9002/video-channels/channel1_server2' | ||
143 | const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken) | ||
144 | expect(res.body.total).to.equal(1) | ||
145 | expect(res.body.data).to.have.lengthOf(1) | ||
146 | |||
147 | const videoChannel: VideoChannel = res.body.data[0] | ||
148 | expect(videoChannel.displayName).to.equal('channel updated') | ||
149 | |||
150 | // We don't return the owner account for now | ||
151 | // expect(videoChannel.ownerAccount.displayName).to.equal('user updated') | ||
152 | }) | ||
153 | |||
154 | it('Should delete video channel of server 2, and delete it on server 1', async function () { | ||
155 | this.timeout(60000) | ||
156 | |||
157 | await deleteVideoChannel(servers[1].url, userServer2Token, 'channel1_server2') | ||
158 | |||
159 | await waitJobs(servers) | ||
160 | // Expire video | ||
161 | await wait(10000) | ||
162 | |||
163 | const res = await searchVideoChannel(servers[0].url, 'http://localhost:9002/video-channels/channel1_server2', servers[0].accessToken) | ||
164 | expect(res.body.total).to.equal(0) | ||
165 | expect(res.body.data).to.have.lengthOf(0) | ||
166 | }) | ||
167 | |||
168 | after(async function () { | ||
169 | killallServers(servers) | ||
170 | |||
171 | // Keep the logs if the test failed | ||
172 | if (this['ok']) { | ||
173 | await flushTests() | ||
174 | } | ||
175 | }) | ||
176 | }) | ||
diff --git a/server/tests/api/search/search-activitypub-videos.ts b/server/tests/api/search/search-activitypub-videos.ts index 6dc792696..28f4fac50 100644 --- a/server/tests/api/search/search-activitypub-videos.ts +++ b/server/tests/api/search/search-activitypub-videos.ts | |||
@@ -59,6 +59,7 @@ describe('Test a ActivityPub videos search', function () { | |||
59 | } | 59 | } |
60 | 60 | ||
61 | { | 61 | { |
62 | // Without token | ||
62 | const res = await searchVideo(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID) | 63 | const res = await searchVideo(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID) |
63 | 64 | ||
64 | expect(res.body.total).to.equal(0) | 65 | expect(res.body.total).to.equal(0) |
diff --git a/server/tests/utils/search/video-channels.ts b/server/tests/utils/search/video-channels.ts new file mode 100644 index 000000000..0532134ae --- /dev/null +++ b/server/tests/utils/search/video-channels.ts | |||
@@ -0,0 +1,22 @@ | |||
1 | import { makeGetRequest } from '../requests/requests' | ||
2 | |||
3 | function searchVideoChannel (url: string, search: string, token?: string, statusCodeExpected = 200) { | ||
4 | const path = '/api/v1/search/video-channels' | ||
5 | |||
6 | return makeGetRequest({ | ||
7 | url, | ||
8 | path, | ||
9 | query: { | ||
10 | sort: '-createdAt', | ||
11 | search | ||
12 | }, | ||
13 | token, | ||
14 | statusCodeExpected | ||
15 | }) | ||
16 | } | ||
17 | |||
18 | // --------------------------------------------------------------------------- | ||
19 | |||
20 | export { | ||
21 | searchVideoChannel | ||
22 | } | ||
diff --git a/server/tests/utils/videos/video-channels.ts b/server/tests/utils/videos/video-channels.ts index 1eea22b31..092985777 100644 --- a/server/tests/utils/videos/video-channels.ts +++ b/server/tests/utils/videos/video-channels.ts | |||
@@ -54,12 +54,12 @@ function addVideoChannel ( | |||
54 | function updateVideoChannel ( | 54 | function updateVideoChannel ( |
55 | url: string, | 55 | url: string, |
56 | token: string, | 56 | token: string, |
57 | channelId: number | string, | 57 | channelName: string, |
58 | attributes: VideoChannelUpdate, | 58 | attributes: VideoChannelUpdate, |
59 | expectedStatus = 204 | 59 | expectedStatus = 204 |
60 | ) { | 60 | ) { |
61 | const body = {} | 61 | const body = {} |
62 | const path = '/api/v1/video-channels/' + channelId | 62 | const path = '/api/v1/video-channels/' + channelName |
63 | 63 | ||
64 | if (attributes.displayName) body['displayName'] = attributes.displayName | 64 | if (attributes.displayName) body['displayName'] = attributes.displayName |
65 | if (attributes.description) body['description'] = attributes.description | 65 | if (attributes.description) body['description'] = attributes.description |
@@ -73,8 +73,8 @@ function updateVideoChannel ( | |||
73 | .expect(expectedStatus) | 73 | .expect(expectedStatus) |
74 | } | 74 | } |
75 | 75 | ||
76 | function deleteVideoChannel (url: string, token: string, channelId: number | string, expectedStatus = 204) { | 76 | function deleteVideoChannel (url: string, token: string, channelName: string, expectedStatus = 204) { |
77 | const path = '/api/v1/video-channels/' + channelId | 77 | const path = '/api/v1/video-channels/' + channelName |
78 | 78 | ||
79 | return request(url) | 79 | return request(url) |
80 | .delete(path) | 80 | .delete(path) |
@@ -83,8 +83,8 @@ function deleteVideoChannel (url: string, token: string, channelId: number | str | |||
83 | .expect(expectedStatus) | 83 | .expect(expectedStatus) |
84 | } | 84 | } |
85 | 85 | ||
86 | function getVideoChannel (url: string, channelId: number | string) { | 86 | function getVideoChannel (url: string, channelName: string) { |
87 | const path = '/api/v1/video-channels/' + channelId | 87 | const path = '/api/v1/video-channels/' + channelName |
88 | 88 | ||
89 | return request(url) | 89 | return request(url) |
90 | .get(path) | 90 | .get(path) |