Add "local" videos in menu
authorChocobozzz <me@florianbigard.com>
Tue, 13 Mar 2018 09:24:28 +0000 (10:24 +0100)
committerChocobozzz <me@florianbigard.com>
Tue, 13 Mar 2018 09:24:28 +0000 (10:24 +0100)
13 files changed:
CHANGELOG.md
client/src/app/menu/menu.component.html
client/src/app/menu/menu.component.scss
client/src/app/shared/video/video.service.ts
client/src/app/videos/video-list/video-local.component.ts [new file with mode: 0644]
client/src/app/videos/videos-routing.module.ts
client/src/app/videos/videos.module.ts
client/src/assets/images/menu/home.svg [new file with mode: 0644]
server/controllers/api/videos/index.ts
server/models/video/video.ts
server/tests/api/videos/multiple-servers.ts
server/tests/utils/videos/videos.ts
shared/models/videos/video-query.type.ts [new file with mode: 0644]

index d845d2f3eaac8a9ba6e7ef273c2dac19a9f734dc..fa5a2032d1ce3f5c85678f7341de88f21ea1de55 100644 (file)
    * Added `video.uuid` field
    * Added `video.url` field
 
+### Features
+
+  * Add "Local" in menu that lists only local videos
+
+
 
 ## v1.0.0-alpha.4
 
index d174c76babf8c0474f5e93ff6eeeb29e3d91e366..d827a4dd432c98f41a17b5cf4600e8da1e4a411e 100644 (file)
       <span class="icon icon-videos-recently-added"></span>
       Recently added
     </a>
+
+    <a routerLink="/videos/local" routerLinkActive="active">
+      <span class="icon icon-videos-local"></span>
+      Local
+    </a>
   </div>
 
   <div class="panel-block">
index 1f3c889cf88da634ed8776561d9d70c4ef3eb278..da5a581a147ca0335956dd62e4af55384440d92e 100644 (file)
@@ -126,6 +126,14 @@ menu {
           background-image: url('../../assets/images/menu/recently-added.svg');
         }
 
+        &.icon-videos-local {
+          width: 23px;
+          height: 23px;
+          position: relative;
+          top: -1px;
+          background-image: url('../../assets/images/menu/home.svg');
+        }
+
         &.icon-administration {
           width: 23px;
           height: 23px;
index a151a2983170854ebb7863d89f8999a47bce7d26..0a8894fd92b6ba58da63516ca12b341171980b65 100644 (file)
@@ -7,6 +7,7 @@ import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } fr
 import { ResultList } from '../../../../../shared/models/result-list.model'
 import { UserVideoRateUpdate } from '../../../../../shared/models/videos/user-video-rate-update.model'
 import { UserVideoRate } from '../../../../../shared/models/videos/user-video-rate.model'
+import { VideoFilter } from '../../../../../shared/models/videos/video-query.type'
 import { VideoRateType } from '../../../../../shared/models/videos/video-rate.type'
 import { VideoUpdate } from '../../../../../shared/models/videos/video-update.model'
 import { environment } from '../../../environments/environment'
@@ -94,12 +95,20 @@ export class VideoService {
       .catch((res) => this.restExtractor.handleError(res))
   }
 
-  getVideos (videoPagination: ComponentPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
+  getVideos (
+    videoPagination: ComponentPagination,
+    sort: SortField,
+    filter?: VideoFilter
+  ): Observable<{ videos: Video[], totalVideos: number}> {
     const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
 
     let params = new HttpParams()
     params = this.restService.addRestGetParams(params, pagination, sort)
 
+    if (filter) {
+      params = params.set('filter', filter)
+    }
+
     return this.authHttp
       .get(VideoService.BASE_VIDEO_URL, { params })
       .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 (file)
index 0000000..8cac2c1
--- /dev/null
@@ -0,0 +1,37 @@
+import { Component, OnInit } from '@angular/core'
+import { ActivatedRoute, Router } from '@angular/router'
+import { immutableAssign } from '@app/shared/misc/utils'
+import { NotificationsService } from 'angular2-notifications'
+import { AuthService } from '../../core/auth'
+import { AbstractVideoList } from '../../shared/video/abstract-video-list'
+import { SortField } from '../../shared/video/sort-field.type'
+import { VideoService } from '../../shared/video/video.service'
+
+@Component({
+  selector: 'my-videos-local',
+  styleUrls: [ '../../shared/video/abstract-video-list.scss' ],
+  templateUrl: '../../shared/video/abstract-video-list.html'
+})
+export class VideoLocalComponent extends AbstractVideoList implements OnInit {
+  titlePage = 'Local videos'
+  currentRoute = '/videos/local'
+  sort = '-createdAt' as SortField
+
+  constructor (protected router: Router,
+               protected route: ActivatedRoute,
+               protected notificationsService: NotificationsService,
+               protected authService: AuthService,
+               private videoService: VideoService) {
+    super()
+  }
+
+  ngOnInit () {
+    super.ngOnInit()
+  }
+
+  getVideosObservable (page: number) {
+    const newPagination = immutableAssign(this.pagination, { currentPage: page })
+
+    return this.videoService.getVideos(newPagination, this.sort, 'local')
+  }
+}
index 29ec5fd4fd1a049608ae3439f2272a68b297409d..561137b7013ed3391a2c47a0294953d9019ab713 100644 (file)
@@ -1,5 +1,6 @@
 import { NgModule } from '@angular/core'
 import { RouterModule, Routes } from '@angular/router'
+import { VideoLocalComponent } from '@app/videos/video-list/video-local.component'
 import { MetaGuard } from '@ngx-meta/core'
 import { VideoSearchComponent } from './video-list'
 import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
@@ -35,6 +36,15 @@ const videosRoutes: Routes = [
           }
         }
       },
+      {
+        path: 'local',
+        component: VideoLocalComponent,
+        data: {
+          meta: {
+            title: 'Local videos'
+          }
+        }
+      },
       {
         path: 'search',
         component: VideoSearchComponent,
index 4b14d1da8fb7e63ab9f34997aa9e11a924fc13bd..7c3d457b3aa9d440a870a41efe08e225f623c866 100644 (file)
@@ -1,4 +1,5 @@
 import { NgModule } from '@angular/core'
+import { VideoLocalComponent } from '@app/videos/video-list/video-local.component'
 import { SharedModule } from '../shared'
 import { VideoSearchComponent } from './video-list'
 import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
@@ -17,6 +18,7 @@ import { VideosComponent } from './videos.component'
 
     VideoTrendingComponent,
     VideoRecentlyAddedComponent,
+    VideoLocalComponent,
     VideoSearchComponent
   ],
 
diff --git a/client/src/assets/images/menu/home.svg b/client/src/assets/images/menu/home.svg
new file mode 100644 (file)
index 0000000..bb95e94
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
+    <title>home</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+        <g id="Artboard-4" transform="translate(-620.000000, -159.000000)" stroke="#808080" stroke-width="2">
+            <g id="34" transform="translate(620.000000, 159.000000)">
+                <path d="M1,11 L12,2 C12,2 22.9999989,11.0000005 23,11" id="Path-50"></path>
+                <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>
+            </g>
+        </g>
+    </g>
+</svg>
index 10b309cd183588539a541dd71d46b1ba65247d1c..690872320056d3751fe5a4797091e657810aba4f 100644 (file)
@@ -388,7 +388,7 @@ async function getVideoDescription (req: express.Request, res: express.Response)
 }
 
 async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const resultList = await VideoModel.listForApi(req.query.start, req.query.count, req.query.sort)
+  const resultList = await VideoModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.filter)
 
   return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
index 0e5dd0d2f13ced8c19c9e0676c7beed89da49b86..14eb6410286873fa16ef85fadb771814686ab91a 100644 (file)
@@ -29,6 +29,7 @@ import {
 import { VideoPrivacy, VideoResolution } from '../../../shared'
 import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
 import { Video, VideoDetails } from '../../../shared/models/videos'
+import { VideoFilter } from '../../../shared/models/videos/video-query.type'
 import { activityPubCollection } from '../../helpers/activitypub'
 import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeFilePromise } from '../../helpers/core-utils'
 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
@@ -91,7 +92,7 @@ enum ScopeNames {
 }
 
 @Scopes({
-  [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number) => ({
+  [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, filter?: VideoFilter) => ({
     where: {
       id: {
         [Sequelize.Op.notIn]: Sequelize.literal(
@@ -129,6 +130,7 @@ enum ScopeNames {
                 attributes: [ 'preferredUsername', 'url', 'serverId' ],
                 model: ActorModel.unscoped(),
                 required: true,
+                where: VideoModel.buildActorWhereWithFilter(filter),
                 include: [
                   {
                     attributes: [ 'host' ],
@@ -639,7 +641,7 @@ export class VideoModel extends Model<VideoModel> {
     })
   }
 
-  static async listForApi (start: number, count: number, sort: string) {
+  static async listForApi (start: number, count: number, sort: string, filter?: VideoFilter) {
     const query = {
       offset: start,
       limit: count,
@@ -648,7 +650,7 @@ export class VideoModel extends Model<VideoModel> {
 
     const serverActor = await getServerActor()
 
-    return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id ] })
+    return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, filter ] })
       .findAndCountAll(query)
       .then(({ rows, count }) => {
         return {
@@ -790,6 +792,16 @@ export class VideoModel extends Model<VideoModel> {
     }
   }
 
+  private static buildActorWhereWithFilter (filter?: VideoFilter) {
+    if (filter && filter === 'local') {
+      return {
+        serverId: null
+      }
+    }
+
+    return {}
+  }
+
   getOriginalFile () {
     if (Array.isArray(this.VideoFiles) === false) return undefined
 
index 3f6b82c50fd8f7c5c2365ebd5fd0f39febe1988c..42a1241f7d1d2b202d23793c61a5746b539d1edb 100644 (file)
@@ -15,7 +15,7 @@ import {
   dateIsValid,
   doubleFollow,
   flushAndRunMultipleServers,
-  flushTests,
+  flushTests, getLocalVideos,
   getVideo,
   getVideoChannelsList,
   getVideosList,
@@ -349,6 +349,36 @@ describe('Test multiple servers', function () {
     })
   })
 
+  describe('It should list local videos', function () {
+    it('Should list only local videos on server 1', async function () {
+      const { body } = await getLocalVideos(servers[0].url)
+
+      expect(body.total).to.equal(1)
+      expect(body.data).to.be.an('array')
+      expect(body.data.length).to.equal(1)
+      expect(body.data[0].name).to.equal('my super name for server 1')
+    })
+
+    it('Should list only local videos on server 2', async function () {
+      const { body } = await getLocalVideos(servers[1].url)
+
+      expect(body.total).to.equal(1)
+      expect(body.data).to.be.an('array')
+      expect(body.data.length).to.equal(1)
+      expect(body.data[0].name).to.equal('my super name for server 2')
+    })
+
+    it('Should list only local videos on server 3', async function () {
+      const { body } = await getLocalVideos(servers[2].url)
+
+      expect(body.total).to.equal(2)
+      expect(body.data).to.be.an('array')
+      expect(body.data.length).to.equal(2)
+      expect(body.data[0].name).to.equal('my super name for server 3')
+      expect(body.data[1].name).to.equal('my super name for server 3-2')
+    })
+  })
+
   describe('Should seed the uploaded video', function () {
     it('Should add the file 1 by asking server 3', async function () {
       this.timeout(10000)
index ec40c546512f2ccffe37a176d398388586638b36..89db16fecd6ef98ebc90417687a69ba57a294e2d 100644 (file)
@@ -123,6 +123,17 @@ function getVideosList (url: string) {
           .expect('Content-Type', /json/)
 }
 
+function getLocalVideos (url: string) {
+  const path = '/api/v1/videos'
+
+  return request(url)
+    .get(path)
+    .query({ sort: 'name', filter: 'local' })
+    .set('Accept', 'application/json')
+    .expect(200)
+    .expect('Content-Type', /json/)
+}
+
 function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) {
   const path = '/api/v1/users/me/videos'
 
@@ -487,6 +498,7 @@ export {
   rateVideo,
   viewVideo,
   parseTorrentVideo,
+  getLocalVideos,
   completeVideoCheck,
   checkVideoFilesWereRemoved
 }
diff --git a/shared/models/videos/video-query.type.ts b/shared/models/videos/video-query.type.ts
new file mode 100644 (file)
index 0000000..ff0f527
--- /dev/null
@@ -0,0 +1 @@
+export type VideoFilter = 'local'