]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Move to HttpClient and PrimeNG data table
authorChocobozzz <florian.bigard@gmail.com>
Thu, 14 Sep 2017 09:57:49 +0000 (11:57 +0200)
committerChocobozzz <florian.bigard@gmail.com>
Thu, 14 Sep 2017 09:57:49 +0000 (11:57 +0200)
41 files changed:
client/package.json
client/src/app/+admin/friends/friend-add/friend-add.component.ts
client/src/app/+admin/friends/friend-list/friend-list.component.html
client/src/app/+admin/friends/friend-list/friend-list.component.ts
client/src/app/+admin/friends/shared/friend.service.ts
client/src/app/+admin/request-schedulers/shared/request-schedulers.service.ts
client/src/app/+admin/users/shared/user.service.ts
client/src/app/+admin/users/user-list/user-list.component.html
client/src/app/+admin/users/user-list/user-list.component.ts
client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html
client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts
client/src/app/account/account-details/account-details.component.ts
client/src/app/app.component.ts
client/src/app/app.module.ts
client/src/app/core/auth/auth.service.ts
client/src/app/core/config/config.service.ts
client/src/app/shared/auth/auth-http.service.ts [deleted file]
client/src/app/shared/auth/auth-interceptor.service.ts [new file with mode: 0644]
client/src/app/shared/auth/index.ts
client/src/app/shared/rest/index.ts
client/src/app/shared/rest/rest-data-source.ts
client/src/app/shared/rest/rest-extractor.service.ts
client/src/app/shared/rest/rest-pagination.ts
client/src/app/shared/rest/rest-table.ts [new file with mode: 0644]
client/src/app/shared/rest/rest.service.ts
client/src/app/shared/shared.module.ts
client/src/app/shared/users/user.service.ts
client/src/app/shared/utils.ts
client/src/app/shared/video-abuse/video-abuse.service.ts
client/src/app/videos/shared/index.ts
client/src/app/videos/shared/video-pagination.model.ts [new file with mode: 0644]
client/src/app/videos/shared/video.model.ts
client/src/app/videos/shared/video.service.ts
client/src/app/videos/video-list/video-list.component.ts
client/src/sass/application.scss
client/yarn.lock
server/lib/friends.ts
shared/models/users/index.ts
shared/models/users/user-login.model.ts [new file with mode: 0644]
shared/models/users/user-refresh-token.model.ts [new file with mode: 0644]
shared/models/videos/video.model.ts

index d69a7b10b62d6eac68da315ee3aaa02b1381aa23..caec34e449334cc2c00bdf5cbe320e526964d35c 100644 (file)
@@ -62,7 +62,6 @@
     "json-loader": "^0.5.4",
     "ng-router-loader": "^2.0.0",
     "ng2-file-upload": "^1.1.4-2",
-    "ng2-smart-table": "1.2.1",
     "ngc-webpack": "3.2.2",
     "ngx-bootstrap": "1.9.1",
     "ngx-chips": "1.5.3",
@@ -97,6 +96,7 @@
     "add-asset-html-webpack-plugin": "^2.0.1",
     "codelyzer": "^3.0.0-beta.4",
     "extract-text-webpack-plugin": "^3.0.0",
+    "primeng": "^4.2.0",
     "purify-css": "^1.2.5",
     "purifycss-webpack": "^0.7.0",
     "standard": "^10.0.0",
index 0449d26a96c9304e4e849e91e1287fb49a8d9c8b..02543d393a027be33ff9f2a48db1322bcc1c413b 100644 (file)
@@ -93,8 +93,9 @@ export class FriendAddComponent implements OnInit {
 
         this.friendService.makeFriends(notEmptyHosts).subscribe(
           status => {
-            this.notificationsService.success('Sucess', 'Make friends request sent!')
-            this.router.navigate([ '/admin/friends/list' ])
+            this.notificationsService.success('Success', 'Make friends request sent!')
+            // Wait requests between pods
+            setTimeout(() => this.router.navigate([ '/admin/friends/list' ]), 1000)
           },
 
           err => this.notificationsService.error('Error', err.text)
index 7b9fff3047cc8a4a85057c974f7fa9462d7c7e8f..7887bc5e3ae5131f00305ad630bf379b4b03dc65 100644 (file)
@@ -2,13 +2,24 @@
   <div class="content-padding">
     <h3>Friends list</h3>
 
-    <ng2-smart-table [settings]="tableSettings" [source]="friendsSource" (delete)="removeFriend($event)"></ng2-smart-table>
+    <p-dataTable [value]="friends">
+      <p-column field="id" header="ID"></p-column>
+      <p-column field="host" header="Host"></p-column>
+      <p-column field="email" header="Email"></p-column>
+      <p-column field="score" header="Score"></p-column>
+      <p-column field="createdAt" header="Created date"></p-column>
+      <p-column header="Delete" styleClass="action-cell">
+        <ng-template pTemplate="body" let-pod="rowData">
+          <span (click)="removeFriend(pod)" class="glyphicon glyphicon-remove glyphicon-black" title="Remove this pod"></span>
+        </ng-template>
+      </p-column>
+    </p-dataTable>
 
     <a *ngIf="hasFriends()" class="btn btn-danger pull-left" (click)="quitFriends()">
       Quit friends
     </a>
 
-    <a *ngIf="!hasFriends()" class="btn btn-success pull-right" [routerLink]="['/admin/friends/add']">
+    <a *ngIf="!hasFriends()" class="btn btn-success pull-right" [routerLink]="[ '/admin/friends/add' ]">
       Make friends
     </a>
   </div>
index 822a112ccc05ebf9c405a46b7c097d36a6440424..6a8bd492cdecd334ef5a24fe778630a4ca12c7f4 100644 (file)
@@ -1,71 +1,31 @@
-import { Component } from '@angular/core'
+import { Component, OnInit } from '@angular/core'
 
 import { NotificationsService } from 'angular2-notifications'
-import { ServerDataSource } from 'ng2-smart-table'
 
 import { ConfirmService } from '../../../core'
-import { Utils } from '../../../shared'
 import { FriendService } from '../shared'
 import { Pod } from '../../../../../../shared'
 
 @Component({
   selector: 'my-friend-list',
   templateUrl: './friend-list.component.html',
-  styleUrls: [ './friend-list.component.scss' ]
+  styleUrls: ['./friend-list.component.scss']
 })
-export class FriendListComponent {
-  friendsSource = null
-  tableSettings = {
-    mode: 'external',
-    attr: {
-      class: 'table-hover'
-    },
-    hideSubHeader: true,
-    actions: {
-      position: 'right',
-      add: false,
-      edit: false,
-      delete: true
-    },
-    delete: {
-      deleteButtonContent: Utils.getRowDeleteButton()
-    },
-    columns: {
-      id: {
-        title: 'ID',
-        sort: false,
-        sortDirection: 'asc'
-      },
-      host: {
-        title: 'Host',
-        sort: false
-      },
-      email: {
-        title: 'Email',
-        sort: false
-      },
-      score: {
-        title: 'Score',
-        sort: false
-      },
-      createdAt: {
-        title: 'Created Date',
-        sort: false,
-        valuePrepareFunction: Utils.dateToHuman
-      }
-    }
-  }
+export class FriendListComponent implements OnInit {
+  friends: Pod[] = []
 
   constructor (
     private notificationsService: NotificationsService,
     private confirmService: ConfirmService,
     private friendService: FriendService
-  ) {
-    this.friendsSource = this.friendService.getDataSource()
+  ) {}
+
+  ngOnInit () {
+    this.loadData()
   }
 
   hasFriends () {
-    return this.friendsSource.count() !== 0
+    return this.friends.length !== 0
   }
 
   quitFriends () {
@@ -77,32 +37,42 @@ export class FriendListComponent {
         this.friendService.quitFriends().subscribe(
           status => {
             this.notificationsService.success('Success', 'Friends left!')
-            this.friendsSource.refresh()
+            this.loadData()
           },
 
-          err => this.notificationsService.error('Error', err.text)
+          err => this.notificationsService.error('Error', err)
         )
       }
     )
   }
 
-  removeFriend ({ data }) {
+  removeFriend (friend: Pod) {
     const confirmMessage = 'Do you really want to remove this friend ? All its videos will be deleted.'
-    const friend: Pod = data
 
     this.confirmService.confirm(confirmMessage, 'Remove').subscribe(
       res => {
         if (res === false) return
 
         this.friendService.removeFriend(friend).subscribe(
-         status => {
-           this.notificationsService.success('Success', 'Friend removed')
-           this.friendsSource.refresh()
-         },
+          status => {
+            this.notificationsService.success('Success', 'Friend removed')
+            this.loadData()
+          },
 
-         err => this.notificationsService.error('Error', err.text)
-       )
+          err => this.notificationsService.error('Error', err)
+        )
       }
     )
   }
+
+  private loadData () {
+    this.friendService.getFriends()
+                      .subscribe(
+                        resultList => {
+                          this.friends = resultList.data
+                        },
+
+                        err => this.notificationsService.error('Error', err)
+                      )
+  }
 }
index 9b3ff04b197cc48025f40e9f1bde84b9d936a706..45607e28dcc6895bf50f66ec8bf86d9dc5949a2f 100644 (file)
@@ -1,24 +1,24 @@
 import { Injectable } from '@angular/core'
-import { Observable } from 'rxjs/Observable'
+import { HttpClient } from '@angular/common/http'
 import 'rxjs/add/operator/catch'
 import 'rxjs/add/operator/map'
 
-import { ServerDataSource } from 'ng2-smart-table'
-
-import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared'
-import { Pod } from '../../../../../../shared'
+import { RestExtractor,  } from '../../../shared'
+import { Pod, ResultList } from '../../../../../../shared'
 
 @Injectable()
 export class FriendService {
   private static BASE_FRIEND_URL = API_URL + '/api/v1/pods/'
 
   constructor (
-    private authHttp: AuthHttp,
+    private authHttp: HttpClient,
     private restExtractor: RestExtractor
   ) {}
 
-  getDataSource () {
-    return new RestDataSource(this.authHttp, FriendService.BASE_FRIEND_URL)
+  getFriends () {
+    return this.authHttp.get<ResultList<Pod>>(FriendService.BASE_FRIEND_URL)
+                        .map(res => this.restExtractor.convertResultListDateToHuman(res))
+                        .catch(res => this.restExtractor.handleError(res))
   }
 
   makeFriends (notEmptyHosts: String[]) {
@@ -28,18 +28,18 @@ export class FriendService {
 
     return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'make-friends', body)
                         .map(this.restExtractor.extractDataBool)
-                        .catch((res) => this.restExtractor.handleError(res))
+                        .catch(res => this.restExtractor.handleError(res))
   }
 
   quitFriends () {
     return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quit-friends')
-                        .map(res => res.status)
-                        .catch((res) => this.restExtractor.handleError(res))
+                        .map(this.restExtractor.extractDataBool)
+                        .catch(res => this.restExtractor.handleError(res))
   }
 
   removeFriend (friend: Pod) {
     return this.authHttp.delete(FriendService.BASE_FRIEND_URL + friend.id)
                         .map(this.restExtractor.extractDataBool)
-                        .catch((res) => this.restExtractor.handleError(res))
+                        .catch(res => this.restExtractor.handleError(res))
   }
 }
index e9b166f787a73e346e75e447349572afa7343cff..44d9cbc3e2010ab2d97570bd5c44ffcc92a2383e 100644 (file)
@@ -1,10 +1,11 @@
 import { Injectable } from '@angular/core'
+import { HttpClient } from '@angular/common/http'
 import { Observable } from 'rxjs/Observable'
 import 'rxjs/add/operator/catch'
 import 'rxjs/add/operator/map'
 
 import { RequestSchedulerStats } from '../../../../../../shared'
-import { AuthHttp, RestExtractor } from '../../../shared'
+import { RestExtractor } from '../../../shared'
 import { RequestSchedulerStatsAttributes } from './request-schedulers-stats-attributes.model'
 
 @Injectable()
@@ -12,19 +13,18 @@ export class RequestSchedulersService {
   private static BASE_REQUEST_URL = API_URL + '/api/v1/request-schedulers/'
 
   constructor (
-    private authHttp: AuthHttp,
+    private authHttp: HttpClient,
     private restExtractor: RestExtractor
   ) {}
 
-  getStats (): Observable<RequestSchedulerStats> {
-    return this.authHttp.get(RequestSchedulersService.BASE_REQUEST_URL + 'stats')
-                        .map(this.restExtractor.extractDataGet)
-                        .map(this.buildRequestObjects)
-                        .catch((res) => this.restExtractor.handleError(res))
+  getStats () {
+    return this.authHttp.get<RequestSchedulerStats>(RequestSchedulersService.BASE_REQUEST_URL + 'stats')
+                        .map(res => this.buildRequestObjects(res))
+                        .catch(res => this.restExtractor.handleError(res))
   }
 
   private buildRequestObjects (data: RequestSchedulerStats) {
-    const requestSchedulers = {}
+    const requestSchedulers: { [ id: string ]: RequestSchedulerStatsAttributes } = {}
 
     Object.keys(data).forEach(requestSchedulerName => {
       requestSchedulers[requestSchedulerName] = new RequestSchedulerStatsAttributes(data[requestSchedulerName])
index 999013bccdbf83aed68f8da12666abac996fb90b..a4b89bf1d5e31e06105b6bd1eadfb56860cc5597 100644 (file)
@@ -1,11 +1,14 @@
 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/primeng'
 import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'
 
-import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared'
-import { UserCreate, UserUpdate } from '../../../../../../shared'
+import { RestExtractor, User, RestPagination, RestService } from '../../../shared'
+import { UserCreate, UserUpdate, ResultList } from '../../../../../../shared'
 
 @Injectable()
 export class UserService {
@@ -13,53 +16,52 @@ export class UserService {
   private bytesPipe = new BytesPipe()
 
   constructor (
-    private authHttp: AuthHttp,
+    private authHttp: HttpClient,
+    private restService: RestService,
     private restExtractor: RestExtractor
   ) {}
 
   addUser (userCreate: UserCreate) {
     return this.authHttp.post(UserService.BASE_USERS_URL, userCreate)
                         .map(this.restExtractor.extractDataBool)
-                        .catch(this.restExtractor.handleError)
+                        .catch(err => this.restExtractor.handleError(err))
   }
 
   updateUser (userId: number, userUpdate: UserUpdate) {
     return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
-               .map(this.restExtractor.extractDataBool)
-               .catch(this.restExtractor.handleError)
+                        .map(this.restExtractor.extractDataBool)
+                        .catch(err => this.restExtractor.handleError(err))
   }
 
   getUser (userId: number) {
-    return this.authHttp.get(UserService.BASE_USERS_URL + userId)
-                        .map(this.restExtractor.extractDataGet)
-                        .catch(this.restExtractor.handleError)
+    return this.authHttp.get<User>(UserService.BASE_USERS_URL + userId)
+                        .catch(err => this.restExtractor.handleError(err))
   }
 
-  getDataSource () {
-    return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL, this.formatDataSource.bind(this))
+  getUsers (pagination: RestPagination, sort: SortMeta): Observable<ResultList<User>> {
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
+
+    return this.authHttp.get<ResultList<User>>(UserService.BASE_USERS_URL, { params })
+                        .map(res => this.restExtractor.convertResultListDateToHuman(res))
+                        .map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this)))
+                        .catch(err => this.restExtractor.handleError(err))
   }
 
   removeUser (user: User) {
     return this.authHttp.delete(UserService.BASE_USERS_URL + user.id)
   }
 
-  private formatDataSource (users: User[]) {
-    const newUsers = []
+  private formatUser (user: User) {
+    let videoQuota
+    if (user.videoQuota === -1) {
+      videoQuota = 'Unlimited'
+    } else {
+      videoQuota = this.bytesPipe.transform(user.videoQuota)
+    }
 
-    users.forEach(user => {
-      let videoQuota
-      if (user.videoQuota === -1) {
-        videoQuota = 'Unlimited'
-      } else {
-        videoQuota = this.bytesPipe.transform(user.videoQuota)
-      }
-
-      const newUser = Object.assign(user, {
-        videoQuota
-      })
-      newUsers.push(newUser)
+    return Object.assign(user, {
+      videoQuota
     })
-
-    return newUsers
   }
 }
index eb5bc9d4a891ac32f21c600c7b37a5d0e52549c9..2944e3cbfe6043b02f1bc2a59509b7654f9c88d5 100644 (file)
@@ -3,10 +3,29 @@
 
     <h3>Users list</h3>
 
-    <ng2-smart-table
-      [settings]="tableSettings" [source]="usersSource"
-      (delete)="removeUser($event)" (edit)="editUser($event)"
-    ></ng2-smart-table>
+    <p-dataTable
+        [value]="users" [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="username" header="Username" [sortable]="true"></p-column>
+      <p-column field="email" header="Email"></p-column>
+      <p-column field="videoQuota" header="Video quota"></p-column>
+      <p-column field="role" header="Role"></p-column>
+      <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
+      <p-column header="Edit" styleClass="action-cell">
+        <ng-template pTemplate="body" let-user="rowData">
+          <a [routerLink]="getRouterUserEditLink(user)" title="Edit this user">
+            <span class="glyphicon glyphicon-pencil glyphicon-black"></span>
+          </a>
+        </ng-template>
+      </p-column>
+      <p-column header="Delete" styleClass="action-cell">
+        <ng-template pTemplate="body" let-user="rowData">
+          <span (click)="removeUser(user)" class="glyphicon glyphicon-remove glyphicon-black" title="Remove this user"></span>
+        </ng-template>
+      </p-column>
+    </p-dataTable>
 
     <a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']">
       <span class="glyphicon glyphicon-plus"></span>
index 7187a2008a3f97a3d0d99e47ae71281df11f1257..c3fa55825bca613e789943c3f5e53c8fd4917d32 100644 (file)
@@ -1,82 +1,37 @@
-import { Component } from '@angular/core'
+import { Component, OnInit } from '@angular/core'
+import { SortMeta } from 'primeng/primeng'
 
 import { NotificationsService } from 'angular2-notifications'
 
 import { ConfirmService } from '../../../core'
-import { RestDataSource, User, Utils } from '../../../shared'
+import { RestTable, RestPagination, User } from '../../../shared'
 import { UserService } from '../shared'
-import { Router } from '@angular/router'
 
 @Component({
   selector: 'my-user-list',
   templateUrl: './user-list.component.html',
   styleUrls: [ './user-list.component.scss' ]
 })
-export class UserListComponent {
-  usersSource: RestDataSource = null
-  tableSettings = {
-    mode: 'external',
-    attr: {
-      class: 'table-hover'
-    },
-    hideSubHeader: true,
-    actions: {
-      position: 'right',
-      add: false,
-      edit: true,
-      delete: true
-    },
-    delete: {
-      deleteButtonContent: Utils.getRowDeleteButton()
-    },
-    edit: {
-      editButtonContent: Utils.getRowEditButton()
-    },
-    pager: {
-      display: true,
-      perPage: 10
-    },
-    columns: {
-      id: {
-        title: 'ID',
-        sortDirection: 'asc'
-      },
-      username: {
-        title: 'Username'
-      },
-      email: {
-        title: 'Email'
-      },
-      videoQuota: {
-        title: 'Video quota'
-      },
-      role: {
-        title: 'Role',
-        sort: false
-      },
-      createdAt: {
-        title: 'Created Date',
-        valuePrepareFunction: Utils.dateToHuman
-      }
-    }
-  }
+export class UserListComponent extends RestTable implements OnInit {
+  users: User[] = []
+  totalRecords = 0
+  rowsPerPage = 10
+  sort: SortMeta = { field: 'id', order: 1 }
+  pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
 
   constructor (
-    private router: Router,
     private notificationsService: NotificationsService,
     private confirmService: ConfirmService,
     private userService: UserService
   ) {
-    this.usersSource = this.userService.getDataSource()
+    super()
   }
 
-  editUser ({ data }: { data: User }) {
-    this.router.navigate([ '/admin', 'users', data.id, 'update' ])
+  ngOnInit () {
+    this.loadData()
   }
 
-  removeUser ({ data }: { data: User }) {
-    const user = data
-
+  removeUser (user: User) {
     if (user.username === 'root') {
       this.notificationsService.error('Error', 'You cannot delete root.')
       return
@@ -89,12 +44,28 @@ export class UserListComponent {
         this.userService.removeUser(user).subscribe(
           () => {
             this.notificationsService.success('Success', `User ${user.username} deleted.`)
-            this.usersSource.refresh()
+            this.loadData()
           },
 
-          err => this.notificationsService.error('Error', err.text)
+          err => this.notificationsService.error('Error', err)
         )
       }
     )
   }
+
+  getRouterUserEditLink (user: User) {
+    return [ '/admin', 'users', user.id, 'update' ]
+  }
+
+  protected loadData () {
+    this.userService.getUsers(this.pagination, this.sort)
+                    .subscribe(
+                      resultList => {
+                        this.users = resultList.data
+                        this.totalRecords = resultList.total
+                      },
+
+                      err => this.notificationsService.error('Error', err)
+                    )
+  }
 }
index c6723a7343a694659e654bcae339bb9d0f2a3c6e..e73f38112cba651eb7bef29ba7884f50b3d44ac4 100644 (file)
@@ -3,9 +3,21 @@
 
   <h3>Video abuses list</h3>
 
-  <ng2-smart-table
-    [settings]="tableSettings" [source]="videoAbusesSource"
-  ></ng2-smart-table>
+  <p-dataTable
+      [value]="videoAbuses" [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="reason" header="Reason"></p-column>
+    <p-column field="reporterPodHost" header="Reporter pod host"></p-column>
+    <p-column field="reporterUsername" header="Reporter username"></p-column>
+    <p-column header="Video" styleClass="action-cell">
+      <ng-template pTemplate="body" let-videoAbuse="rowData">
+        <a [routerLink]="getRouterVideoLink(videoAbuse.videoId)" title="Go to the video">{{ videoAbuse.videoId }}</a>
+      </ng-template>
+    </p-column>
+    <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
+  </p-dataTable>
 
   </div>
 </div>
index 7c838fbf0c526982b9fa7c0201b2335369b06715..cc9c1bdf43891c0e0df80c2bac69076b1ef86380 100644 (file)
@@ -1,72 +1,46 @@
-import { Component } from '@angular/core'
+import { Component, OnInit } from '@angular/core'
 
 import { NotificationsService } from 'angular2-notifications'
+import { SortMeta } from 'primeng/primeng'
 
-import { Utils, VideoAbuseService } from '../../../shared'
-import { VideoAbuse } from '../../../../../shared'
+import { RestTable, RestPagination, VideoAbuseService } from '../../../shared'
+import { VideoAbuse } from '../../../../../../shared'
 
 @Component({
   selector: 'my-video-abuse-list',
   templateUrl: './video-abuse-list.component.html'
 })
-export class VideoAbuseListComponent {
-  videoAbusesSource = null
-  tableSettings = {
-    mode: 'external',
-    attr: {
-      class: 'table-hover'
-    },
-    hideSubHeader: true,
-    actions: {
-      position: 'right',
-      add: false,
-      edit: false,
-      delete: false
-    },
-    pager: {
-      display: true,
-      perPage: 10
-    },
-    columns: {
-      id: {
-        title: 'ID',
-        sortDirection: 'asc'
-      },
-      reason: {
-        title: 'Reason',
-        sort: false
-      },
-      reporterPodHost: {
-        title: 'Reporter pod host',
-        sort: false
-      },
-      reporterUsername: {
-        title: 'Reporter username',
-        sort: false
-      },
-      videoId: {
-        title: 'Video',
-        type: 'html',
-        sort: false,
-        valuePrepareFunction: this.buildVideoLink
-      },
-      createdAt: {
-        title: 'Created Date',
-        valuePrepareFunction: Utils.dateToHuman
-      }
-    }
-  }
+export class VideoAbuseListComponent extends RestTable implements OnInit {
+  videoAbuses: VideoAbuse[] = []
+  totalRecords = 0
+  rowsPerPage = 1
+  sort: SortMeta = { field: 'id', order: 1 }
+  pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
 
   constructor (
     private notificationsService: NotificationsService,
     private videoAbuseService: VideoAbuseService
   ) {
-    this.videoAbusesSource = this.videoAbuseService.getDataSource()
+    super()
+  }
+
+  ngOnInit () {
+    this.loadData()
   }
 
-  buildVideoLink (videoId: string) {
-    // TODO: transform to routerLink
-    // https://github.com/akveo/ng2-smart-table/issues/57
-    return `<a href="/videos/${videoId}" title="Go to the video">${videoId}</a>`
+  getRouterVideoLink (videoId: number) {
+    return [ '/videos', videoId ]
+  }
+
+  protected loadData () {
+    return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort)
+               .subscribe(
+                 resultList => {
+                   this.videoAbuses = resultList.data
+                   this.totalRecords = resultList.total
+                 },
+
+                 err => this.notificationsService.error('Error', err)
+               )
   }
 }
index 8cbed50095279d7dee59d2d5afb987257e5a9b3a..78e365a624296bafe51135c3e5a3f7bac761df57 100644 (file)
@@ -59,7 +59,7 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
       () => {
         this.notificationsService.success('Success', 'Information updated.')
 
-        this.authService.refreshUserInformations()
+        this.authService.refreshUserInformation()
       },
 
       err => this.error = err
index a90654e269dc4f0958c43b4326f643e86a3ca86a..57bf64f69d8455e1336d0fcad9e204420778ef43 100644 (file)
@@ -36,6 +36,8 @@ export class AppComponent implements OnInit {
   ) {}
 
   ngOnInit () {
+    this.authService.loadClientCredentials()
+
     if (this.authService.isLoggedIn()) {
       // The service will automatically redirect to the login page if the token is not valid anymore
       this.userService.checkTokenValidity()
index 804a7a71ed693d7510a97f8f33fad5b58146a98c..6aa56b8a797b2c895b2a0ab64eacd6b202828a7e 100644 (file)
@@ -7,8 +7,6 @@ import {
 } from '@angularclass/hmr'
 
 import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core'
-// TODO: remove, we need this to avoid error in ng2-smart-table
-import 'rxjs/add/operator/toPromise'
 import 'bootstrap-loader'
 
 import { ENV_PROVIDERS } from './environment'
index de9e14b2d7d4823e3fffeff0f08b9bf766548d24..522efb23ced707eef55a07bd7df67bac7e1fd2af 100644 (file)
@@ -1,8 +1,8 @@
 import { Injectable } from '@angular/core'
-import { Headers, Http, Response, URLSearchParams } from '@angular/http'
 import { Router } from '@angular/router'
 import { Observable } from 'rxjs/Observable'
 import { Subject } from 'rxjs/Subject'
+import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
 import 'rxjs/add/operator/map'
 import 'rxjs/add/operator/mergeMap'
 import 'rxjs/add/observable/throw'
@@ -11,15 +11,35 @@ import { NotificationsService } from 'angular2-notifications'
 
 import { AuthStatus } from './auth-status.model'
 import { AuthUser } from './auth-user.model'
-import { OAuthClientLocal, UserRole } from '../../../../../shared'
+import { OAuthClientLocal, UserRole, UserRefreshToken } from '../../../../../shared'
 // Do not use the barrel (dependency loop)
 import { RestExtractor } from '../../shared/rest'
+import { UserLogin } from '../../../../../shared/models/users/user-login.model'
+import { User } from '../../shared/users/user.model'
+
+interface UserLoginWithUsername extends UserLogin {
+  access_token: string
+  refresh_token: string
+  token_type: string
+  username: string
+}
+
+interface UserLoginWithUserInformation extends UserLogin {
+  access_token: string
+  refresh_token: string
+  token_type: string
+  username: string
+  id: number
+  role: UserRole
+  displayNSFW: boolean
+  email: string
+}
 
 @Injectable()
 export class AuthService {
   private static BASE_CLIENT_URL = API_URL + '/api/v1/oauth-clients/local'
   private static BASE_TOKEN_URL = API_URL + '/api/v1/users/token'
-  private static BASE_USER_INFORMATIONS_URL = API_URL + '/api/v1/users/me'
+  private static BASE_USER_INFORMATION_URL = API_URL + '/api/v1/users/me'
 
   loginChangedSource: Observable<AuthStatus>
 
@@ -29,7 +49,7 @@ export class AuthService {
   private user: AuthUser = null
 
   constructor (
-    private http: Http,
+    private http: HttpClient,
     private notificationsService: NotificationsService,
     private restExtractor: RestExtractor,
     private router: Router
@@ -37,32 +57,33 @@ export class AuthService {
     this.loginChanged = new Subject<AuthStatus>()
     this.loginChangedSource = this.loginChanged.asObservable()
 
-    // Fetch the client_id/client_secret
-    // FIXME: save in local storage?
-    this.http.get(AuthService.BASE_CLIENT_URL)
-      .map(this.restExtractor.extractDataGet)
-      .catch(res => this.restExtractor.handleError(res))
-      .subscribe(
-        (result: OAuthClientLocal) => {
-          this.clientId = result.client_id
-          this.clientSecret = result.client_secret
-          console.log('Client credentials loaded.')
-        },
-
-        error => {
-          let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n`
-          errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.'
-
-          // We put a bigger timeout
-          // This is an important message
-          this.notificationsService.error('Error', errorMessage, { timeOut: 7000 })
-        }
-      )
-
     // Return null if there is nothing to load
     this.user = AuthUser.load()
   }
 
+  loadClientCredentials () {
+    // Fetch the client_id/client_secret
+    // FIXME: save in local storage?
+    this.http.get<OAuthClientLocal>(AuthService.BASE_CLIENT_URL)
+             .catch(res => this.restExtractor.handleError(res))
+             .subscribe(
+               res => {
+                 this.clientId = res.client_id
+                 this.clientSecret = res.client_secret
+                 console.log('Client credentials loaded.')
+               },
+
+               error => {
+                 let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n`
+                 errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.'
+
+                 // We put a bigger timeout
+                 // This is an important message
+                 this.notificationsService.error('Error', errorMessage, { timeOut: 7000 })
+               }
+             )
+  }
+
   getRefreshToken () {
     if (this.user === null) return null
 
@@ -70,7 +91,11 @@ export class AuthService {
   }
 
   getRequestHeaderValue () {
-    return `${this.getTokenType()} ${this.getAccessToken()}`
+    const accessToken = this.getAccessToken()
+
+    if (accessToken === null) return null
+
+    return `${this.getTokenType()} ${accessToken}`
   }
 
   getAccessToken () {
@@ -96,39 +121,26 @@ export class AuthService {
   }
 
   isLoggedIn () {
-    if (this.getAccessToken()) {
-      return true
-    } else {
-      return false
-    }
+    return !!this.getAccessToken()
   }
 
   login (username: string, password: string) {
-    let body = new URLSearchParams()
-    body.set('client_id', this.clientId)
-    body.set('client_secret', this.clientSecret)
-    body.set('response_type', 'code')
-    body.set('grant_type', 'password')
-    body.set('scope', 'upload')
-    body.set('username', username)
-    body.set('password', password)
-
-    let headers = new Headers()
-    headers.append('Content-Type', 'application/x-www-form-urlencoded')
-
-    let options = {
-      headers: headers
-    }
-
-    return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options)
-                    .map(this.restExtractor.extractDataGet)
-                    .map(res => {
-                      res.username = username
-                      return res
-                    })
-                    .flatMap(res => this.mergeUserInformations(res))
+    // Form url encoded
+    const body = new HttpParams().set('client_id', this.clientId)
+                                 .set('client_secret', this.clientSecret)
+                                 .set('response_type', 'code')
+                                 .set('grant_type', 'password')
+                                 .set('scope', 'upload')
+                                 .set('username', username)
+                                 .set('password', password)
+
+    const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
+
+    return this.http.post<UserLogin>(AuthService.BASE_TOKEN_URL, body, { headers })
+                    .map(res => Object.assign(res, { username }))
+                    .flatMap(res => this.mergeUserInformation(res))
                     .map(res => this.handleLogin(res))
-                    .catch((res) => this.restExtractor.handleError(res))
+                    .catch(res => this.restExtractor.handleError(res))
   }
 
   logout () {
@@ -145,33 +157,26 @@ export class AuthService {
 
     const refreshToken = this.getRefreshToken()
 
-    let body = new URLSearchParams()
-    body.set('refresh_token', refreshToken)
-    body.set('client_id', this.clientId)
-    body.set('client_secret', this.clientSecret)
-    body.set('response_type', 'code')
-    body.set('grant_type', 'refresh_token')
-
-    let headers = new Headers()
-    headers.append('Content-Type', 'application/x-www-form-urlencoded')
+    // Form url encoded
+    const body = new HttpParams().set('refresh_token', refreshToken)
+                                 .set('client_id', this.clientId)
+                                 .set('client_secret', this.clientSecret)
+                                 .set('response_type', 'code')
+                                 .set('grant_type', 'refresh_token')
 
-    let options = {
-      headers: headers
-    }
+    const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
 
-    return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options)
-                    .map(this.restExtractor.extractDataGet)
+    return this.http.post<UserRefreshToken>(AuthService.BASE_TOKEN_URL, body, { headers })
                     .map(res => this.handleRefreshToken(res))
-                    .catch((res: Response) => {
+                    .catch(res => {
                       // The refresh token is invalid?
-                      if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') {
+                      if (res.status === 400 && res.error === 'invalid_grant') {
                         console.error('Cannot refresh token -> logout...')
                         this.logout()
                         this.router.navigate(['/login'])
 
                         return Observable.throw({
-                          json: () => '',
-                          text: () => 'You need to reconnect.'
+                          error: 'You need to reconnect.'
                         })
                       }
 
@@ -179,7 +184,7 @@ export class AuthService {
                     })
   }
 
-  refreshUserInformations () {
+  refreshUserInformation () {
     const obj = {
       access_token: this.user.getAccessToken(),
       refresh_token: null,
@@ -187,7 +192,7 @@ export class AuthService {
       username: this.user.username
     }
 
-    this.mergeUserInformation(obj)
+    this.mergeUserInformation(obj)
         .subscribe(
           res => {
             this.user.displayNSFW = res.displayNSFW
@@ -198,42 +203,25 @@ export class AuthService {
         )
   }
 
-  private mergeUserInformations (obj: {
-    access_token: string,
-    refresh_token: string,
-    token_type: string,
-    username: string
-  }) {
-    // Do not call authHttp here to avoid circular dependencies headaches
-
-    const headers = new Headers()
-    headers.set('Authorization', `Bearer ${obj.access_token}`)
-
-    return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers })
-             .map(res => res.json())
-             .map(res => {
-               const newProperties = {
-                 id: res.id as number,
-                 role: res.role as UserRole,
-                 displayNSFW: res.displayNSFW as boolean,
-                 email: res.email as string
-               }
+  private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> {
+    // User is not loaded yet, set manually auth header
+    const headers = new HttpHeaders().set('Authorization', `${obj.token_type} ${obj.access_token}`)
+
+    return this.http.get<User>(AuthService.BASE_USER_INFORMATION_URL, { headers })
+                    .map(res => {
+                      const newProperties = {
+                        id: res.id as number,
+                        role: res.role as UserRole,
+                        displayNSFW: res.displayNSFW as boolean,
+                        email: res.email as string
+                      }
 
-               return Object.assign(obj, newProperties)
-             }
+                      return Object.assign(obj, newProperties)
+                    }
     )
   }
 
-  private handleLogin (obj: {
-    access_token: string,
-    refresh_token: string,
-    token_type: string,
-    id: number,
-    username: string,
-    email: string,
-    role: UserRole,
-    displayNSFW: boolean
-  }) {
+  private handleLogin (obj: UserLoginWithUserInformation) {
     const id = obj.id
     const username = obj.username
     const role = obj.role
@@ -251,7 +239,7 @@ export class AuthService {
     this.setStatus(AuthStatus.LoggedIn)
   }
 
-  private handleRefreshToken (obj: { access_token: string, refresh_token: string }) {
+  private handleRefreshToken (obj: UserRefreshToken) {
     this.user.refreshTokens(obj.access_token, obj.refresh_token)
     this.user.save()
   }
@@ -259,5 +247,4 @@ export class AuthService {
   private setStatus (status: AuthStatus) {
     this.loginChanged.next(status)
   }
-
 }
index acdc12cc66b5e60065c458161c29702c26178f08..3c479bcb8cc2fdb75a4314e8bc4959a673b060cc 100644 (file)
@@ -1,7 +1,6 @@
 import { Injectable } from '@angular/core'
-import { Http } from '@angular/http'
+import { HttpClient } from '@angular/common/http'
 
-import { RestExtractor } from '../../shared/rest'
 import { ServerConfig } from '../../../../../shared'
 
 @Injectable()
@@ -14,17 +13,11 @@ export class ConfigService {
     }
   }
 
-  constructor (
-    private http: Http,
-    private restExtractor: RestExtractor
-  ) {}
+  constructor (private http: HttpClient) {}
 
   loadConfig () {
-    this.http.get(ConfigService.BASE_CONFIG_URL)
-             .map(this.restExtractor.extractDataGet)
-             .subscribe(data => {
-               this.config = data
-             })
+    this.http.get<ServerConfig>(ConfigService.BASE_CONFIG_URL)
+             .subscribe(data => this.config = data)
   }
 
   getConfig () {
diff --git a/client/src/app/shared/auth/auth-http.service.ts b/client/src/app/shared/auth/auth-http.service.ts
deleted file mode 100644 (file)
index 0fbaab0..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-import { Injectable } from '@angular/core'
-import {
-  ConnectionBackend,
-  Headers,
-  Http,
-  Request,
-  RequestMethod,
-  RequestOptions,
-  RequestOptionsArgs,
-  Response,
-  XHRBackend
-} from '@angular/http'
-import { Observable } from 'rxjs/Observable'
-
-import { AuthService } from '../../core'
-
-@Injectable()
-export class AuthHttp extends Http {
-  constructor (backend: ConnectionBackend, defaultOptions: RequestOptions, private authService: AuthService) {
-    super(backend, defaultOptions)
-  }
-
-  request (url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
-    if (!options) options = {}
-
-    options.headers = new Headers()
-    this.setAuthorizationHeader(options.headers)
-
-    return super.request(url, options)
-                .catch((err) => {
-                  if (err.status === 401) {
-                    return this.handleTokenExpired(url, options)
-                  }
-
-                  return Observable.throw(err)
-                })
-  }
-
-  delete (url: string, options?: RequestOptionsArgs): Observable<Response> {
-    if (!options) options = {}
-    options.method = RequestMethod.Delete
-
-    return this.request(url, options)
-  }
-
-  get (url: string, options?: RequestOptionsArgs): Observable<Response> {
-    if (!options) options = {}
-    options.method = RequestMethod.Get
-
-    return this.request(url, options)
-  }
-
-  post (url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
-    if (!options) options = {}
-    options.method = RequestMethod.Post
-    options.body = body
-
-    return this.request(url, options)
-  }
-
-  put (url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
-    if (!options) options = {}
-    options.method = RequestMethod.Put
-    options.body = body
-
-    return this.request(url, options)
-  }
-
-  private handleTokenExpired (url: string | Request, options: RequestOptionsArgs) {
-    return this.authService.refreshAccessToken()
-                           .flatMap(() => {
-                             this.setAuthorizationHeader(options.headers)
-
-                             return super.request(url, options)
-                           })
-  }
-
-  private setAuthorizationHeader (headers: Headers) {
-    headers.set('Authorization', this.authService.getRequestHeaderValue())
-  }
-}
-
-export function useFactory (backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) {
-  return new AuthHttp(backend, defaultOptions, authService)
-}
-
-export const AUTH_HTTP_PROVIDERS = [
-  {
-    provide: AuthHttp,
-    useFactory,
-    deps: [ XHRBackend, RequestOptions, AuthService ]
-  }
-]
diff --git a/client/src/app/shared/auth/auth-interceptor.service.ts b/client/src/app/shared/auth/auth-interceptor.service.ts
new file mode 100644 (file)
index 0000000..1e890d8
--- /dev/null
@@ -0,0 +1,62 @@
+import { Injectable, Injector } from '@angular/core'
+import {
+  HttpInterceptor,
+  HttpRequest,
+  HttpEvent,
+  HttpHandler, HTTP_INTERCEPTORS
+} from '@angular/common/http'
+import { Observable } from 'rxjs/Observable'
+
+import { AuthService } from '../../core'
+import 'rxjs/add/operator/switchMap'
+
+@Injectable()
+export class AuthInterceptor implements HttpInterceptor {
+  private authService: AuthService
+
+  // https://github.com/angular/angular/issues/18224#issuecomment-316957213
+  constructor (private injector: Injector) {}
+
+  intercept (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
+    if (this.authService === undefined) {
+      this.authService = this.injector.get(AuthService)
+    }
+
+    const authReq = this.cloneRequestWithAuth(req)
+
+    // Pass on the cloned request instead of the original request
+    // Catch 401 errors (refresh token expired)
+    return next.handle(authReq)
+               .catch(err => {
+                 if (err.status === 401) {
+                   return this.handleTokenExpired(req, next)
+                 }
+
+                 return Observable.throw(err)
+               })
+  }
+
+  private handleTokenExpired (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
+    return this.authService.refreshAccessToken()
+                           .switchMap(() => {
+                             const authReq = this.cloneRequestWithAuth(req)
+
+                             return next.handle(authReq)
+                           })
+  }
+
+  private cloneRequestWithAuth (req: HttpRequest<any>) {
+    const authHeaderValue = this.authService.getRequestHeaderValue()
+
+    if (authHeaderValue === null) return req
+
+    // Clone the request to add the new header
+    return req.clone({ headers: req.headers.set('Authorization', authHeaderValue) })
+  }
+}
+
+export const AUTH_INTERCEPTOR_PROVIDER = {
+  provide: HTTP_INTERCEPTORS,
+  useClass: AuthInterceptor,
+  multi: true
+}
index 0f2bfb0d68cb111deb0dbfdeb297ddf6f108ff6a..84a07196f3f688507c5a7bee95edfd685b399982 100644 (file)
@@ -1 +1 @@
-export * from './auth-http.service'
+export * from './auth-interceptor.service'
index e0be155cf7e87415dfc56ca641a58d2688310203..3f1996130b86ebf49ced866e17cac310f05cd745 100644 (file)
@@ -2,3 +2,4 @@ export * from './rest-data-source'
 export * from './rest-extractor.service'
 export * from './rest-pagination'
 export * from './rest.service'
+export * from './rest-table'
index 5c205d280ce1ecaad453817a09f1d652ffacac44..57a2efb57bd9f061f9a11901fee78e73f2ab5422 100644 (file)
@@ -1,68 +1,32 @@
-import { Http, RequestOptionsArgs, URLSearchParams, Response } from '@angular/http'
-
-import { ServerDataSource } from 'ng2-smart-table'
-
-export class RestDataSource extends ServerDataSource {
-  private updateResponse: (input: any[]) => any[]
-
-  constructor (http: Http, endpoint: string, updateResponse?: (input: any[]) => any[]) {
-    const options = {
-      endPoint: endpoint,
-      sortFieldKey: 'sort',
-      dataKey: 'data'
-    }
-    super(http, options)
-
-    if (updateResponse) {
-      this.updateResponse = updateResponse
-    }
-  }
-
-  protected extractDataFromResponse (res: Response) {
-    const json = res.json()
-    if (!json) return []
-    let data = json.data
-
-    if (this.updateResponse !== undefined) {
-      data = this.updateResponse(data)
-    }
-
-    return data
-  }
-
-  protected extractTotalFromResponse (res: Response) {
-    const rawData = res.json()
-    return rawData ? parseInt(rawData.total, 10) : 0
-  }
-
-  protected addSortRequestOptions (requestOptions: RequestOptionsArgs) {
-    const searchParams = requestOptions.params as URLSearchParams
-
-    if (this.sortConf) {
-      this.sortConf.forEach((fieldConf) => {
-        const sortPrefix = fieldConf.direction === 'desc' ? '-' : ''
-
-        searchParams.set(this.conf.sortFieldKey, sortPrefix + fieldConf.field)
-      })
-    }
-
-    return requestOptions
-  }
-
-  protected addPagerRequestOptions (requestOptions: RequestOptionsArgs) {
-    const searchParams = requestOptions.params as URLSearchParams
-
-    if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) {
-      const perPage = this.pagingConf['perPage']
-      const page = this.pagingConf['page']
-
-      const start = (page - 1) * perPage
-      const count = perPage
-
-      searchParams.set('start', start.toString())
-      searchParams.set('count', count.toString())
-    }
-
-    return requestOptions
-  }
+export class RestDataSource  {
+  // protected addSortRequestOptions (requestOptions: RequestOptionsArgs) {
+  //   const searchParams = requestOptions.params as URLSearchParams
+  //
+  //   if (this.sortConf) {
+  //     this.sortConf.forEach((fieldConf) => {
+  //       const sortPrefix = fieldConf.direction === 'desc' ? '-' : ''
+  //
+  //       searchParams.set(this.conf.sortFieldKey, sortPrefix + fieldConf.field)
+  //     })
+  //   }
+  //
+  //   return requestOptions
+  // }
+  //
+  // protected addPagerRequestOptions (requestOptions: RequestOptionsArgs) {
+  //   const searchParams = requestOptions.params as URLSearchParams
+  //
+  //   if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) {
+  //     const perPage = this.pagingConf['perPage']
+  //     const page = this.pagingConf['page']
+  //
+  //     const start = (page - 1) * perPage
+  //     const count = perPage
+  //
+  //     searchParams.set('start', start.toString())
+  //     searchParams.set('count', count.toString())
+  //   }
+  //
+  //   return requestOptions
+  // }
 }
index f6a818ec86515abaf24fb6f4fca6d036d2ecefb5..32dad5c732e00e47cecf3e67710ad7df7d713388 100644 (file)
@@ -1,52 +1,58 @@
 import { Injectable } from '@angular/core'
-import { Response } from '@angular/http'
 import { Observable } from 'rxjs/Observable'
+import { HttpErrorResponse } from '@angular/common/http'
 
-export interface ResultList {
-  data: any[]
-  total: number
-}
+import { Utils } from '../utils'
+import { ResultList } from '../../../../../shared'
 
 @Injectable()
 export class RestExtractor {
 
-  extractDataBool (res: Response) {
+  extractDataBool () {
     return true
   }
 
-  extractDataList (res: Response) {
-    const body = res.json()
+  applyToResultListData <T> (result: ResultList<T>, fun: Function, additionalArgs?: any[]): ResultList<T> {
+    const data: T[] = result.data
+    const newData: T[] = []
 
-    const ret: ResultList = {
-      data: body.data,
-      total: body.total
-    }
+    data.forEach(d => newData.push(fun.call(this, d, additionalArgs)))
 
-    return ret
+    return {
+      total: result.total,
+      data: newData
+    }
   }
 
-  extractDataGet (res: Response) {
-    return res.json()
+  convertResultListDateToHuman <T> (result: ResultList<T>, fieldsToConvert: string[] = [ 'createdAt' ]): ResultList<T> {
+    return this.applyToResultListData(result, this.convertDateToHuman, [ fieldsToConvert ])
   }
 
-  handleError (res: Response) {
-    let text = 'Server error: '
-    text += res.text()
-    let json = ''
+  convertDateToHuman (target: object, fieldsToConvert: string[]) {
+    const source = {}
+    fieldsToConvert.forEach(field => {
+      source[field] = Utils.dateToHuman(target[field])
+    })
 
-    try {
-      json = res.json()
-    } catch (err) {
-      console.error('Cannot get JSON from response.')
-    }
+    return Object.assign(target, source)
+  }
 
-    const error = {
-      json,
-      text
+  handleError (err: HttpErrorResponse) {
+    let errorMessage
+
+    if (err.error instanceof Error) {
+      // A client-side or network error occurred. Handle it accordingly.
+      errorMessage = err.error.message
+      console.error('An error occurred:', errorMessage)
+    } else if (err.status !== undefined) {
+      // The backend returned an unsuccessful response code.
+      // The response body may contain clues as to what went wrong,
+      errorMessage = err.error
+      console.error(`Backend returned code ${err.status}, body was: ${errorMessage}`)
+    } else {
+      errorMessage = err
     }
 
-    console.error(error)
-
-    return Observable.throw(error)
+    return Observable.throw(errorMessage)
   }
 }
index 766e7a9e5cf91ba24bd674a55b167abb97050e03..0faa593032015ee8c60608fe076a3c0a9e005784 100644 (file)
@@ -1,5 +1,4 @@
 export interface RestPagination {
-  currentPage: number
-  itemsPerPage: number
-  totalItems: number
+  start: number
+  count: number
 }
diff --git a/client/src/app/shared/rest/rest-table.ts b/client/src/app/shared/rest/rest-table.ts
new file mode 100644 (file)
index 0000000..db2cb5e
--- /dev/null
@@ -0,0 +1,27 @@
+import { LazyLoadEvent, SortMeta } from 'primeng/primeng'
+
+import { RestPagination } from './rest-pagination'
+
+export abstract class RestTable {
+  abstract totalRecords: number
+  abstract rowsPerPage: number
+  abstract sort: SortMeta
+  abstract pagination: RestPagination
+
+  protected abstract loadData (): void
+
+  loadLazy (event: LazyLoadEvent) {
+    this.sort = {
+      order: event.sortOrder,
+      field: event.sortField
+    }
+
+    this.pagination = {
+      start: event.first,
+      count: this.rowsPerPage
+    }
+
+    this.loadData()
+  }
+
+}
index 43dc20b34e09eb4618e17e43708baf2e34f276e5..f7838ba06a51e899a7cf784af2369f098da801a3 100644 (file)
@@ -1,27 +1,34 @@
 import { Injectable } from '@angular/core'
-import { URLSearchParams } from '@angular/http'
+import { HttpParams } from '@angular/common/http'
+import { SortMeta } from 'primeng/primeng'
 
 import { RestPagination } from './rest-pagination'
 
 @Injectable()
 export class RestService {
 
-  buildRestGetParams (pagination?: RestPagination, sort?: string) {
-    const params = new URLSearchParams()
+  addRestGetParams (params: HttpParams, pagination?: RestPagination, sort?: SortMeta | string) {
+    let newParams = params
 
-    if (pagination) {
-      const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage
-      const count: number = pagination.itemsPerPage
-
-      params.set('start', start.toString())
-      params.set('count', count.toString())
+    if (pagination !== undefined) {
+      newParams = newParams.set('start', pagination.start.toString())
+                           .set('count', pagination.count.toString())
     }
 
-    if (sort) {
-      params.set('sort', sort)
+    if (sort !== undefined) {
+      let sortString = ''
+
+      if (typeof sort === 'string') {
+        sortString = sort
+      } else {
+        const sortPrefix = sort.order === 1 ? '' : '-'
+        sortString = sortPrefix + sort.field
+      }
+
+      newParams = newParams.set('sort', sortString)
     }
 
-    return params
+    return newParams
   }
 
 }
index 99b51aa4e6f2706c4041bb98e03fb2cb4fbf2315..118ce822d03f8a1f7b4b7b6b401c421cbef73056 100644 (file)
@@ -1,6 +1,6 @@
 import { NgModule } from '@angular/core'
+import { HttpClientModule } from '@angular/common/http'
 import { CommonModule } from '@angular/common'
-import { HttpModule } from '@angular/http'
 import { FormsModule, ReactiveFormsModule } from '@angular/forms'
 import { RouterModule } from '@angular/router'
 
@@ -11,9 +11,9 @@ import { ProgressbarModule } from 'ngx-bootstrap/progressbar'
 import { PaginationModule } from 'ngx-bootstrap/pagination'
 import { ModalModule } from 'ngx-bootstrap/modal'
 import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload'
-import { Ng2SmartTableModule } from 'ng2-smart-table'
+import { DataTableModule, SharedModule as PrimeSharedModule } from 'primeng/primeng'
 
-import { AUTH_HTTP_PROVIDERS } from './auth'
+import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
 import { RestExtractor, RestService } from './rest'
 import { SearchComponent, SearchService } from './search'
 import { UserService } from './users'
@@ -24,8 +24,8 @@ import { VideoAbuseService } from './video-abuse'
     CommonModule,
     FormsModule,
     ReactiveFormsModule,
-    HttpModule,
     RouterModule,
+    HttpClientModule,
 
     BsDropdownModule.forRoot(),
     ModalModule.forRoot(),
@@ -33,7 +33,9 @@ import { VideoAbuseService } from './video-abuse'
     ProgressbarModule.forRoot(),
 
     FileUploadModule,
-    Ng2SmartTableModule
+
+    DataTableModule,
+    PrimeSharedModule
   ],
 
   declarations: [
@@ -46,15 +48,16 @@ import { VideoAbuseService } from './video-abuse'
     CommonModule,
     FormsModule,
     ReactiveFormsModule,
-    HttpModule,
     RouterModule,
+    HttpClientModule,
 
     BsDropdownModule,
     FileUploadModule,
     ModalModule,
     PaginationModule,
     ProgressbarModule,
-    Ng2SmartTableModule,
+    DataTableModule,
+    PrimeSharedModule,
     BytesPipe,
     KeysPipe,
 
@@ -62,7 +65,7 @@ import { VideoAbuseService } from './video-abuse'
   ],
 
   providers: [
-    AUTH_HTTP_PROVIDERS,
+    AUTH_INTERCEPTOR_PROVIDER,
     RestExtractor,
     RestService,
     SearchService,
index 35180be4d5a9359be1044591c6df77b5c9c05e14..5c089d221a3b6d8cf0f57c4f872bc989c22facc1 100644 (file)
@@ -1,10 +1,8 @@
 import { Injectable } from '@angular/core'
-import { Http } from '@angular/http'
+import { HttpClient } from '@angular/common/http'
 import 'rxjs/add/operator/catch'
 import 'rxjs/add/operator/map'
 
-import { AuthService } from '../../core'
-import { AuthHttp } from '../auth'
 import { RestExtractor } from '../rest'
 import { UserCreate, UserUpdateMe } from '../../../../../shared'
 
@@ -13,9 +11,7 @@ export class UserService {
   static BASE_USERS_URL = API_URL + '/api/v1/users/'
 
   constructor (
-    private http: Http,
-    private authHttp: AuthHttp,
-    private authService: AuthService,
+    private authHttp: HttpClient,
     private restExtractor: RestExtractor
   ) {}
 
@@ -34,7 +30,7 @@ export class UserService {
 
     return this.authHttp.put(url, body)
                         .map(this.restExtractor.extractDataBool)
-                        .catch((res) => this.restExtractor.handleError(res))
+                        .catch(res => this.restExtractor.handleError(res))
   }
 
   updateMyDetails (details: UserUpdateMe) {
@@ -42,12 +38,12 @@ export class UserService {
 
     return this.authHttp.put(url, details)
                         .map(this.restExtractor.extractDataBool)
-                        .catch((res) => this.restExtractor.handleError(res))
+                        .catch(res => this.restExtractor.handleError(res))
   }
 
   signup (userCreate: UserCreate) {
-    return this.http.post(UserService.BASE_USERS_URL + 'register', userCreate)
+    return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate)
                         .map(this.restExtractor.extractDataBool)
-                        .catch(this.restExtractor.handleError)
+                        .catch(res => this.restExtractor.handleError(res))
   }
 }
index c3189a570500680ed132d83593adee7ae8c8a8a0..7c8ae2e3e5e664c135925c38b801fe3953ad0afc 100644 (file)
@@ -2,15 +2,7 @@ import { DatePipe } from '@angular/common'
 
 export class Utils {
 
-  static dateToHuman (date: String) {
+  static dateToHuman (date: Date) {
     return new DatePipe('en').transform(date, 'medium')
   }
-
-  static getRowDeleteButton () {
-    return '<span class="glyphicon glyphicon-remove glyphicon-black"></span>'
-  }
-
-  static getRowEditButton () {
-    return '<span class="glyphicon glyphicon-pencil glyphicon-black"></span>'
-  }
 }
index 636a02084ccbf72f962b6e9e14b191b6a7d0e258..98458111415c52d7676b7f728fd26f1fc6a75e68 100644 (file)
@@ -1,42 +1,53 @@
 import { Injectable } from '@angular/core'
-import { Http } from '@angular/http'
-import { Observable } from 'rxjs/Observable'
+import { HttpClient, HttpParams } from '@angular/common/http'
 import 'rxjs/add/operator/catch'
 import 'rxjs/add/operator/map'
+import { Observable } from 'rxjs/Observable'
+
+import { SortMeta } from 'primeng/primeng'
 
 import { AuthService } from '../core'
-import { AuthHttp } from '../auth'
-import { RestDataSource, RestExtractor, ResultList } from '../rest'
-import { VideoAbuse } from '../../../../../shared'
+import { RestExtractor, RestPagination, RestService } from '../rest'
+import { Utils } from '../utils'
+import { ResultList, VideoAbuse } from '../../../../../shared'
 
 @Injectable()
 export class VideoAbuseService {
   private static BASE_VIDEO_ABUSE_URL = API_URL + '/api/v1/videos/'
 
   constructor (
-    private authHttp: AuthHttp,
+    private authHttp: HttpClient,
+    private restService: RestService,
     private restExtractor: RestExtractor
   ) {}
 
-  getDataSource () {
-    return new RestDataSource(this.authHttp, VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse')
+  getVideoAbuses (pagination: RestPagination, sort: SortMeta): Observable<ResultList<VideoAbuse>> {
+    const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse'
+
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
+
+    return this.authHttp.get<ResultList<VideoAbuse>>(url, { params })
+                        .map(res => this.restExtractor.convertResultListDateToHuman(res))
+                        .map(res => this.restExtractor.applyToResultListData(res, this.formatVideoAbuse.bind(this)))
+                        .catch(res => this.restExtractor.handleError(res))
   }
 
   reportVideo (id: number, reason: string) {
+    const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse'
     const body = {
       reason
     }
-    const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse'
 
     return this.authHttp.post(url, body)
                         .map(this.restExtractor.extractDataBool)
-                        .catch((res) => this.restExtractor.handleError(res))
+                        .catch(res => this.restExtractor.handleError(res))
   }
 
-  private extractVideoAbuses (result: ResultList) {
-    const videoAbuses: VideoAbuse[] = result.data
-    const totalVideoAbuses = result.total
-
-    return { videoAbuses, totalVideoAbuses }
+  private formatVideoAbuse (videoAbuse: VideoAbuse) {
+    return Object.assign(videoAbuse, {
+      createdAt: Utils.dateToHuman(videoAbuse.createdAt)
+    })
   }
+
 }
index 97d795321a93f994bd89525b0198686f1968d062..8168e3bfdc859e61067ec3de7ce95684b4b222b0 100644 (file)
@@ -1,3 +1,4 @@
 export * from './sort-field.type'
 export * from './video.model'
 export * from './video.service'
+export * from './video-pagination.model'
diff --git a/client/src/app/videos/shared/video-pagination.model.ts b/client/src/app/videos/shared/video-pagination.model.ts
new file mode 100644 (file)
index 0000000..9e71769
--- /dev/null
@@ -0,0 +1,5 @@
+export interface VideoPagination {
+  currentPage: number
+  itemsPerPage: number
+  totalItems: number
+}
index 1a413db9d2da2c4b60420e7f04c443484b520a6e..17f41059d0be37778002a07d487d7971d708769f 100644 (file)
@@ -46,7 +46,7 @@ export class Video implements VideoServerModel {
 
   constructor (hash: {
     author: string,
-    createdAt: string,
+    createdAt: Date | string,
     categoryLabel: string,
     category: number,
     licenceLabel: string,
@@ -70,7 +70,7 @@ export class Video implements VideoServerModel {
     files: VideoFile[]
   }) {
     this.author = hash.author
-    this.createdAt = new Date(hash.createdAt)
+    this.createdAt = new Date(hash.createdAt.toString())
     this.categoryLabel = hash.categoryLabel
     this.category = hash.category
     this.licenceLabel = hash.licenceLabel
index 67091a8d852287594e784cf4934f003cf3cff990..b6d2a06662d3c6982464a9dcb1643cf16ee4fd48 100644 (file)
@@ -1,27 +1,26 @@
 import { Injectable } from '@angular/core'
-import { Http, Headers, RequestOptions } from '@angular/http'
 import { Observable } from 'rxjs/Observable'
 import 'rxjs/add/operator/catch'
 import 'rxjs/add/operator/map'
+import { HttpClient, HttpParams } from '@angular/common/http'
 
 import { Search } from '../../shared'
 import { SortField } from './sort-field.type'
-import { AuthService } from '../../core'
 import {
-  AuthHttp,
   RestExtractor,
-  RestPagination,
   RestService,
-  ResultList,
   UserService
 } from '../../shared'
 import { Video } from './video.model'
+import { VideoPagination } from './video-pagination.model'
 import {
-  UserVideoRate,
-  VideoRateType,
-  VideoUpdate,
-  VideoAbuseCreate,
-  UserVideoRateUpdate
+UserVideoRate,
+VideoRateType,
+VideoUpdate,
+VideoAbuseCreate,
+UserVideoRateUpdate,
+Video as VideoServerModel,
+ResultList
 } from '../../../../../shared'
 
 @Injectable()
@@ -33,9 +32,7 @@ export class VideoService {
   videoLanguages: Array<{ id: number, label: string }> = []
 
   constructor (
-    private authService: AuthService,
-    private authHttp: AuthHttp,
-    private http: Http,
+    private authHttp: HttpClient,
     private restExtractor: RestExtractor,
     private restService: RestService
   ) {}
@@ -52,11 +49,10 @@ export class VideoService {
     return this.loadVideoAttributeEnum('languages', this.videoLanguages)
   }
 
-  getVideo (uuid: string): Observable<Video> {
-    return this.http.get(VideoService.BASE_VIDEO_URL + uuid)
-                    .map(this.restExtractor.extractDataGet)
-                    .map(videoHash => new Video(videoHash))
-                    .catch((res) => this.restExtractor.handleError(res))
+  getVideo (uuid: string) {
+    return this.authHttp.get<VideoServerModel>(VideoService.BASE_VIDEO_URL + uuid)
+                        .map(videoHash => new Video(videoHash))
+                        .catch((res) => this.restExtractor.handleError(res))
   }
 
   updateVideo (video: Video) {
@@ -72,38 +68,41 @@ export class VideoService {
       nsfw: video.nsfw
     }
 
-    const headers = new Headers({ 'Content-Type': 'application/json' })
-    const options = new RequestOptions({ headers: headers })
-
-    return this.authHttp.put(`${VideoService.BASE_VIDEO_URL}/${video.id}`, body, options)
+    return this.authHttp.put(`${VideoService.BASE_VIDEO_URL}/${video.id}`, body)
                         .map(this.restExtractor.extractDataBool)
                         .catch(this.restExtractor.handleError)
   }
 
-  getVideos (pagination: RestPagination, sort: SortField) {
-    const params = this.restService.buildRestGetParams(pagination, sort)
+  getVideos (videoPagination: VideoPagination, sort: SortField) {
+    const pagination = this.videoPaginationToRestPagination(videoPagination)
 
-    return this.http.get(VideoService.BASE_VIDEO_URL, { search: params })
-                    .map(res => res.json())
-                    .map(this.extractVideos)
-                    .catch((res) => this.restExtractor.handleError(res))
-  }
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
 
-  removeVideo (id: number) {
-    return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id)
-                        .map(this.restExtractor.extractDataBool)
+    return this.authHttp.get(VideoService.BASE_VIDEO_URL, { params })
+                        .map(this.extractVideos)
                         .catch((res) => this.restExtractor.handleError(res))
   }
 
-  searchVideos (search: Search, pagination: RestPagination, sort: SortField) {
-    const params = this.restService.buildRestGetParams(pagination, sort)
+  searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField) {
+    const url = VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value)
+
+    const pagination = this.videoPaginationToRestPagination(videoPagination)
+
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
 
     if (search.field) params.set('field', search.field)
 
-    return this.http.get(VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value), { search: params })
-                    .map(this.restExtractor.extractDataList)
-                    .map(this.extractVideos)
-                    .catch((res) => this.restExtractor.handleError(res))
+    return this.authHttp.get<ResultList<VideoServerModel>>(url, { params })
+                        .map(this.extractVideos)
+                        .catch((res) => this.restExtractor.handleError(res))
+  }
+
+  removeVideo (id: number) {
+    return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id)
+               .map(this.restExtractor.extractDataBool)
+               .catch((res) => this.restExtractor.handleError(res))
   }
 
   reportVideo (id: number, reason: string) {
@@ -114,7 +113,7 @@ export class VideoService {
 
     return this.authHttp.post(url, body)
                         .map(this.restExtractor.extractDataBool)
-                        .catch((res) => this.restExtractor.handleError(res))
+                        .catch(res => this.restExtractor.handleError(res))
   }
 
   setVideoLike (id: number) {
@@ -129,14 +128,20 @@ export class VideoService {
     const url = UserService.BASE_USERS_URL + '/me/videos/' + id + '/rating'
 
     return this.authHttp.get(url)
-                        .map(this.restExtractor.extractDataGet)
-                        .catch((res) => this.restExtractor.handleError(res))
+                        .catch(res => this.restExtractor.handleError(res))
   }
 
   blacklistVideo (id: number) {
     return this.authHttp.post(VideoService.BASE_VIDEO_URL + id + '/blacklist', {})
                         .map(this.restExtractor.extractDataBool)
-                        .catch((res) => this.restExtractor.handleError(res))
+                        .catch(res => this.restExtractor.handleError(res))
+  }
+
+  private videoPaginationToRestPagination (videoPagination: VideoPagination) {
+    const start: number = (videoPagination.currentPage - 1) * videoPagination.itemsPerPage
+    const count: number = videoPagination.itemsPerPage
+
+    return { start, count }
   }
 
   private setVideoRate (id: number, rateType: VideoRateType) {
@@ -147,13 +152,14 @@ export class VideoService {
 
     return this.authHttp.put(url, body)
                         .map(this.restExtractor.extractDataBool)
-                        .catch((res) => this.restExtractor.handleError(res))
+                        .catch(res => this.restExtractor.handleError(res))
   }
 
-  private extractVideos (result: ResultList) {
+  private extractVideos (result: ResultList<VideoServerModel>) {
     const videosJson = result.data
     const totalVideos = result.total
     const videos = []
+
     for (const videoJson of videosJson) {
       videos.push(new Video(videoJson))
     }
@@ -162,15 +168,14 @@ export class VideoService {
   }
 
   private loadVideoAttributeEnum (attributeName: 'categories' | 'licences' | 'languages', hashToPopulate: { id: number, label: string }[]) {
-    return this.http.get(VideoService.BASE_VIDEO_URL + attributeName)
-                    .map(this.restExtractor.extractDataGet)
-                    .subscribe(data => {
-                      Object.keys(data).forEach(dataKey => {
-                        hashToPopulate.push({
-                          id: parseInt(dataKey, 10),
-                          label: data[dataKey]
+    return this.authHttp.get(VideoService.BASE_VIDEO_URL + attributeName)
+                        .subscribe(data => {
+                          Object.keys(data).forEach(dataKey => {
+                            hashToPopulate.push({
+                              id: parseInt(dataKey, 10),
+                              label: data[dataKey]
+                            })
+                          })
                         })
-                      })
-                    })
   }
 }
index 4ac539960363d31cdb053e08a49d76bb0d9285a5..5906320632e447ef4da1be71d568c798e0c69517 100644 (file)
@@ -8,11 +8,12 @@ import { NotificationsService } from 'angular2-notifications'
 import {
   SortField,
   Video,
-  VideoService
+  VideoService,
+  VideoPagination
 } from '../shared'
 import { AuthService, AuthUser } from '../../core'
-import { RestPagination, Search, SearchField } from '../../shared'
-import { SearchService } from '../../shared'
+import { Search, SearchField, SearchService } from '../../shared'
+import {  } from '../../shared'
 
 @Component({
   selector: 'my-videos-list',
@@ -21,7 +22,7 @@ import { SearchService } from '../../shared'
 })
 export class VideoListComponent implements OnInit, OnDestroy {
   loading: BehaviorSubject<boolean> = new BehaviorSubject(false)
-  pagination: RestPagination = {
+  pagination: VideoPagination = {
     currentPage: 1,
     itemsPerPage: 25,
     totalItems: null
@@ -152,6 +153,6 @@ export class VideoListComponent implements OnInit, OnDestroy {
 
   private navigateToNewParams () {
     const routeParams = this.buildRouteParams()
-    this.router.navigate(['/videos/list', routeParams])
+    this.router.navigate([ '/videos/list', routeParams ])
   }
 }
index 285339d4230612def2fbe38cd5abd06e946f7b10..cd573841d21d52c4d4b6ec8a524849107c3ce2ec 100644 (file)
@@ -1,4 +1,6 @@
-@import '../../node_modules/video.js/dist/video-js.css';
+@import '~primeng/resources/themes/bootstrap/theme.css';
+@import '~primeng/resources/primeng.css';
+@import '~video.js/dist/video-js.css';
 @import './video-js-custom.scss';
 
 [hidden] {
@@ -45,23 +47,13 @@ input.readonly {
   }
 }
 
-/* some fixes for ng2-smart-table */
-ng2-smart-table {
-  thead tr {
-    border-top: 1px solid rgb(233, 235, 236)
-  }
-
-  td, th {
-    padding: 8px !important;
-    color: #333333 !important;
-    font-size: 14px !important;
-  }
+/* ngprime data table customizations */
+p-datatable {
+  .action-cell {
+    text-align: center;
 
-  .ng2-smart-pagination-nav .page-link {
-    font-size: 11px !important;
-  }
-
-  .glyphicon {
-    font-family: 'Glyphicons Halflings' !important;
+    .glyphicon {
+      cursor: pointer;
+    }
   }
 }
index 9478e23b2eac9acf3f0a01d2c28b0f8708d8d522..3552dbf990659aac9e53b7346aef109c3a4c7379 100644 (file)
@@ -4436,10 +4436,6 @@ ng-router-loader@^2.0.0:
     loader-utils "^0.2.16"
     recast "^0.11.20"
 
-ng2-completer@^1.2.2:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/ng2-completer/-/ng2-completer-1.6.1.tgz#62bad1a0a1d99c62b15f6723911ee0a3a00c91bb"
-
 ng2-file-upload@^1.1.4-2:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/ng2-file-upload/-/ng2-file-upload-1.2.1.tgz#5563c5dfd6f43fbfbe815c206e343464a0a6a197"
@@ -4448,13 +4444,6 @@ ng2-material-dropdown@0.7.10:
   version "0.7.10"
   resolved "https://registry.yarnpkg.com/ng2-material-dropdown/-/ng2-material-dropdown-0.7.10.tgz#093471f2a9cadd726cbcb120b0ad7818a54fa5ed"
 
-ng2-smart-table@1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/ng2-smart-table/-/ng2-smart-table-1.2.1.tgz#b25102c1a8b0588c508cf913c539ddf0f0b3341d"
-  dependencies:
-    lodash "^4.17.4"
-    ng2-completer "^1.2.2"
-
 ngc-webpack@3.2.2:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/ngc-webpack/-/ngc-webpack-3.2.2.tgz#1905c40e3c7d30c86fe029c7a7fda71cb4dc59df"
@@ -5340,6 +5329,10 @@ pretty-error@^2.0.2:
     renderkid "^2.0.1"
     utila "~0.4"
 
+primeng@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/primeng/-/primeng-4.2.0.tgz#49c8c99de26d254f41d3fbb8759227fe1d269772"
+
 private@^0.1.6, private@^0.1.7, private@~0.1.5:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
index ea9ddbe8d3a2776b2fa373584beaedbc6d459bde..c0dd24c534a12743012c902708f99b9c0267f7de 100644 (file)
@@ -190,6 +190,7 @@ function quitFriends () {
       .catch(err => {
         logger.error('Some errors while quitting friends.', err)
         // Don't stop the process
+        return pods
       })
     })
     .then(pods => {
index 45dbc7b8fb3aae9332dc0f9ee4e809c460a7f947..efb58c320c598e08785bb5f997905366657437dd 100644 (file)
@@ -1,5 +1,7 @@
 export * from './user.model'
 export * from './user-create.model'
+export * from './user-login.model'
+export * from './user-refresh-token.model'
 export * from './user-update.model'
 export * from './user-update-me.model'
 export * from './user-role.type'
diff --git a/shared/models/users/user-login.model.ts b/shared/models/users/user-login.model.ts
new file mode 100644 (file)
index 0000000..b0383c6
--- /dev/null
@@ -0,0 +1,5 @@
+export interface UserLogin {
+  access_token: string
+  refresh_token: string
+  token_type: string
+}
\ No newline at end of file
diff --git a/shared/models/users/user-refresh-token.model.ts b/shared/models/users/user-refresh-token.model.ts
new file mode 100644 (file)
index 0000000..f528dd9
--- /dev/null
@@ -0,0 +1,4 @@
+export interface UserRefreshToken {
+  access_token: string
+  refresh_token: string
+}
index 6277dbe59dee1631c5e5766e71392ebc674da498..75070bfd6892ba9e81a2722c135cbdaab0209fe2 100644 (file)
@@ -9,8 +9,8 @@ export interface Video {
   id: number
   uuid: string
   author: string
-  createdAt: Date
-  updatedAt: Date
+  createdAt: Date | string
+  updatedAt: Date | string
   categoryLabel: string
   category: number
   licenceLabel: string