aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md5
-rw-r--r--client/src/app/menu/menu.component.html5
-rw-r--r--client/src/app/menu/menu.component.scss8
-rw-r--r--client/src/app/shared/video/video.service.ts11
-rw-r--r--client/src/app/videos/video-list/video-local.component.ts37
-rw-r--r--client/src/app/videos/videos-routing.module.ts10
-rw-r--r--client/src/app/videos/videos.module.ts2
-rw-r--r--client/src/assets/images/menu/home.svg15
-rw-r--r--server/controllers/api/videos/index.ts2
-rw-r--r--server/models/video/video.ts18
-rw-r--r--server/tests/api/videos/multiple-servers.ts32
-rw-r--r--server/tests/utils/videos/videos.ts12
-rw-r--r--shared/models/videos/video-query.type.ts1
13 files changed, 152 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d845d2f3e..fa5a2032d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,11 @@
24 * Added `video.uuid` field 24 * Added `video.uuid` field
25 * Added `video.url` field 25 * Added `video.url` field
26 26
27### Features
28
29 * Add "Local" in menu that lists only local videos
30
31
27 32
28## v1.0.0-alpha.4 33## v1.0.0-alpha.4
29 34
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index d174c76ba..d827a4dd4 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -43,6 +43,11 @@
43 <span class="icon icon-videos-recently-added"></span> 43 <span class="icon icon-videos-recently-added"></span>
44 Recently added 44 Recently added
45 </a> 45 </a>
46
47 <a routerLink="/videos/local" routerLinkActive="active">
48 <span class="icon icon-videos-local"></span>
49 Local
50 </a>
46 </div> 51 </div>
47 52
48 <div class="panel-block"> 53 <div class="panel-block">
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index 1f3c889cf..da5a581a1 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -126,6 +126,14 @@ menu {
126 background-image: url('../../assets/images/menu/recently-added.svg'); 126 background-image: url('../../assets/images/menu/recently-added.svg');
127 } 127 }
128 128
129 &.icon-videos-local {
130 width: 23px;
131 height: 23px;
132 position: relative;
133 top: -1px;
134 background-image: url('../../assets/images/menu/home.svg');
135 }
136
129 &.icon-administration { 137 &.icon-administration {
130 width: 23px; 138 width: 23px;
131 height: 23px; 139 height: 23px;
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index a151a2983..0a8894fd9 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -7,6 +7,7 @@ import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } fr
7import { ResultList } from '../../../../../shared/models/result-list.model' 7import { ResultList } from '../../../../../shared/models/result-list.model'
8import { UserVideoRateUpdate } from '../../../../../shared/models/videos/user-video-rate-update.model' 8import { UserVideoRateUpdate } from '../../../../../shared/models/videos/user-video-rate-update.model'
9import { UserVideoRate } from '../../../../../shared/models/videos/user-video-rate.model' 9import { UserVideoRate } from '../../../../../shared/models/videos/user-video-rate.model'
10import { VideoFilter } from '../../../../../shared/models/videos/video-query.type'
10import { VideoRateType } from '../../../../../shared/models/videos/video-rate.type' 11import { VideoRateType } from '../../../../../shared/models/videos/video-rate.type'
11import { VideoUpdate } from '../../../../../shared/models/videos/video-update.model' 12import { VideoUpdate } from '../../../../../shared/models/videos/video-update.model'
12import { environment } from '../../../environments/environment' 13import { environment } from '../../../environments/environment'
@@ -94,12 +95,20 @@ export class VideoService {
94 .catch((res) => this.restExtractor.handleError(res)) 95 .catch((res) => this.restExtractor.handleError(res))
95 } 96 }
96 97
97 getVideos (videoPagination: ComponentPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> { 98 getVideos (
99 videoPagination: ComponentPagination,
100 sort: SortField,
101 filter?: VideoFilter
102 ): Observable<{ videos: Video[], totalVideos: number}> {
98 const pagination = this.restService.componentPaginationToRestPagination(videoPagination) 103 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
99 104
100 let params = new HttpParams() 105 let params = new HttpParams()
101 params = this.restService.addRestGetParams(params, pagination, sort) 106 params = this.restService.addRestGetParams(params, pagination, sort)
102 107
108 if (filter) {
109 params = params.set('filter', filter)
110 }
111
103 return this.authHttp 112 return this.authHttp
104 .get(VideoService.BASE_VIDEO_URL, { params }) 113 .get(VideoService.BASE_VIDEO_URL, { params })
105 .map(this.extractVideos) 114 .map(this.extractVideos)
diff --git a/client/src/app/videos/video-list/video-local.component.ts b/client/src/app/videos/video-list/video-local.component.ts
new file mode 100644
index 000000000..8cac2c12c
--- /dev/null
+++ b/client/src/app/videos/video-list/video-local.component.ts
@@ -0,0 +1,37 @@
1import { Component, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'
3import { immutableAssign } from '@app/shared/misc/utils'
4import { NotificationsService } from 'angular2-notifications'
5import { AuthService } from '../../core/auth'
6import { AbstractVideoList } from '../../shared/video/abstract-video-list'
7import { SortField } from '../../shared/video/sort-field.type'
8import { VideoService } from '../../shared/video/video.service'
9
10@Component({
11 selector: 'my-videos-local',
12 styleUrls: [ '../../shared/video/abstract-video-list.scss' ],
13 templateUrl: '../../shared/video/abstract-video-list.html'
14})
15export class VideoLocalComponent extends AbstractVideoList implements OnInit {
16 titlePage = 'Local videos'
17 currentRoute = '/videos/local'
18 sort = '-createdAt' as SortField
19
20 constructor (protected router: Router,
21 protected route: ActivatedRoute,
22 protected notificationsService: NotificationsService,
23 protected authService: AuthService,
24 private videoService: VideoService) {
25 super()
26 }
27
28 ngOnInit () {
29 super.ngOnInit()
30 }
31
32 getVideosObservable (page: number) {
33 const newPagination = immutableAssign(this.pagination, { currentPage: page })
34
35 return this.videoService.getVideos(newPagination, this.sort, 'local')
36 }
37}
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts
index 29ec5fd4f..561137b70 100644
--- a/client/src/app/videos/videos-routing.module.ts
+++ b/client/src/app/videos/videos-routing.module.ts
@@ -1,5 +1,6 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router' 2import { RouterModule, Routes } from '@angular/router'
3import { VideoLocalComponent } from '@app/videos/video-list/video-local.component'
3import { MetaGuard } from '@ngx-meta/core' 4import { MetaGuard } from '@ngx-meta/core'
4import { VideoSearchComponent } from './video-list' 5import { VideoSearchComponent } from './video-list'
5import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' 6import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
@@ -36,6 +37,15 @@ const videosRoutes: Routes = [
36 } 37 }
37 }, 38 },
38 { 39 {
40 path: 'local',
41 component: VideoLocalComponent,
42 data: {
43 meta: {
44 title: 'Local videos'
45 }
46 }
47 },
48 {
39 path: 'search', 49 path: 'search',
40 component: VideoSearchComponent, 50 component: VideoSearchComponent,
41 data: { 51 data: {
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts
index 4b14d1da8..7c3d457b3 100644
--- a/client/src/app/videos/videos.module.ts
+++ b/client/src/app/videos/videos.module.ts
@@ -1,4 +1,5 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { VideoLocalComponent } from '@app/videos/video-list/video-local.component'
2import { SharedModule } from '../shared' 3import { SharedModule } from '../shared'
3import { VideoSearchComponent } from './video-list' 4import { VideoSearchComponent } from './video-list'
4import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' 5import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
@@ -17,6 +18,7 @@ import { VideosComponent } from './videos.component'
17 18
18 VideoTrendingComponent, 19 VideoTrendingComponent,
19 VideoRecentlyAddedComponent, 20 VideoRecentlyAddedComponent,
21 VideoLocalComponent,
20 VideoSearchComponent 22 VideoSearchComponent
21 ], 23 ],
22 24
diff --git a/client/src/assets/images/menu/home.svg b/client/src/assets/images/menu/home.svg
new file mode 100644
index 000000000..bb95e949a
--- /dev/null
+++ b/client/src/assets/images/menu/home.svg
@@ -0,0 +1,15 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3 <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
4 <title>home</title>
5 <desc>Created with Sketch.</desc>
6 <defs></defs>
7 <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
8 <g id="Artboard-4" transform="translate(-620.000000, -159.000000)" stroke="#808080" stroke-width="2">
9 <g id="34" transform="translate(620.000000, 159.000000)">
10 <path d="M1,11 L12,2 C12,2 22.9999989,11.0000005 23,11" id="Path-50"></path>
11 <path d="M3,10 C3,10 3,10.4453982 3,10.9968336 L3,20.0170446 C3,20.5675806 3.43788135,21.0138782 4.00292933,21.0138781 L8.99707067,21.0138779 C9.55097324,21.0138779 10,20.5751284 10,20.0089602 L10,15.0049177 C10,14.449917 10.4433532,14 11.0093689,14 L12.9906311,14 C13.5480902,14 14,14.4387495 14,15.0049177 L14,20.0089602 C14,20.5639609 14.4378817,21.0138779 15.0029302,21.0138779 L19.9970758,21.0138781 C20.5509789,21.0138782 21.000006,20.56848 21.000006,20.0170446 L21.0000057,10" id="Path-51"></path>
12 </g>
13 </g>
14 </g>
15</svg>
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 10b309cd1..690872320 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -388,7 +388,7 @@ async function getVideoDescription (req: express.Request, res: express.Response)
388} 388}
389 389
390async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 390async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
391 const resultList = await VideoModel.listForApi(req.query.start, req.query.count, req.query.sort) 391 const resultList = await VideoModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.filter)
392 392
393 return res.json(getFormattedObjects(resultList.data, resultList.total)) 393 return res.json(getFormattedObjects(resultList.data, resultList.total))
394} 394}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 0e5dd0d2f..14eb64102 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -29,6 +29,7 @@ import {
29import { VideoPrivacy, VideoResolution } from '../../../shared' 29import { VideoPrivacy, VideoResolution } from '../../../shared'
30import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' 30import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
31import { Video, VideoDetails } from '../../../shared/models/videos' 31import { Video, VideoDetails } from '../../../shared/models/videos'
32import { VideoFilter } from '../../../shared/models/videos/video-query.type'
32import { activityPubCollection } from '../../helpers/activitypub' 33import { activityPubCollection } from '../../helpers/activitypub'
33import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeFilePromise } from '../../helpers/core-utils' 34import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeFilePromise } from '../../helpers/core-utils'
34import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 35import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
@@ -91,7 +92,7 @@ enum ScopeNames {
91} 92}
92 93
93@Scopes({ 94@Scopes({
94 [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number) => ({ 95 [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, filter?: VideoFilter) => ({
95 where: { 96 where: {
96 id: { 97 id: {
97 [Sequelize.Op.notIn]: Sequelize.literal( 98 [Sequelize.Op.notIn]: Sequelize.literal(
@@ -129,6 +130,7 @@ enum ScopeNames {
129 attributes: [ 'preferredUsername', 'url', 'serverId' ], 130 attributes: [ 'preferredUsername', 'url', 'serverId' ],
130 model: ActorModel.unscoped(), 131 model: ActorModel.unscoped(),
131 required: true, 132 required: true,
133 where: VideoModel.buildActorWhereWithFilter(filter),
132 include: [ 134 include: [
133 { 135 {
134 attributes: [ 'host' ], 136 attributes: [ 'host' ],
@@ -639,7 +641,7 @@ export class VideoModel extends Model<VideoModel> {
639 }) 641 })
640 } 642 }
641 643
642 static async listForApi (start: number, count: number, sort: string) { 644 static async listForApi (start: number, count: number, sort: string, filter?: VideoFilter) {
643 const query = { 645 const query = {
644 offset: start, 646 offset: start,
645 limit: count, 647 limit: count,
@@ -648,7 +650,7 @@ export class VideoModel extends Model<VideoModel> {
648 650
649 const serverActor = await getServerActor() 651 const serverActor = await getServerActor()
650 652
651 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id ] }) 653 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, filter ] })
652 .findAndCountAll(query) 654 .findAndCountAll(query)
653 .then(({ rows, count }) => { 655 .then(({ rows, count }) => {
654 return { 656 return {
@@ -790,6 +792,16 @@ export class VideoModel extends Model<VideoModel> {
790 } 792 }
791 } 793 }
792 794
795 private static buildActorWhereWithFilter (filter?: VideoFilter) {
796 if (filter && filter === 'local') {
797 return {
798 serverId: null
799 }
800 }
801
802 return {}
803 }
804
793 getOriginalFile () { 805 getOriginalFile () {
794 if (Array.isArray(this.VideoFiles) === false) return undefined 806 if (Array.isArray(this.VideoFiles) === false) return undefined
795 807
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index 3f6b82c50..42a1241f7 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -15,7 +15,7 @@ import {
15 dateIsValid, 15 dateIsValid,
16 doubleFollow, 16 doubleFollow,
17 flushAndRunMultipleServers, 17 flushAndRunMultipleServers,
18 flushTests, 18 flushTests, getLocalVideos,
19 getVideo, 19 getVideo,
20 getVideoChannelsList, 20 getVideoChannelsList,
21 getVideosList, 21 getVideosList,
@@ -349,6 +349,36 @@ describe('Test multiple servers', function () {
349 }) 349 })
350 }) 350 })
351 351
352 describe('It should list local videos', function () {
353 it('Should list only local videos on server 1', async function () {
354 const { body } = await getLocalVideos(servers[0].url)
355
356 expect(body.total).to.equal(1)
357 expect(body.data).to.be.an('array')
358 expect(body.data.length).to.equal(1)
359 expect(body.data[0].name).to.equal('my super name for server 1')
360 })
361
362 it('Should list only local videos on server 2', async function () {
363 const { body } = await getLocalVideos(servers[1].url)
364
365 expect(body.total).to.equal(1)
366 expect(body.data).to.be.an('array')
367 expect(body.data.length).to.equal(1)
368 expect(body.data[0].name).to.equal('my super name for server 2')
369 })
370
371 it('Should list only local videos on server 3', async function () {
372 const { body } = await getLocalVideos(servers[2].url)
373
374 expect(body.total).to.equal(2)
375 expect(body.data).to.be.an('array')
376 expect(body.data.length).to.equal(2)
377 expect(body.data[0].name).to.equal('my super name for server 3')
378 expect(body.data[1].name).to.equal('my super name for server 3-2')
379 })
380 })
381
352 describe('Should seed the uploaded video', function () { 382 describe('Should seed the uploaded video', function () {
353 it('Should add the file 1 by asking server 3', async function () { 383 it('Should add the file 1 by asking server 3', async function () {
354 this.timeout(10000) 384 this.timeout(10000)
diff --git a/server/tests/utils/videos/videos.ts b/server/tests/utils/videos/videos.ts
index ec40c5465..89db16fec 100644
--- a/server/tests/utils/videos/videos.ts
+++ b/server/tests/utils/videos/videos.ts
@@ -123,6 +123,17 @@ function getVideosList (url: string) {
123 .expect('Content-Type', /json/) 123 .expect('Content-Type', /json/)
124} 124}
125 125
126function getLocalVideos (url: string) {
127 const path = '/api/v1/videos'
128
129 return request(url)
130 .get(path)
131 .query({ sort: 'name', filter: 'local' })
132 .set('Accept', 'application/json')
133 .expect(200)
134 .expect('Content-Type', /json/)
135}
136
126function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) { 137function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) {
127 const path = '/api/v1/users/me/videos' 138 const path = '/api/v1/users/me/videos'
128 139
@@ -487,6 +498,7 @@ export {
487 rateVideo, 498 rateVideo,
488 viewVideo, 499 viewVideo,
489 parseTorrentVideo, 500 parseTorrentVideo,
501 getLocalVideos,
490 completeVideoCheck, 502 completeVideoCheck,
491 checkVideoFilesWereRemoved 503 checkVideoFilesWereRemoved
492} 504}
diff --git a/shared/models/videos/video-query.type.ts b/shared/models/videos/video-query.type.ts
new file mode 100644
index 000000000..ff0f527f3
--- /dev/null
+++ b/shared/models/videos/video-query.type.ts
@@ -0,0 +1 @@
export type VideoFilter = 'local'