aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-08-24 11:04:02 +0200
committerChocobozzz <me@florianbigard.com>2018-08-27 09:41:54 +0200
commitf5b0af50c85e2f8b6b2b054ac1f47123cacbe08d (patch)
tree1b2f8a578b2d35138ac326d65674a9c9d740e8c9
parentaa55a4da422330fe2816f1764b64f6607a0ca4aa (diff)
downloadPeerTube-f5b0af50c85e2f8b6b2b054ac1f47123cacbe08d.tar.gz
PeerTube-f5b0af50c85e2f8b6b2b054ac1f47123cacbe08d.tar.zst
PeerTube-f5b0af50c85e2f8b6b2b054ac1f47123cacbe08d.zip
Search video channel handle/uri
-rw-r--r--client/src/app/+accounts/account-video-channels/account-video-channels.component.html2
-rw-r--r--client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.html6
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html8
-rw-r--r--client/src/app/+video-channels/video-channels.component.scss7
-rw-r--r--client/src/app/search/search.component.html6
-rw-r--r--client/src/app/shared/video/video.service.ts12
-rw-r--r--server/controllers/api/search.ts25
-rw-r--r--server/helpers/webfinger.ts9
-rw-r--r--server/initializers/constants.ts2
-rw-r--r--server/lib/activitypub/actor.ts38
-rw-r--r--server/models/activitypub/actor-follow.ts10
-rw-r--r--server/models/activitypub/actor.ts4
-rw-r--r--server/tests/api/check-params/search.ts27
-rw-r--r--server/tests/api/search/index.ts1
-rw-r--r--server/tests/api/search/search-activitypub-video-channels.ts176
-rw-r--r--server/tests/api/search/search-activitypub-videos.ts1
-rw-r--r--server/tests/utils/search/video-channels.ts22
-rw-r--r--server/tests/utils/videos/video-channels.ts12
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'
4import { Observable } from 'rxjs' 4import { Observable } from 'rxjs'
5import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } from '../../../../../shared' 5import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } from '../../../../../shared'
6import { ResultList } from '../../../../../shared/models/result-list.model' 6import { ResultList } from '../../../../../shared/models/result-list.model'
7import { 7import { 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'
15import { FeedFormat } from '../../../../../shared/models/feeds/feed-format.enum' 8import { FeedFormat } from '../../../../../shared/models/feeds/feed-format.enum'
16import { environment } from '../../../environments/environment' 9import { environment } from '../../../environments/environment'
17import { ComponentPagination } from '../rest/component-pagination.model' 10import { ComponentPagination } from '../rest/component-pagination.model'
@@ -28,6 +21,7 @@ import { AccountService } from '@app/shared/account/account.service'
28import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' 21import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
29import { ServerService } from '@app/core' 22import { ServerService } from '@app/core'
30import { UserSubscriptionService } from '@app/shared/user-subscription' 23import { UserSubscriptionService } from '@app/shared/user-subscription'
24import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
31 25
32@Injectable() 26@Injectable()
33export class VideoService { 27export 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
84async function searchVideoChannelURI (search: string, isHandleSearch: boolean, res: express.Response) { 83async 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'
3import { ActorModel } from '../models/activitypub/actor' 3import { ActorModel } from '../models/activitypub/actor'
4import { isTestInstance } from './core-utils' 4import { isTestInstance } from './core-utils'
5import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' 5import { isActivityPubUrlValid } from './custom-validators/activitypub/misc'
6import { CONFIG } from '../initializers'
6 7
7const webfinger = new WebFinger({ 8const webfinger = new WebFinger({
8 webfist_fallback: false, 9 webfist_fallback: false,
@@ -13,8 +14,14 @@ const webfinger = new WebFinger({
13 14
14async function loadActorUrlOrGetFromWebfinger (uri: string) { 15async 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
50const OAUTH_LIFETIME = { 50const 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
76function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) { 82function 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}
267async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResult> { 273async 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,
6import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' 6import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
7 7
8describe('Test videos API validator', function () { 8describe('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 @@
1import './search-activitypub-video-channels'
1import './search-activitypub-videos' 2import './search-activitypub-videos'
2import './search-videos' 3import './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
3import * as chai from 'chai'
4import 'mocha'
5import {
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'
21import { waitJobs } from '../../utils/server/jobs'
22import { VideoChannel } from '../../../../shared/models/videos'
23import { searchVideoChannel } from '../../utils/search/video-channels'
24
25const expect = chai.expect
26
27describe('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 @@
1import { makeGetRequest } from '../requests/requests'
2
3function 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
20export {
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 (
54function updateVideoChannel ( 54function 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
76function deleteVideoChannel (url: string, token: string, channelId: number | string, expectedStatus = 204) { 76function 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
86function getVideoChannel (url: string, channelId: number | string) { 86function 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)