Handle blacklist (#84)
authorGreen-Star <Green-Star@users.noreply.github.com>
Fri, 22 Sep 2017 07:13:43 +0000 (09:13 +0200)
committerBigard Florian <florian.bigard@gmail.com>
Fri, 22 Sep 2017 07:13:43 +0000 (09:13 +0200)
* Client: Add list blacklist feature

* Server: Add list blacklist feature

* Client: Add videoId column

* Server: Add some video infos in the REST api

* Client: Add video information in the blacklist list

* Fix sortable columns :)

* Client: Add removeFromBlacklist feature

* Server: Add removeFromBlacklist feature

* Move to TypeScript

* Move to TypeScript and Promises

* Server: Fix blacklist list sort

* Server: Fetch videos informations

* Use common shared interface for client and server

* Add check-params remove blacklisted video tests

* Add check-params list blacklisted videos tests

* Add list blacklist tests

* Add remove from blacklist tests

* Add video blacklist management tests

* Fix rebase onto develop issues

* Server: Add sort on blacklist id column

* Server: Add blacklists library

* Add blacklist id sort test

* Add check-params tests for blacklist list pagination, count and sort

* Fix coding style

* Increase Remote API tests timeout

* Increase Request scheduler API tests timeout

* Fix typo

* Increase video transcoding API tests timeout

* Move tests to Typescript

* Use lodash orderBy method

* Fix typos

* Client: Remove optional tests in blacklist model attributes

* Move blacklist routes from 'blacklists' to 'blacklist'

* CLient: Remove blacklist-list.component.scss

* Rename 'blacklists' files to 'blacklist'

* Use only BlacklistedVideo interface

* Server: Use getFormattedObjects method in listBlacklist method

* Client: Use new coding style

* Server: Use new sort validator methods

* Server: Use new checkParams methods

* Client: Fix sortable columns

36 files changed:
client/src/app/+admin/admin-routing.module.ts
client/src/app/+admin/admin.module.ts
client/src/app/+admin/blacklist/blacklist-list/blacklist-list.component.html [new file with mode: 0644]
client/src/app/+admin/blacklist/blacklist-list/blacklist-list.component.ts [new file with mode: 0644]
client/src/app/+admin/blacklist/blacklist-list/index.ts [new file with mode: 0644]
client/src/app/+admin/blacklist/blacklist.component.ts [new file with mode: 0644]
client/src/app/+admin/blacklist/blacklist.routes.ts [new file with mode: 0644]
client/src/app/+admin/blacklist/index.ts [new file with mode: 0644]
client/src/app/+admin/blacklist/shared/blacklist.service.ts [new file with mode: 0644]
client/src/app/+admin/blacklist/shared/index.ts [new file with mode: 0644]
client/src/app/core/menu/menu-admin.component.html
server/controllers/api/blacklist.ts [new file with mode: 0644]
server/controllers/api/index.ts
server/helpers/utils.ts
server/initializers/constants.ts
server/lib/blacklist.ts [new file with mode: 0644]
server/lib/index.ts
server/middlewares/sort.ts
server/middlewares/validators/blacklist.ts [new file with mode: 0644]
server/middlewares/validators/index.ts
server/middlewares/validators/sort.ts
server/models/utils.ts
server/models/video/video-blacklist-interface.ts
server/models/video/video-blacklist.ts
server/tests/api/check-params/index.ts
server/tests/api/check-params/remotes.ts
server/tests/api/check-params/request-schedulers.ts
server/tests/api/check-params/video-blacklist.ts [new file with mode: 0644]
server/tests/api/check-params/video-blacklists.ts [deleted file]
server/tests/api/index.ts
server/tests/api/video-blacklist-management.ts [new file with mode: 0644]
server/tests/api/video-transcoder.ts
server/tests/utils/index.ts
server/tests/utils/video-blacklist.ts [new file with mode: 0644]
server/tests/utils/video-blacklists.ts [deleted file]
shared/models/videos/video-blacklist.model.ts

index a3845b72c936ae3d0868b364a6f6c13a14c16f44..0cd3e54c27c98a1361c67660fafbba7e54171deb 100644 (file)
@@ -9,6 +9,7 @@ import { RequestSchedulersRoutes } from './request-schedulers'
 import { UsersRoutes } from './users'
 import { VideoAbusesRoutes } from './video-abuses'
 import { AdminGuard } from './admin-guard.service'
+import { BlacklistRoutes } from './blacklist'
 
 const adminRoutes: Routes = [
   {
@@ -25,7 +26,8 @@ const adminRoutes: Routes = [
       ...FriendsRoutes,
       ...RequestSchedulersRoutes,
       ...UsersRoutes,
-      ...VideoAbusesRoutes
+      ...VideoAbusesRoutes,
+      ...BlacklistRoutes
     ]
   }
 ]
index 786dbc15c3d70bf5d2f245402050a97e7f510708..c2dd607744205081a3a0200bf0e28b1942e40efa 100644 (file)
@@ -6,6 +6,7 @@ import { FriendsComponent, FriendAddComponent, FriendListComponent, FriendServic
 import { RequestSchedulersComponent, RequestSchedulersStatsComponent, RequestSchedulersService } from './request-schedulers'
 import { UsersComponent, UserAddComponent, UserUpdateComponent, UserListComponent, UserService } from './users'
 import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses'
+import { BlacklistComponent, BlacklistListComponent, BlacklistService } from './blacklist'
 import { SharedModule } from '../shared'
 import { AdminGuard } from './admin-guard.service'
 
@@ -30,6 +31,9 @@ import { AdminGuard } from './admin-guard.service'
     UserUpdateComponent,
     UserListComponent,
 
+    BlacklistComponent,
+    BlacklistListComponent,
+
     VideoAbusesComponent,
     VideoAbuseListComponent
   ],
@@ -42,7 +46,8 @@ import { AdminGuard } from './admin-guard.service'
     FriendService,
     RequestSchedulersService,
     UserService,
-    AdminGuard
+    AdminGuard,
+    BlacklistService
   ]
 })
 export class AdminModule { }
diff --git a/client/src/app/+admin/blacklist/blacklist-list/blacklist-list.component.html b/client/src/app/+admin/blacklist/blacklist-list/blacklist-list.component.html
new file mode 100644 (file)
index 0000000..5d4636e
--- /dev/null
@@ -0,0 +1,26 @@
+<div class="row">
+  <div class="content-padding">
+    <h3>Blacklisted videos</h3>
+
+    <p-dataTable
+      [value]="blacklist" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
+      sortField="id" (onLazyLoad)="loadLazy($event)"
+    >
+      <p-column field="id" header="ID" [sortable]="true"></p-column>
+      <p-column field="name" header="Name" [sortable]="true"></p-column>
+      <p-column field="description" header="Description"></p-column>
+      <p-column field="duration" header="Duration" [sortable]="true"></p-column>
+      <p-column field="views" header="Views" [sortable]="true"></p-column>
+      <p-column field="likes" header="Likes" [sortable]="true"></p-column>
+      <p-column field="dislikes" header="Dislikes" [sortable]="true"></p-column>
+      <p-column field="nsfw" header="NSFW"></p-column>
+      <p-column field="uuid" header="UUID" [sortable]="true"></p-column>
+      <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
+      <p-column header="Delete" styleClass="action-cell">
+        <ng-template pTemplate="body" let-entry="rowData">
+         <span (click)="removeVideoFromBlacklist(entry)" class="glyphicon glyphicon-remove glyphicon-black" title="Remove this video"></span>
+       </ng-template>
+      </p-column>
+    </p-dataTable>
+  </div>
+</div>
diff --git a/client/src/app/+admin/blacklist/blacklist-list/blacklist-list.component.ts b/client/src/app/+admin/blacklist/blacklist-list/blacklist-list.component.ts
new file mode 100644 (file)
index 0000000..b308054
--- /dev/null
@@ -0,0 +1,65 @@
+import { Component, OnInit } from '@angular/core'
+import { SortMeta } from 'primeng/components/common/sortmeta'
+
+import { NotificationsService } from 'angular2-notifications'
+
+import { ConfirmService } from '../../../core'
+import { RestTable, RestPagination } from '../../../shared'
+import { BlacklistService } from '../shared'
+import { BlacklistedVideo } from '../../../../../../shared'
+
+@Component({
+  selector: 'my-blacklist-list',
+  templateUrl: './blacklist-list.component.html',
+  styleUrls: []
+})
+export class BlacklistListComponent extends RestTable implements OnInit {
+  blacklist: BlacklistedVideo[] = []
+  totalRecords = 0
+  rowsPerPage = 10
+  sort: SortMeta = { field: 'id', order: 1 }
+  pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
+
+  constructor (
+    private notificationsService: NotificationsService,
+    private confirmService: ConfirmService,
+    private blacklistService: BlacklistService
+  ) {
+    super()
+  }
+
+  ngOnInit () {
+    this.loadData()
+  }
+
+  removeVideoFromBlacklist (entry: BlacklistedVideo) {
+    const confirmMessage = 'Do you really want to remove this video from the blacklist ? It will be available again in the video list.'
+
+    this.confirmService.confirm(confirmMessage, 'Remove').subscribe(
+      res => {
+        if (res === false) return
+
+        this.blacklistService.removeVideoFromBlacklist(entry).subscribe(
+          status => {
+            this.notificationsService.success('Success', `Video ${entry.name} removed from the blacklist.`)
+            this.loadData()
+          },
+
+          err => this.notificationsService.error('Error', err.message)
+        )
+      }
+    )
+  }
+
+  protected loadData () {
+    this.blacklistService.getBlacklist(this.pagination, this.sort)
+      .subscribe(
+        resultList => {
+          this.blacklist = resultList.data
+          this.totalRecords = resultList.total
+        },
+
+        err => this.notificationsService.error('Error', err.message)
+      )
+  }
+}
diff --git a/client/src/app/+admin/blacklist/blacklist-list/index.ts b/client/src/app/+admin/blacklist/blacklist-list/index.ts
new file mode 100644 (file)
index 0000000..45f60a2
--- /dev/null
@@ -0,0 +1 @@
+export * from './blacklist-list.component'
diff --git a/client/src/app/+admin/blacklist/blacklist.component.ts b/client/src/app/+admin/blacklist/blacklist.component.ts
new file mode 100644 (file)
index 0000000..ce8fe42
--- /dev/null
@@ -0,0 +1,8 @@
+import { Component } from '@angular/core'
+
+@Component({
+  template: '<router-outlet></router-outlet>'
+})
+
+export class BlacklistComponent {
+}
diff --git a/client/src/app/+admin/blacklist/blacklist.routes.ts b/client/src/app/+admin/blacklist/blacklist.routes.ts
new file mode 100644 (file)
index 0000000..780347c
--- /dev/null
@@ -0,0 +1,27 @@
+import { Routes } from '@angular/router'
+
+import { BlacklistComponent } from './blacklist.component'
+import { BlacklistListComponent } from './blacklist-list'
+
+export const BlacklistRoutes: Routes = [
+  {
+    path: 'blacklist',
+    component: BlacklistComponent,
+    children: [
+      {
+        path: '',
+        redirectTo: 'list',
+        pathMatch: 'full'
+      },
+      {
+        path: 'list',
+        component: BlacklistListComponent,
+        data: {
+          meta: {
+            title: 'Blacklisted videos'
+          }
+        }
+      }
+    ]
+  }
+]
diff --git a/client/src/app/+admin/blacklist/index.ts b/client/src/app/+admin/blacklist/index.ts
new file mode 100644 (file)
index 0000000..675dc12
--- /dev/null
@@ -0,0 +1,4 @@
+export * from './shared'
+export * from './blacklist-list'
+export * from './blacklist.component'
+export * from './blacklist.routes'
diff --git a/client/src/app/+admin/blacklist/shared/blacklist.service.ts b/client/src/app/+admin/blacklist/shared/blacklist.service.ts
new file mode 100644 (file)
index 0000000..1b090c9
--- /dev/null
@@ -0,0 +1,44 @@
+import { Injectable } from '@angular/core'
+import { HttpClient, HttpParams } from '@angular/common/http'
+import { Observable } from 'rxjs/Observable'
+import 'rxjs/add/operator/catch'
+import 'rxjs/add/operator/map'
+
+import { SortMeta } from 'primeng/components/common/sortmeta'
+
+import { RestExtractor, RestPagination, RestService } from '../../../shared'
+import { Utils } from '../../../shared'
+import { BlacklistedVideo, ResultList } from '../../../../../../shared'
+
+@Injectable()
+export class BlacklistService {
+  private static BASE_BLACKLISTS_URL = '/api/v1/blacklist/'
+
+  constructor (
+    private authHttp: HttpClient,
+    private restService: RestService,
+    private restExtractor: RestExtractor
+  ) {}
+
+  getBlacklist (pagination: RestPagination, sort: SortMeta): Observable<ResultList<BlacklistedVideo>> {
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
+
+    return this.authHttp.get<ResultList<BlacklistedVideo>>(BlacklistService.BASE_BLACKLISTS_URL, { params })
+                        .map(res => this.restExtractor.convertResultListDateToHuman(res))
+                        .map(res => this.restExtractor.applyToResultListData(res, this.formatBlacklistedVideo.bind(this)))
+                        .catch(res => this.restExtractor.handleError(res))
+  }
+
+  removeVideoFromBlacklist (entry: BlacklistedVideo) {
+    return this.authHttp.delete(BlacklistService.BASE_BLACKLISTS_URL + entry.id)
+                        .map(this.restExtractor.extractDataBool)
+                        .catch(res => this.restExtractor.handleError(res))
+  }
+
+  private formatBlacklistedVideo (blacklistedVideo: BlacklistedVideo) {
+    return Object.assign(blacklistedVideo, {
+      createdAt: Utils.dateToHuman(blacklistedVideo.createdAt)
+    })
+  }
+}
diff --git a/client/src/app/+admin/blacklist/shared/index.ts b/client/src/app/+admin/blacklist/shared/index.ts
new file mode 100644 (file)
index 0000000..ad22e2d
--- /dev/null
@@ -0,0 +1 @@
+export * from './blacklist.service'
index 0dfe22d84ce5e735863411f29f018541782eb092..f512a4e6778309fa2336398587fd59a537f4d6c1 100644 (file)
       <span class="hidden-xs glyphicon glyphicon-alert"></span>
       Video abuses
     </a>
+
+    <a routerLink="/admin/blacklist/list" routerLinkActive="active">
+      <span class="hidden-xs glyphicon glyphicon-eye-close"></span>
+      Video blacklist
+    </a>
   </div>
 
   <div class="panel-block">
diff --git a/server/controllers/api/blacklist.ts b/server/controllers/api/blacklist.ts
new file mode 100644 (file)
index 0000000..9b2d801
--- /dev/null
@@ -0,0 +1,60 @@
+import * as express from 'express'
+
+import { database } from '../../initializers'
+import { getFormattedObjects } from '../../helpers'
+import { BlacklistedVideo } from '../../../shared'
+import { BlacklistedVideoInstance } from '../../models'
+
+import {
+  removeVideoFromBlacklist
+} from '../../lib'
+import {
+  authenticate,
+  ensureIsAdmin,
+  paginationValidator,
+  blacklistSortValidator,
+  setBlacklistSort,
+  setPagination,
+  blacklistRemoveValidator
+} from '../../middlewares'
+
+const blacklistRouter = express.Router()
+
+blacklistRouter.get('/',
+  authenticate,
+  ensureIsAdmin,
+  paginationValidator,
+  blacklistSortValidator,
+  setBlacklistSort,
+  setPagination,
+  listBlacklist
+)
+
+blacklistRouter.delete('/:id',
+  authenticate,
+  ensureIsAdmin,
+  blacklistRemoveValidator,
+  removeVideoFromBlacklistController
+)
+
+// ---------------------------------------------------------------------------
+
+export {
+  blacklistRouter
+}
+
+// ---------------------------------------------------------------------------
+
+function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
+  database.BlacklistedVideo.listForApi(req.query.start, req.query.count, req.query.sort)
+    .then(resultList => res.json(getFormattedObjects<BlacklistedVideo, BlacklistedVideoInstance>(resultList.data, resultList.total)))
+    .catch(err => next(err))
+}
+
+function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const entry = res.locals.blacklistEntryToRemove as BlacklistedVideoInstance
+
+  removeVideoFromBlacklist(entry)
+    .then(() => res.sendStatus(204))
+    .catch(err => next(err))
+}
index a9205b33c6581a9dabfb089bde2a270b9dd23b2a..fdc8879151bc8023f03038ec39a93180b34a9a13 100644 (file)
@@ -9,6 +9,7 @@ import { remoteRouter } from './remote'
 import { requestSchedulerRouter } from './request-schedulers'
 import { usersRouter } from './users'
 import { videosRouter } from './videos'
+import { blacklistRouter } from './blacklist'
 
 const apiRouter = express.Router()
 
@@ -19,6 +20,7 @@ apiRouter.use('/remote', remoteRouter)
 apiRouter.use('/request-schedulers', requestSchedulerRouter)
 apiRouter.use('/users', usersRouter)
 apiRouter.use('/videos', videosRouter)
+apiRouter.use('/blacklist', blacklistRouter)
 apiRouter.use('/ping', pong)
 apiRouter.use('/*', badRequest)
 
index af5be0c69dcbc40b845c22bcd82b7c90d122d956..ce07ceff9f231b1ac37da765992f7c718ab045e2 100644 (file)
@@ -47,11 +47,14 @@ function isSignupAllowed () {
   })
 }
 
+type SortType = { sortModel: any, sortValue: string }
+
 // ---------------------------------------------------------------------------
 
 export {
   badRequest,
   generateRandomString,
   getFormattedObjects,
-  isSignupAllowed
+  isSignupAllowed,
+  SortType
 }
index e01b6a4d47f7fd1d6b1fed24f06ceab11feff1d2..54e91d35ded135091e7ae4b63f8e50718320774e 100644 (file)
@@ -34,7 +34,8 @@ const SEARCHABLE_COLUMNS = {
 const SORTABLE_COLUMNS = {
   USERS: [ 'id', 'username', 'createdAt' ],
   VIDEO_ABUSES: [ 'id', 'createdAt' ],
-  VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ]
+  VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ],
+  BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ]
 }
 
 const OAUTH_LIFETIME = {
diff --git a/server/lib/blacklist.ts b/server/lib/blacklist.ts
new file mode 100644 (file)
index 0000000..dcf8aa0
--- /dev/null
@@ -0,0 +1,20 @@
+import { logger } from '../helpers'
+import { BlacklistedVideoInstance } from '../models'
+
+function removeVideoFromBlacklist (entry: BlacklistedVideoInstance) {
+  return entry.destroy()
+    .then(() => {
+      logger.info('Video removed from the blacklist')
+    })
+    .catch(err => {
+      logger.error('Some error while removing video from the blacklist.', err)
+    })
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  removeVideoFromBlacklist
+}
+
+// ---------------------------------------------------------------------------
index 8628da4dded5c4e18d1fd05f189b8dbb6cfc064e..df781f29f748eb7fd5aaf81507c4156c89105cb2 100644 (file)
@@ -3,3 +3,4 @@ export * from './jobs'
 export * from './request'
 export * from './friends'
 export * from './oauth-model'
+export * from './blacklist'
index 632b2fae4158fd2bbfa0ff18e4dbb23844f9d0ca..687ce097b7c8b2f32702df186fd63e8ceb7c1a94 100644 (file)
@@ -1,6 +1,9 @@
 import 'express-validator'
 import * as express from 'express'
 
+import { SortType } from '../helpers'
+import { database } from '../initializers'
+
 function setUsersSort (req: express.Request, res: express.Response, next: express.NextFunction) {
   if (!req.query.sort) req.query.sort = '-createdAt'
 
@@ -19,10 +22,32 @@ function setVideosSort (req: express.Request, res: express.Response, next: expre
   return next()
 }
 
+function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) {
+  let newSort: SortType = { sortModel: undefined, sortValue: undefined }
+
+  if (!req.query.sort) req.query.sort = '-createdAt'
+
+  // Set model we want to sort onto
+  if (req.query.sort === '-createdAt' || req.query.sort === 'createdAt' ||
+      req.query.sort === '-id' || req.query.sort === 'id') {
+    // If we want to sort onto the BlacklistedVideos relation, we won't specify it in the query parameter ...
+    newSort.sortModel = undefined
+  } else {
+    newSort.sortModel = database.Video
+  }
+
+  newSort.sortValue = req.query.sort
+
+  req.query.sort = newSort
+
+  return next()
+}
+
 // ---------------------------------------------------------------------------
 
 export {
   setUsersSort,
   setVideoAbusesSort,
-  setVideosSort
+  setVideosSort,
+  setBlacklistSort
 }
diff --git a/server/middlewares/validators/blacklist.ts b/server/middlewares/validators/blacklist.ts
new file mode 100644 (file)
index 0000000..fe8fa40
--- /dev/null
@@ -0,0 +1,35 @@
+import { param } from 'express-validator/check'
+import * as express from 'express'
+
+import { database as db } from '../../initializers/database'
+import { checkErrors } from './utils'
+import { logger } from '../../helpers'
+
+const blacklistRemoveValidator = [
+  param('id').isNumeric().not().isEmpty().withMessage('Should have a valid id'),
+
+  (req: express.Request, res: express.Response, next: express.NextFunction) => {
+    logger.debug('Checking blacklistRemove parameters.', { parameters: req.params })
+
+    checkErrors(req, res, () => {
+      db.BlacklistedVideo.loadById(req.params.id)
+        .then(entry => {
+          if (!entry) return res.status(404).send('Blacklisted video not found')
+
+          res.locals.blacklistEntryToRemove = entry
+
+          next()
+        })
+        .catch(err => {
+          logger.error('Error in blacklistRemove request validator', { error: err })
+          return res.sendStatus(500)
+        })
+    })
+  }
+]
+
+// ---------------------------------------------------------------------------
+
+export {
+  blacklistRemoveValidator
+}
index 42ba465ecbfaac205c0b5becab9501b152a9b763..a6198e22c801c06e427336c5dd187fa1f9a1e0ce 100644 (file)
@@ -4,3 +4,4 @@ export * from './pods'
 export * from './sort'
 export * from './users'
 export * from './videos'
+export * from './blacklist'
index 71b18acb01b99ba83576dfe5391e996dcc6022dd..a6f5ccb6b41c8bd5ca2e53d606045d79315fb696 100644 (file)
@@ -9,17 +9,20 @@ import { SORTABLE_COLUMNS } from '../../initializers'
 const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS)
 const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES)
 const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS)
+const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS)
 
 const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
 const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS)
 const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
+const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS)
 
 // ---------------------------------------------------------------------------
 
 export {
   usersSortValidator,
   videoAbusesSortValidator,
-  videosSortValidator
+  videosSortValidator,
+  blacklistSortValidator
 }
 
 // ---------------------------------------------------------------------------
index 7ba96815e282a512db020243c68816ea6899ca8a..1bf61d2a693021aff86a88732587dfabe3ccd87b 100644 (file)
@@ -19,9 +19,17 @@ function addMethodsToModel (model: any, classMethods: Function[], instanceMethod
   instanceMethods.forEach(m => model.prototype[m.name] = m)
 }
 
+function getSortOnModel (model: any, value: string) {
+  let sort = getSort(value)
+
+  if (model) return [ { model: model }, sort[0], sort[1] ]
+  return sort
+}
+
 // ---------------------------------------------------------------------------
 
 export {
   addMethodsToModel,
-  getSort
+  getSort,
+  getSortOnModel
 }
index ba48b1b6e0abc3b53af521f0809ab6efd1fdd4b2..9d167c0374584afe32522edc1b757d61ab9ea6bb 100644 (file)
@@ -1,7 +1,9 @@
 import * as Sequelize from 'sequelize'
 import * as Promise from 'bluebird'
 
+import { SortType } from '../../helpers'
 import { ResultList } from '../../../shared'
+import { VideoInstance } from './video-interface'
 
 // Don't use barrel, import just what we need
 import { BlacklistedVideo as FormattedBlacklistedVideo } from '../../../shared/models/videos/video-blacklist.model'
@@ -13,7 +15,7 @@ export namespace BlacklistedVideoMethods {
 
   export type List = () => Promise<BlacklistedVideoInstance[]>
 
-  export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<BlacklistedVideoInstance> >
+  export type ListForApi = (start: number, count: number, sort: SortType) => Promise< ResultList<BlacklistedVideoInstance> >
 
   export type LoadById = (id: number) => Promise<BlacklistedVideoInstance>
 
@@ -31,6 +33,8 @@ export interface BlacklistedVideoClass {
 
 export interface BlacklistedVideoAttributes {
   videoId: number
+
+  Video?: VideoInstance
 }
 
 export interface BlacklistedVideoInstance
index dc49852b66a3ca8e239bd6ca61310ecf527c91b6..1c279b1baefdb6eaa9edbc4313b5ecf2377b3ff0 100644 (file)
@@ -1,6 +1,8 @@
 import * as Sequelize from 'sequelize'
 
-import { addMethodsToModel, getSort } from '../utils'
+import { SortType } from '../../helpers'
+import { addMethodsToModel, getSortOnModel } from '../utils'
+import { VideoInstance } from './video-interface'
 import {
   BlacklistedVideoInstance,
   BlacklistedVideoAttributes,
@@ -49,10 +51,23 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
 // ------------------------------ METHODS ------------------------------
 
 toFormattedJSON = function (this: BlacklistedVideoInstance) {
+  let video: VideoInstance
+
+  video = this.Video
+
   return {
     id: this.id,
     videoId: this.videoId,
-    createdAt: this.createdAt
+    createdAt: this.createdAt,
+    updatedAt: this.updatedAt,
+    name: video.name,
+    uuid: video.uuid,
+    description: video.description,
+    duration: video.duration,
+    views: video.views,
+    likes: video.likes,
+    dislikes: video.dislikes,
+    nsfw: video.nsfw
   }
 }
 
@@ -76,11 +91,12 @@ list = function () {
   return BlacklistedVideo.findAll()
 }
 
-listForApi = function (start: number, count: number, sort: string) {
+listForApi = function (start: number, count: number, sort: SortType) {
   const query = {
     offset: start,
     limit: count,
-    order: [ getSort(sort) ]
+    order: [ getSortOnModel(sort.sortModel, sort.sortValue) ],
+    include: [ { model: BlacklistedVideo['sequelize'].models.Video } ]
   }
 
   return BlacklistedVideo.findAndCountAll(query).then(({ rows, count }) => {
index 97f2a19d736ebb0aef341f28eeac1e9a7746c29b..399a05bc347ebe2dd6293b14a4236f7f734b8986 100644 (file)
@@ -5,4 +5,4 @@ import './users'
 import './request-schedulers'
 import './videos'
 import './video-abuses'
-import './video-blacklists'
+import './video-blacklist'
index b36f1c08be0fe6efe5ce9ac8592819d9cc346423..9456ae665241ff8cf57f439a1e076f885753ae50 100644 (file)
@@ -14,7 +14,7 @@ describe('Test remote videos API validators', function () {
   // ---------------------------------------------------------------
 
   before(async function () {
-    this.timeout(20000)
+    this.timeout(60000)
 
     await flushTests()
 
index c39f5947babf0662aaae35bf72ae561098ccf65d..01a54ffa12366396efa7b8d2218be8a52c4ff04b 100644 (file)
@@ -20,7 +20,7 @@ describe('Test request schedulers stats API validators', function () {
   // ---------------------------------------------------------------
 
   before(async function () {
-    this.timeout(20000)
+    this.timeout(60000)
 
     await flushTests()
 
diff --git a/server/tests/api/check-params/video-blacklist.ts b/server/tests/api/check-params/video-blacklist.ts
new file mode 100644 (file)
index 0000000..80e6f80
--- /dev/null
@@ -0,0 +1,195 @@
+/* tslint:disable:no-unused-expression */
+
+import 'mocha'
+import * as request from 'supertest'
+
+import {
+  ServerInfo,
+  flushTests,
+  runServer,
+  uploadVideo,
+  getVideosList,
+  createUser,
+  setAccessTokensToServers,
+  killallServers,
+  makePostBodyRequest,
+  getUserAccessToken
+} from '../../utils'
+
+describe('Test video blacklist API validators', function () {
+  let server: ServerInfo
+  let userAccessToken = ''
+
+  // ---------------------------------------------------------------
+
+  before(async function () {
+    this.timeout(120000)
+
+    await flushTests()
+
+    server = await runServer(1)
+
+    await setAccessTokensToServers([ server ])
+
+    const username = 'user1'
+    const password = 'my super password'
+    await createUser(server.url, server.accessToken, username, password)
+    userAccessToken = await getUserAccessToken(server, { username, password })
+
+    // Upload a video
+    const videoAttributes = {}
+    await uploadVideo(server.url, server.accessToken, videoAttributes)
+
+    const res = await getVideosList(server.url)
+
+    const videos = res.body.data
+    server.video = videos[0]
+  })
+
+  describe('When adding a video in blacklist', function () {
+    const basePath = '/api/v1/videos/'
+
+    it('Should fail with nothing', async function () {
+      const path = basePath + server.video + '/blacklist'
+      const fields = {}
+      await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
+    })
+
+    it('Should fail with a wrong video', async function () {
+      const wrongPath = '/api/v1/videos/blabla/blacklist'
+      const fields = {}
+      await makePostBodyRequest({ url: server.url, path: wrongPath, token: server.accessToken, fields })
+    })
+
+    it('Should fail with a non authenticated user', async function () {
+      const fields = {}
+      const path = basePath + server.video + '/blacklist'
+      await makePostBodyRequest({ url: server.url, path, token: 'hello', fields, statusCodeExpected: 401 })
+    })
+
+    it('Should fail with a non admin user', async function () {
+      const fields = {}
+      const path = basePath + server.video + '/blacklist'
+      await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields, statusCodeExpected: 403 })
+    })
+
+    it('Should fail with a local video', async function () {
+      const fields = {}
+      const path = basePath + server.video.id + '/blacklist'
+      await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 403 })
+    })
+  })
+
+  describe('When removing a video in blacklist', function () {
+    const basePath = '/api/v1/blacklist/'
+
+    it('Should fail with a non authenticated user', async function () {
+      const path = basePath + server.video.id
+
+      await request(server.url)
+              .delete(path)
+              .set('Authorization', 'Bearer ' + 'fake token')
+              .set('Accept', 'application/json')
+              .expect(401)
+    })
+
+    it('Should fail with a non admin user', async function () {
+      const path = basePath + server.video.id
+
+      await request(server.url)
+              .delete(path)
+              .set('Authorization', 'Bearer ' + userAccessToken)
+              .set('Accept', 'application/json')
+              .expect(403)
+    })
+
+    it('Should fail with an incorrect id', async function () {
+      const path = basePath + 'foobar'
+
+      await request(server.url)
+              .delete(path)
+              .set('Authorization', 'Bearer ' + server.accessToken)
+              .set('Accept', 'application/json')
+              .expect(400)
+    })
+
+    it('Should fail with a not blacklisted video', async function () {
+      // The video was not added to the blacklist so it should fail
+      const path = basePath + server.video.id
+
+      await request(server.url)
+              .delete(path)
+              .set('Authorization', 'Bearer ' + server.accessToken)
+              .set('Accept', 'application/json')
+              .expect(404)
+    })
+  })
+
+  describe('When listing videos in blacklist', function () {
+    const basePath = '/api/v1/blacklist/'
+
+    it('Should fail with a non authenticated user', async function () {
+      const path = basePath
+
+      await request(server.url)
+              .get(path)
+              .query({ sort: 'createdAt' })
+              .set('Accept', 'application/json')
+              .set('Authorization', 'Bearer ' + 'fake token')
+              .expect(401)
+    })
+
+    it('Should fail with a non admin user', async function () {
+      const path = basePath
+
+      await request(server.url)
+              .get(path)
+              .query({ sort: 'createdAt' })
+              .set('Authorization', 'Bearer ' + userAccessToken)
+              .set('Accept', 'application/json')
+              .expect(403)
+    })
+
+    it('Should fail with a bad start pagination', async function () {
+      const path = basePath
+
+      await request(server.url)
+              .get(path)
+              .query({ start: 'foobar' })
+              .set('Accept', 'application/json')
+              .set('Authorization', 'Bearer ' + server.accessToken)
+              .expect(400)
+    })
+
+    it('Should fail with a bad count pagination', async function () {
+      const path = basePath
+
+      await request(server.url)
+              .get(path)
+              .query({ count: 'foobar' })
+              .set('Accept', 'application/json')
+              .set('Authorization', 'Bearer ' + server.accessToken)
+              .expect(400)
+    })
+
+    it('Should fail with an incorrect sort', async function () {
+      const path = basePath
+
+      await request(server.url)
+              .get(path)
+              .query({ sort: 'foobar' })
+              .set('Accept', 'application/json')
+              .set('Authorization', 'Bearer ' + server.accessToken)
+              .expect(400)
+    })
+  })
+
+  after(async function () {
+    killallServers([ server ])
+
+    // Keep the logs if the test failed
+    if (this['ok']) {
+      await flushTests()
+    }
+  })
+})
diff --git a/server/tests/api/check-params/video-blacklists.ts b/server/tests/api/check-params/video-blacklists.ts
deleted file mode 100644 (file)
index d0ad78f..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-/* tslint:disable:no-unused-expression */
-
-import 'mocha'
-
-import {
-  ServerInfo,
-  flushTests,
-  runServer,
-  uploadVideo,
-  getVideosList,
-  createUser,
-  setAccessTokensToServers,
-  killallServers,
-  makePostBodyRequest,
-  getUserAccessToken
-} from '../../utils'
-
-describe('Test video blacklists API validators', function () {
-  let server: ServerInfo
-  let userAccessToken = ''
-
-  // ---------------------------------------------------------------
-
-  before(async function () {
-    this.timeout(120000)
-
-    await flushTests()
-
-    server = await runServer(1)
-
-    await setAccessTokensToServers([ server ])
-
-    const username = 'user1'
-    const password = 'my super password'
-    await createUser(server.url, server.accessToken, username, password)
-    userAccessToken = await getUserAccessToken(server, { username, password })
-
-    // Upload a video
-    const videoAttributes = {}
-    await uploadVideo(server.url, server.accessToken, videoAttributes)
-
-    const res = await getVideosList(server.url)
-
-    const videos = res.body.data
-    server.video = videos[0]
-  })
-
-  describe('When adding a video in blacklist', function () {
-    const basePath = '/api/v1/videos/'
-
-    it('Should fail with nothing', async function () {
-      const path = basePath + server.video + '/blacklist'
-      const fields = {}
-      await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
-    })
-
-    it('Should fail with a wrong video', async function () {
-      const wrongPath = '/api/v1/videos/blabla/blacklist'
-      const fields = {}
-      await makePostBodyRequest({ url: server.url, path: wrongPath, token: server.accessToken, fields })
-    })
-
-    it('Should fail with a non authenticated user', async function () {
-      const fields = {}
-      const path = basePath + server.video + '/blacklist'
-      await makePostBodyRequest({ url: server.url, path, token: 'hello', fields, statusCodeExpected: 401 })
-    })
-
-    it('Should fail with a non admin user', async function () {
-      const fields = {}
-      const path = basePath + server.video + '/blacklist'
-      await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields, statusCodeExpected: 403 })
-    })
-
-    it('Should fail with a local video', async function () {
-      const fields = {}
-      const path = basePath + server.video.id + '/blacklist'
-      await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 403 })
-    })
-  })
-
-  after(async function () {
-    killallServers([ server ])
-
-    // Keep the logs if the test failed
-    if (this['ok']) {
-      await flushTests()
-    }
-  })
-})
index f60d709c8e120b9c5592447c11472ad975a9938d..03711e68aa2da45419ec212f1f06b6d78e78a930 100644 (file)
@@ -6,6 +6,7 @@ import './users'
 import './single-pod'
 import './video-abuse'
 import './video-blacklist'
+import './video-blacklist-management'
 import './multiple-pods'
 import './request-schedulers'
 import './friends-advanced'
diff --git a/server/tests/api/video-blacklist-management.ts b/server/tests/api/video-blacklist-management.ts
new file mode 100644 (file)
index 0000000..7057f4b
--- /dev/null
@@ -0,0 +1,162 @@
+/* tslint:disable:no-unused-expressions */
+
+import 'mocha'
+import * as chai from 'chai'
+const expect = chai.expect
+import * as lodash from 'lodash'
+const orderBy = lodash.orderBy
+
+import {
+  ServerInfo,
+  flushTests,
+  wait,
+  setAccessTokensToServers,
+  flushAndRunMultipleServers,
+  killallServers,
+  makeFriends,
+  getVideosList,
+  uploadVideo,
+  addVideoToBlacklist,
+  removeVideoFromBlacklist,
+  getBlacklistedVideosList,
+  getSortedBlacklistedVideosList
+} from '../utils'
+
+describe('Test video blacklist management', function () {
+  let servers: ServerInfo[] = []
+
+  async function blacklistVideosOnPod (server: ServerInfo) {
+    const res = await getVideosList(server.url)
+
+    const videos = res.body.data
+    for (let video of videos) {
+      await addVideoToBlacklist(server.url, server.accessToken, video.id)
+    }
+  }
+
+  before(async function () {
+    this.timeout(120000)
+
+    // Run servers
+    servers = await flushAndRunMultipleServers(2)
+
+    // Get the access tokens
+    await setAccessTokensToServers(servers)
+
+    // Pod 1 makes friend with pod 2
+    await makeFriends(servers[0].url, servers[0].accessToken)
+
+    // Upload 2 videos on pod 2
+    await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'My 1st video', description: 'A video on pod 2' })
+    await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'My 2nd video', description: 'A video on pod 2' })
+
+    // Wait videos propagation
+    await wait(22000)
+
+    // Blacklist the two videos on pod 1
+    await blacklistVideosOnPod(servers[0])
+  })
+
+  describe('When listing blacklisted videos', function () {
+    it('Should display all the blacklisted videos', async function () {
+      const res = await getBlacklistedVideosList(servers[0].url, servers[0].accessToken)
+
+      expect(res.body.total).to.equal(2)
+
+      const videos = res.body.data
+      expect(videos).to.be.an('array')
+      expect(videos.length).to.equal(2)
+    })
+
+    it('Should get the correct sort when sorting by descending id', async function () {
+      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-id')
+      expect(res.body.total).to.equal(2)
+
+      const videos = res.body.data
+      expect(videos).to.be.an('array')
+      expect(videos.length).to.equal(2)
+
+      const result = orderBy(res.body.data, [ 'id' ], [ 'desc' ])
+
+      expect(videos).to.deep.equal(result)
+    })
+
+    it('Should get the correct sort when sorting by descending video name', async function () {
+      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
+      expect(res.body.total).to.equal(2)
+
+      const videos = res.body.data
+      expect(videos).to.be.an('array')
+      expect(videos.length).to.equal(2)
+
+      const result = orderBy(res.body.data, [ 'name' ], [ 'desc' ])
+
+      expect(videos).to.deep.equal(result)
+    })
+
+    it('Should get the correct sort when sorting by ascending creation date', async function () {
+      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, 'createdAt')
+      expect(res.body.total).to.equal(2)
+
+      const videos = res.body.data
+      expect(videos).to.be.an('array')
+      expect(videos.length).to.equal(2)
+
+      const result = orderBy(res.body.data, [ 'createdAt' ])
+
+      expect(videos).to.deep.equal(result)
+    })
+  })
+
+  describe('When removing a blacklisted video', function () {
+    let videoToRemove
+    let blacklist = []
+
+    it('Should not have any video in videos list on pod 1', async function () {
+      const res = await getVideosList(servers[0].url)
+      expect(res.body.total).to.equal(0)
+      expect(res.body.data).to.be.an('array')
+      expect(res.body.data.length).to.equal(0)
+    })
+
+    it('Should remove a video from the blacklist on pod 1', async function () {
+      // Get one video in the blacklist
+      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
+      videoToRemove = res.body.data[0]
+      blacklist = res.body.data.slice(1)
+
+      // Remove it
+      await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, videoToRemove.videoId)
+    })
+
+    it('Should have the ex-blacklisted video in videos list on pod 1', async function () {
+      const res = await getVideosList(servers[0].url)
+      expect(res.body.total).to.equal(1)
+
+      const videos = res.body.data
+      expect(videos).to.be.an('array')
+      expect(videos.length).to.equal(1)
+
+      expect(videos[0].name).to.equal(videoToRemove.name)
+      expect(videos[0].id).to.equal(videoToRemove.videoId)
+    })
+
+    it('Should not have the ex-blacklisted video in videos blacklist list on pod 1', async function () {
+      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
+      expect(res.body.total).to.equal(1)
+
+      const videos = res.body.data
+      expect(videos).to.be.an('array')
+      expect(videos.length).to.equal(1)
+      expect(videos).to.deep.equal(blacklist)
+    })
+  })
+
+  after(async function () {
+    killallServers(servers)
+
+    if (this['ok']) {
+      await flushTests()
+    }
+  })
+})
index 228cef007537c30d7a3a3994724affec96113979..c6d4c61f5b7676fea295c9c8d42149ece74a9a01 100644 (file)
@@ -20,7 +20,7 @@ describe('Test video transcoding', function () {
   let servers: ServerInfo[] = []
 
   before(async function () {
-    this.timeout(30000)
+    this.timeout(60000)
 
     // Run servers
     servers = await flushAndRunMultipleServers(2)
index 0fa28f2afdbebd3445728de9c2d38dfaca4871a3..99c44588747b45800e054d598a3c17318ecb8316 100644 (file)
@@ -9,5 +9,5 @@ export * from './requests'
 export * from './servers'
 export * from './users'
 export * from './video-abuses'
-export * from './video-blacklists'
+export * from './video-blacklist'
 export * from './videos'
diff --git a/server/tests/utils/video-blacklist.ts b/server/tests/utils/video-blacklist.ts
new file mode 100644 (file)
index 0000000..5729d13
--- /dev/null
@@ -0,0 +1,54 @@
+import * as request from 'supertest'
+
+function addVideoToBlacklist (url: string, token: string, videoId: number, specialStatus = 204) {
+  const path = '/api/v1/videos/' + videoId + '/blacklist'
+
+  return request(url)
+          .post(path)
+          .set('Accept', 'application/json')
+          .set('Authorization', 'Bearer ' + token)
+          .expect(specialStatus)
+}
+
+function removeVideoFromBlacklist (url: string, token: string, videoId: number, specialStatus = 204) {
+  const path = '/api/v1/blacklist/' + videoId
+
+  return request(url)
+          .delete(path)
+          .set('Accept', 'application/json')
+          .set('Authorization', 'Bearer ' + token)
+          .expect(specialStatus)
+}
+
+function getBlacklistedVideosList (url: string, token: string, specialStatus = 200) {
+  const path = '/api/v1/blacklist/'
+
+  return request(url)
+          .get(path)
+          .query({ sort: 'createdAt' })
+          .set('Accept', 'application/json')
+          .set('Authorization', 'Bearer ' + token)
+          .expect(specialStatus)
+          .expect('Content-Type', /json/)
+}
+
+function getSortedBlacklistedVideosList (url: string, token: string, sort: string, specialStatus = 200) {
+  const path = '/api/v1/blacklist/'
+
+  return request(url)
+          .get(path)
+          .query({ sort: sort })
+          .set('Accept', 'application/json')
+          .set('Authorization', 'Bearer ' + token)
+          .expect(specialStatus)
+          .expect('Content-Type', /json/)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  addVideoToBlacklist,
+  removeVideoFromBlacklist,
+  getBlacklistedVideosList,
+  getSortedBlacklistedVideosList
+}
diff --git a/server/tests/utils/video-blacklists.ts b/server/tests/utils/video-blacklists.ts
deleted file mode 100644 (file)
index 6812d3a..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-import * as request from 'supertest'
-
-function addVideoToBlacklist (url: string, token: string, videoId: number, specialStatus = 204) {
-  const path = '/api/v1/videos/' + videoId + '/blacklist'
-
-  return request(url)
-          .post(path)
-          .set('Accept', 'application/json')
-          .set('Authorization', 'Bearer ' + token)
-          .expect(specialStatus)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
-  addVideoToBlacklist
-}
index 6086250ac3ccfc906f810d15a56bee042402f1e0..af04502e8b61f682fb12e4f91b43560020a996e0 100644 (file)
@@ -2,4 +2,13 @@ export interface BlacklistedVideo {
   id: number
   videoId: number
   createdAt: Date
+  updatedAt: Date
+  name: string
+  uuid: string
+  description: string
+  duration: number
+  views: number
+  likes: number
+  dislikes: number
+  nsfw: boolean
 }